UNPKG

@mastra/core

Version:
1,485 lines (1,477 loc) • 259 kB
import { GatewayManager, defaultGateways, createOpenAIWebSocketFetch } from './chunk-J7CHY2GW.js'; import { modelSupportsTemperature, GatewayRegistry } from './chunk-26L3FC2R.js'; import { lazySchema, zodSchema, createJsonErrorResponseHandler, createProviderDefinedToolFactoryWithOutputSchema, createProviderDefinedToolFactory, lazyValidator, withoutTrailingSlash, loadOptionalSetting, generateId, parseProviderOptions, combineHeaders, resolve, postJsonToApi, createJsonResponseHandler, createEventSourceResponseHandler, withUserAgentSuffix, loadApiKey, UnsupportedFunctionalityError, convertToBase64, APICallError, validateTypes, createOpenAICompatible, createOpenAI, MASTRA_USER_AGENT, TooManyEmbeddingValuesForCallError, InvalidResponseDataError, isParsableJson, convertBase64ToUint8Array, mediaTypeToExtension, postFormDataToApi, createBinaryResponseHandler, MASTRA_GATEWAY_STREAM_TRANSPORT, InvalidPromptError } from './chunk-DRULWNQ6.js'; import { AISDKV5LanguageModel, createStreamFromGenerateResult } from './chunk-52EXWWED.js'; import { RequestContext } from './chunk-W4DLMY73.js'; import { createHash } from 'crypto'; import { z } from 'zod/v4'; // src/stream/types.ts var ChunkFrom = /* @__PURE__ */ ((ChunkFrom2) => { ChunkFrom2["AGENT"] = "AGENT"; ChunkFrom2["USER"] = "USER"; ChunkFrom2["SYSTEM"] = "SYSTEM"; ChunkFrom2["WORKFLOW"] = "WORKFLOW"; ChunkFrom2["NETWORK"] = "NETWORK"; return ChunkFrom2; })(ChunkFrom || {}); var MASTRA_MODEL_STREAM_TRANSPORT = /* @__PURE__ */ Symbol.for("@mastra/core.modelStreamTransport"); function attachModelStreamTransport(target, transport) { if (!transport) return; Object.defineProperty(target, MASTRA_MODEL_STREAM_TRANSPORT, { configurable: true, value: transport }); } function readModelStreamTransport(target) { return target?.[MASTRA_MODEL_STREAM_TRANSPORT]; } // src/llm/model/aisdk/v6/model.ts function remapToolsToV3(options) { if (!options.tools?.length) { return options; } const remappedTools = options.tools.map((tool) => { if (tool.type === "provider-defined") { return { ...tool, type: "provider" }; } return tool; }); return { ...options, tools: remappedTools }; } var AISDKV6LanguageModel = class { /** * The language model must specify which language model interface version it implements. */ specificationVersion = "v3"; /** * Name of the provider for logging purposes. */ provider; /** * Provider-specific model ID for logging purposes. */ modelId; /** * Supported URL patterns by media type for the provider. * * The keys are media type patterns or full media types (e.g. `*\/*` for everything, `audio/*`, `video/*`, or `application/pdf`). * and the values are arrays of regular expressions that match the URL paths. * The matching should be against lower-case URLs. * Matched URLs are supported natively by the model and are not downloaded. * @returns A map of supported URL patterns by media type (as a promise or a plain object). */ supportedUrls; #model; constructor(config) { this.#model = config; this.provider = this.#model.provider; this.modelId = this.#model.modelId; this.supportedUrls = this.#model.supportedUrls; } async doGenerate(options) { const result = await this.#model.doGenerate(remapToolsToV3(options)); return { ...result, request: result.request, response: result.response, stream: createStreamFromGenerateResult(result) }; } async doStream(options) { return await this.#model.doStream(remapToolsToV3(options)); } /** * Custom serialization for tracing/observability spans. * `#model` is already a true JS private field and not enumerable, so * the wrapped provider SDK client can't leak. This method makes the * safe shape explicit and avoids walking `supportedUrls` (a * PromiseLike / regex map that isn't useful in spans). */ serializeForSpan() { return { specificationVersion: this.specificationVersion, modelId: this.modelId, provider: this.provider }; } }; // src/llm/model/aisdk/v7/model.ts function remapToolsToV4(options) { if (!options.tools?.length) { return options; } const remappedTools = options.tools.map((tool) => { if (tool.type === "provider-defined") { return { ...tool, type: "provider" }; } return tool; }); return { ...options, tools: remappedTools }; } var AISDKV7LanguageModel = class { /** * The language model must specify which language model interface version it implements. */ specificationVersion = "v4"; /** * Name of the provider for logging purposes. */ provider; /** * Provider-specific model ID for logging purposes. */ modelId; /** * Supported URL patterns by media type for the provider. * * The keys are media type patterns or full media types (e.g. `*\/*` for everything, `audio/*`, `video/*`, or `application/pdf`). * and the values are arrays of regular expressions that match the URL paths. * The matching should be against lower-case URLs. * Matched URLs are supported natively by the model and are not downloaded. * @returns A map of supported URL patterns by media type (as a promise or a plain object). */ supportedUrls; #model; constructor(config) { this.#model = config; this.provider = this.#model.provider; this.modelId = this.#model.modelId; this.supportedUrls = this.#model.supportedUrls; } async doGenerate(options) { const result = await this.#model.doGenerate(remapToolsToV4(options)); return { ...result, request: result.request, response: result.response, stream: createStreamFromGenerateResult(result) }; } async doStream(options) { return await this.#model.doStream(remapToolsToV4(options)); } /** * Custom serialization for tracing/observability spans. * `#model` is already a true JS private field and not enumerable, so * the wrapped provider SDK client can't leak. This method makes the * safe shape explicit and avoids walking `supportedUrls` (a * PromiseLike / regex map that isn't useful in spans). */ serializeForSpan() { return { specificationVersion: this.specificationVersion, modelId: this.modelId, provider: this.provider }; } }; // src/llm/model/router.ts function isLanguageModelV4(model) { return model.specificationVersion === "v4"; } function isLanguageModelV3(model) { return model.specificationVersion === "v3"; } var OPENAI_WS_ALLOWLIST = /* @__PURE__ */ new Set(["openai"]); var OPENAI_API_HOST = "api.openai.com"; function createGatewayModelCache() { return { modelInstances: /* @__PURE__ */ new Map(), webSocketFetches: /* @__PURE__ */ new Map(), gatewayStreamTransports: /* @__PURE__ */ new Map() }; } function getOpenAITransport(providerOptions, providerId) { const transportOptions = providerId === "azure-openai" ? providerOptions?.azure : providerOptions?.openai; return { transport: transportOptions?.transport ?? "fetch", websocket: transportOptions?.websocket }; } function isOpenAIBaseUrl(baseURL) { if (!baseURL) return true; try { const hostname = new URL(baseURL).hostname; return hostname === OPENAI_API_HOST; } catch { return false; } } function stableHeaderKey(headers) { if (!headers) return ""; const entries = Object.entries(headers); if (entries.length === 0) return ""; return JSON.stringify(entries.sort(([a], [b]) => a.localeCompare(b))); } function mergeHeaders(baseHeaders, authHeaders) { if (!baseHeaders && !authHeaders) return void 0; return { ...baseHeaders, ...authHeaders }; } var ModelRouterLanguageModel = class _ModelRouterLanguageModel { specificationVersion = "v2"; defaultObjectGenerationMode = "json"; supportsStructuredOutputs = true; supportsImageUrls = true; /** * Supported URL patterns by media type for the provider. * This is a lazy promise that resolves the underlying model's supportedUrls. * Models like Mistral define which URL patterns they support (e.g., application/pdf for https URLs). * * @see https://github.com/mastra-ai/mastra/issues/12152 */ supportedUrls; modelId; provider; gatewayId; config; gateway; _supportedUrlsPromise = null; instanceGatewayCache = createGatewayModelCache(); #lastStreamTransport; #manager; constructor(config, customGateways) { let normalizedConfig; if (typeof config === "string") { normalizedConfig = { id: config }; } else if ("providerId" in config && "modelId" in config) { normalizedConfig = { id: `${config.providerId}/${config.modelId}`, url: config.url, apiKey: config.apiKey, headers: config.headers }; } else { normalizedConfig = { id: config.id, url: config.url, apiKey: config.apiKey, headers: config.headers }; } const parsedConfig = { ...normalizedConfig, routerId: normalizedConfig.id }; this.#manager = new GatewayManager([...customGateways ?? [], ...defaultGateways]); const resolved = this.#manager.resolveModelId(normalizedConfig.id); this.gateway = resolved.gateway; this.gatewayId = resolved.gatewayId; this.provider = resolved.providerId || "openai-compatible"; if (resolved.providerId && resolved.modelId !== normalizedConfig.id) { parsedConfig.id = resolved.modelId; } this.modelId = parsedConfig.id; this.config = parsedConfig; const self = this; this.supportedUrls = { then(onfulfilled, onrejected) { return self._resolveSupportedUrls().then(onfulfilled, onrejected); } }; } /** * Lazily resolves the underlying model's supportedUrls. * This is cached to avoid multiple model resolutions. * @internal */ async _resolveSupportedUrls() { if (this._supportedUrlsPromise) { return this._supportedUrlsPromise; } this._supportedUrlsPromise = this._fetchSupportedUrls(); return this._supportedUrlsPromise; } /** * Fetches supportedUrls from the underlying model. * @internal */ async _fetchSupportedUrls() { let apiKey; try { const resolved = this.#manager.resolveModelId(this.config.routerId); const auth = await this.resolveAuth(resolved.providerId, resolved.modelId); apiKey = auth.apiKey ?? ""; const model = await this.resolveLanguageModel({ apiKey, auth, headers: mergeHeaders(this.config.headers, auth.headers), ...resolved }); const modelSupportedUrls = model.supportedUrls; if (!modelSupportedUrls) { return {}; } if (typeof modelSupportedUrls.then === "function") { const resolved2 = await modelSupportedUrls; return resolved2 ?? {}; } return modelSupportedUrls ?? {}; } catch { return {}; } } /** @internal */ _getStreamTransport() { return this.#lastStreamTransport; } /** * Custom serialization for tracing/observability spans. * Excludes `config` (holds apiKey, headers, url) and `gateway` * (may hold proxy credentials or cached tokens) so they cannot leak * into telemetry backends. */ serializeForSpan() { return { specificationVersion: this.specificationVersion, modelId: this.modelId, provider: this.provider, gatewayId: this.gatewayId }; } getGatewayCache() { let cache = _ModelRouterLanguageModel.gatewayCaches.get(this.gateway); if (!cache) { cache = createGatewayModelCache(); _ModelRouterLanguageModel.gatewayCaches.set(this.gateway, cache); } return cache; } shouldUseInstanceGatewayCache(auth) { return this.config.apiKey !== void 0 || auth.source === "explicit" || auth.source === "gateway"; } setStreamTransportHandle({ resolvedTransport, transport, responsesWebSocket }) { if (resolvedTransport !== "websocket") { this.#lastStreamTransport = void 0; return; } if (!transport) { this.#lastStreamTransport = void 0; return; } this.#lastStreamTransport = { type: transport.type, close: transport.close, closeOnFinish: responsesWebSocket?.closeOnFinish ?? true }; } async resolveAuth(_providerId, _modelId) { if (this.config.url) { return { apiKey: this.config.apiKey ?? "", headers: this.config.headers, source: "explicit" }; } const explicitHeaders = this.config.headers; if (this.config.apiKey) { return { apiKey: this.config.apiKey, headers: explicitHeaders, source: "explicit" }; } const gatewayAuth = await this.#manager.resolveAuth(this.config.routerId); return explicitHeaders ? { ...gatewayAuth, headers: { ...explicitHeaders, ...gatewayAuth.headers } } : gatewayAuth; } setStreamTransportFromCache({ cache, resolvedTransport, key, responsesWebSocket }) { const wsFetch = cache.webSocketFetches.get(key); const gatewayTransport = cache.gatewayStreamTransports.get(key); const transport = wsFetch ? { type: "openai-websocket", close: () => wsFetch.close() } : gatewayTransport; this.setStreamTransportHandle({ resolvedTransport, transport, responsesWebSocket }); } stripUnsupportedSamplingParams(options) { const supports = modelSupportsTemperature(this.config.routerId); if (supports !== false) return options; const { temperature, topP, topK, ...rest } = options; if (temperature === void 0 && topP === void 0 && topK === void 0) return options; return rest; } async doGenerate(options) { const resolved = this.#manager.resolveModelId(this.config.routerId); let auth; try { auth = await this.resolveAuth(resolved.providerId, resolved.modelId); } catch (error) { return { stream: new ReadableStream({ start(controller) { controller.enqueue({ type: "error", error }); controller.close(); } }) }; } const sanitizedOptions = this.stripUnsupportedSamplingParams(options); const model = await this.resolveLanguageModel({ apiKey: auth.apiKey ?? "", auth, headers: mergeHeaders(this.config.headers, auth.headers), ...resolved }); if (isLanguageModelV4(model)) { const aiSDKV7Model = new AISDKV7LanguageModel(model); return aiSDKV7Model.doGenerate(sanitizedOptions); } if (isLanguageModelV3(model)) { const aiSDKV6Model = new AISDKV6LanguageModel(model); return aiSDKV6Model.doGenerate(sanitizedOptions); } const aiSDKV5Model = new AISDKV5LanguageModel(model); return aiSDKV5Model.doGenerate(sanitizedOptions); } async doStream(options) { const resolved = this.#manager.resolveModelId(this.config.routerId); let auth; try { auth = await this.resolveAuth(resolved.providerId, resolved.modelId); } catch (error) { return { stream: new ReadableStream({ start(controller) { controller.enqueue({ type: "error", error }); controller.close(); } }) }; } const sanitizedOptions = this.stripUnsupportedSamplingParams(options); const { transport, websocket } = getOpenAITransport( sanitizedOptions.providerOptions, resolved.providerId ); const requestedTransport = transport === "auto" ? "websocket" : transport; const allowWebSocket = requestedTransport === "websocket" && !this.config.url && (this.gatewayId === "models.dev" && OPENAI_WS_ALLOWLIST.has(this.provider) || this.gatewayId === "azure-openai"); const resolvedTransport = allowWebSocket ? "websocket" : "fetch"; const model = await this.resolveLanguageModel({ apiKey: auth.apiKey ?? "", auth, headers: mergeHeaders(this.config.headers, auth.headers), transport: resolvedTransport, responsesWebSocket: websocket, ...resolved }); const streamTransport = this.#lastStreamTransport; if (isLanguageModelV4(model)) { const aiSDKV7Model = new AISDKV7LanguageModel(model); const streamResult2 = await aiSDKV7Model.doStream(sanitizedOptions); attachModelStreamTransport(streamResult2, streamTransport); return streamResult2; } if (isLanguageModelV3(model)) { const aiSDKV6Model = new AISDKV6LanguageModel(model); const streamResult2 = await aiSDKV6Model.doStream(sanitizedOptions); attachModelStreamTransport(streamResult2, streamTransport); return streamResult2; } const aiSDKV5Model = new AISDKV5LanguageModel(model); const streamResult = await aiSDKV5Model.doStream(sanitizedOptions); attachModelStreamTransport(streamResult, streamTransport); return streamResult; } async resolveLanguageModel({ modelId, providerId, apiKey, auth, headers, transport, responsesWebSocket }) { const resolvedTransport = transport ?? "fetch"; const websocketKey = resolvedTransport === "websocket" ? `${responsesWebSocket?.url ?? ""}:${stableHeaderKey(responsesWebSocket?.headers)}` : ""; const useInstanceCache = this.shouldUseInstanceGatewayCache(auth); const cache = useInstanceCache ? this.instanceGatewayCache : this.getGatewayCache(); const authScopeKey = useInstanceCache ? `${auth.source ?? ""}` : ""; const key = createHash("sha256").update( JSON.stringify([ this.gatewayId, modelId, providerId, this.config.url || "", stableHeaderKey(headers), resolvedTransport, websocketKey, authScopeKey ]) ).digest("hex"); if (cache.modelInstances.has(key)) { this.setStreamTransportFromCache({ cache, resolvedTransport, key, responsesWebSocket }); return cache.modelInstances.get(key); } if (this.config.url) { const modelInstance2 = createOpenAICompatible({ name: providerId, apiKey, baseURL: this.config.url, headers, supportsStructuredOutputs: true }).chatModel(modelId); cache.modelInstances.set(key, modelInstance2); this.setStreamTransportHandle({ resolvedTransport, responsesWebSocket }); return modelInstance2; } if (resolvedTransport === "websocket" && providerId === "openai" && this.gatewayId === "models.dev") { const baseURL = await this.gateway.buildUrl(this.config.routerId, process.env); if (isOpenAIBaseUrl(baseURL)) { const { modelInstance: modelInstance2, wsFetch } = this.resolveOpenAIWebSocketModel({ modelId, apiKey, baseURL, headers, responsesWebSocket }); cache.modelInstances.set(key, modelInstance2); cache.webSocketFetches.set(key, wsFetch); this.setStreamTransportFromCache({ cache, resolvedTransport, key, responsesWebSocket }); return modelInstance2; } } const modelInstance = await this.gateway.resolveLanguageModel({ modelId, providerId, apiKey, headers, transport: resolvedTransport, responsesWebSocket }); const gatewayTransport = readGatewayStreamTransport(modelInstance); cache.modelInstances.set(key, modelInstance); if (gatewayTransport) { cache.gatewayStreamTransports.set(key, gatewayTransport); } this.setStreamTransportHandle({ resolvedTransport, transport: gatewayTransport, responsesWebSocket }); return modelInstance; } resolveOpenAIWebSocketModel({ modelId, apiKey, baseURL, headers, responsesWebSocket }) { const wsFetch = createOpenAIWebSocketFetch({ url: responsesWebSocket?.url, headers: responsesWebSocket?.headers }); const modelInstance = createOpenAI({ apiKey, baseURL, headers, fetch: wsFetch }).responses(modelId); return { modelInstance, wsFetch }; } static _clearCachesForTests() { _ModelRouterLanguageModel.gatewayCaches = /* @__PURE__ */ new WeakMap(); } static gatewayCaches = /* @__PURE__ */ new WeakMap(); }; function readGatewayStreamTransport(model) { return model[MASTRA_GATEWAY_STREAM_TRANSPORT]; } // src/llm/model/aisdk/v4/model.ts var AISDKV4LegacyLanguageModel = class { specificationVersion = "v1"; provider; modelId; defaultObjectGenerationMode; supportsImageUrls; supportsStructuredOutputs; #model; constructor(config) { this.#model = config; this.provider = config.provider; this.modelId = config.modelId; this.defaultObjectGenerationMode = config.defaultObjectGenerationMode; this.supportsImageUrls = config.supportsImageUrls; this.supportsStructuredOutputs = config.supportsStructuredOutputs; } supportsUrl(url) { return this.#model.supportsUrl?.(url) ?? false; } doGenerate(options) { return this.#model.doGenerate(options); } doStream(options) { return this.#model.doStream(options); } /** * Custom serialization for tracing/observability spans. * `#model` is already a true JS private field and not enumerable, so * the wrapped provider SDK client can't leak. This method makes the * safe shape explicit. */ serializeForSpan() { return { specificationVersion: this.specificationVersion, modelId: this.modelId, provider: this.provider }; } }; // src/llm/model/resolve-model.ts function isOpenAICompatibleObjectConfig(modelConfig) { if (typeof modelConfig === "object" && "specificationVersion" in modelConfig) return false; if (typeof modelConfig === "object" && !("model" in modelConfig)) { if ("id" in modelConfig) return true; if ("providerId" in modelConfig && "modelId" in modelConfig) return true; } return false; } async function resolveModelConfig(modelConfig, requestContext = new RequestContext(), mastra) { if (typeof modelConfig === "function") { modelConfig = await modelConfig({ requestContext, mastra }); } if (modelConfig instanceof ModelRouterLanguageModel || modelConfig instanceof AISDKV4LegacyLanguageModel || modelConfig instanceof AISDKV5LanguageModel || modelConfig instanceof AISDKV6LanguageModel || modelConfig instanceof AISDKV7LanguageModel) { return modelConfig; } if (typeof modelConfig === "object" && "specificationVersion" in modelConfig) { if (modelConfig.specificationVersion === "v2") { return new AISDKV5LanguageModel(modelConfig); } if (modelConfig.specificationVersion === "v3") { return new AISDKV6LanguageModel(modelConfig); } if (modelConfig.specificationVersion === "v4") { return new AISDKV7LanguageModel(modelConfig); } if (modelConfig.specificationVersion === "v1") { return new AISDKV4LegacyLanguageModel(modelConfig); } if (typeof modelConfig.doStream === "function" && typeof modelConfig.doGenerate === "function") { return new AISDKV5LanguageModel(modelConfig); } return modelConfig; } const gatewayRecord = mastra?.listGateways(); const customGateways = gatewayRecord ? Object.values(gatewayRecord) : void 0; if (typeof modelConfig === "string" || isOpenAICompatibleObjectConfig(modelConfig)) { return new ModelRouterLanguageModel(modelConfig, customGateways); } throw new Error("Invalid model configuration provided"); } // src/llm/model/model-auth-resolver.ts function mergeAuthHeaders(auth) { if (!auth?.bearerToken) return auth; return { ...auth, headers: { ...auth.headers, Authorization: `Bearer ${auth.bearerToken}` } }; } function hasExplicitAuth(explicit) { return Boolean( explicit?.apiKey || explicit?.bearerToken || explicit?.headers && Object.keys(explicit.headers).length > 0 ); } async function resolveModelAuth({ gateway, request, explicit }) { if (hasExplicitAuth(explicit)) { return mergeAuthHeaders({ ...explicit, source: "explicit" }) ?? { source: "explicit" }; } const gatewayAuth = mergeAuthHeaders(await gateway.resolveAuth?.(request)); if (gatewayAuth?.apiKey || gatewayAuth?.headers || gatewayAuth?.bearerToken) { return { ...gatewayAuth, source: gatewayAuth.source ?? "gateway" }; } return { apiKey: await gateway.getApiKey(request.routerId), source: "legacy" }; } var VERSION = "2.0.72" ; var googleErrorDataSchema = lazySchema( () => zodSchema( z.object({ error: z.object({ code: z.number().nullable(), message: z.string(), status: z.string() }) }) ) ); var googleFailedResponseHandler = createJsonErrorResponseHandler({ errorSchema: googleErrorDataSchema, errorToMessage: (data) => data.error.message }); var googleEmbeddingContentPartSchema = z.union([ z.object({ text: z.string() }), z.object({ inlineData: z.object({ mimeType: z.string(), data: z.string() }) }) ]); var googleGenerativeAIEmbeddingProviderOptions = lazySchema( () => zodSchema( z.object({ /** * Optional. Optional reduced dimension for the output embedding. * If set, excessive values in the output embedding are truncated from the end. */ outputDimensionality: z.number().optional(), /** * Optional. Specifies the task type for generating embeddings. * Supported task types: * - SEMANTIC_SIMILARITY: Optimized for text similarity. * - CLASSIFICATION: Optimized for text classification. * - CLUSTERING: Optimized for clustering texts based on similarity. * - RETRIEVAL_DOCUMENT: Optimized for document retrieval. * - RETRIEVAL_QUERY: Optimized for query-based retrieval. * - QUESTION_ANSWERING: Optimized for answering questions. * - FACT_VERIFICATION: Optimized for verifying factual information. * - CODE_RETRIEVAL_QUERY: Optimized for retrieving code blocks based on natural language queries. */ taskType: z.enum([ "SEMANTIC_SIMILARITY", "CLASSIFICATION", "CLUSTERING", "RETRIEVAL_DOCUMENT", "RETRIEVAL_QUERY", "QUESTION_ANSWERING", "FACT_VERIFICATION", "CODE_RETRIEVAL_QUERY" ]).optional(), /** * Optional. Per-value multimodal content parts for embedding non-text * content (images, video, PDF, audio). Each entry corresponds to the * embedding value at the same index and its parts are merged with the * text value in the request. Use `null` for entries that are text-only. * * The array length must match the number of values being embedded. In * the case of a single embedding, the array length must be 1. */ content: z.array(z.array(googleEmbeddingContentPartSchema).min(1).nullable()).optional() }) ) ); var GoogleGenerativeAIEmbeddingModel = class { constructor(modelId, config) { this.specificationVersion = "v2"; this.maxEmbeddingsPerCall = 2048; this.supportsParallelCalls = true; this.modelId = modelId; this.config = config; } get provider() { return this.config.provider; } async doEmbed({ values, headers, abortSignal, providerOptions }) { const googleOptions = await parseProviderOptions({ provider: "google", providerOptions, schema: googleGenerativeAIEmbeddingProviderOptions }); if (values.length > this.maxEmbeddingsPerCall) { throw new TooManyEmbeddingValuesForCallError({ provider: this.provider, modelId: this.modelId, maxEmbeddingsPerCall: this.maxEmbeddingsPerCall, values }); } const mergedHeaders = combineHeaders( await resolve(this.config.headers), headers ); const multimodalContent = googleOptions == null ? void 0 : googleOptions.content; if (multimodalContent != null && multimodalContent.length !== values.length) { throw new Error( `The number of multimodal content entries (${multimodalContent.length}) must match the number of values (${values.length}).` ); } if (values.length === 1) { const valueParts = multimodalContent == null ? void 0 : multimodalContent[0]; const textPart = values[0] ? [{ text: values[0] }] : []; const parts = valueParts != null ? [...textPart, ...valueParts] : [{ text: values[0] }]; const { responseHeaders: responseHeaders2, value: response2, rawValue: rawValue2 } = await postJsonToApi({ url: `${this.config.baseURL}/models/${this.modelId}:embedContent`, headers: mergedHeaders, body: { model: `models/${this.modelId}`, content: { parts }, outputDimensionality: googleOptions == null ? void 0 : googleOptions.outputDimensionality, taskType: googleOptions == null ? void 0 : googleOptions.taskType }, failedResponseHandler: googleFailedResponseHandler, successfulResponseHandler: createJsonResponseHandler( googleGenerativeAISingleEmbeddingResponseSchema ), abortSignal, fetch: this.config.fetch }); return { embeddings: [response2.embedding.values], usage: void 0, response: { headers: responseHeaders2, body: rawValue2 } }; } const { responseHeaders, value: response, rawValue } = await postJsonToApi({ url: `${this.config.baseURL}/models/${this.modelId}:batchEmbedContents`, headers: mergedHeaders, body: { requests: values.map((value, index) => { const valueParts = multimodalContent == null ? void 0 : multimodalContent[index]; const textPart = value ? [{ text: value }] : []; return { model: `models/${this.modelId}`, content: { role: "user", parts: valueParts != null ? [...textPart, ...valueParts] : [{ text: value }] }, outputDimensionality: googleOptions == null ? void 0 : googleOptions.outputDimensionality, taskType: googleOptions == null ? void 0 : googleOptions.taskType }; }) }, failedResponseHandler: googleFailedResponseHandler, successfulResponseHandler: createJsonResponseHandler( googleGenerativeAITextEmbeddingResponseSchema ), abortSignal, fetch: this.config.fetch }); return { embeddings: response.embeddings.map((item) => item.values), usage: void 0, response: { headers: responseHeaders, body: rawValue } }; } }; var googleGenerativeAITextEmbeddingResponseSchema = lazySchema( () => zodSchema( z.object({ embeddings: z.array(z.object({ values: z.array(z.number()) })) }) ) ); var googleGenerativeAISingleEmbeddingResponseSchema = lazySchema( () => zodSchema( z.object({ embedding: z.object({ values: z.array(z.number()) }) }) ) ); function convertJSONSchemaToOpenAPISchema(jsonSchema, isRoot = true) { if (jsonSchema == null) { return void 0; } if (isEmptyObjectSchema(jsonSchema)) { if (isRoot) { return void 0; } if (typeof jsonSchema === "object" && jsonSchema.description) { return { type: "object", description: jsonSchema.description }; } return { type: "object" }; } if (typeof jsonSchema === "boolean") { return { type: "boolean", properties: {} }; } const { type, description, required, properties, items, allOf, anyOf, oneOf, format, const: constValue, minLength, enum: enumValues } = jsonSchema; const result = {}; if (description) result.description = description; if (required) result.required = required; if (format) result.format = format; if (constValue !== void 0) { result.enum = [constValue]; } if (type) { if (Array.isArray(type)) { const hasNull = type.includes("null"); const nonNullTypes = type.filter((t) => t !== "null"); if (nonNullTypes.length === 0) { result.type = "null"; } else { result.anyOf = nonNullTypes.map((t) => ({ type: t })); if (hasNull) { result.nullable = true; } } } else { result.type = type; } } if (enumValues !== void 0) { result.enum = enumValues; } if (properties != null) { result.properties = Object.entries(properties).reduce( (acc, [key, value]) => { acc[key] = convertJSONSchemaToOpenAPISchema(value, false); return acc; }, {} ); } if (items) { result.items = Array.isArray(items) ? items.map((item) => convertJSONSchemaToOpenAPISchema(item, false)) : convertJSONSchemaToOpenAPISchema(items, false); } if (allOf) { result.allOf = allOf.map( (item) => convertJSONSchemaToOpenAPISchema(item, false) ); } if (anyOf) { if (anyOf.some( (schema) => typeof schema === "object" && (schema == null ? void 0 : schema.type) === "null" )) { const nonNullSchemas = anyOf.filter( (schema) => !(typeof schema === "object" && (schema == null ? void 0 : schema.type) === "null") ); if (nonNullSchemas.length === 1) { const converted = convertJSONSchemaToOpenAPISchema( nonNullSchemas[0], false ); if (typeof converted === "object") { result.nullable = true; Object.assign(result, converted); } } else { result.anyOf = nonNullSchemas.map( (item) => convertJSONSchemaToOpenAPISchema(item, false) ); result.nullable = true; } } else { result.anyOf = anyOf.map( (item) => convertJSONSchemaToOpenAPISchema(item, false) ); } } if (oneOf) { result.oneOf = oneOf.map( (item) => convertJSONSchemaToOpenAPISchema(item, false) ); } if (minLength !== void 0) { result.minLength = minLength; } return result; } function isEmptyObjectSchema(jsonSchema) { return jsonSchema != null && typeof jsonSchema === "object" && jsonSchema.type === "object" && (jsonSchema.properties == null || Object.keys(jsonSchema.properties).length === 0) && !jsonSchema.additionalProperties; } function convertToGoogleGenerativeAIMessages(prompt, options) { var _a, _b; const systemInstructionParts = []; const contents = []; let systemMessagesAllowed = true; const isGemmaModel = (_a = options == null ? void 0 : options.isGemmaModel) != null ? _a : false; const supportsFunctionResponseParts = (_b = options == null ? void 0 : options.supportsFunctionResponseParts) != null ? _b : true; for (const { role, content } of prompt) { switch (role) { case "system": { if (!systemMessagesAllowed) { throw new UnsupportedFunctionalityError({ functionality: "system messages are only supported at the beginning of the conversation" }); } systemInstructionParts.push({ text: content }); break; } case "user": { systemMessagesAllowed = false; const parts = []; for (const part of content) { switch (part.type) { case "text": { parts.push({ text: part.text }); break; } case "file": { const mediaType = part.mediaType === "image/*" ? "image/jpeg" : part.mediaType; parts.push( part.data instanceof URL ? { fileData: { mimeType: mediaType, fileUri: part.data.toString() } } : { inlineData: { mimeType: mediaType, data: convertToBase64(part.data) } } ); break; } } } contents.push({ role: "user", parts }); break; } case "assistant": { systemMessagesAllowed = false; contents.push({ role: "model", parts: content.map((part) => { var _a2, _b2, _c; const thoughtSignature = ((_b2 = (_a2 = part.providerOptions) == null ? void 0 : _a2.google) == null ? void 0 : _b2.thoughtSignature) != null ? String((_c = part.providerOptions.google) == null ? void 0 : _c.thoughtSignature) : void 0; switch (part.type) { case "text": { return part.text.length === 0 ? void 0 : { text: part.text, thoughtSignature }; } case "reasoning": { return part.text.length === 0 ? void 0 : { text: part.text, thought: true, thoughtSignature }; } case "file": { if (part.mediaType !== "image/png") { throw new UnsupportedFunctionalityError({ functionality: "Only PNG images are supported in assistant messages" }); } if (part.data instanceof URL) { throw new UnsupportedFunctionalityError({ functionality: "File data URLs in assistant messages are not supported" }); } return { inlineData: { mimeType: part.mediaType, data: convertToBase64(part.data) } }; } case "tool-call": { return { functionCall: { name: part.toolName, args: part.input }, thoughtSignature }; } } }).filter((part) => part !== void 0) }); break; } case "tool": { systemMessagesAllowed = false; const parts = []; for (const part of content) { const output = part.output; if (output.type === "content") { if (supportsFunctionResponseParts) { appendToolResultParts({ parts, part, output }); } else { appendLegacyToolResultParts({ parts, part, output }); } } else { parts.push({ functionResponse: { name: part.toolName, response: { name: part.toolName, content: output.value } } }); } } contents.push({ role: "user", parts }); break; } } } if (isGemmaModel && systemInstructionParts.length > 0 && contents.length > 0 && contents[0].role === "user") { const systemText = systemInstructionParts.map((part) => part.text).join("\n\n"); contents[0].parts.unshift({ text: systemText + "\n\n" }); } return { systemInstruction: systemInstructionParts.length > 0 && !isGemmaModel ? { parts: systemInstructionParts } : void 0, contents }; } function appendToolResultParts({ parts, part, output }) { const responseTextParts = []; const functionResponseParts = []; for (const contentPart of output.value) { switch (contentPart.type) { case "text": responseTextParts.push(contentPart.text); break; case "media": functionResponseParts.push({ inlineData: { mimeType: contentPart.mediaType, data: contentPart.data } }); break; } } const responseText = responseTextParts.length > 0 ? responseTextParts.join("\n") : "Tool executed successfully."; parts.push({ functionResponse: { name: part.toolName, response: { name: part.toolName, content: responseText }, ...functionResponseParts.length > 0 ? { parts: functionResponseParts } : {} } }); } function appendLegacyToolResultParts({ parts, part, output }) { for (const contentPart of output.value) { switch (contentPart.type) { case "text": parts.push({ functionResponse: { name: part.toolName, response: { name: part.toolName, content: contentPart.text } } }); break; case "media": parts.push( { inlineData: { mimeType: contentPart.mediaType, data: contentPart.data } }, { text: "Tool executed successfully and returned this image as a response" } ); break; default: parts.push({ text: JSON.stringify(contentPart) }); break; } } } function getModelPath(modelId) { return modelId.includes("/") ? modelId : `models/${modelId}`; } var googleGenerativeAIProviderOptions = lazySchema( () => zodSchema( z.object({ responseModalities: z.array(z.enum(["TEXT", "IMAGE"])).optional(), thinkingConfig: z.object({ thinkingBudget: z.number().optional(), includeThoughts: z.boolean().optional(), // https://ai.google.dev/gemini-api/docs/gemini-3?thinking=high#thinking_level thinkingLevel: z.enum(["minimal", "low", "medium", "high"]).optional() }).optional(), /** * Optional. * The name of the cached content used as context to serve the prediction. * Format: cachedContents/{cachedContent} */ cachedContent: z.string().optional(), /** * Optional. Enable structured output. Default is true. * * This is useful when the JSON Schema contains elements that are * not supported by the OpenAPI schema version that * Google Generative AI uses. You can use this to disable * structured outputs if you need to. */ structuredOutputs: z.boolean().optional(), /** * Optional. A list of unique safety settings for blocking unsafe content. */ safetySettings: z.array( z.object({ category: z.enum([ "HARM_CATEGORY_UNSPECIFIED", "HARM_CATEGORY_HATE_SPEECH", "HARM_CATEGORY_DANGEROUS_CONTENT", "HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_SEXUALLY_EXPLICIT", "HARM_CATEGORY_CIVIC_INTEGRITY" ]), threshold: z.enum([ "HARM_BLOCK_THRESHOLD_UNSPECIFIED", "BLOCK_LOW_AND_ABOVE", "BLOCK_MEDIUM_AND_ABOVE", "BLOCK_ONLY_HIGH", "BLOCK_NONE", "OFF" ]) }) ).optional(), threshold: z.enum([ "HARM_BLOCK_THRESHOLD_UNSPECIFIED", "BLOCK_LOW_AND_ABOVE", "BLOCK_MEDIUM_AND_ABOVE", "BLOCK_ONLY_HIGH", "BLOCK_NONE", "OFF" ]).optional(), /** * Optional. Enables timestamp understanding for audio-only files. * * https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/audio-understanding */ audioTimestamp: z.boolean().optional(), /** * Optional. Defines labels used in billing reports. Available on Vertex AI only. * * https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/add-labels-to-api-calls */ labels: z.record(z.string(), z.string()).optional(), /** * Optional. If specified, the media resolution specified will be used. * * https://ai.google.dev/api/generate-content#MediaResolution */ mediaResolution: z.enum([ "MEDIA_RESOLUTION_UNSPECIFIED", "MEDIA_RESOLUTION_LOW", "MEDIA_RESOLUTION_MEDIUM", "MEDIA_RESOLUTION_HIGH" ]).optional(), /** * Optional. Configures the image generation aspect ratio for Gemini models. * * https://ai.google.dev/gemini-api/docs/image-generation#aspect_ratios */ imageConfig: z.object({ aspectRatio: z.enum([ "1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9", "1:8", "8:1", "1:4", "4:1" ]).optional(), imageSize: z.enum(["1K", "2K", "4K", "512"]).optional() }).optional(), /** * Optional. Configuration for grounding retrieval. * Used to provide location context for Google Maps and Google Search grounding. * * https://cloud.google.com/vertex-ai/generative-ai/docs/grounding/grounding-with-google-maps */ retrievalConfig: z.object({ latLng: z.object({ latitude: z.number(), longitude: z.number() }).optional() }).optional(), /** * Optional. The service tier to use for the request. */ serviceTier: z.enum(["standard", "flex", "priority"]).optional() }) ) ); var VertexServiceTierMap = { standard: "SERVICE_TIER_STANDARD", flex: "SERVICE_TIER_FLEX", priority: "SERVICE_TIER_PRIORITY" }; function prepareTools({ tools, toolChoice, modelId }) { var _a; tools = (tools == null ? void 0 : tools.length) ? tools : void 0; const toolWarnings = []; const isLatest = [ "gemini-flash-latest", "gemini-flash-lite-latest", "gemini-pro-latest" ].some((id) => id === modelId); const isGemini2orNewer = modelId.includes("gemini-2") || modelId.includes("gemini-3") || modelId.includes("nano-banana") || isLatest; const supportsFileSearch = modelId.includes("gemini-2.5") || modelId.includes("gemini-3"); if (tools == null) { return { tools: void 0, toolConfig: void 0, toolWarnings }; } const hasFunctionTools = tools.some((tool) => tool.type === "function"); const hasProviderDefinedTools = tools.some( (tool) => tool.type === "provider-defined" ); if (hasFunctionTools && hasProviderDefinedTools) { const functionTools = tools.filter((tool) => tool.type === "function"); toolWarnings.push({ type: "unsupported-tool", tool: tools.find((tool) => tool.type === "function"), details: `Cannot mix function tools with provider-defined tools in the same request. Falling back to provider-defined tools only. The following function tools will be ignored: ${functionTools.map((t) => t.name).join(", ")}. Please use either function tools or provider-defined tools, but not both.` }); } if (hasProviderDefinedTools) { const googleTools2 = []; const providerDefinedTools = tools.filter( (tool) => tool.type === "provider-defined" ); providerDefinedTools.forEach((tool) => { switch (tool.id) { case "google.google_search": if (isGemini2orNewer) { googleTools2.push({ googleSearch: { ...tool.args } }); } else { toolWarnings.push({ type: "unsupported-tool", tool, details: "Google Search requires Gemini 2.0 or newer." }); } break; case "google.enterprise_web_search": if (isGemini2orNewer) { googleTools2.push({ enterpriseWebSearch: {} }); } else { toolWarnings.push({ type: "unsupported-tool", tool, details: "Enterprise Web Search requires Gemini 2.0 or newer." }); } break; case "google.url_context": if (isGemini2orNewer) { googleTools2.push({ urlContext: {} }); } else { toolWarnings.push({ type: "unsupported-tool", tool, details: "The URL context tool is not supported with other Gemini models than Gemini 2." }); } break; case "google.code_execution": if (isGemini2orNewer) { googleTools2.push({ codeExecution: {} }); } else { toolWarnings.push({ type: "unsupported-tool", tool, details: "The code execution tools is not supported with other Gemini models than Gemini 2." }); } break; case "google.file_search": if (supportsFileSearch) { googleTools2.push({ fileSearch: { ...tool.args } }); } else { toolWarnings.push({ type: "unsupported-tool", tool, details: "The file search tool is only supported with Gemini 2.5 models." }); } break; case "google.vertex_rag_store": if (isGemini2orNewer) { googleTools2.push({ retrieval: { vertex_rag_store: { rag_resources: { rag_corpus: tool.args.ragCorpus }, similarity_top_k: tool.args.topK } } }); } else { toolWarnings.push({ type: "unsupported-tool", tool, details: "The RAG store tool is not supported with other Gemini models than Gemini 2." }); } break; case "google.google_maps": if (isGemini2orNewer) { googleTools2.push({ googleMaps: {} }); } else { toolWarnings.push({ type: "unsupported-tool", tool, details: "The Google Maps grounding tool is not supported with Gemini models other than Gemini 2 or newer." }); } break; default: toolWarnings.push({ type: "unsupported-tool", tool }); break; } }); return { tools: googleTools2.length > 0 ? googleTools2 : void 0, toolConfig: void 0, toolWarnings