@ai-sdk/google
Version:
6,064 lines • 220 kB
JavaScript
import { WORKFLOW_DESERIALIZE, WORKFLOW_SERIALIZE, combineHeaders, convertBase64ToUint8Array, convertJsonResponseToolStream, convertToBase64, createEventSourceResponseHandler, createJsonErrorResponseHandler, createJsonResponseHandler, createNullLanguageModelUsage, createProviderExecutedToolFactory, createProviderStreamError, createToolNameMapping, delay, detectMediaType, downloadBlob, generateId, getFromApi, getRuntimeEnvironmentUserAgent, getTopLevelMediaType, isAbortError, isCustomReasoning, isFullMediaType, isUrlSupported, lazySchema, mapReasoningToProviderBudget, mapReasoningToProviderEffort, parseProviderOptions, postJsonToApi, resolve, resolveFullMediaType, resolveProviderOptions, resolveProviderReference, secureJsonParse, serializeModelOptions, stringifyWithBigIntAndCycles, withUserAgentSuffix, zodSchema } from "@ai-sdk/provider-utils";
import { z } from "zod/v4";
import { InvalidArgumentError, UnsupportedFunctionalityError } from "@ai-sdk/provider";
//#region src/continue-google-stream.ts
/**
* Concatenates the event streams of a long decode response that spans several
* requests (Gemini 4 long decode).
*
* Events of the current stream are passed through unchanged. When an event
* signals that the per-request cap was reached while budget remains
* (`getContinuationToken` returns a token), the next request is sent with that
* token once the current stream ends, and its events are appended after a
* `continuation-start` marker. The marker orders per-response state changes
* with buffered events, including when the new stream is empty. If the
* continuation request fails, the error is emitted as a failed parse result
* and the stream ends.
*/
function continueGoogleStream({ stream, getContinuationToken, continueStream }) {
let reader = stream.getReader();
let pendingContinuationToken;
return new ReadableStream({
async pull(controller) {
while (true) {
const { done, value } = await reader.read();
if (!done) {
if (value.success) {
const token = getContinuationToken(value.value);
if (token != null) pendingContinuationToken = token;
}
controller.enqueue(value);
return;
}
if (pendingContinuationToken == null) {
controller.close();
return;
}
const continuationToken = pendingContinuationToken;
pendingContinuationToken = void 0;
try {
reader = (await continueStream(continuationToken)).getReader();
controller.enqueue({ type: "continuation-start" });
return;
} catch (error) {
controller.enqueue({
success: false,
error,
rawValue: void 0
});
controller.close();
return;
}
}
},
async cancel(reason) {
await reader.cancel(reason);
}
});
}
//#endregion
//#region src/convert-google-usage.ts
function convertGoogleUsage(usage) {
if (usage == null) return createNullLanguageModelUsage();
const promptTokens = usage.promptTokenCount ?? 0;
const candidatesTokens = usage.candidatesTokenCount ?? 0;
const toolUsePromptTokens = usage.toolUsePromptTokenCount ?? 0;
const cachedContentTokens = usage.cachedContentTokenCount ?? 0;
const thoughtsTokens = usage.thoughtsTokenCount ?? 0;
const inputTokens = promptTokens + toolUsePromptTokens;
return {
inputTokens: {
total: inputTokens,
noCache: inputTokens - cachedContentTokens,
cacheRead: cachedContentTokens,
cacheWrite: void 0
},
outputTokens: {
total: candidatesTokens + thoughtsTokens,
text: candidatesTokens,
reasoning: thoughtsTokens
},
raw: usage
};
}
/**
* Sums the usage metadata of two requests, e.g. the hops of a long decode
* response that was continued automatically. Non-numeric fields are taken
* from the later request.
*/
function addGoogleUsageMetadata(a, b) {
if (a == null || b == null) return b ?? a;
return {
...a,
...b,
promptTokenCount: addCounts(a.promptTokenCount, b.promptTokenCount),
candidatesTokenCount: addCounts(a.candidatesTokenCount, b.candidatesTokenCount),
toolUsePromptTokenCount: addCounts(a.toolUsePromptTokenCount, b.toolUsePromptTokenCount),
totalTokenCount: addCounts(a.totalTokenCount, b.totalTokenCount),
cachedContentTokenCount: addCounts(a.cachedContentTokenCount, b.cachedContentTokenCount),
thoughtsTokenCount: addCounts(a.thoughtsTokenCount, b.thoughtsTokenCount),
promptTokensDetails: addTokenDetails(a.promptTokensDetails, b.promptTokensDetails),
cacheTokensDetails: addTokenDetails(a.cacheTokensDetails, b.cacheTokensDetails),
candidatesTokensDetails: addTokenDetails(a.candidatesTokensDetails, b.candidatesTokensDetails),
toolUsePromptTokensDetails: addTokenDetails(a.toolUsePromptTokensDetails, b.toolUsePromptTokensDetails)
};
}
function addCounts(a, b) {
return a == null && b == null ? b ?? a : (a ?? 0) + (b ?? 0);
}
function addTokenDetails(a, b) {
if (a == null || b == null) return b ?? a;
const tokenCounts = /* @__PURE__ */ new Map();
for (const { modality, tokenCount } of [...a, ...b]) tokenCounts.set(modality, (tokenCounts.get(modality) ?? 0) + tokenCount);
return Array.from(tokenCounts, ([modality, tokenCount]) => ({
modality,
tokenCount
}));
}
//#endregion
//#region src/google-prompt.ts
const googleVideoMetadataSchema = z.object({
/** Start offset as a duration in seconds, e.g. `'3.5s'`. */
startOffset: z.string().optional(),
/** End offset as a duration in seconds, e.g. `'10s'`. */
endOffset: z.string().optional(),
/** Frames per second in the range (0, 24]. Defaults to 1. */
fps: z.number().gt(0).max(24).optional()
});
//#endregion
//#region src/tool/code-execution.ts
const codeExecutionInputSchema = z.object({
language: z.string().describe("The programming language of the code."),
code: z.string().describe("The code to be executed.")
});
const codeExecutionOutputSchema = z.object({
outcome: z.string().describe("The outcome of the execution (e.g., \"OUTCOME_OK\")."),
output: z.string().describe("The output from the code execution.")
});
/**
* A tool that enables the model to generate and run Python code.
*
* @note Ensure the selected model supports Code Execution.
* Multi-tool usage with the code execution tool is typically compatible with Gemini >=2 models.
*
* @see https://ai.google.dev/gemini-api/docs/code-execution (Google AI)
* @see https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/code-execution-api (Vertex AI)
*/
const codeExecution = createProviderExecutedToolFactory({
id: "google.code_execution",
inputSchema: codeExecutionInputSchema,
outputSchema: codeExecutionOutputSchema
});
//#endregion
//#region src/convert-to-google-messages.ts
/**
* Sentinel value Google documents for replaying functionCall parts whose
* original thoughtSignature is not available to the client.
*
* See https://ai.google.dev/gemini-api/docs/thought-signatures.
*/
const SKIP_THOUGHT_SIGNATURE_VALIDATOR = "skip_thought_signature_validator";
const dataUrlRegex = /^data:([^;,]+);base64,(.+)$/s;
function parseBase64DataUrl(value) {
const match = dataUrlRegex.exec(value);
if (match == null) return;
return {
mediaType: match[1],
data: match[2]
};
}
function convertUrlToolResultPart(url) {
const parsedDataUrl = parseBase64DataUrl(url);
if (parsedDataUrl == null) return;
return { inlineData: {
mimeType: parsedDataUrl.mediaType,
data: parsedDataUrl.data
} };
}
function containsJSONSchemaReference(value, seen = /* @__PURE__ */ new WeakSet()) {
if (value === null || typeof value !== "object") return false;
if (seen.has(value)) return false;
seen.add(value);
if (Array.isArray(value)) return value.some((item) => containsJSONSchemaReference(item, seen));
return Object.entries(value).some(([key, nestedValue]) => key === "$ref" || containsJSONSchemaReference(nestedValue, seen));
}
function serializeFunctionResponseContent(value) {
if (containsJSONSchemaReference(value)) return stringifyWithBigIntAndCycles(value);
return containsNonJsonValue(value) ? secureJsonParse(stringifyWithBigIntAndCycles(value)) : value;
}
function containsNonJsonValue(value, ancestors = /* @__PURE__ */ new Set()) {
if (typeof value === "bigint") return true;
if (typeof value !== "object" || value === null) return false;
if (typeof value.toJSON === "function") return true;
if (ancestors.has(value)) return true;
ancestors.add(value);
try {
return Object.values(value).some((item) => containsNonJsonValue(item, ancestors));
} finally {
ancestors.delete(value);
}
}
function appendToolResultParts(parts, toolName, outputValue, toolCallId, includeFunctionCallIds = true, supportedUrls = {}) {
const functionResponseParts = [];
const responseTextParts = [];
for (const contentPart of outputValue) switch (contentPart.type) {
case "text":
responseTextParts.push(contentPart.text);
break;
case "file":
if (contentPart.data.type === "data") functionResponseParts.push({ inlineData: {
mimeType: resolveFullMediaType({ part: contentPart }),
data: convertToBase64(contentPart.data.data)
} });
else if (contentPart.data.type === "url") {
const url = contentPart.data.url.toString();
const convertedUrlPart = convertUrlToolResultPart(url);
const supportedUrl = contentPart.data.url.protocol === "gs:" && contentPart.data.originalUrl != null ? contentPart.data.originalUrl : url;
if (convertedUrlPart != null) functionResponseParts.push(convertedUrlPart);
else if (isFullMediaType(contentPart.mediaType) && isUrlSupported({
url: supportedUrl,
mediaType: contentPart.mediaType,
supportedUrls
})) functionResponseParts.push({ fileData: {
mimeType: contentPart.mediaType,
fileUri: supportedUrl
} });
else responseTextParts.push(JSON.stringify(contentPart));
} else responseTextParts.push(JSON.stringify(contentPart));
break;
default: responseTextParts.push(JSON.stringify(contentPart));
}
parts.push({ functionResponse: {
...includeFunctionCallIds && toolCallId != null ? { id: toolCallId } : {},
name: toolName,
response: {
name: toolName,
content: responseTextParts.length > 0 ? responseTextParts.join("\n") : "Tool executed successfully."
},
...functionResponseParts.length > 0 ? { parts: functionResponseParts } : {}
} });
}
function appendLegacyToolResultParts(parts, toolName, outputValue, toolCallId, includeFunctionCallIds = true) {
for (const contentPart of outputValue) switch (contentPart.type) {
case "text":
parts.push({ functionResponse: {
...includeFunctionCallIds && toolCallId != null ? { id: toolCallId } : {},
name: toolName,
response: {
name: toolName,
content: contentPart.text
}
} });
break;
case "file":
if (contentPart.data.type === "data") {
const topLevelMediaType = getTopLevelMediaType(contentPart.mediaType);
parts.push({ inlineData: {
mimeType: resolveFullMediaType({ part: contentPart }),
data: convertToBase64(contentPart.data.data)
} }, { text: `Tool executed successfully and returned this ${topLevelMediaType === "image" ? "image" : "file"} as a response` });
} else parts.push({ text: JSON.stringify(contentPart) });
break;
default: parts.push({ text: JSON.stringify(contentPart) });
}
}
function convertToGoogleMessages(prompt, options) {
const systemInstructionParts = [];
const contents = [];
let systemMessagesAllowed = true;
const isGemmaModel = options?.isGemmaModel ?? false;
const isGemini3Model = options?.isGemini3Model ?? false;
const onWarning = options?.onWarning;
const providerOptionsNames = options?.providerOptionsNames ?? ["google"];
const isVertexLike = !providerOptionsNames.includes("google");
const supportsFunctionResponseParts = options?.supportsFunctionResponseParts ?? true;
const includeFunctionCallIds = options?.includeFunctionCallIds ?? true;
const supportedFunctionResponseUrls = options?.supportedFunctionResponseUrls ?? {};
let sentinelInjected = false;
const missingSignatureToolNames = [];
const injectSkipSignature = (toolName) => {
missingSignatureToolNames.push(toolName);
sentinelInjected = true;
return SKIP_THOUGHT_SIGNATURE_VALIDATOR;
};
const readProviderOpts = (part) => {
for (const name of providerOptionsNames) {
const v = part.providerOptions?.[name];
if (v != null) return v;
}
if (isVertexLike) return part.providerOptions?.google;
return part.providerOptions?.googleVertex ?? part.providerOptions?.vertex;
};
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 rawVideoMetadata = readProviderOpts(part)?.videoMetadata;
const videoMetadata = rawVideoMetadata == null ? void 0 : googleVideoMetadataSchema.parse(rawVideoMetadata);
switch (part.data.type) {
case "url":
parts.push({
fileData: {
mimeType: resolveFullMediaType({ part }),
fileUri: part.data.url.protocol === "gs:" && part.data.originalUrl != null ? part.data.originalUrl : part.data.url.toString()
},
...videoMetadata != null ? { videoMetadata } : {}
});
break;
case "reference":
if (isVertexLike) throw new UnsupportedFunctionalityError({ functionality: "file parts with provider references" });
parts.push({
fileData: {
mimeType: resolveFullMediaType({ part }),
fileUri: resolveProviderReference({
reference: part.data.reference,
provider: "google"
})
},
...videoMetadata != null ? { videoMetadata } : {}
});
break;
case "text":
parts.push({
inlineData: {
mimeType: isFullMediaType(part.mediaType) ? part.mediaType : "text/plain",
data: convertToBase64(new TextEncoder().encode(part.data.text))
},
...videoMetadata != null ? { videoMetadata } : {}
});
break;
case "data": parts.push({
inlineData: {
mimeType: resolveFullMediaType({ part }),
data: convertToBase64(part.data.data)
},
...videoMetadata != null ? { videoMetadata } : {}
});
}
break;
}
}
contents.push({
role: "user",
parts
});
break;
}
case "assistant": {
systemMessagesAllowed = false;
let modelResponseHasSignedFunctionCall = false;
contents.push({
role: "model",
parts: content.map((part) => {
const providerOpts = readProviderOpts(part);
const thoughtSignature = providerOpts?.thoughtSignature != null ? String(providerOpts.thoughtSignature) : void 0;
switch (part.type) {
case "text": return part.text.length === 0 && thoughtSignature == null ? void 0 : {
text: part.text,
thoughtSignature
};
case "reasoning": return part.text.length === 0 && thoughtSignature == null ? void 0 : {
text: part.text,
thought: true,
thoughtSignature
};
case "reasoning-file":
switch (part.data.type) {
case "url": throw new UnsupportedFunctionalityError({ functionality: "File data URLs in assistant messages are not supported" });
case "data": return {
inlineData: {
mimeType: part.mediaType,
data: convertToBase64(part.data.data)
},
thought: true,
thoughtSignature
};
}
break;
case "file":
switch (part.data.type) {
case "url": throw new UnsupportedFunctionalityError({ functionality: "File data URLs in assistant messages are not supported" });
case "reference":
if (isVertexLike) throw new UnsupportedFunctionalityError({ functionality: "file parts with provider references" });
return {
fileData: {
mimeType: part.mediaType,
fileUri: resolveProviderReference({
reference: part.data.reference,
provider: "google"
})
},
...providerOpts?.thought === true ? { thought: true } : {},
thoughtSignature
};
case "text": return {
inlineData: {
mimeType: isFullMediaType(part.mediaType) ? part.mediaType : "text/plain",
data: convertToBase64(new TextEncoder().encode(part.data.text))
},
...providerOpts?.thought === true ? { thought: true } : {},
thoughtSignature
};
case "data": return {
inlineData: {
mimeType: part.mediaType,
data: convertToBase64(part.data.data)
},
...providerOpts?.thought === true ? { thought: true } : {},
thoughtSignature
};
}
break;
case "tool-call": {
if (part.providerExecuted === true && part.toolName === "code_execution") return { executableCode: codeExecutionInputSchema.parse(typeof part.input === "string" ? secureJsonParse(part.input) : part.input) };
const serverToolCallId = providerOpts?.serverToolCallId != null ? String(providerOpts.serverToolCallId) : void 0;
const serverToolType = providerOpts?.serverToolType != null ? String(providerOpts.serverToolType) : void 0;
const isServerToolCall = serverToolCallId != null && serverToolType != null;
const effectiveThoughtSignature = thoughtSignature ?? (isGemini3Model && !(!isServerToolCall && thoughtSignature == null && modelResponseHasSignedFunctionCall) ? injectSkipSignature(part.toolName) : void 0);
if (!isServerToolCall && thoughtSignature != null) modelResponseHasSignedFunctionCall = true;
if (isServerToolCall) return {
toolCall: {
toolType: serverToolType,
args: typeof part.input === "string" ? secureJsonParse(part.input) : part.input,
id: serverToolCallId
},
thoughtSignature: effectiveThoughtSignature
};
return {
functionCall: {
...includeFunctionCallIds && part.toolCallId != null ? { id: part.toolCallId } : {},
name: part.toolName,
args: parseToolCallInput(part.input)
},
thoughtSignature: effectiveThoughtSignature
};
}
case "tool-result": {
if (part.toolName === "code_execution" && part.output.type === "json") return { codeExecutionResult: codeExecutionOutputSchema.parse(part.output.value) };
const serverToolCallId = providerOpts?.serverToolCallId != null ? String(providerOpts.serverToolCallId) : void 0;
const serverToolType = providerOpts?.serverToolType != null ? String(providerOpts.serverToolType) : void 0;
if (serverToolCallId && serverToolType) return {
toolResponse: {
toolType: serverToolType,
response: part.output.type === "json" ? part.output.value : {},
id: serverToolCallId
},
thoughtSignature
};
return;
}
}
}).filter((part) => part !== void 0)
});
break;
}
case "tool": {
systemMessagesAllowed = false;
const parts = [];
for (const part of content) {
if (part.type === "tool-approval-response") continue;
const partProviderOpts = readProviderOpts(part);
const serverToolCallId = partProviderOpts?.serverToolCallId != null ? String(partProviderOpts.serverToolCallId) : void 0;
const serverToolType = partProviderOpts?.serverToolType != null ? String(partProviderOpts.serverToolType) : void 0;
if (serverToolCallId && serverToolType) {
const serverThoughtSignature = partProviderOpts?.thoughtSignature != null ? String(partProviderOpts.thoughtSignature) : void 0;
if (contents.length > 0) {
const lastContent = contents[contents.length - 1];
if (lastContent.role === "model") {
lastContent.parts.push({
toolResponse: {
toolType: serverToolType,
response: part.output.type === "json" ? part.output.value : {},
id: serverToolCallId
},
thoughtSignature: serverThoughtSignature
});
continue;
}
}
}
const output = part.output;
if (output.type === "content") {
if (supportsFunctionResponseParts) appendToolResultParts(parts, part.toolName, output.value, part.toolCallId, includeFunctionCallIds, supportedFunctionResponseUrls);
else appendLegacyToolResultParts(parts, part.toolName, output.value, part.toolCallId, includeFunctionCallIds);
} else {
const isError = output.type === "error-text" || output.type === "error-json" || output.type === "execution-denied";
parts.push({ functionResponse: {
...includeFunctionCallIds && part.toolCallId != null ? { id: part.toolCallId } : {},
name: part.toolName,
response: {
name: part.toolName,
...isError ? { error: output.type === "execution-denied" ? output.reason ?? "Tool call execution denied." : serializeFunctionResponseContent(output.value) } : { content: serializeFunctionResponseContent(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" });
}
if (sentinelInjected && onWarning != null) {
const uniqueToolNames = Array.from(new Set(missingSignatureToolNames));
onWarning({
type: "other",
message: `Replayed ${missingSignatureToolNames.length} \`functionCall\` part(s) for a Gemini 3 model without a \`thoughtSignature\` (tools: ${uniqueToolNames.map((name) => `\`${name}\``).join(", ")}). Injected the documented \`skip_thought_signature_validator\` sentinel to keep the request from failing with HTTP 400. The likely cause is application code that drops \`providerOptions.google.thoughtSignature\` when persisting or serializing assistant tool-call messages. See https://ai.google.dev/gemini-api/docs/thought-signatures.`
});
}
return {
systemInstruction: systemInstructionParts.length > 0 && !isGemmaModel ? { parts: systemInstructionParts } : void 0,
contents
};
}
function parseToolCallInput(input) {
if (typeof input !== "string") return input;
try {
return secureJsonParse(input);
} catch {
return input;
}
}
//#endregion
//#region src/download-tool-result-files.ts
/**
* Download tool result file URLs that Vertex cannot reference directly before
* converting the prompt to the Google request format.
*/
async function downloadToolResultFiles(prompt, { abortSignal, fetch, maxBytes, supportedUrls = {} }) {
const result = [];
for (const message of prompt) {
if (message.role === "assistant") {
const content = [];
for (const part of message.content) content.push(part.type === "tool-result" ? {
...part,
output: await downloadToolResultOutput(part.output, {
abortSignal,
fetch,
maxBytes,
supportedUrls
})
} : part);
result.push({
...message,
content
});
continue;
}
if (message.role === "tool") {
const content = [];
for (const part of message.content) {
if (part.type !== "tool-result") {
content.push(part);
continue;
}
content.push({
...part,
output: await downloadToolResultOutput(part.output, {
abortSignal,
fetch,
maxBytes,
supportedUrls
})
});
}
result.push({
...message,
content
});
continue;
}
result.push(message);
}
return result;
}
async function downloadToolResultOutput(output, { abortSignal, fetch, maxBytes, supportedUrls }) {
if (output.type !== "content") return output;
const value = [];
for (const part of output.value) {
if (part.type !== "file" || part.data.type !== "url") {
value.push(part);
continue;
}
if (isUrlSupported({
url: part.data.url.toString(),
mediaType: part.mediaType,
supportedUrls
})) {
value.push(part);
continue;
}
const blob = await downloadBlob(part.data.url.toString(), {
abortSignal,
fetch,
maxBytes
});
const data = new Uint8Array(await blob.arrayBuffer());
const detectedMediaType = detectMediaType({
data,
topLevelType: "image"
});
value.push({
...part,
data: {
type: "data",
data
},
mediaType: detectedMediaType ?? (blob.type && !isFullMediaType(part.mediaType) ? blob.type : part.mediaType)
});
}
return {
...output,
value
};
}
//#endregion
//#region src/get-model-path.ts
function getModelPath(modelId) {
return modelId.includes("/") ? modelId : `models/${modelId}`;
}
//#endregion
//#region src/google-error.ts
const getGoogleErrorSchema = () => z.object({
code: z.number().nullable(),
message: z.string(),
status: z.string(),
details: z.array(z.unknown()).nullish()
});
const googleErrorDataSchema = lazySchema(() => zodSchema(z.object({ error: getGoogleErrorSchema() })));
const googleFailedResponseHandler = createJsonErrorResponseHandler({
errorSchema: googleErrorDataSchema,
errorToMessage: (data) => data.error.message
});
function mapGoogleErrorStatusToHttpStatusCode(status) {
switch (status) {
case "CANCELLED": return 499;
case "INVALID_ARGUMENT":
case "FAILED_PRECONDITION":
case "OUT_OF_RANGE": return 400;
case "DEADLINE_EXCEEDED": return 504;
case "NOT_FOUND": return 404;
case "ALREADY_EXISTS":
case "ABORTED": return 409;
case "PERMISSION_DENIED": return 403;
case "UNAUTHENTICATED": return 401;
case "RESOURCE_EXHAUSTED": return 429;
case "UNIMPLEMENTED": return 501;
case "UNKNOWN":
case "INTERNAL":
case "DATA_LOSS": return 500;
case "UNAVAILABLE": return 503;
default: return;
}
}
//#endregion
//#region src/sanitize-response-json-schema.ts
/**
* Recursively replaces `const` with a single-value `enum` in the JSON Schema
* locations supported by Google because `responseJsonSchema` does not support
* `const`. All other schema properties are preserved.
*/
function sanitizeResponseJsonSchema(schema) {
const { const: constValue, properties, items, additionalProperties, anyOf, oneOf, ...result } = schema;
return {
...result,
...constValue !== void 0 ? { enum: [constValue] } : {},
...properties != null ? { properties: sanitizeDefinitions(properties) } : {},
...items != null ? { items: Array.isArray(items) ? items.map(sanitizeDefinition) : sanitizeDefinition(items) } : {},
...additionalProperties != null ? { additionalProperties: typeof additionalProperties === "boolean" ? additionalProperties : sanitizeDefinition(additionalProperties) } : {},
...anyOf != null ? { anyOf: anyOf.map(sanitizeDefinition) } : {},
...oneOf != null ? { oneOf: oneOf.map(sanitizeDefinition) } : {},
...result.$defs != null ? { $defs: sanitizeDefinitions(result.$defs) } : {}
};
}
function sanitizeDefinitions(definitions) {
return Object.fromEntries(Object.entries(definitions).map(([name, definition]) => [name, sanitizeDefinition(definition)]));
}
function sanitizeDefinition(definition) {
return typeof definition === "boolean" ? definition : sanitizeResponseJsonSchema(definition);
}
//#endregion
//#region src/google-language-model-options.ts
const googleLanguageModelOptions = lazySchema(() => zodSchema(z.object({
responseModalities: z.array(z.enum([
"TEXT",
"IMAGE",
"AUDIO"
])).optional(),
thinkingConfig: z.object({
thinkingBudget: z.number().optional(),
includeThoughts: z.boolean().optional(),
thinkingLevel: z.enum([
"minimal",
"low",
"medium",
"high",
"xhigh"
]).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 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. Controls the generation of people in images.
* Vertex AI only.
*/
personGeneration: z.enum([
"PERSON_GENERATION_UNSPECIFIED",
"ALLOW_ALL",
"ALLOW_ADULT",
"ALLOW_NONE"
]).optional(),
/**
* Optional. Controls whether generation of prominent people
* (celebrities) is allowed. When set together with
* `personGeneration`, `personGeneration` takes precedence.
* Vertex AI only.
*
* https://docs.cloud.google.com/vertex-ai/generative-ai/docs/reference/rest/v1/GenerationConfig
*/
prominentPeople: z.enum([
"PROMINENT_PEOPLE_UNSPECIFIED",
"ALLOW_PROMINENT_PEOPLE",
"BLOCK_PROMINENT_PEOPLE"
]).optional(),
/**
* Optional. The image output format for generated images.
* Vertex AI only.
*/
imageOutputOptions: z.object({
mimeType: z.enum(["image/jpeg", "image/png"]).optional(),
compressionQuality: z.number().optional()
}).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. When set to true, function call arguments will be streamed
* incrementally via partialArgs in streaming responses. Only supported
* on the Vertex AI API (not the Gemini API) and only for Gemini 3+
* models.
*
* @default false
*
* https://docs.cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling#streaming-fc
*/
streamFunctionCallArguments: z.boolean().optional(),
/**
* Optional. The service tier to use for the request. Sent as the
* `serviceTier` body field. Gemini API only.
*/
serviceTier: z.enum([
"standard",
"flex",
"priority"
]).optional(),
/**
* Optional. Resumes a response that stopped with `MAX_TOKENS` because it
* used up its `maxOutputTokens` budget (Gemini 4 long decode). Pass the
* `continuationToken` from the previous response's provider metadata
* together with the original prompt and a higher `maxOutputTokens` (the
* budget is cumulative across requests). Requests that reach the
* per-request cap while budget remains (`CONTINUATION`) are continued
* automatically. Sent as the `continuationToken` body field.
*/
continuationToken: z.string().optional(),
/**
* Optional. Vertex AI only. Sent as the
* `X-Vertex-AI-LLM-Shared-Request-Type` request header to select a
* shared (PayGo) tier. With Provisioned Throughput allocated and
* `requestType` unset, the request falls back to this tier only if
* PT capacity is exhausted.
*
* https://docs.cloud.google.com/vertex-ai/generative-ai/docs/priority-paygo
* https://docs.cloud.google.com/vertex-ai/generative-ai/docs/flex-paygo
*/
sharedRequestType: z.enum([
"priority",
"flex",
"standard"
]).optional(),
/**
* Optional. Vertex AI only. Sent as the `X-Vertex-AI-LLM-Request-Type`
* request header. Set to `'shared'` together with `sharedRequestType`
* to bypass Provisioned Throughput entirely.
*
* https://docs.cloud.google.com/vertex-ai/generative-ai/docs/priority-paygo
*/
requestType: z.enum(["shared"]).optional()
})));
//#endregion
//#region src/google-model-catalog.ts
const gemini1ModelPattern = /(^|[/.])gemini-1(?:[.-]|$)/i;
const gemini2ModelPattern = /(^|[/.])gemini-2(?:[.-]|$)/i;
const gemini25ModelPattern = /(^|[/.])gemini-2\.5(?:[.-]|$)/i;
const geminiModelPattern = /(^|[/.])gemini-/i;
/** Options accepted under providerOptions.google (or googleVertex on Vertex). */
const googleProviderOptions = { language: {
responseModalities: { description: "Response modalities to generate." },
thinkingConfig: { description: "Thinking budget, level, and thought output." },
cachedContent: { description: "Cached content to use as request context." },
structuredOutputs: { description: "Whether to send the JSON response schema." },
safetySettings: { description: "Safety settings for the request." },
threshold: { description: "Default threshold for configurable safety categories." },
audioTimestamp: { description: "Enable timestamp understanding for audio." },
labels: { description: "Billing labels. Vertex AI only." },
mediaResolution: { description: "Resolution used to process media inputs." },
imageConfig: { description: "Image generation configuration." },
retrievalConfig: { description: "Retrieval configuration for tools." },
streamFunctionCallArguments: { description: "Stream function arguments. Vertex AI only." },
serviceTier: { description: "Service tier. Gemini API only." },
continuationToken: { description: "Resume a response using its continuation token." },
sharedRequestType: { description: "Shared PayGo tier. Vertex AI only." },
requestType: { description: "Request routing type. Vertex AI only." }
} };
const standard = {
image: true,
objectGeneration: true,
tools: true,
streaming: true,
googleSearch: true,
urlContext: true
};
const googleModelCatalog = Object.fromEntries(Object.entries({
"gemini-nano-banana-2.1": {
image: true,
objectGeneration: false,
tools: false,
streaming: false,
googleSearch: true,
urlContext: false
},
"gemini-4-argon": standard,
"gemini-4-carbon": standard,
"gemini-4-hydrogen": standard,
"gemini-3.8-flash": standard,
"gemini-3.7-flash": standard,
"gemini-3.6-flash": standard,
"gemini-3.5-flash": standard,
"gemini-3.5-flash-lite": standard,
"gemini-3.1-pro-preview": standard,
"gemini-3.1-flash-image-preview": standard,
"gemini-3.1-flash-lite-preview": standard,
"gemini-3-pro-preview": standard,
"gemini-3-pro-image-preview": standard,
"gemini-3-flash-preview": standard,
"gemini-2.5-pro": standard,
"gemini-2.5-flash": standard,
"gemini-2.5-flash-lite": standard,
"gemini-2.5-flash-lite-preview-06-17": standard,
"gemini-2.0-flash": standard,
aqa: void 0,
"deep-research-max-preview-04-2026": void 0,
"deep-research-preview-04-2026": void 0,
"deep-research-pro-preview-12-2025": void 0,
"gemini-2.0-flash-001": void 0,
"gemini-2.0-flash-lite": void 0,
"gemini-2.0-flash-lite-001": void 0,
"gemini-2.5-computer-use-preview-10-2025": void 0,
"gemini-2.5-flash-image": void 0,
"gemini-2.5-flash-native-audio-latest": void 0,
"gemini-2.5-flash-native-audio-preview-09-2025": void 0,
"gemini-2.5-flash-native-audio-preview-12-2025": void 0,
"gemini-2.5-flash-preview-tts": void 0,
"gemini-2.5-pro-preview-tts": void 0,
"gemini-3.1-flash-tts-preview": void 0,
"gemini-3.1-pro-preview-customtools": void 0,
"gemini-flash-latest": void 0,
"gemini-flash-lite-latest": void 0,
"gemini-pro-latest": void 0,
"gemini-robotics-er-1.5-preview": void 0,
"gemma-3-12b-it": void 0,
"gemma-3-1b-it": void 0,
"gemma-3-27b-it": void 0,
"gemma-3-4b-it": void 0,
"gemma-3n-e2b-it": void 0,
"gemma-3n-e4b-it": void 0,
"nano-banana-pro-preview": void 0
}).filter(([, definition]) => definition != null).map(([modelId, documented]) => [modelId, createGoogleLanguageModelEntry(modelId, documented)]));
function getGoogleLanguageModelEntry(modelId) {
return Object.prototype.hasOwnProperty.call(googleModelCatalog, modelId) ? googleModelCatalog[modelId] : createGoogleLanguageModelEntry(modelId);
}
function createGoogleLanguageModelEntry(modelId, documented) {
const runtime = resolveRuntimeDefinition(modelId);
const declared = documented != null;
const id = modelId.toLowerCase();
const thinkingFormat = runtime.usesGemini3Features && !modelId.includes("gemini-3-pro-image") ? "level" : "budget";
const isHydrogen = /(^|[/.])gemini-4-hydrogen(?:-|$)/i.test(modelId);
const minLevel = isHydrogen ? "low" : getMinimumThinkingLevelForGemini3Model(modelId);
return {
capabilities: { language: {
inputs: {
image: documented?.image ?? "unknown",
audio: "unknown"
},
objectGeneration: documented?.objectGeneration ?? "unknown",
tools: {
usage: documented?.tools ?? "unknown",
streaming: documented?.streaming ?? "unknown",
gemini2: runtime.supportsGemini2Tools,
googleSearch: documented?.googleSearch ?? "unknown",
urlContext: documented?.urlContext ?? "unknown",
fileSearch: runtime.supportsFileSearch,
mixed: runtime.usesGemini3Features,
functionResponseParts: runtime.usesGemini3Features
},
reasoning: {
support: "unknown",
efforts: thinkingFormat === "level" ? [
...minLevel === "minimal" ? ["minimal"] : [],
"low",
"medium",
"high",
...isHydrogen ? ["xhigh"] : []
] : void 0,
minBudget: id.includes("2.5-pro") ? 128 : 0,
maxBudget: id.includes("2.5-pro") || id.includes("gemini-3-pro-image") ? 32768 : 24576
},
sampling: {
temperature: "unknown",
topP: "unknown",
topK: "unknown",
logprobs: false,
penalties: gemini25ModelPattern.test(modelId) ? { condition: "Only supported on the Vertex AI API." } : true
},
structuredOutput: { withFunctionTools: runtime.usesGemini3Features }
} },
providerOptions: { language: declareProviderOptions(declared ? true : "unknown", {
labels: { condition: "Only supported on the Vertex AI API." },
sharedRequestType: { condition: "Only supported on the Vertex AI API." },
requestType: { condition: "Only supported on the Vertex AI API." },
serviceTier: { condition: "Only supported on the Gemini API." },
streamFunctionCallArguments: { condition: "Only supported for streaming requests on the Vertex AI API with Gemini 3 or newer." }
}) },
requestConfig: { language: {
systemInstruction: id.startsWith("gemma-") ? "user" : "system",
thinkingFormat,
reasoningMaxOutputTokens: 65536,
jsonResponseTool: /(^|\/)gemini-/i.test(modelId)
} }
};
}
function declareProviderOptions(support, exceptions) {
return Object.fromEntries(Object.keys(googleProviderOptions.language).map((option) => [option, exceptions[option] ?? support]));
}
function isKnownPreGemini2Model(modelId) {
return gemini1ModelPattern.test(modelId) || /(^|[/.])gemini-pro(?:-vision)?$/i.test(modelId) || /(^|[/.])gemini-robotics-er-1\.5(?:[.-]|$)/i.test(modelId);
}
/**
* Classifies Gemini capabilities by excluding known older generations.
*
* Google model IDs are open-ended, so unrecognized Gemini IDs and aliases
* intentionally inherit the newest supported behavior. Add exceptions here
* only when a model is known to require a legacy request shape.
*/
function resolveRuntimeDefinition(modelId) {
const isGeminiModel = geminiModelPattern.test(modelId);
const isGemini2Model = gemini2ModelPattern.test(modelId);
const isKnownPreGemini2 = isKnownPreGemini2Model(modelId);
const usesGemini3Features = isGeminiModel && !(isKnownPreGemini2 || isGemini2Model);
return {
supportsGemini2Tools: isGeminiModel && !isKnownPreGemini2 || modelId.toLowerCase().includes("nano-banana"),
supportsFileSearch: gemini25ModelPattern.test(modelId) || usesGemini3Features,
usesGemini3Features
};
}
function getMinimumThinkingLevelForGemini3Model(modelId) {
const modelName = /(?:^|[/.])(gemini-[^/]*)$/i.exec(modelId)?.[1].toLowerCase();
if (modelName === "gemini-flash-latest") return "low";
const proVersionMatch = /^gemini-(\d+)(?:\.\d+)?-pro(?:$|-)/.exec(modelName ?? "");
if (Number(proVersionMatch?.[1]) >= 3) return "low";
const versionMatch = /^gemini-(\d+)\.(\d+)-flash(?:$|-(?!lite(?:-|$)))/.exec(modelName ?? "");
if (versionMatch == null) return "minimal";
const majorVersion = Number(versionMatch[1]);
const minorVersion = Number(versionMatch[2]);
return majorVersion > 3 || majorVersion === 3 && minorVersion >= 7 ? "low" : "minimal";
}
//#endregion
//#region src/google-model-capabilities.ts
/** Compatibility projection of the resolved catalog for existing request code. */
function getGoogleModelCapabilities(modelId) {
const { tools } = getGoogleLanguageModelEntry(modelId).capabilities.language;
return {
supportsGemini2Tools: tools.gemini2,
supportsFileSearch: tools.fileSearch,
usesGemini3Features: tools.mixed
};
}
//#endregion
//#region src/google-prepare-tools.ts
function prepareTools({ tools, toolChoice, modelId, isVertexProvider = false }) {
if (toolChoice?.type === "none") tools = tools?.filter((tool) => tool.type === "function");
tools = tools?.length ? tools : void 0;
const toolWarnings = [];
const { supportsGemini2Tools, supportsFileSearch, usesGemini3Features } = getGoogleModelCapabilities(modelId);
if (tools == null) return {
tools: void 0,
toolConfig: void 0,
toolWarnings
};
const hasFunctionTools = tools.some((tool) => tool.type === "function");
const hasProviderTools = tools.some((tool) => tool.type === "provider");
if (hasFunctionTools && hasProviderTools && !usesGemini3Features) toolWarnings.push({
type: "unsupported",
feature: `combination of function and provider-defined tools`
});
if (hasProviderTools) {
const googleTools = [];
tools.filter((tool) => tool.type === "provider").forEach((tool) => {
switch (tool.id) {
case "google.google_search":
if (supportsGemini2Tools) googleTools.push({ googleSearch: { ...tool.args } });
else toolWarnings.push({
type: "unsupported",
feature: `provider-defined tool ${tool.id}`,
details: "Google Search requires Gemini 2.0 or newer."
});
break;
case "google.enterprise_web_search":
if (supportsGemini2Tools) googleTools.push({ enterpriseWebSearch: {} });
else toolWarnings.push({
type: "unsupported",
feature: `provider-defined tool ${tool.id}`,
details: "Enterprise Web Search requires Gemini 2.0 or newer."
});
break;
case "google.url_context":
if (supportsGemini2Tools) googleTools.push({ urlContext: {} });
else toolWarnings.push({
type: "unsupported",
feature: `provider-defined tool ${tool.id}`,
details: "The URL context tool is not supported with other Gemini models than Gemini 2."
});
break;
case "google.code_execution":
if (supportsGemini2Tools) googleTools.push({ codeExecution: {} });
else toolWarnings.push({
type: "unsupported",
feature: `provider-defined tool ${tool.id}`,
details: "The code execution tool is not supported with other Gemini models than Gemini 2."
});
break;
case "google.file_search":
if (supportsFileSearch) googleTools.push({ fileSearch: { ...tool.args } });
else toolWarnings.push({
type: "unsupported",
feature: `provider-defined tool ${tool.id}`,
details: "The file search tool is only supported with Gemini 2.5 models and Gemini 3 models."
});
break;
case "google.vertex_rag_store":
if (supportsGemini2Tools) googleTools.push({ retrieval: { vertex_rag_store: {
rag_resources: { rag_corpus: tool.args.ragCorpus },
similarity_top_k: tool.args.topK
} } });
else toolWarnings.push({
type: "unsupported",
feature: `provider-defined tool ${tool.id}`,
details: "The RAG store tool is not supported with other Gemini models than Gemini 2."
});
break;
case "google.google_maps":
if (supportsGemini2Tools) googleTools.push({ googleMaps: {} });
else toolWarnings.push({
type: "unsupported",
feature: `provider-defined tool ${tool.id}`,
details: "The Google Maps grounding tool is not supported with Gemini models other than Gemini 2 or newer."
});
break;
default: toolWarnings.push({
type: "unsupported",
feature: `provider-defined tool ${tool.id}`
});
}
});
if (hasFunctionTools && usesGemini3Features && googleTools.length > 0) {
const functionDeclarations = [];
for (const tool of tools) if (tool.type === "function") functionDeclarations.push(prepareFunctionDeclaration(tool));
const combinedToolConfig = {
functionCallingConfig: { mode: "VALIDATED" },
...!isVertexProvider && { includeServerSideToolInvocations: true }
};
if (toolChoice != null) switch (toolChoice.type) {
case "auto": break;
case "none":
combinedToolConfig.functionCallingConfig = { mode: "NONE" };
break;
case "required":
combinedToolConfig.functionCallingConfig = { mode: "ANY" };
break;
case "tool": combinedToolConfig.functionCallingConfig = {
mode: "ANY",
allowedFunctionNames: [toolChoice.toolName]
};
}
return {
tools: [...googleTools, { functionDeclarations }],
toolConfig: combinedToolConfig,
toolWarnings
};
}
return {
tools: googleTools.length > 0 ? googleTools : void 0,
toolConfig: void 0,
toolWarnings
};
}
const functionDeclarations = [];
let hasStrictTools = false;
for (const tool of tools) switch (tool.type) {
case "function":
functionDeclarations.push(prepareFunctionDeclaration(tool));
if (tool.strict === true) hasStrictTools = true;
break;
default: toolWarnings.push({
type: "unsupported",
feature: `function tool ${tool.name}`
});
}
if (toolChoice == null) return {
tools: [{ functionDeclarations }],
toolConfig: hasStrictTools ? { functionCallingConfig: { mode: "VALIDATED" } } : void 0,
toolWarnings
};
const type = toolChoice.type;
switch (type) {
case "auto": return {
tools: [{ functionDeclarations }],
toolConfig: { functionCallingConfig: { mode: hasStrictTools ? "VALIDATED" : "AUTO" } },
toolWarnings
};
case "none": return {
tools: [{ functionDeclarations }],
toolConfig: { functionCallingConfig: { mode: "NONE" } },
toolWarnings
};
case "required": return {
tools: [{ functionDeclarations }],
toolConfig: { functionCallingConfig: { mode: "ANY" } },
toolWarnings
};
case "tool": return {
tools: [{ functionDeclarations }],
toolConfig: { functionCallingConfig: {
mode: "ANY",
allowedFunctionNames: [toolChoice.toolName]
} },
toolWarnings
};
default: throw new UnsupportedFunctionalityError({ functionality: `tool choice type: ${type}` });
}
}
function prepareFunctionDeclaration(tool) {
return {
name: tool.name,
description: tool.description ?? "",
parametersJsonSchema: tool.inputSchema
};
}
//#endregion
//#region src/google-json-accumulator.ts
/**
* Incrementally builds a JSON object from Google's streaming `partialArgs`
* chunks emitted during tool-call function calling. Tracks both the structured
* object and a running JSON text representation so callers can emit text deltas
* that, when concatenated, form valid nested JSON matching JSON.stringify output.
*
* Input: [{jsonPath:"$.location",stringValue:"Boston"}]
* Output: '{"location":"Boston"', then finalize() → closingDelta='}'
*/
var GoogleJSONAccumulator = class {
constructor() {
this.accumulatedArgs = {};
this.jsonText = "";
this.pathStack = [];
this.stringOpen = false;
}
/**
* Input: [{jsonPath:"$.brightness",numberValue:50}]
* Output: { currentJSON:{brightness:50}, textDelta:'{"brightness":50' }
*/
processPartialArgs(partialArgs) {
let delta = "";
for (const arg of partialArgs) {
const isArrayTerminator = arg.jsonPath.endsWith("[]");
const rawPath = (isArrayTerminator ? arg.jsonPath.slice(0, -2) : arg.jsonPath).replace(/^\$\./, "");
if (!rawPath || rawPath === "$") continue;
const segments = parsePath(rawPath);
if (!normalizeAndCheckIndexes(this.accumulatedArgs, segments)) continue;
const existingValue = getNestedValue(this.accumulatedArgs, segments);
if (arg.stringValue != null && existingValue !== void 0) {
const escaped = JSON.stringify(arg.stringValue).slice(1, -1);
setNestedValue(this.accumulatedArgs, segments, existingValue + arg.stringValue);
delta += escaped;
if (arg.willContinue !== true) delta += this.closeOpenString();
continue;
}
const resolved = resolvePartialArgValue(arg);
if (resolved == null) {
if (existingValue !== void 0) {
if (typeof existingValue === "string" && arg.willContinue !== true) delta += this.closeOpenString();
continue;
}
if (arg.willContinue === true) continue;
setNestedValue(this.accumulatedArgs, segments, isArrayTerminator ? [] : {});
delta += this.emitContainer(segments, isArrayTerminator);
continue;
}
setNestedValue(this.accumulatedArgs, segments, resolved.value);
delta += this.emitNavigationTo(segments, arg, resolved.json);
}
this.jsonText += delta;
return {
currentJSON: this.accumulatedArgs,
textDelta: delta
};
}
/**
* Input: jsonText='{"brightness":50', accumulatedArgs={brightness:50}
* Output: { finalJSON:'{"brightness":50}', closingDelta:'}' }
*/
finalize() {
const finalArgs = JSON.stringify(this.accumulatedArgs);
return {
finalJSON: finalArgs,
closingDelta: finalArgs.slice(this.jsonText.length)
};
}
/**
* Input: stringOpen=true
* Output: '"' (and stringOpen=false), or '' when no string is open
*/
closeOpenString() {
if (!this.stringOpen) return "";
this.stringOpen = false;
return "\"";
}
/**
* Input: pathStack=[] (first call) or pathStack=[root,...] (subsequent calls)
* Output: '{' (first call) or '' (subsequent calls)
*/
ensureRoot() {
if (this.pathStack.length === 0) {
this.pathStack.push({
segment: "",
isArray: false,
childCount: 0
});
return "{";
}
return "";
}
/**
* Emits the JSON text fragment needed to navigate from the current open
* path to the new leaf at `targetSegments`, then writes the value.
*
* Input: targetSegments=["recipe","name"], arg={jsonPath:"$.recipe.name",stringValue:"Lasagna"}, valueJson='"Lasagna"'
* Output: '{"recipe":{"name":"Lasagna"'
*/
emitNavigationTo(targetSegments, arg, valueJson) {
let fragment = this.closeOpenString();
fragment += this.ensureRoot();
const targetContainerSegments = targetSegments.slice(0, -1);
const leafSegment = targetSegments[targetSegments.length - 1];
const commonDepth = this.findCommonStackDepth(targetContainerSegments);
fragment += this.closeDownTo(commonDepth);
fragment += this.openDownTo(targetContainerSegments, leafSegment);
fragment += this.emitLeaf(leafSegment, arg, valueJson);
return fragment;
}
/**
* Navigates to `targetSegments` and opens a container there. The container
* stays open so later entries for its children continue inside it; it is
* closed by subsequent navigation or by `finalize()`.
*
* Input: targetSegments=["nested","tags"], isArray=true
* Output: '{"nested":{"tags":['
*/
emitContainer(targetSegments, isArray) {
let fragment = this.closeOpenString();
fragment += this.ensureRoot();
const commonDepth = this.findCommonStackDepth(targetSegments);
fragment += this.closeDownTo(commonDepth);
fragment += this.openDownTo(targetSegments, isArray ? 0 : "");
return fragment;
}
/**
* Returns the stack depth to preserve when navigating to a new target
* container path. Always >= 1 (the root is never popped).
*
* Input: stack=[root,"recipe","ingredients",0], target=["recipe","ingredients",1]
* Output: 3 (keep root+"recipe"+"ingredients")
*/
findCommonStackDepth(targetContainer) {
const maxDepth = Math.min(this.pathStack.length - 1, targetContainer.length);
let common = 0;
for (let i = 0; i < maxDepth; i++) if (this.pathStack[i + 1].segment === targetContainer[i]) common++;
else break;
return common + 1;
}
/**
* Closes containers from the current stack depth back down to `targetDepth`.
*
* Input: this.pathStack=[root,"recipe","ingredients",0], targetDepth=3
* Output: '}'
*/
closeDownTo(targetDepth) {
let fragment = "";
while (this.pathStack.length > targetDepth) {
const entry = this.pathStack.pop();
fragment += entry.isArray ? "]" : "}";
}
return fragment;
}
/**
* Opens containers from the current stack depth down to the full target
* container path, emitting opening `{`, `[`, keys, and commas as needed.
* `leafSegment` is used to determine if the innermost container is an array.
*
* Input: this.pathStack=[root], targetContainer=["recipe","ingredients"], leafSegment=0
* Output: '"recipe":{"ingredients":['
*/
openDownTo(targetContainer, leafSegment) {
let fragment = "";
const startIdx = this.pathStack.length - 1;
for (let i = startIdx; i < targetContainer.length; i++) {
const pathSegment = targetContainer[i];
const parentEntry = this.pathStack[this.pathStack.length - 1];
if (parentEntry.childCount > 0) fragment += ",";
parentEntry.childCount++;
if (typeof pathSegment === "string") fragment += `${JSON.stringify(pathSegment)}:`;
const isArray = typeof (i + 1 < targetContainer.length ? targetContainer[i + 1] : leafSegment) === "number";
fragment += isArray ? "[" : "{";
this.pathStack.push({
segment: pathSegment,
isArray,
childCount: 0
});
}
return fragment;
}
/**
* Emits the comma, key, and value for a leaf entry in the current container.
*
* Input: leafSegment="name", arg={stringValue:"Lasagna"}, valueJson='"Lasagna"'
* Output: '"name":"Lasagna"' (or ',"name":"Lasagna"' if container.childCount > 0)
*/
emitLeaf(leafSegment, arg, valueJson) {
let fragment = "";
const container = this.pathStack[this.pathStack.length - 1];
if (container.childCount > 0) fragment += ",";
container.childCount++;
if (typeof leafSegment === "string") fragment += `${JSON.stringify(leafSegment)}:`;
if (arg.stringValue != null && arg.willContinue) {
fragment += valueJson.slice(0, -1);
this.stringOpen = true;
} else fragment += valueJson;
return fragment;
}
};
/**
* Splits a dotted/bracketed JSON path like `recipe.ingredients[0].name` into segments.
*
* Input: "recipe.ingredients[0].name"
* Output: ["recipe", "ingredients", 0, "name"]
*/
function parsePath(rawPath) {
const segments = [];
for (const part of rawPath.split(".")) {
const bracketIdx = part.indexOf("[");
if (bracketIdx === -1) segments.push(part);
else {
if (bracketIdx > 0) segments.push(part.slice(0, bracketIdx));
for (const m of part.matchAll(/\[(\d+)\]/g)) segments.push(parseInt(m[1], 10));
}
}
return segments;
}
const hasOwn = Object.prototype.hasOwnProperty;
/**
* Checks only direct properties so path traversal never follows the prototype chain.
*/
function hasOwnProperty(obj, key) {
return hasOwn.call(obj, key);
}
/**
* Defines path values as own data properties so special keys like `__proto__`
* cannot invoke prototype setters while accumulating streamed arguments.
*/
function defineOwnProperty(obj, key, value) {
Object.defineProperty(obj, key, {
value,
enumerable: true,
configurable: true,
writable: true
});
}
/**
* Traverses a nested object along the given path segments and returns the leaf value.
*
* Input: ({recipe:{name:"Lasagna"}}, ["recipe","name"])
* Output: "Lasagna"
*/
function getNestedValue(obj, segments) {
let current = obj;
for (const pathSegment of segments) {
if (current == null || typeof current !== "object") return void 0;
const currentRecord = current;
if (!hasOwnProperty(currentRecord, pathSegment)) return void 0;
current = currentRecord[pathSegment];
}
return current;
}
/**
* Normalizes dotted array indexes in place and checks that every array index
* is an existing element or the next one. Arrays are streamed in order, and a
* sparse index would make `finalize()` serialize one `null` per hole, so a tiny
* response could allocate without bound.
* A missing or `null` parent is replaced with a new array, so it starts at 0.
*
* Input: ({items:["a"]}, ["items",1]) or ({items:["a"]}, ["items",5])
* Output: true or false
*/
function normalizeAndCheckIndexes(obj, segments) {
let current = obj;
for (let i = 0; i < segments.length; i++) {
let pathSegment = segments[i];
if (Array.isArray(current) && typeof pathSegment === "string") {
const index = Number(pathSegment);
if (Number.isInteger(index) && index >= 0 && index < 2 ** 32 - 1 && String(index) === pathSegment) {
pathSegment = index;
segments[i] = index;
}
}
if (typeof pathSegment === "number") {
const length = Array.isArray(current) ? current.length : current == null ? 0 : void 0;
if (length !== void 0 && pathSegment > length) return false;
}
current = current != null && typeof current === "object" && hasOwnProperty(current, pathSegment) ? current[pathSegment] : void 0;
}
return true;
}
/**
* Sets a value at a nested path, creating intermediate objects or arrays as needed.
*
* Input: obj={}, segments=["recipe","ingredients",0,"name"], value="Noodles"
* Output: {recipe:{ingredients:[{name:"Noodles"}]}}
*/
function setNestedValue(obj, segments, value) {
let current = obj;
for (let i = 0; i < segments.length - 1; i++) {
const pathSegment = segments[i];
const nextSeg = segments[i + 1];
if (!hasOwnProperty(current, pathSegment) || current[pathSegment] == null) defineOwnProperty(current, pathSegment, typeof nextSeg === "number" ? [] : {});
current = current[pathSegment];
}
defineOwnProperty(current, segments[segments.length - 1], value);
}
/**
* Extracts the first non-null typed value from a partial arg and returns it with its JSON representation.
*
* Input: arg={stringValue:"Boston"} or arg={numberValue:50}
* Output: {value:"Boston", json:'"Boston"'} or {value:50, json:'50'}
*/
function resolvePartialArgValue(arg) {
const value = arg.stringValue ?? arg.numberValue ?? arg.boolValue;
if (value != null) return {
value,
json: JSON.stringify(value)
};
if ("nullValue" in arg) return {
value: null,
json: "null"
};
}
//#endregion
//#region src/map-google-finish-reason.ts
function mapGoogleFinishReason({ finishReason, hasToolCalls }) {
switch (finishReason) {
case "STOP": return hasToolCalls ? "tool-calls" : "stop";
case "MAX_TOKENS":
case "CONTINUATION": return "length";
case "IMAGE_SAFETY":
case "RECITATION":
case "SAFETY":
case "BLOCKLIST":
case "PROHIBITED_CONTENT":
case "SPII": return "content-filter";
case "MALFORMED_FUNCTION_CALL": return "error";
default: return "other";
}
}
//#endregion
//#region src/google-language-model.ts
const configurableSafetySettingCategories = [
"HARM_CATEGORY_HATE_SPEECH",
"HARM_CATEGORY_DANGEROUS_CONTENT",
"HARM_CATEGORY_HARASSMENT",
"HARM_CATEGORY_SEXUALLY_EXPLICIT"
];
const googleCloudStorageFunctionResponseUrls = {
"image/png": [/^gs:\/\/.*$/],
"image/jpeg": [/^gs:\/\/.*$/],
"image/webp": [/^gs:\/\/.*$/],
"application/pdf": [/^gs:\/\/.*$/],
"text/plain": [/^gs:\/\/.*$/]
};
var GoogleLanguageModel = class GoogleLanguageModel {
static [WORKFLOW_SERIALIZE](model) {
return serializeModelOptions({
modelId: model.modelId,
config: model.config
});
}
static [WORKFLOW_DESERIALIZE](options) {
return new GoogleLanguageModel(options.modelId, options.config);
}
constructor(modelId, config) {
this.specificationVersion = "v4";
this.modelId = modelId;
this.config = config;
this.generateId = config.generateId ?? generateId;
}
get provider() {
return this.config.provider;
}
get supportedUrls() {
return this.config.supportedUrls?.() ?? {};
}
static async prepareRequest({ modelId, config, options: { prompt, maxOutputTokens, temperature, topP, topK, frequencyPenalty, presencePenalty, stopSequences, responseFormat, seed, tools, toolChoice, reasoning, providerOptions, abortSignal }, isStreaming = false }) {
const warnings = [];
const providerOptionsNames = config.provider.includes("vertex") ? ["googleVertex", "vertex"] : ["google"];
const { options: googleOptions } = await resolveProviderOptions({
providerOptions,
namespaces: [...providerOptionsNames, "google"],
resolution: "first",
schema: googleLanguageModelOptions
});
const isVertexProvider = config.provider.startsWith("google.vertex.");
if (tools?.some((tool) => tool.type === "provider" && tool.id === "google.vertex_rag_store") && !isVertexProvider) warnings.push({
type: "other",
message: `The 'vertex_rag_store' tool is only supported with the Google Vertex provider and might not be supported or could behave unexpectedly with the current Google provider (${config.provider}).`
});
if (googleOptions?.streamFunctionCallArguments && !isVertexProvider) warnings.push({
type: "other",
message: `'streamFunctionCallArguments' is only supported on the Vertex AI API and will be ignored with the current Google provider (${config.provider}). See https://docs.cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling#streaming-fc`
});
if (googleOptions?.serviceTier && isVertexProvider) warnings.push({
type: "other",
message: "'serviceTier' is a Gemini API option and is not supported on Vertex AI. Use 'sharedRequestType' (and optionally 'requestType') instead. See https://docs.cloud.google.com/vertex-ai/generative-ai/docs/priority-paygo"
});
if ((googleOptions?.sharedRequestType || googleOptions?.requestType) && !isVertexProvider) warnings.push({
type: "other",
message: `'sharedRequestType' and 'requestType' are Vertex AI options and are ignored with the current Google provider (${config.provider}).`
});
const vertexPaygoHeaders = isVertexProvider && (googleOptions?.sharedRequestType || googleOptions?.requestType) ? {
...googleOptions.sharedRequestType && { "X-Vertex-AI-LLM-Shared-Request-Type": googleOptions.sharedRequestType },
...googleOptions.requestType && { "X-Vertex-AI-LLM-Request-Type": googleOptions.requestType }
} : void 0;
const bodyServiceTier = isVertexProvider ? void 0 : googleOptions?.serviceTier;
let imageConfig = googleOptions?.imageConfig;
if (imageConfig != null && !isVertexProvider) {
const { personGeneration, prominentPeople, imageOutputOptions, ...geminiApiImageConfig } = imageConfig;
const droppedImageConfigFields = Object.entries({
personGeneration,
prominentPeople,
imageOutputOptions
}).filter(([, value]) => value != null).map(([key]) => `'imageConfig.${key}'`);
if (droppedImageConfigFields.length > 0) {
warnings.push({
type: "other",
message: `${droppedImageConfigFields.join(", ")} ${droppedImageConfigFields.length === 1 ? "is a Vertex AI option and is" : "are Vertex AI options and are"} ignored with the current Google provider (${config.provider}).`
});
imageConfig = geminiApiImageConfig;
}
}
const entry = getGoogleLanguageModelEntry(modelId);
const isGemmaModel = entry.requestConfig.language.systemInstruction === "user";
const isGemini25DeveloperApiModel = !isVertexProvider && entry.capabilities.language.sampling.penalties !== true;
if (isGemini25DeveloperApiModel && frequencyPenalty != null) warnings.push({
type: "unsupported",
feature: "frequencyPenalty"
});
if (isGemini25DeveloperApiModel && presencePenalty != null) warnings.push({
type: "unsupported",
feature: "presencePenalty"
});
const usesGemini3Features = entry.capabilities.language.tools.functionResponseParts;
const supportedFunctionResponseUrls = usesGemini3Features && config.downloadToolResultFiles?.supportsGoogleCloudStorageUrls ? googleCloudStorageFunctionResponseUrls : void 0;
const { contents, systemInstruction } = convertToGoogleMessages(config.downloadToolResultFiles ? await downloadToolResultFiles(prompt, {
abortSignal,
maxBytes: config.downloadToolResultFiles.maxBytes,
fetch: config.downloadToolResultFiles.fetch,
supportedUrls: supportedFunctionResponseUrls
}) : prompt, {
isGemmaModel,
isGemini3Model: usesGemini3Features,
onWarning: (warning) => warnings.push(warning),
providerOptionsNames,
supportsFunctionResponseParts: usesGemini3Features,
includeFunctionCallIds: !isVertexProvider,
supportedFunctionResponseUrls
});
const { tools: googleTools, toolConfig: googleToolConfig, toolWarnings } = prepareTools({
tools,
toolChoice,
modelId,
isVertexProvider
});
const toolNameMapping = createToolNameMapping({
tools,
providerToolNames: { "google.code_execution": "code_execution" }
});
const resolvedThinking = googleOptions?.thinkingConfig?.thinkingBudget != null || googleOptions?.thinkingConfig?.thinkingLevel != null ? void 0 : resolveThinkingConfig({
reasoning,
modelId,
warnings
});
const thinkingConfig = googleOptions?.thinkingConfig || resolvedThinking ? {
...resolvedThinking,
...googleOptions?.thinkingConfig
} : void 0;
const streamFunctionCallArguments = isStreaming && isVertexProvider ? googleOptions?.streamFunctionCallArguments ?? false : void 0;
const safetyThreshold = googleOptions?.threshold;
const safetySettings = googleOptions?.safetySettings ?? (safetyThreshold != null ? configurableSafetySettingCategories.map((category) => ({
category,
threshold: safetyThreshold
})) : void 0);
const toolConfig = googleToolConfig || streamFunctionCallArguments || googleOptions?.retrievalConfig ? {
...googleToolConfig,
...streamFunctionCallArguments && { functionCallingConfig: {
...googleToolConfig?.functionCallingConfig,
streamFunctionCallArguments: true
} },
...googleOptions?.retrievalConfig && { retrievalConfig: googleOptions.retrievalConfig }
} : void 0;
return {
args: {
generationConfig: {
maxOutputTokens,
temperature,
topK,
topP,
frequencyPenalty: isGemini25DeveloperApiModel ? void 0 : frequencyPenalty,
presencePenalty: isGemini25DeveloperApiModel ? void 0 : presencePenalty,
stopSequences,
seed,
responseMimeType: responseFormat?.type === "json" ? "application/json" : void 0,
responseJsonSchema: responseFormat?.type === "json" && responseFormat.schema != null && (googleOptions?.structuredOutputs ?? true) ? sanitizeResponseJsonSchema(responseFormat.schema) : void 0,
...googleOptions?.audioTimestamp && { audioTimestamp: googleOptions.audioTimestamp },
responseModalities: googleOptions?.responseModalities,
thinkingConfig,
...googleOptions?.mediaResolution && { mediaResolution: googleOptions.mediaResolution },
...imageConfig && { imageConfig }
},
contents,
systemInstruction: isGemmaModel ? void 0 : systemInstruction,
safetySettings,
tools: googleTools,
toolConfig,
cachedContent: googleOptions?.cachedContent,
labels: googleOptions?.labels,
serviceTier: bodyServiceTier,
...googleOptions?.continuationToken != null && { continuationToken: googleOptions.continuationToken }
},
warnings: [...warnings, ...toolWarnings],
providerOptionsNames,
extraHeaders: vertexPaygoHeaders,
toolNameMapping
};
}
async getArgs(options, { isStreaming = false } = {}) {
const { responseFormat, toolChoice } = options;
const tools = toolChoice?.type === "none" ? options.tools?.filter((tool) => tool.type === "function") : options.tools;
const entry = getGoogleLanguageModelEntry(this.modelId);
const { structuredOutput } = entry.capabilities.language;
let jsonResponseTool;
if (responseFormat?.type === "json" && responseFormat.schema != null && tools?.some((tool) => tool.type === "function") && entry.requestConfig.language.jsonResponseTool && (!structuredOutput.withFunctionTools || toolChoice?.type === "required" || toolChoice?.type === "tool")) {
let name = "json";
for (let suffix = 1; tools.some((tool) => tool.name === name); suffix++) name = `json_${suffix}`;
jsonResponseTool = {
type: "function",
name,
description: responseFormat.description ?? "Respond with a JSON object.",
inputSchema: responseFormat.schema
};
}
return {
...await GoogleLanguageModel.prepareRequest({
modelId: this.modelId,
config: this.config,
options: jsonResponseTool == null ? options : {
...options,
responseFormat: void 0,
tools: [...tools ?? [], jsonResponseTool],
toolChoice: toolChoice?.type === "tool" ? toolChoice : toolChoice?.type === "none" ? {
type: "tool",
toolName: jsonResponseTool.name
} : { type: "required" }
},
isStreaming
}),
jsonResponseToolName: jsonResponseTool?.name
};
}
static convertGenerateContentResponse({ config, response, responseHeaders, warnings, providerOptionsNames, toolNameMapping, jsonResponseToolName, initialPromptTokenCount, groundingMetadataPerRequest }) {
const wrapProviderMetadata = (payload) => Object.fromEntries(providerOptionsNames.map((name) => [name, payload]));
const candidate = response.candidates?.[0];
const promptBlockReason = response.promptFeedback?.blockReason;
const confirmedPromptBlockReason = isConfirmedPromptBlockReason(promptBlockReason) ? promptBlockReason : void 0;
const isPromptBlocked = candidate?.finishReason == null && confirmedPromptBlockReason != null;
const rawFinishReason = candidate?.finishReason ?? confirmedPromptBlockReason;
const content = [];
const parts = candidate?.content?.parts ?? [];
const usageMetadata = response.usageMetadata;
let lastCodeExecutionToolCallId;
let lastServerToolCallId;
for (const part of parts) if ("executableCode" in part && part.executableCode?.code) {
const toolCallId = config.generateId();
lastCodeExecutionToolCallId = toolCallId;
content.push({
type: "tool-call",
toolCallId,
toolName: toolNameMapping?.toCustomToolName("code_execution") ?? "code_execution",
input: JSON.stringify(part.executableCode),
providerExecuted: true
});
} else if ("codeExecutionResult" in part && part.codeExecutionResult) content.push({
type: "tool-result",
toolCallId: lastCodeExecutionToolCallId,
toolName: toolNameMapping?.toCustomToolName("code_execution") ?? "code_execution",
result: {
outcome: part.codeExecutionResult.outcome,
output: part.codeExecutionResult.output ?? ""
}
});
else if ("text" in part && part.text != null) {
if (jsonResponseToolName != null && part.thought !== true) continue;
const thoughtSignatureMetadata = part.thoughtSignature ? wrapProviderMetadata({ thoughtSignature: part.thoughtSignature }) : void 0;
if (part.text.length === 0) {
if (thoughtSignatureMetadata != null && content.length > 0) {
const lastContent = content[content.length - 1];
lastContent.providerMetadata = thoughtSignatureMetadata;
}
} else content.push({
type: part.thought === true ? "reasoning" : "text",
text: part.text,
providerMetadata: thoughtSignatureMetadata
});
} else if ("functionCall" in part && part.functionCall.name != null) {
if (part.functionCall.name === jsonResponseToolName) {
content.push({
type: "text",
text: typeof part.functionCall.args === "string" ? part.functionCall.args : JSON.stringify(part.functionCall.args ?? {}),
...part.thoughtSignature ? { providerMetadata: wrapProviderMetadata({ thoughtSignature: part.thoughtSignature }) } : {}
});
continue;
}
content.push({
type: "tool-call",
toolCallId: part.functionCall.id || config.generateId(),
toolName: part.functionCall.name,
input: JSON.stringify(part.functionCall.args ?? {}),
providerMetadata: part.thoughtSignature ? wrapProviderMetadata({ thoughtSignature: part.thoughtSignature }) : void 0
});
} else if ("inlineData" in part) {
const hasThought = part.thought === true;
const hasThoughtSignature = !!part.thoughtSignature;
content.push({
type: hasThought ? "reasoning-file" : "file",
data: {
type: "data",
data: part.inlineData.data
},
mediaType: part.inlineData.mimeType,
providerMetadata: hasThoughtSignature ? wrapProviderMetadata({ thoughtSignature: part.thoughtSignature }) : void 0
});
} else if ("toolCall" in part && part.toolCall) {
const toolCallId = part.toolCall.id || config.generateId();
lastServerToolCallId = toolCallId;
content.push({
type: "tool-call",
toolCallId,
toolName: `server:${part.toolCall.toolType}`,
input: JSON.stringify(part.toolCall.args ?? {}),
providerExecuted: true,
dynamic: true,
providerMetadata: part.thoughtSignature ? wrapProviderMetadata({
thoughtSignature: part.thoughtSignature,
serverToolCallId: toolCallId,
serverToolType: part.toolCall.toolType
}) : wrapProviderMetadata({
serverToolCallId: toolCallId,
serverToolType: part.toolCall.toolType
})
});
} else if ("toolResponse" in part && part.toolResponse) {
const responseToolCallId = lastServerToolCallId || part.toolResponse.id || config.generateId();
content.push({
type: "tool-result",
toolCallId: responseToolCallId,
toolName: `server:${part.toolResponse.toolType}`,
result: part.toolResponse.response ?? {},
providerMetadata: part.thoughtSignature ? wrapProviderMetadata({
thoughtSignature: part.thoughtSignature,
serverToolCallId: responseToolCallId,
serverToolType: part.toolResponse.toolType
}) : wrapProviderMetadata({
serverToolCallId: responseToolCallId,
serverToolType: part.toolResponse.toolType
})
});
lastServerToolCallId = void 0;
}
const sources = extractSources({
groundingMetadata: candidate?.groundingMetadata,
generateId: config.generateId
}) ?? [];
for (const source of sources) content.push(source);
return {
content,
finishReason: {
unified: isPromptBlocked ? "content-filter" : mapGoogleFinishReason({
finishReason: rawFinishReason,
hasToolCalls: content.some((part) => part.type === "tool-call" && !part.providerExecuted)
}),
raw: rawFinishReason
},
usage: convertGoogleUsage(usageMetadata),
warnings,
providerMetadata: wrapProviderMetadata({
promptFeedback: response.promptFeedback ?? null,
groundingMetadata: candidate?.groundingMetadata ?? null,
...groundingMetadataPerRequest != null ? { groundingMetadataPerRequest } : {},
urlContextMetadata: candidate?.urlContextMetadata ?? null,
safetyRatings: candidate?.safetyRatings ?? null,
usageMetadata: usageMetadata ?? null,
finishMessage: candidate?.finishMessage ?? null,
serviceTier: usageMetadata?.serviceTier ?? responseHeaders?.["x-gemini-service-tier"] ?? null,
...candidate?.continuationToken != null ? { continuationToken: candidate.continuationToken } : {},
...initialPromptTokenCount != null ? { initialPromptTokenCount } : {}
}),
response: { id: response.responseId ?? void 0 }
};
}
async doGenerate(options) {
const { args, warnings, providerOptionsNames, extraHeaders, toolNameMapping, jsonResponseToolName } = await this.getArgs(options);
const generateContent = async (body) => postJsonToApi({
url: `${this.config.baseURL}/${getModelPath(this.modelId)}:generateContent`,
headers: combineHeaders(await resolve(this.config.headers), options.headers, extraHeaders),
body,
failedResponseHandler: googleFailedResponseHandler,
successfulResponseHandler: createJsonResponseHandler(responseSchema),
abortSignal: options.abortSignal,
fetch: this.config.fetch
});
let { responseHeaders, value: response, rawValue: rawResponse } = await generateContent(args);
const content = [];
const groundingMetadataPerRequest = [];
let usageMetadata;
const firstPromptTokenCount = response.usageMetadata?.promptTokenCount;
let isContinued = false;
while (true) {
const candidate = response.candidates?.[0];
groundingMetadataPerRequest.push(candidate?.groundingMetadata ?? null);
const continuationToken = candidate?.finishReason === "CONTINUATION" ? candidate.continuationToken : void 0;
usageMetadata = addGoogleUsageMetadata(usageMetadata, response.usageMetadata ?? void 0);
if (continuationToken == null) break;
content.push(...GoogleLanguageModel.convertGenerateContentResponse({
config: this.config,
response,
warnings,
providerOptionsNames,
toolNameMapping,
jsonResponseToolName
}).content);
isContinued = true;
({responseHeaders, value: response, rawValue: rawResponse} = await generateContent({
...args,
continuationToken
}));
}
const lastResult = GoogleLanguageModel.convertGenerateContentResponse({
config: this.config,
response: {
...response,
usageMetadata
},
responseHeaders,
warnings,
providerOptionsNames,
toolNameMapping,
jsonResponseToolName,
initialPromptTokenCount: isContinued ? firstPromptTokenCount : void 0,
groundingMetadataPerRequest: isContinued ? groundingMetadataPerRequest : void 0
});
const result = {
...lastResult,
content: [...content, ...lastResult.content]
};
return {
...result,
request: { body: args },
response: {
...result.response,
headers: responseHeaders,
body: rawResponse
}
};
}
async doStream(options) {
const { args, warnings, providerOptionsNames, extraHeaders, toolNameMapping, jsonResponseToolName } = await this.getArgs(options, { isStreaming: true });
const wrapProviderMetadata = (payload) => Object.fromEntries(providerOptionsNames.map((name) => [name, payload]));
const streamGenerateContent = async (body) => postJsonToApi({
url: `${this.config.baseURL}/${getModelPath(this.modelId)}:streamGenerateContent?alt=sse`,
headers: combineHeaders(await resolve(this.config.headers), options.headers, extraHeaders),
body,
failedResponseHandler: googleFailedResponseHandler,
successfulResponseHandler: createEventSourceResponseHandler(chunkSchema),
abortSignal: options.abortSignal,
fetch: this.config.fetch
});
const { responseHeaders, value: firstResponse } = await streamGenerateContent(args);
const groundingMetadataPerRequest = [];
let requestGroundingMetadata = null;
const response = continueGoogleStream({
stream: firstResponse,
getContinuationToken: (chunk) => {
const candidate = chunk.candidates?.[0];
if (candidate?.groundingMetadata != null) requestGroundingMetadata = candidate.groundingMetadata;
return candidate?.finishReason === "CONTINUATION" ? candidate.continuationToken ?? void 0 : void 0;
},
continueStream: async (continuationToken) => {
const { value } = await streamGenerateContent({
...args,
continuationToken
});
groundingMetadataPerRequest.push(requestGroundingMetadata);
requestGroundingMetadata = null;
return value;
}
});
let finishReason = {
unified: "interrupted",
raw: void 0
};
let usage = void 0;
let continuedUsage = void 0;
let initialPromptTokenCount = void 0;
let promptFeedback = null;
let lastGroundingMetadata = null;
let lastUrlContextMetadata = null;
let lastSafetyRatings = null;
let lastFinishMessage = null;
let lastContinuationToken = null;
let confirmedPromptBlockReason;
const generateId = this.config.generateId;
let hasToolCalls = false;
let hasEmittedResponseMetadata = false;
let currentTextBlockId = null;
let currentReasoningBlockId = null;
let blockCounter = 0;
const emittedSourceKeys = /* @__PURE__ */ new Set();
let lastCodeExecutionToolCallId;
let lastServerToolCallId;
const activeStreamingToolCalls = [];
const finishActiveStreamingToolCall = (controller) => {
const active = activeStreamingToolCalls.pop();
if (active == null) return;
if (currentTextBlockId !== null) {
controller.enqueue({
type: "text-end",
id: currentTextBlockId
});
currentTextBlockId = null;
}
if (currentReasoningBlockId !== null) {
controller.enqueue({
type: "reasoning-end",
id: currentReasoningBlockId
});
currentReasoningBlockId = null;
}
const { finalJSON, closingDelta } = active.accumulator.finalize();
if (closingDelta.length > 0) controller.enqueue({
type: "tool-input-delta",
id: active.toolCallId,
delta: closingDelta,
providerMetadata: active.providerMetadata
});
controller.enqueue({
type: "tool-input-end",
id: active.toolCallId,
providerMetadata: active.providerMetadata
});
controller.enqueue({
type: "tool-call",
toolCallId: active.toolCallId,
toolName: active.toolName,
input: finalJSON,
providerMetadata: active.providerMetadata
});
hasToolCalls = true;
};
const result = {
stream: response.pipeThrough(new TransformStream({
start(controller) {
controller.enqueue({
type: "stream-start",
warnings
});
},
transform(chunk, controller) {
if ("type" in chunk) {
finishReason = {
unified: "interrupted",
raw: void 0
};
return;
}
if (options.includeRawChunks) controller.enqueue({
type: "raw",
rawValue: chunk.rawValue
});
if (!chunk.success) {
if (finishReason.unified === "interrupted") finishReason = {
unified: "error",
raw: void 0
};
controller.enqueue({
type: "error",
error: chunk.error
});
return;
}
const value = chunk.value;
if (value.error != null) {
finishReason = {
unified: "error",
raw: void 0
};
const error = value.error;
const statusCode = error.code ?? mapGoogleErrorStatusToHttpStatusCode(error.status);
controller.enqueue({
type: "error",
error: createProviderStreamError({
message: error.message,
type: error.status,
code: error.code ?? void 0,
statusCode,
isRetryable: statusCode != null && (statusCode === 408 || statusCode === 409 || statusCode === 429 || statusCode >= 500),
data: error
})
});
return;
}
if (!hasEmittedResponseMetadata && value.responseId != null) {
hasEmittedResponseMetadata = true;
controller.enqueue({
type: "response-metadata",
id: value.responseId
});
}
const usageMetadata = value.usageMetadata;
if (usageMetadata != null) usage = usageMetadata;
if (value.promptFeedback != null && confirmedPromptBlockReason == null) {
promptFeedback = value.promptFeedback;
if (isConfirmedPromptBlockReason(value.promptFeedback.blockReason)) {
confirmedPromptBlockReason = value.promptFeedback.blockReason;
finishReason = {
unified: "content-filter",
raw: confirmedPromptBlockReason
};
}
}
const candidate = value.candidates?.[0];
if (candidate != null) {
if (candidate.groundingMetadata != null) lastGroundingMetadata = candidate.groundingMetadata;
if (candidate.urlContextMetadata != null) lastUrlContextMetadata = candidate.urlContextMetadata;
if (candidate.safetyRatings != null) lastSafetyRatings = candidate.safetyRatings;
if (candidate.finishMessage != null) lastFinishMessage = candidate.finishMessage;
if (candidate.continuationToken != null) lastContinuationToken = candidate.continuationToken;
}
if (confirmedPromptBlockReason != null || candidate == null) return;
const content = candidate.content;
const sources = extractSources({
groundingMetadata: candidate.groundingMetadata,
generateId,
emittedSourceKeys
});
if (sources != null) for (const source of sources) controller.enqueue(source);
if (content != null) {
const parts = content.parts ?? [];
for (const part of parts) if ("executableCode" in part && part.executableCode?.code) {
const toolCallId = generateId();
lastCodeExecutionToolCallId = toolCallId;
controller.enqueue({
type: "tool-call",
toolCallId,
toolName: toolNameMapping.toCustomToolName("code_execution"),
input: JSON.stringify(part.executableCode),
providerExecuted: true
});
} else if ("codeExecutionResult" in part && part.codeExecutionResult) {
const toolCallId = lastCodeExecutionToolCallId;
if (toolCallId) controller.enqueue({
type: "tool-result",
toolCallId,
toolName: toolNameMapping.toCustomToolName("code_execution"),
result: {
outcome: part.codeExecutionResult.outcome,
output: part.codeExecutionResult.output ?? ""
}
});
} else if ("text" in part && part.text != null) {
const thoughtSignatureMetadata = part.thoughtSignature ? wrapProviderMetadata({ thoughtSignature: part.thoughtSignature }) : void 0;
if (part.text.length === 0) {
if (thoughtSignatureMetadata != null) {
if (currentTextBlockId !== null) controller.enqueue({
type: "text-delta",
id: currentTextBlockId,
delta: "",
providerMetadata: thoughtSignatureMetadata
});
else {
if (currentReasoningBlockId !== null) {
controller.enqueue({
type: "reasoning-end",
id: currentReasoningBlockId
});
currentReasoningBlockId = null;
}
const id = String(blockCounter++);
const type = part.thought === true ? "reasoning" : "text";
controller.enqueue({
type: `${type}-start`,
id,
providerMetadata: thoughtSignatureMetadata
});
controller.enqueue({
type: `${type}-delta`,
id,
delta: "",
providerMetadata: thoughtSignatureMetadata
});
controller.enqueue({
type: `${type}-end`,
id
});
}
}
} else if (part.thought === true) {
if (currentTextBlockId !== null) {
controller.enqueue({
type: "text-end",
id: currentTextBlockId
});
currentTextBlockId = null;
}
if (currentReasoningBlockId === null) {
currentReasoningBlockId = String(blockCounter++);
controller.enqueue({
type: "reasoning-start",
id: currentReasoningBlockId,
providerMetadata: thoughtSignatureMetadata
});
}
controller.enqueue({
type: "reasoning-delta",
id: currentReasoningBlockId,
delta: part.text,
providerMetadata: thoughtSignatureMetadata
});
} else {
if (currentReasoningBlockId !== null) {
controller.enqueue({
type: "reasoning-end",
id: currentReasoningBlockId
});
currentReasoningBlockId = null;
}
if (currentTextBlockId === null) {
currentTextBlockId = String(blockCounter++);
controller.enqueue({
type: "text-start",
id: currentTextBlockId,
providerMetadata: thoughtSignatureMetadata
});
}
controller.enqueue({
type: "text-delta",
id: currentTextBlockId,
delta: part.text,
providerMetadata: thoughtSignatureMetadata
});
}
} else if ("inlineData" in part) {
if (currentTextBlockId !== null) {
controller.enqueue({
type: "text-end",
id: currentTextBlockId
});
currentTextBlockId = null;
}
if (currentReasoningBlockId !== null) {
controller.enqueue({
type: "reasoning-end",
id: currentReasoningBlockId
});
currentReasoningBlockId = null;
}
const hasThought = part.thought === true;
const fileMeta = !!part.thoughtSignature ? wrapProviderMetadata({ thoughtSignature: part.thoughtSignature }) : void 0;
controller.enqueue({
type: hasThought ? "reasoning-file" : "file",
mediaType: part.inlineData.mimeType,
data: {
type: "data",
data: part.inlineData.data
},
providerMetadata: fileMeta
});
} else if ("toolCall" in part && part.toolCall) {
const toolCallId = part.toolCall.id || generateId();
lastServerToolCallId = toolCallId;
const serverMeta = wrapProviderMetadata({
...part.thoughtSignature ? { thoughtSignature: part.thoughtSignature } : {},
serverToolCallId: toolCallId,
serverToolType: part.toolCall.toolType
});
controller.enqueue({
type: "tool-call",
toolCallId,
toolName: `server:${part.toolCall.toolType}`,
input: JSON.stringify(part.toolCall.args ?? {}),
providerExecuted: true,
dynamic: true,
providerMetadata: serverMeta
});
} else if ("toolResponse" in part && part.toolResponse) {
const responseToolCallId = lastServerToolCallId || part.toolResponse.id || generateId();
const serverMeta = wrapProviderMetadata({
...part.thoughtSignature ? { thoughtSignature: part.thoughtSignature } : {},
serverToolCallId: responseToolCallId,
serverToolType: part.toolResponse.toolType
});
controller.enqueue({
type: "tool-result",
toolCallId: responseToolCallId,
toolName: `server:${part.toolResponse.toolType}`,
result: part.toolResponse.response ?? {},
providerMetadata: serverMeta
});
lastServerToolCallId = void 0;
} else if ("functionCall" in part) {
const providerMeta = part.thoughtSignature ? wrapProviderMetadata({ thoughtSignature: part.thoughtSignature }) : void 0;
const isStreamingChunk = part.functionCall.partialArgs != null || part.functionCall.name != null && part.functionCall.willContinue === true;
const isTerminalChunk = part.functionCall.name == null && part.functionCall.args == null && part.functionCall.partialArgs == null && part.functionCall.willContinue == null;
const isCompleteCall = part.functionCall.name != null && part.functionCall.args != null && part.functionCall.partialArgs == null;
const isNoArgsCompleteCall = part.functionCall.name != null && part.functionCall.args == null && part.functionCall.partialArgs == null && part.functionCall.willContinue !== true;
if (!isStreamingChunk && (isCompleteCall || isNoArgsCompleteCall)) {
if (currentTextBlockId !== null) {
controller.enqueue({
type: "text-end",
id: currentTextBlockId
});
currentTextBlockId = null;
}
if (currentReasoningBlockId !== null) {
controller.enqueue({
type: "reasoning-end",
id: currentReasoningBlockId
});
currentReasoningBlockId = null;
}
}
if (isStreamingChunk) {
if (part.functionCall.name != null) {
const toolCallId = part.functionCall.id || generateId();
const accumulator = new GoogleJSONAccumulator();
activeStreamingToolCalls.push({
toolCallId,
toolName: part.functionCall.name,
accumulator,
providerMetadata: providerMeta
});
controller.enqueue({
type: "tool-input-start",
id: toolCallId,
toolName: part.functionCall.name,
providerMetadata: providerMeta
});
if (part.functionCall.partialArgs != null) {
const partialArgs = part.functionCall.partialArgs;
const { textDelta } = accumulator.processPartialArgs(partialArgs);
if (textDelta.length > 0) controller.enqueue({
type: "tool-input-delta",
id: toolCallId,
delta: textDelta,
providerMetadata: providerMeta
});
if (part.functionCall.willContinue !== true && partialArgs.every((arg) => arg.willContinue !== true)) finishActiveStreamingToolCall(controller);
}
} else if (part.functionCall.partialArgs != null && activeStreamingToolCalls.length > 0) {
const active = activeStreamingToolCalls[activeStreamingToolCalls.length - 1];
const partialArgs = part.functionCall.partialArgs;
const { textDelta } = active.accumulator.processPartialArgs(partialArgs);
if (textDelta.length > 0) controller.enqueue({
type: "tool-input-delta",
id: active.toolCallId,
delta: textDelta,
providerMetadata: providerMeta
});
if (part.functionCall.willContinue !== true && partialArgs.every((arg) => arg.willContinue !== true)) finishActiveStreamingToolCall(controller);
}
} else if (isTerminalChunk && activeStreamingToolCalls.length > 0) finishActiveStreamingToolCall(controller);
else if (isCompleteCall) {
const toolCallId = part.functionCall.id || generateId();
const toolName = part.functionCall.name;
const args = typeof part.functionCall.args === "string" ? part.functionCall.args : JSON.stringify(part.functionCall.args ?? {});
controller.enqueue({
type: "tool-input-start",
id: toolCallId,
toolName,
providerMetadata: providerMeta
});
controller.enqueue({
type: "tool-input-delta",
id: toolCallId,
delta: args,
providerMetadata: providerMeta
});
controller.enqueue({
type: "tool-input-end",
id: toolCallId,
providerMetadata: providerMeta
});
controller.enqueue({
type: "tool-call",
toolCallId,
toolName,
input: args,
providerMetadata: providerMeta
});
hasToolCalls = true;
} else if (isNoArgsCompleteCall) {
const toolCallId = part.functionCall.id || generateId();
const toolName = part.functionCall.name;
controller.enqueue({
type: "tool-input-start",
id: toolCallId,
toolName,
providerMetadata: providerMeta
});
controller.enqueue({
type: "tool-input-end",
id: toolCallId,
providerMetadata: providerMeta
});
controller.enqueue({
type: "tool-call",
toolCallId,
toolName,
input: "{}",
providerMetadata: providerMeta
});
hasToolCalls = true;
}
}
}
if (candidate.finishReason === "CONTINUATION") {
if (continuedUsage == null) initialPromptTokenCount = usage?.promptTokenCount;
continuedUsage = addGoogleUsageMetadata(continuedUsage, usage);
usage = void 0;
}
if (candidate.finishReason != null) finishReason = {
unified: mapGoogleFinishReason({
finishReason: candidate.finishReason,
hasToolCalls
}),
raw: candidate.finishReason
};
},
flush(controller) {
usage = addGoogleUsageMetadata(continuedUsage, usage);
if (currentTextBlockId !== null) controller.enqueue({
type: "text-end",
id: currentTextBlockId
});
if (currentReasoningBlockId !== null) controller.enqueue({
type: "reasoning-end",
id: currentReasoningBlockId
});
controller.enqueue({
type: "finish",
finishReason,
usage: convertGoogleUsage(usage),
providerMetadata: wrapProviderMetadata({
promptFeedback,
groundingMetadata: lastGroundingMetadata,
...groundingMetadataPerRequest.length > 0 ? { groundingMetadataPerRequest: [...groundingMetadataPerRequest, requestGroundingMetadata] } : {},
urlContextMetadata: lastUrlContextMetadata,
safetyRatings: lastSafetyRatings,
usageMetadata: usage ?? null,
finishMessage: lastFinishMessage,
serviceTier: usage?.serviceTier ?? responseHeaders?.["x-gemini-service-tier"] ?? null,
...lastContinuationToken != null && (finishReason.raw === "MAX_TOKENS" || finishReason.raw === "CONTINUATION") ? { continuationToken: lastContinuationToken } : {},
...initialPromptTokenCount != null ? { initialPromptTokenCount } : {}
})
});
}
})),
response: { headers: responseHeaders },
request: { body: args }
};
return {
...result,
stream: jsonResponseToolName == null ? result.stream : result.stream.pipeThrough(convertJsonResponseToolStream(jsonResponseToolName))
};
}
};
function resolveThinkingConfig({ reasoning, modelId, warnings }) {
if (!isCustomReasoning(reasoning)) return;
if (getGoogleLanguageModelEntry(modelId).requestConfig.language.thinkingFormat === "level") return resolveGemini3ThinkingConfig({
reasoning,
modelId,
warnings
});
return resolveGemini25ThinkingConfig({
reasoning,
modelId,
warnings
});
}
function resolveGemini3ThinkingConfig({ reasoning, modelId, warnings }) {
const thinkingLevels = getGoogleLanguageModelEntry(modelId).capabilities.language.reasoning.efforts;
const thinkingLevel = mapReasoningToProviderEffort({
reasoning,
effortMap: Object.fromEntries((thinkingLevels ?? []).map((level) => [level, level])),
warnings
});
if (thinkingLevel == null) return;
return { thinkingLevel };
}
function resolveGemini25ThinkingConfig({ reasoning, modelId, warnings }) {
const entry = getGoogleLanguageModelEntry(modelId);
const minThinkingBudget = entry.capabilities.language.reasoning.minBudget;
if (reasoning === "none" && minThinkingBudget === 0) return { thinkingBudget: 0 };
return { thinkingBudget: mapReasoningToProviderBudget({
reasoning,
maxOutputTokens: entry.requestConfig.language.reasoningMaxOutputTokens,
maxReasoningBudget: entry.capabilities.language.reasoning.maxBudget,
minReasoningBudget: minThinkingBudget,
warnings
}) };
}
function extractSources({ groundingMetadata, generateId, emittedSourceKeys }) {
if (!groundingMetadata?.groundingChunks) return;
const sources = [];
const addSource = (source, key) => {
if (emittedSourceKeys?.has(key)) return;
emittedSourceKeys?.add(key);
sources.push(source);
};
for (const chunk of groundingMetadata.groundingChunks) if (chunk.web != null) addSource({
type: "source",
sourceType: "url",
id: generateId(),
url: chunk.web.uri,
title: chunk.web.title ?? void 0
}, JSON.stringify(["url", chunk.web.uri]));
else if (chunk.image != null) addSource({
type: "source",
sourceType: "url",
id: generateId(),
url: chunk.image.sourceUri,
title: chunk.image.title ?? void 0
}, JSON.stringify(["url", chunk.image.sourceUri]));
else if (chunk.retrievedContext != null) {
const uri = chunk.retrievedContext.uri;
const fileSearchStore = chunk.retrievedContext.fileSearchStore;
if (uri && (uri.startsWith("http://") || uri.startsWith("https://"))) addSource({
type: "source",
sourceType: "url",
id: generateId(),
url: uri,
title: chunk.retrievedContext.title ?? void 0
}, JSON.stringify(["url", uri]));
else if (uri) {
const title = chunk.retrievedContext.title ?? "Unknown Document";
let mediaType = "application/octet-stream";
let filename = void 0;
if (uri.endsWith(".pdf")) {
mediaType = "application/pdf";
filename = uri.split("/").pop();
} else if (uri.endsWith(".txt")) {
mediaType = "text/plain";
filename = uri.split("/").pop();
} else if (uri.endsWith(".docx")) {
mediaType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
filename = uri.split("/").pop();
} else if (uri.endsWith(".doc")) {
mediaType = "application/msword";
filename = uri.split("/").pop();
} else if (uri.match(/\.(md|markdown)$/)) {
mediaType = "text/markdown";
filename = uri.split("/").pop();
} else filename = uri.split("/").pop();
addSource({
type: "source",
sourceType: "document",
id: generateId(),
mediaType,
title,
filename
}, JSON.stringify(["document", uri]));
} else if (fileSearchStore) {
const title = chunk.retrievedContext.title ?? "Unknown Document";
addSource({
type: "source",
sourceType: "document",
id: generateId(),
mediaType: "application/octet-stream",
title,
filename: fileSearchStore.split("/").pop()
}, JSON.stringify([
"file-search",
fileSearchStore,
title
]));
}
} else if (chunk.maps != null) {
if (chunk.maps.uri) addSource({
type: "source",
sourceType: "url",
id: generateId(),
url: chunk.maps.uri,
title: chunk.maps.title ?? void 0
}, JSON.stringify(["url", chunk.maps.uri]));
}
return sources.length > 0 ? sources : void 0;
}
const getGroundingMetadataSchema = () => z.object({
webSearchQueries: z.array(z.string()).nullish(),
imageSearchQueries: z.array(z.string()).nullish(),
retrievalQueries: z.array(z.string()).nullish(),
searchEntryPoint: z.object({ renderedContent: z.string() }).nullish(),
groundingChunks: z.array(z.object({
web: z.object({
uri: z.string(),
title: z.string().nullish()
}).nullish(),
image: z.object({
sourceUri: z.string(),
imageUri: z.string(),
title: z.string().nullish(),
domain: z.string().nullish()
}).nullish(),
retrievedContext: z.object({
uri: z.string().nullish(),
title: z.string().nullish(),
text: z.string().nullish(),
fileSearchStore: z.string().nullish(),
customMetadata: z.array(z.object({
key: z.string(),
stringValue: z.string().nullish(),
numericValue: z.number().nullish(),
stringListValue: z.object({ values: z.array(z.string()).nullish() }).nullish()
})).nullish()
}).nullish(),
maps: z.object({
uri: z.string().nullish(),
title: z.string().nullish(),
text: z.string().nullish(),
placeId: z.string().nullish()
}).nullish()
})).nullish(),
groundingSupports: z.array(z.object({
segment: z.object({
startIndex: z.number().nullish(),
endIndex: z.number().nullish(),
text: z.string().nullish()
}).nullish(),
segment_text: z.string().nullish(),
groundingChunkIndices: z.array(z.number()).nullish(),
supportChunkIndices: z.array(z.number()).nullish(),
confidenceScores: z.array(z.number()).nullish(),
confidenceScore: z.array(z.number()).nullish()
})).nullish(),
retrievalMetadata: z.union([z.object({ webDynamicRetrievalScore: z.number() }), z.object({})]).nullish()
});
const partialArgSchema = () => z.object({
jsonPath: z.string(),
stringValue: z.string().nullish(),
numberValue: z.number().nullish(),
boolValue: z.boolean().nullish(),
nullValue: z.unknown().nullish(),
willContinue: z.boolean().nullish()
});
const getContentSchema = () => z.object({ parts: z.array(z.union([
z.object({
functionCall: z.object({
id: z.string().nullish(),
name: z.string().nullish(),
args: z.unknown().nullish(),
partialArgs: z.array(partialArgSchema()).nullish(),
willContinue: z.boolean().nullish()
}),
thoughtSignature: z.string().nullish()
}),
z.object({
inlineData: z.object({
mimeType: z.string(),
data: z.string()
}),
thought: z.boolean().nullish(),
thoughtSignature: z.string().nullish()
}),
z.object({
toolCall: z.object({
toolType: z.string(),
args: z.unknown().nullish(),
id: z.string()
}),
thoughtSignature: z.string().nullish()
}),
z.object({
toolResponse: z.object({
toolType: z.string(),
response: z.unknown().nullish(),
id: z.string()
}),
thoughtSignature: z.string().nullish()
}),
z.object({
executableCode: z.object({
language: z.string(),
code: z.string()
}).nullish(),
codeExecutionResult: z.object({
outcome: z.string(),
output: z.string().nullish()
}).nullish(),
text: z.string().nullish(),
thought: z.boolean().nullish(),
thoughtSignature: z.string().nullish()
})
])).nullish() });
const getSafetyRatingSchema = () => z.object({
category: z.string().nullish(),
probability: z.string().nullish(),
probabilityScore: z.number().nullish(),
severity: z.string().nullish(),
severityScore: z.number().nullish(),
blocked: z.boolean().nullish()
});
const tokenDetailsSchema = () => z.array(z.object({
modality: z.string(),
tokenCount: z.number()
}).loose()).nullish();
const usageSchema$1 = () => z.object({
cachedContentTokenCount: z.number().nullish(),
thoughtsTokenCount: z.number().nullish(),
promptTokenCount: z.number().nullish(),
candidatesTokenCount: z.number().nullish(),
toolUsePromptTokenCount: z.number().nullish(),
totalTokenCount: z.number().nullish(),
trafficType: z.string().nullish(),
serviceTier: z.string().nullish(),
promptTokensDetails: tokenDetailsSchema(),
cacheTokensDetails: tokenDetailsSchema(),
candidatesTokensDetails: tokenDetailsSchema(),
toolUsePromptTokensDetails: tokenDetailsSchema()
}).loose();
const getUrlContextMetadataSchema = () => z.object({ urlMetadata: z.array(z.object({
retrievedUrl: z.string(),
urlRetrievalStatus: z.string()
})).nullish() });
const responseSchema = lazySchema(() => zodSchema(z.object({
responseId: z.string().nullish(),
candidates: z.array(z.object({
content: getContentSchema().nullish().or(z.object({}).strict()),
finishReason: z.string().nullish(),
finishMessage: z.string().nullish(),
continuationToken: z.string().nullish(),
safetyRatings: z.array(getSafetyRatingSchema()).nullish(),
groundingMetadata: getGroundingMetadataSchema().nullish(),
urlContextMetadata: getUrlContextMetadataSchema().nullish()
})).nullish(),
usageMetadata: usageSchema$1().nullish(),
promptFeedback: z.object({
blockReason: z.string().nullish(),
safetyRatings: z.array(getSafetyRatingSchema()).nullish()
}).nullish()
})));
const chunkSchema = lazySchema(() => zodSchema(z.object({
error: getGoogleErrorSchema().nullish(),
responseId: z.string().nullish(),
candidates: z.array(z.object({
content: getContentSchema().nullish(),
finishReason: z.string().nullish(),
finishMessage: z.string().nullish(),
continuationToken: z.string().nullish(),
safetyRatings: z.array(getSafetyRatingSchema()).nullish(),
groundingMetadata: getGroundingMetadataSchema().nullish(),
urlContextMetadata: getUrlContextMetadataSchema().nullish()
})).nullish(),
usageMetadata: usageSchema$1().nullish(),
promptFeedback: z.object({
blockReason: z.string().nullish(),
safetyRatings: z.array(getSafetyRatingSchema()).nullish()
}).nullish()
})));
function isConfirmedPromptBlockReason(blockReason) {
return blockReason != null && blockReason !== "" && blockReason !== "BLOCK_REASON_UNSPECIFIED" && blockReason !== "BLOCKED_REASON_UNSPECIFIED";
}
//#endregion
//#region src/google-speech-api.ts
/**
* Response schema for the Gemini `:generateContent` endpoint when called with
* `responseModalities: ['AUDIO']`. The generated audio is returned as base64
* encoded audio in the first inline-data part (WAV by default on Gemini 3.8,
* raw PCM on older models).
*/
const googleSpeechResponseSchema = lazySchema(() => zodSchema(z.object({
candidates: z.array(z.object({ content: z.object({ parts: z.array(z.object({ inlineData: z.object({
mimeType: z.string().nullish(),
data: z.string().nullish()
}).nullish() })).nullish() }).nullish() })).nullish(),
usageMetadata: z.record(z.string(), z.json()).nullish()
})));
//#endregion
//#region src/google-speech-input.ts
/**
* Inspects speech input without replacing provider option validation. This is
* also used by intermediaries that need the transcript or voice kind before
* invoking a model. Model support for structured turns is validated separately.
*/
function getGoogleSpeechInput({ text, voice, providerOptions }) {
const google = providerOptions?.google;
const options = google != null && typeof google === "object" ? google : void 0;
const turns = options && "turns" in options ? options.turns : void 0;
const turnTexts = [];
if (Array.isArray(turns) && turns.length > 0) {
for (const turn of turns) {
if (turn == null || typeof turn !== "object" || !("text" in turn) || typeof turn.text !== "string") break;
turnTexts.push(turn.text);
}
if (turnTexts.length === turns.length) text = turnTexts.join("");
}
const config = options && "multiSpeakerVoiceConfig" in options ? options.multiSpeakerVoiceConfig : void 0;
const speakers = config != null && typeof config === "object" && "speakerVoiceConfigs" in config && Array.isArray(config.speakerVoiceConfigs) ? config.speakerVoiceConfigs : [];
return {
text,
usesCustomVoice: voice?.startsWith("voice_") === true || voice?.startsWith("voicekey_") === true || speakers.some((speaker) => speaker != null && typeof speaker === "object" && "voiceConfig" in speaker && speaker.voiceConfig != null && typeof speaker.voiceConfig === "object" && "voice" in speaker.voiceConfig)
};
}
//#endregion
//#region src/google-speech-model-options.ts
const prebuiltVoiceConfigSchema = () => z.object({ voiceName: z.string() });
const voiceConfigSchema = () => z.object({
prebuiltVoiceConfig: prebuiltVoiceConfigSchema(),
voice: z.never().optional()
});
const speechMetadataSchema = () => z.object({
speaker: z.string().min(1).optional(),
style: z.string().optional()
});
const googleSpeechProviderOptionsSchema = lazySchema(() => zodSchema(z.object({
/** Turn-level directions for the top-level text, for Gemini 3.8 TTS. */
speechMetadata: speechMetadataSchema().optional(),
/**
* Structured transcript for Gemini 3.8 TTS. Replaces the top-level text;
* pass text: '' when using turns. Each multi-speaker turn must name a
* configured speaker in speechMetadata. Per-turn styles override instructions.
*/
turns: z.array(z.object({
text: z.string(),
speechMetadata: speechMetadataSchema().optional()
})).min(1).optional(),
/**
* Multi-speaker configuration for dialogue audio. When provided, this
* overrides the top-level `voice`. The Gemini TTS API supports up to two
* speakers. For Gemini 3.8, each turn's speechMetadata.speaker must match
* a configured speaker; older models use speaker labels in the text.
*
* https://ai.google.dev/gemini-api/docs/speech-generation#multi-speaker
*/
multiSpeakerVoiceConfig: z.object({ speakerVoiceConfigs: z.array(z.object({
speaker: z.string(),
voiceConfig: voiceConfigSchema()
})) }).optional()
})));
//#endregion
//#region src/google-speech-model.ts
const DEFAULT_VOICE = "Kore";
const DEFAULT_SAMPLE_RATE = 24e3;
var GoogleSpeechModel = class GoogleSpeechModel {
static [WORKFLOW_SERIALIZE](model) {
return serializeModelOptions({
modelId: model.modelId,
config: model.config
});
}
static [WORKFLOW_DESERIALIZE](options) {
return new GoogleSpeechModel(options.modelId, options.config);
}
get provider() {
return this.config.provider;
}
constructor(modelId, config) {
this.modelId = modelId;
this.config = config;
this.specificationVersion = "v4";
}
async getArgs({ text, voice = DEFAULT_VOICE, outputFormat, instructions, speed, language, providerOptions }) {
const warnings = [];
const providerOptionsNames = this.config.provider.includes("vertex") ? ["googleVertex", "vertex"] : ["google"];
let googleOptions;
for (const name of providerOptionsNames) {
googleOptions = await parseProviderOptions({
provider: name,
providerOptions,
schema: googleSpeechProviderOptionsSchema
});
if (googleOptions != null) break;
}
if (googleOptions == null && !providerOptionsNames.includes("google")) googleOptions = await parseProviderOptions({
provider: "google",
providerOptions,
schema: googleSpeechProviderOptionsSchema
});
const usesStructuredSpeech = !this.modelId.startsWith("gemini-2.5-") && !this.modelId.startsWith("gemini-3.1-");
const input = getGoogleSpeechInput({
text,
voice,
providerOptions: { google: googleOptions }
});
if (input.usesCustomVoice) throw new InvalidArgumentError({
argument: "voice",
message: "Custom voices are not supported. Use a prebuilt voice instead."
});
const multiSpeakerVoiceConfig = googleOptions?.multiSpeakerVoiceConfig;
const languageCode = language === "auto" ? void 0 : language;
const speechConfig = multiSpeakerVoiceConfig ? {
multiSpeakerVoiceConfig,
...languageCode != null && { languageCode }
} : {
voiceConfig: { prebuiltVoiceConfig: { voiceName: voice } },
...languageCode != null && { languageCode }
};
let promptText = text;
if (instructions != null && !usesStructuredSpeech) {
if (multiSpeakerVoiceConfig) warnings.push({
type: "unsupported",
feature: "instructions",
details: "Google Gemini TTS ignores `instructions` when `multiSpeakerVoiceConfig` is set, because prepending them would break multi-speaker transcript parsing."
});
else promptText = `${instructions}: ${text}`;
}
let parts = [{ text: promptText }];
if (usesStructuredSpeech) {
if (googleOptions?.turns && googleOptions.speechMetadata) throw new InvalidArgumentError({
argument: "providerOptions",
message: "Set speechMetadata on each turn when using turns."
});
if (googleOptions?.turns && text !== "") warnings.push({
type: "unsupported",
feature: "text",
details: "Google TTS turns replace the top-level text."
});
parts = (googleOptions?.turns ?? [{
text,
speechMetadata: googleOptions?.speechMetadata
}]).map((part) => {
const style = part.speechMetadata?.style ?? instructions;
const speaker = part.speechMetadata?.speaker;
if (multiSpeakerVoiceConfig && !multiSpeakerVoiceConfig.speakerVoiceConfigs.some((config) => config.speaker === speaker)) throw new InvalidArgumentError({
argument: "speechMetadata.speaker",
message: "Every multi-speaker turn must specify a speechMetadata.speaker matching a configured speaker."
});
return {
text: part.text,
...style != null || speaker != null ? { speechMetadata: {
style,
speaker
} } : {}
};
});
} else if (googleOptions?.turns || googleOptions?.speechMetadata) throw new InvalidArgumentError({
argument: "providerOptions",
message: "Structured speech metadata and turns require Gemini 3.8 TTS."
});
if (input.text.length === 0) throw new InvalidArgumentError({
argument: "text",
message: "Speech input must contain a non-empty transcript."
});
if (speed != null) warnings.push({
type: "unsupported",
feature: "speed",
details: "Google Gemini TTS models do not support the `speed` option. It was ignored."
});
const formats = usesStructuredSpeech ? {
wav: "AUDIO_WAV",
"audio/wav": "AUDIO_WAV",
pcm: "AUDIO_L16",
"audio/l16": "AUDIO_L16",
mulaw: "AUDIO_MULAW",
"audio/mulaw": "AUDIO_MULAW",
alaw: "AUDIO_ALAW",
"audio/alaw": "AUDIO_ALAW"
} : {
wav: "AUDIO_WAV",
pcm: "AUDIO_L16"
};
let resolvedOutputFormat = "wav";
if (outputFormat != null && Object.prototype.hasOwnProperty.call(formats, outputFormat)) resolvedOutputFormat = outputFormat;
else if (outputFormat != null) warnings.push({
type: "unsupported",
feature: "outputFormat",
details: `Unsupported output format: ${outputFormat}. Using wav instead.`
});
return {
requestBody: {
contents: [{
role: "user",
parts
}],
generationConfig: {
responseModalities: ["AUDIO"],
speechConfig,
...usesStructuredSpeech && outputFormat != null ? { responseFormat: { audio: { mimeType: formats[resolvedOutputFormat] } } } : {}
}
},
warnings,
outputFormat: formats[resolvedOutputFormat],
usesStructuredSpeech
};
}
async doGenerate(options) {
const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
const { requestBody, warnings, outputFormat, usesStructuredSpeech } = await this.getArgs(options);
const { value: response, responseHeaders, rawValue: rawResponse } = await postJsonToApi({
url: `${this.config.baseURL}/models/${this.modelId}:generateContent`,
headers: combineHeaders(this.config.headers ? await resolve(this.config.headers) : void 0, options.headers),
body: requestBody,
failedResponseHandler: googleFailedResponseHandler,
successfulResponseHandler: createJsonResponseHandler(googleSpeechResponseSchema),
abortSignal: options.abortSignal,
fetch: this.config.fetch
});
let base64Audio;
let mimeType;
for (const candidate of response.candidates ?? []) {
for (const part of candidate.content?.parts ?? []) if (part.inlineData?.data) {
base64Audio = part.inlineData.data;
mimeType = part.inlineData.mimeType ?? void 0;
break;
}
if (base64Audio != null) break;
}
const sampleRate = parseSampleRate(mimeType) ?? DEFAULT_SAMPLE_RATE;
const bytes = base64Audio != null ? convertBase64ToUint8Array(base64Audio) : /* @__PURE__ */ new Uint8Array(0);
const isPcm = /^audio\/(?:l16|pcm)(?:;|$)/i.test(mimeType ?? "") || mimeType == null && !usesStructuredSpeech;
const audio = outputFormat === "AUDIO_WAV" && isPcm && bytes.length > 0 ? addWavHeader(bytes, sampleRate) : bytes;
if (outputFormat === "AUDIO_L16" && bytes.length > 0 && !usesStructuredSpeech) warnings.push({
type: "unsupported",
feature: "outputFormat",
details: `Returning raw PCM audio (signed 16-bit little-endian, mono, ${sampleRate} Hz). These bytes have no container header and are not directly playable; see providerMetadata.google for the sample rate and mime type.`
});
return {
audio,
warnings,
...response.usageMetadata != null && { usage: response.usageMetadata },
request: { body: JSON.stringify(requestBody) },
response: {
timestamp: currentDate,
modelId: this.modelId,
headers: responseHeaders,
body: rawResponse
},
providerMetadata: { google: {
sampleRate,
mimeType: mimeType ?? null
} }
};
}
};
/**
* Parses the sample rate from a PCM mime type such as `audio/L16;rate=24000`.
*/
function parseSampleRate(mimeType) {
if (mimeType == null) return;
const match = /rate=(\d+)/.exec(mimeType);
return match ? Number.parseInt(match[1], 10) : void 0;
}
/**
* Wraps raw signed 16-bit little-endian mono PCM in a minimal 44-byte WAV
* (RIFF/WAVE) container so the output is playable and detectable as `audio/wav`.
*/
function addWavHeader(pcm, sampleRate) {
const numChannels = 1;
const bitsPerSample = 16;
const blockAlign = 2;
const byteRate = sampleRate * blockAlign;
const dataSize = pcm.length;
const buffer = new ArrayBuffer(44 + dataSize);
const view = new DataView(buffer);
writeAscii(view, 0, "RIFF");
view.setUint32(4, 36 + dataSize, true);
writeAscii(view, 8, "WAVE");
writeAscii(view, 12, "fmt ");
view.setUint32(16, 16, true);
view.setUint16(20, 1, true);
view.setUint16(22, numChannels, true);
view.setUint32(24, sampleRate, true);
view.setUint32(28, byteRate, true);
view.setUint16(32, blockAlign, true);
view.setUint16(34, bitsPerSample, true);
writeAscii(view, 36, "data");
view.setUint32(40, dataSize, true);
const out = new Uint8Array(buffer);
out.set(pcm, 44);
return out;
}
function writeAscii(view, offset, text) {
for (let i = 0; i < text.length; i++) view.setUint8(offset + i, text.charCodeAt(i));
}
//#endregion
//#region src/tool/enterprise-web-search.ts
const enterpriseWebSearch = createProviderExecutedToolFactory({
id: "google.enterprise_web_search",
inputSchema: lazySchema(() => zodSchema(z.object({}))),
outputSchema: lazySchema(() => zodSchema(z.object({})))
});
//#endregion
//#region src/tool/file-search.ts
lazySchema(() => zodSchema(z.looseObject({
/** The names of the file_search_stores to retrieve from.
* Example: `fileSearchStores/my-file-search-store-123`
*/
fileSearchStoreNames: z.array(z.string()).describe("The names of the file_search_stores to retrieve from. Example: `fileSearchStores/my-file-search-store-123`"),
/** The number of file search retrieval chunks to retrieve. */
topK: z.number().int().positive().describe("The number of file search retrieval chunks to retrieve.").optional(),
/** Metadata filter to apply to the file search retrieval documents.
* See https://google.aip.dev/160 for the syntax of the filter expression.
*/
metadataFilter: z.string().describe("Metadata filter to apply to the file search retrieval documents. See https://google.aip.dev/160 for the syntax of the filter expression.").optional()
})));
const fileSearch = createProviderExecutedToolFactory({
id: "google.file_search",
inputSchema: lazySchema(() => zodSchema(z.object({}))),
outputSchema: lazySchema(() => zodSchema(z.object({})))
});
//#endregion
//#region src/tool/google-maps.ts
const googleMaps = createProviderExecutedToolFactory({
id: "google.google_maps",
inputSchema: lazySchema(() => zodSchema(z.object({}))),
outputSchema: lazySchema(() => zodSchema(z.object({})))
});
//#endregion
//#region src/google-tools.ts
const googleTools = {
/**
* Creates a Google search tool that gives Google direct access to real-time web content.
* Must have name "google_search".
*/
googleSearch: createProviderExecutedToolFactory({
id: "google.google_search",
inputSchema: lazySchema(() => zodSchema(z.object({}))),
outputSchema: lazySchema(() => zodSchema(z.object({})))
}),
/**
* Creates an Enterprise Web Search tool for grounding responses using a compliance-focused web index.
* Designed for highly-regulated industries (finance, healthcare, public sector).
* Does not log customer data and supports VPC service controls.
* Must have name "enterprise_web_search".
*
* @note Only available on Vertex AI. Requires Gemini 2.0 or newer.
*
* @see https://cloud.google.com/vertex-ai/generative-ai/docs/grounding/web-grounding-enterprise
*/
enterpriseWebSearch,
/**
* Creates a Google Maps grounding tool that gives the model access to Google Maps data.
* Must have name "google_maps".
*
* @see https://ai.google.dev/gemini-api/docs/maps-grounding
* @see https://cloud.google.com/vertex-ai/generative-ai/docs/grounding/grounding-with-google-maps
*/
googleMaps,
/**
* Creates a URL context tool that gives Google direct access to real-time web content.
* Must have name "url_context".
*/
urlContext: createProviderExecutedToolFactory({
id: "google.url_context",
inputSchema: lazySchema(() => zodSchema(z.object({}))),
outputSchema: lazySchema(() => zodSchema(z.object({})))
}),
/**
* Enables Retrieval Augmented Generation (RAG) via the Gemini File Search tool.
* Must have name "file_search".
*
* @param fileSearchStoreNames - Fully-qualified File Search store resource names.
* @param metadataFilter - Optional filter expression to restrict the files that can be retrieved.
* @param topK - Optional result limit for the number of chunks returned from File Search.
*
* @see https://ai.google.dev/gemini-api/docs/file-search
*/
fileSearch,
/**
* A tool that enables the model to generate and run Python code.
* Must have name "code_execution".
*
* @note Ensure the selected model supports Code Execution.
* Multi-tool usage with the code execution tool is typically compatible with Gemini >=2 models.
*
* @see https://ai.google.dev/gemini-api/docs/code-execution (Google AI)
* @see https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/code-execution-api (Vertex AI)
*/
codeExecution,
/**
* Creates a Vertex RAG Store tool that enables the model to perform RAG searches against a Vertex RAG Store.
* Must have name "vertex_rag_store".
*/
vertexRagStore: createProviderExecutedToolFactory({
id: "google.vertex_rag_store",
inputSchema: lazySchema(() => zodSchema(z.object({}))),
outputSchema: lazySchema(() => zodSchema(z.object({})))
})
};
//#endregion
//#region src/interactions/convert-google-interactions-usage.ts
function convertGoogleInteractionsUsage(usage) {
if (usage == null) return createNullLanguageModelUsage();
const totalInput = usage.total_input_tokens ?? 0;
const totalOutput = usage.total_output_tokens ?? 0;
const totalThought = usage.total_thought_tokens ?? 0;
const totalCached = usage.total_cached_tokens ?? 0;
return {
inputTokens: {
total: usage.total_input_tokens ?? void 0,
noCache: usage.total_input_tokens == null ? void 0 : totalInput - totalCached,
cacheRead: usage.total_cached_tokens ?? void 0,
cacheWrite: void 0
},
outputTokens: {
total: usage.total_output_tokens == null && usage.total_thought_tokens == null ? void 0 : totalOutput + totalThought,
text: usage.total_output_tokens ?? void 0,
reasoning: usage.total_thought_tokens ?? void 0
},
raw: usage
};
}
/**
* Extracts the per-modality output token breakdown from an Interactions usage
* record (e.g. `{ video: 57920, text: 12 }`).
*/
function getGoogleInteractionsOutputTokensByModality(usage) {
const byModality = usage?.output_tokens_by_modality;
if (byModality == null) return;
const result = {};
for (const entry of byModality) if (entry?.modality != null && entry.tokens != null) result[entry.modality] = entry.tokens;
return Object.keys(result).length > 0 ? result : void 0;
}
//#endregion
//#region src/interactions/extract-google-interactions-sources.ts
const KNOWN_DOC_EXTENSIONS = {
pdf: "application/pdf",
txt: "text/plain",
md: "text/markdown",
markdown: "text/markdown",
doc: "application/msword",
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
};
function inferDocMediaType(uriOrName) {
const lower = uriOrName.toLowerCase();
for (const [ext, media] of Object.entries(KNOWN_DOC_EXTENSIONS)) if (lower.endsWith(`.${ext}`)) return media;
return "application/octet-stream";
}
function basename(uriOrName) {
const parts = uriOrName.split("/");
const last = parts[parts.length - 1];
return last && last.length > 0 ? last : void 0;
}
/**
* Maps a single text-block annotation (`url_citation` / `file_citation` /
* `place_citation`) onto a `LanguageModelV4Source`. Returns `undefined` when
* the annotation lacks the minimum payload to form a source (e.g. a URL
* citation without a `url`).
*/
function annotationToSource({ annotation, generateId }) {
switch (annotation.type) {
case "url_citation": {
const urlCitation = annotation;
if (urlCitation.url == null || urlCitation.url.length === 0) return;
return {
type: "source",
sourceType: "url",
id: generateId(),
url: urlCitation.url,
...urlCitation.title != null ? { title: urlCitation.title } : {}
};
}
case "file_citation": {
const fileCitation = annotation;
const uri = fileCitation.url ?? fileCitation.document_uri ?? fileCitation.file_name;
if (uri == null || uri.length === 0) return void 0;
if (uri.startsWith("http://") || uri.startsWith("https://")) return {
type: "source",
sourceType: "url",
id: generateId(),
url: uri,
...fileCitation.file_name != null ? { title: fileCitation.file_name } : {}
};
const filename = fileCitation.file_name ?? basename(uri);
const mediaType = inferDocMediaType(uri);
return {
type: "source",
sourceType: "document",
id: generateId(),
mediaType,
title: fileCitation.file_name ?? filename ?? uri,
...filename != null ? { filename } : {}
};
}
case "place_citation": {
const placeCitation = annotation;
if (placeCitation.url == null || placeCitation.url.length === 0) return;
return {
type: "source",
sourceType: "url",
id: generateId(),
url: placeCitation.url,
...placeCitation.name != null ? { title: placeCitation.name } : {}
};
}
default: return;
}
}
/**
* Maps a built-in tool *result* content block to zero or more
* `LanguageModelV4Source` parts. The Interactions API exposes grounding
* sources both inline (via `text_annotation` deltas) and via tool-result
* content blocks; the latter is what this function consumes.
*
* Supported result kinds:
* - `url_context_result` -> URL sources for each fetched URL with `status: 'success'`
* - `google_search_result` -> URL sources (when `url` is present), search-suggestion
* entries are skipped (they are HTML widgets, not citations)
* - `google_maps_result` -> URL sources for each place with a `url`
* - `file_search_result` -> document sources (best-effort -- `result[]` is loosely typed)
*/
function builtinToolResultToSources({ block, generateId }) {
const sources = [];
switch (block.type) {
case "url_context_result": {
const result = block.result ?? [];
for (const entry of result) {
if (entry?.url == null || entry.url.length === 0) continue;
if (entry.status != null && entry.status !== "success") continue;
sources.push({
type: "source",
sourceType: "url",
id: generateId(),
url: entry.url
});
}
break;
}
case "google_search_result": {
const result = block.result ?? [];
for (const entry of result) {
const url = entry?.url;
if (url == null || url.length === 0) continue;
sources.push({
type: "source",
sourceType: "url",
id: generateId(),
url,
...entry.title != null ? { title: entry.title } : {}
});
}
break;
}
case "google_maps_result": {
const result = block.result ?? [];
for (const entry of result) for (const place of entry.places ?? []) {
if (place.url == null || place.url.length === 0) continue;
sources.push({
type: "source",
sourceType: "url",
id: generateId(),
url: place.url,
...place.name != null ? { title: place.name } : {}
});
}
break;
}
case "file_search_result": {
const result = block.result ?? [];
for (const raw of result) {
if (raw == null || typeof raw !== "object") continue;
const entry = raw;
const uri = entry.url ?? entry.document_uri ?? entry.file_name;
if (uri == null || uri.length === 0) continue;
if (uri.startsWith("http://") || uri.startsWith("https://")) {
sources.push({
type: "source",
sourceType: "url",
id: generateId(),
url: uri,
...entry.title != null ? { title: entry.title } : {}
});
continue;
}
const filename = entry.file_name ?? basename(uri);
const mediaType = inferDocMediaType(uri);
sources.push({
type: "source",
sourceType: "document",
id: generateId(),
mediaType,
title: entry.title ?? entry.file_name ?? filename ?? uri,
...filename != null ? { filename } : {}
});
}
break;
}
}
return sources;
}
/**
* Given a list of annotations attached to a single `text` content block,
* returns the corresponding `LanguageModelV4Source` parts (de-duplicated by
* URL/filename to avoid double-counting when the same citation reappears
* across deltas).
*/
function annotationsToSources({ annotations, generateId }) {
if (annotations == null) return [];
const seen = /* @__PURE__ */ new Set();
const sources = [];
for (const annotation of annotations) {
const source = annotationToSource({
annotation,
generateId
});
if (source == null) continue;
const key = source.sourceType === "url" ? `url:${source.url}` : `doc:${source.filename ?? source.title}`;
if (seen.has(key)) continue;
seen.add(key);
sources.push(source);
}
return sources;
}
//#endregion
//#region src/interactions/map-google-interactions-finish-reason.ts
function mapGoogleInteractionsFinishReason({ status, hasFunctionCall }) {
switch (status) {
case "completed": return hasFunctionCall ? "tool-calls" : "stop";
case "requires_action": return "tool-calls";
case "failed": return "error";
case "incomplete": return "length";
case "cancelled": return "other";
default: return "other";
}
}
//#endregion
//#region src/interactions/build-google-interactions-stream-transform.ts
const BUILTIN_TOOL_CALL_TYPES$1 = /* @__PURE__ */ new Set([
"google_search_call",
"code_execution_call",
"url_context_call",
"file_search_call",
"google_maps_call",
"mcp_server_tool_call"
]);
const BUILTIN_TOOL_RESULT_TYPES$1 = /* @__PURE__ */ new Set([
"google_search_result",
"code_execution_result",
"url_context_result",
"file_search_result",
"google_maps_result",
"mcp_server_tool_result"
]);
function builtinToolNameFromCallType$1(type) {
return type.replace(/_call$/, "");
}
function builtinToolNameFromResultType$1(type) {
return type.replace(/_result$/, "");
}
/**
* Builds a `TransformStream<ParseResult<GoogleInteractionsEvent>, LanguageModelV4StreamPart>`
* over the Interactions API SSE event stream.
*
* Surfaces text + thought (reasoning), function_call, image, built-in tool
* call/result steps, and `text_annotation` -> `source` parts.
*/
function buildGoogleInteractionsStreamTransform({ warnings, generateId, includeRawChunks, serviceTier: headerServiceTier }) {
let interactionId;
let usage;
let serviceTier = headerServiceTier;
let finishStatus;
let hasFunctionCall = false;
let hasCompletionEvent = false;
const openBlocks = /* @__PURE__ */ new Map();
const emittedSourceKeys = /* @__PURE__ */ new Set();
function sourceKey(source) {
return source.sourceType === "url" ? `url:${source.url}` : `doc:${source.filename ?? source.title}`;
}
return new TransformStream({
start(controller) {
controller.enqueue({
type: "stream-start",
warnings
});
},
transform(chunk, controller) {
if (includeRawChunks) controller.enqueue({
type: "raw",
rawValue: chunk.rawValue
});
if (!chunk.success) {
finishStatus = "failed";
controller.enqueue({
type: "error",
error: chunk.error
});
return;
}
const value = chunk.value;
const eventType = value.event_type;
switch (eventType) {
case "interaction.created": {
const interaction = value.interaction;
interactionId = interaction?.id != null && interaction.id.length > 0 ? interaction.id : void 0;
const created = interaction?.created;
let timestamp;
if (typeof created === "string") {
const parsed = new Date(created);
if (!Number.isNaN(parsed.getTime())) timestamp = parsed;
}
controller.enqueue({
type: "response-metadata",
...interactionId != null ? { id: interactionId } : {},
modelId: interaction?.model,
...timestamp ? { timestamp } : {}
});
break;
}
case "step.start": {
const event = value;
const step = event.step;
const index = event.index;
const blockId = `${interactionId ?? "interaction"}:${index}`;
const stepType = step?.type;
if (stepType === "model_output") {
const initial = step?.content?.[0];
if (initial?.type === "text") {
openBlocks.set(index, {
kind: "text",
id: blockId,
emittedSourceKeys: /* @__PURE__ */ new Set()
});
controller.enqueue({
type: "text-start",
id: blockId
});
const initialSources = annotationsToSources({
annotations: initial.annotations,
generateId
});
for (const source of initialSources) {
const key = sourceKey(source);
if (emittedSourceKeys.has(key)) continue;
emittedSourceKeys.add(key);
controller.enqueue(source);
}
} else if (initial?.type === "image") openBlocks.set(index, {
kind: "image",
id: blockId,
...initial.data != null ? { data: initial.data } : {},
...initial.mime_type != null ? { mimeType: initial.mime_type } : {},
...initial.uri != null ? { uri: initial.uri } : {}
});
else openBlocks.set(index, {
kind: "pending_model_output",
id: blockId
});
} else if (stepType === "thought") {
const signature = step?.signature;
openBlocks.set(index, {
kind: "reasoning",
id: blockId,
...signature != null ? { signature } : {}
});
controller.enqueue({
type: "reasoning-start",
id: blockId
});
if (Array.isArray(step?.summary)) {
for (const item of step.summary) if (item?.type === "text" && typeof item.text === "string") controller.enqueue({
type: "reasoning-delta",
id: blockId,
delta: item.text
});
}
} else if (stepType === "processing_call" || stepType === "processing_result") {
const google = {};
if (step?.signature != null) google.signature = step.signature;
if (interactionId != null) google.interactionId = interactionId;
if (stepType === "processing_call") google.processingId = step?.id || blockId;
else google.processingCallId = step?.call_id || blockId;
openBlocks.set(index, {
kind: "custom",
id: blockId,
customKind: `google.${stepType}`,
google
});
} else if (stepType === "function_call") {
const toolCallId = step?.id || blockId;
const toolName = step?.name ?? "unknown";
hasFunctionCall = true;
const state = {
kind: "function_call",
id: blockId,
toolCallId,
toolName,
argumentsAccum: "",
...step?.signature != null ? { signature: step.signature } : {}
};
openBlocks.set(index, state);
controller.enqueue({
type: "tool-input-start",
id: toolCallId,
toolName
});
} else if (stepType != null && BUILTIN_TOOL_CALL_TYPES$1.has(stepType)) {
const toolName = stepType === "mcp_server_tool_call" ? step?.name ?? "mcp_server_tool" : builtinToolNameFromCallType$1(stepType);
const state = {
kind: "builtin_tool_call",
id: blockId,
blockType: stepType,
toolCallId: step?.id || blockId,
toolName,
arguments: step?.arguments ?? {},
callEmitted: false
};
openBlocks.set(index, state);
} else if (stepType != null && BUILTIN_TOOL_RESULT_TYPES$1.has(stepType)) {
const toolName = stepType === "mcp_server_tool_result" ? step?.name ?? "mcp_server_tool" : builtinToolNameFromResultType$1(stepType);
const state = {
kind: "builtin_tool_result",
id: blockId,
blockType: stepType,
callId: step?.call_id || blockId,
toolName,
result: step?.result ?? null,
...step?.is_error != null ? { isError: step.is_error } : {},
resultEmitted: false
};
openBlocks.set(index, state);
} else openBlocks.set(index, {
kind: "unknown",
id: blockId
});
break;
}
case "step.delta": {
const event = value;
let open = openBlocks.get(event.index);
if (open == null) break;
const dtype = event.delta?.type;
if (open.kind === "pending_model_output") {
if (dtype === "text" || dtype === "text_annotation" || dtype === "text_annotation_delta") {
const promoted = {
kind: "text",
id: open.id,
emittedSourceKeys: /* @__PURE__ */ new Set()
};
openBlocks.set(event.index, promoted);
open = promoted;
controller.enqueue({
type: "text-start",
id: promoted.id
});
}
}
if (dtype === "image" && (open.kind === "pending_model_output" || open.kind === "text" || open.kind === "image")) {
const imageDelta = event.delta;
const google = {};
if (interactionId != null) google.interactionId = interactionId;
const providerMetadata = Object.keys(google).length > 0 ? { google } : void 0;
if (imageDelta?.data != null && imageDelta.data.length > 0) controller.enqueue({
type: "file",
mediaType: imageDelta.mime_type ?? "image/png",
data: {
type: "data",
data: imageDelta.data
},
...providerMetadata ? { providerMetadata } : {}
});
else if (imageDelta?.uri != null && imageDelta.uri.length > 0) controller.enqueue({
type: "file",
mediaType: imageDelta.mime_type ?? "image/png",
data: {
type: "url",
url: new URL(imageDelta.uri)
},
...providerMetadata ? { providerMetadata } : {}
});
if (open.kind === "image") {
open.data = void 0;
open.uri = void 0;
}
break;
}
if (dtype === "video" && (open.kind === "pending_model_output" || open.kind === "text")) {
const videoDelta = event.delta;
const google = {};
if (interactionId != null) google.interactionId = interactionId;
const providerMetadata = Object.keys(google).length > 0 ? { google } : void 0;
if (videoDelta?.data != null && videoDelta.data.length > 0) controller.enqueue({
type: "file",
mediaType: videoDelta.mime_type ?? "video/mp4",
data: {
type: "data",
data: videoDelta.data
},
...providerMetadata ? { providerMetadata } : {}
});
else if (videoDelta?.uri != null && videoDelta.uri.length > 0) controller.enqueue({
type: "file",
mediaType: videoDelta.mime_type ?? "video/mp4",
data: {
type: "url",
url: new URL(videoDelta.uri)
},
...providerMetadata ? { providerMetadata } : {}
});
break;
}
const delta = event.delta;
if (open.kind === "custom" && (delta?.type === "processing_call" || delta?.type === "processing_result")) {
if (delta.signature != null) open.google.signature = delta.signature;
if (delta.type === "processing_call" && delta.id != null && delta.id.length > 0) open.google.processingId = delta.id;
if (delta.type === "processing_result" && delta.call_id != null && delta.call_id.length > 0) open.google.processingCallId = delta.call_id;
} else if (open.kind === "text" && delta?.type === "text") {
const text = delta.text ?? "";
if (text.length > 0) controller.enqueue({
type: "text-delta",
id: open.id,
delta: text
});
} else if (open.kind === "text" && (delta?.type === "text_annotation" || delta?.type === "text_annotation_delta")) {
const sources = annotationsToSources({
annotations: delta.annotations,
generateId
});
for (const source of sources) {
const key = sourceKey(source);
if (emittedSourceKeys.has(key)) continue;
emittedSourceKeys.add(key);
open.emittedSourceKeys.add(key);
controller.enqueue(source);
}
} else if (open.kind === "image" && delta?.type === "image") {
if (delta.data != null) open.data = delta.data;
if (delta.mime_type != null) open.mimeType = delta.mime_type;
if (delta.uri != null) open.uri = delta.uri;
} else if (open.kind === "reasoning") {
if (delta?.type === "thought_summary") {
const item = delta.content;
if (item?.type === "text" && typeof item.text === "string") controller.enqueue({
type: "reasoning-delta",
id: open.id,
delta: item.text
});
} else if (delta?.type === "thought_signature") {
const signature = delta.signature;
if (signature != null) open.signature = signature;
}
} else if (open.kind === "function_call" && delta?.type === "arguments_delta") {
const slice = typeof delta.arguments === "string" ? delta.arguments : "";
if (slice.length > 0) {
open.argumentsAccum += slice;
controller.enqueue({
type: "tool-input-delta",
id: open.toolCallId,
delta: slice
});
}
if (delta.id != null && delta.id.length > 0) open.toolCallId = delta.id;
if (delta.signature != null) open.signature = delta.signature;
hasFunctionCall = true;
} else if (open.kind === "builtin_tool_call" && delta?.type === open.blockType) {
if (delta.id != null && delta.id.length > 0) open.toolCallId = delta.id;
if (delta.arguments != null && typeof delta.arguments === "object") open.arguments = delta.arguments;
if (delta.name != null && open.blockType === "mcp_server_tool_call") open.toolName = delta.name;
} else if (open.kind === "builtin_tool_result" && delta?.type === open.blockType) {
if (delta.call_id != null && delta.call_id.length > 0) open.callId = delta.call_id;
if (delta.result !== void 0) open.result = delta.result;
if (delta.is_error != null) open.isError = delta.is_error;
if (delta.name != null && open.blockType === "mcp_server_tool_result") open.toolName = delta.name;
}
break;
}
case "step.stop": {
const event = value;
const open = openBlocks.get(event.index);
if (open == null) break;
if (open.kind === "text") {
const textProviderMetadata = interactionId != null ? { google: { interactionId } } : void 0;
controller.enqueue({
type: "text-end",
id: open.id,
...textProviderMetadata ? { providerMetadata: textProviderMetadata } : {}
});
} else if (open.kind === "reasoning") {
const google = {};
if (open.signature != null) google.signature = open.signature;
if (interactionId != null) google.interactionId = interactionId;
const providerMetadata = Object.keys(google).length > 0 ? { google } : void 0;
controller.enqueue({
type: "reasoning-end",
id: open.id,
...providerMetadata ? { providerMetadata } : {}
});
} else if (open.kind === "image") {
const google = {};
if (interactionId != null) google.interactionId = interactionId;
const providerMetadata = Object.keys(google).length > 0 ? { google } : void 0;
if (open.data != null && open.data.length > 0) controller.enqueue({
type: "file",
mediaType: open.mimeType ?? "image/png",
data: {
type: "data",
data: open.data
},
...providerMetadata ? { providerMetadata } : {}
});
else if (open.uri != null && open.uri.length > 0) controller.enqueue({
type: "file",
mediaType: open.mimeType ?? "image/png",
data: {
type: "url",
url: new URL(open.uri)
},
...providerMetadata ? { providerMetadata } : {}
});
} else if (open.kind === "function_call") {
const accumulated = open.argumentsAccum.length > 0 ? open.argumentsAccum : "{}";
controller.enqueue({
type: "tool-input-end",
id: open.toolCallId
});
const google = {};
if (open.signature != null) google.signature = open.signature;
if (interactionId != null) google.interactionId = interactionId;
const providerMetadata = Object.keys(google).length > 0 ? { google } : void 0;
controller.enqueue({
type: "tool-call",
toolCallId: open.toolCallId,
toolName: open.toolName,
input: accumulated,
...providerMetadata ? { providerMetadata } : {}
});
} else if (open.kind === "custom") controller.enqueue({
type: "custom",
kind: open.customKind,
providerMetadata: { google: open.google }
});
else if (open.kind === "builtin_tool_call" && !open.callEmitted) {
controller.enqueue({
type: "tool-call",
toolCallId: open.toolCallId,
toolName: open.toolName,
input: JSON.stringify(open.arguments ?? {}),
providerExecuted: true
});
open.callEmitted = true;
} else if (open.kind === "builtin_tool_result" && !open.resultEmitted) {
controller.enqueue({
type: "tool-result",
toolCallId: open.callId,
toolName: open.toolName,
result: open.result ?? null
});
open.resultEmitted = true;
const sources = builtinToolResultToSources({
block: {
type: open.blockType,
call_id: open.callId,
result: open.result
},
generateId
});
for (const source of sources) {
const key = sourceKey(source);
if (emittedSourceKeys.has(key)) continue;
emittedSourceKeys.add(key);
controller.enqueue(source);
}
}
openBlocks.delete(event.index);
break;
}
case "interaction.status_update":
case "interaction.in_progress":
case "interaction.requires_action": {
const event = value;
if (event.status != null) finishStatus = event.status;
else if (eventType === "interaction.requires_action") finishStatus = "requires_action";
else finishStatus = "in_progress";
break;
}
case "interaction.completed": {
hasCompletionEvent = true;
const interaction = value.interaction;
if (interaction?.id != null && interaction.id.length > 0) interactionId = interaction.id;
if (interaction?.status != null) finishStatus = interaction.status;
if (interaction?.usage != null) usage = interaction.usage;
if (interaction?.service_tier != null) serviceTier = interaction.service_tier;
break;
}
case "error": {
const event = value;
finishStatus = "failed";
controller.enqueue({
type: "error",
error: createProviderStreamError({
message: event.error?.message ?? "Unknown interaction error",
type: event.event_type,
code: event.error?.code ?? void 0,
data: event
})
});
break;
}
}
},
flush(controller) {
const isInterrupted = !hasCompletionEvent && (finishStatus == null || finishStatus === "in_progress");
const finishReason = {
unified: isInterrupted ? "interrupted" : mapGoogleInteractionsFinishReason({
status: finishStatus,
hasFunctionCall
}),
raw: isInterrupted ? void 0 : finishStatus
};
const outputTokensByModality = getGoogleInteractionsOutputTokensByModality(usage);
const providerMetadata = { google: {
...interactionId != null ? { interactionId } : {},
...serviceTier != null ? { serviceTier } : {},
...outputTokensByModality != null ? { outputTokensByModality } : {}
} };
controller.enqueue({
type: "finish",
finishReason,
usage: convertGoogleInteractionsUsage(usage),
providerMetadata
});
}
});
}
//#endregion
//#region src/interactions/convert-to-google-interactions-input.ts
/**
* Converts an AI SDK `LanguageModelV4Prompt` into the Gemini Interactions
* request shape (`{ input: Array<Step>, system_instruction }`).
*
* Prior assistant content round-trips as discrete steps:
* - text / image content → `model_output` step with a single `content` array
* - reasoning → `thought` step (`signature` + `summary`)
* - tool-call → `function_call` step
* User turns (and tool-result turns from the previous round) are sent as
* `user_input` steps whose `content[]` holds the user's parts (text, files,
* and — for tool-result turns — `function_result` blocks).
*
* Handles text parts, file parts (image / audio / document / video, all four
* `data.type` shapes), tool-call/tool-result round-tripping, per-step
* `signature` round-tripping, and statefulness compaction (drop assistant/tool
* turns whose `providerOptions.google.interactionId === previousInteractionId`).
*/
function convertToGoogleInteractionsInput({ prompt, previousInteractionId, store, mediaResolution }) {
const warnings = [];
const incoherentCombo = previousInteractionId != null && store === false;
const shouldCompact = previousInteractionId != null && store !== false;
if (incoherentCombo) warnings.push({
type: "other",
message: "google.interactions: providerOptions.google.previousInteractionId was set together with store: false. These are incoherent (the prior interaction cannot be referenced when nothing was stored on the server); the full history will be sent and previous_interaction_id will still be emitted."
});
const compactedPrompt = shouldCompact ? compactPromptForPreviousInteraction({
prompt,
previousInteractionId
}) : prompt;
const systemTexts = [];
const steps = [];
for (const message of compactedPrompt) switch (message.role) {
case "system":
systemTexts.push(message.content);
break;
case "user": {
const content = [];
for (const part of message.content) if (part.type === "text") content.push({
type: "text",
text: part.text
});
else if (part.type === "file") {
const fileBlock = convertFilePartToContent({
part,
warnings,
mediaResolution
});
if (fileBlock != null) content.push(fileBlock);
}
const merged = mergeAdjacentTextContent(content);
if (merged.length > 0) steps.push({
type: "user_input",
content: merged
});
break;
}
case "assistant": {
let pendingModelOutput = [];
const flushModelOutput = () => {
if (pendingModelOutput.length > 0) {
steps.push({
type: "model_output",
content: pendingModelOutput
});
pendingModelOutput = [];
}
};
for (const part of message.content) if (part.type === "text") pendingModelOutput.push({
type: "text",
text: part.text
});
else if (part.type === "reasoning") {
flushModelOutput();
const signature = part.providerOptions?.google?.signature;
steps.push({
type: "thought",
...signature != null ? { signature } : {},
summary: part.text.length > 0 ? [{
type: "text",
text: part.text
}] : void 0
});
} else if (part.type === "file") {
const fileBlock = convertFilePartToContent({
part,
warnings,
mediaResolution
});
if (fileBlock != null) pendingModelOutput.push(fileBlock);
} else if (part.type === "custom") {
flushModelOutput();
const google = part.providerOptions?.google;
const signature = typeof google?.signature === "string" ? google.signature : void 0;
if (part.kind === "google.processing_call" && typeof google?.processingId === "string") steps.push({
type: "processing_call",
id: google.processingId,
...signature != null ? { signature } : {}
});
else if (part.kind === "google.processing_result" && typeof google?.processingCallId === "string") steps.push({
type: "processing_result",
call_id: google.processingCallId,
...signature != null ? { signature } : {}
});
else warnings.push({
type: "other",
message: `google.interactions: unsupported or invalid custom assistant content part "${part.kind}"; part dropped.`
});
} else if (part.type === "tool-call") {
flushModelOutput();
const signature = part.providerOptions?.google?.signature;
const args = typeof part.input === "string" ? safeParseToolArgs(part.input) : part.input ?? {};
steps.push({
type: "function_call",
id: part.toolCallId,
name: part.toolName,
arguments: args,
...signature != null ? { signature } : {}
});
} else warnings.push({
type: "other",
message: `google.interactions: unsupported assistant content part type "${part.type}"; part dropped.`
});
flushModelOutput();
break;
}
case "tool": {
const content = [];
for (const part of message.content) {
if (part.type !== "tool-result") {
warnings.push({
type: "other",
message: `google.interactions: unsupported tool message part type "${part.type}"; part dropped.`
});
continue;
}
const block = convertToolResultPart({
toolCallId: part.toolCallId,
toolName: part.toolName,
output: part.output,
signature: part.providerOptions?.google?.signature,
warnings
});
content.push(block);
}
if (content.length > 0) steps.push({
type: "user_input",
content
});
break;
}
}
return {
input: steps,
systemInstruction: systemTexts.length > 0 ? systemTexts.join("\n\n") : void 0,
warnings
};
}
/**
* Maps a single AI SDK `LanguageModelV4FilePart` to a Gemini Interactions
* content block (`image` / `audio` / `document` / `video`).
*/
function convertFilePartToContent({ part, warnings, mediaResolution }) {
if (part.data.type === "text") return {
type: "text",
text: part.data.text
};
const topLevel = getTopLevelMediaType(part.mediaType);
let kind;
switch (topLevel) {
case "image":
kind = "image";
break;
case "audio":
kind = "audio";
break;
case "video":
kind = "video";
break;
case "application":
case "text":
kind = "document";
break;
default: kind = void 0;
}
if (kind == null) {
warnings.push({
type: "other",
message: `google.interactions: unsupported file media type "${part.mediaType}"; part dropped.`
});
return;
}
const resolutionField = mediaResolution != null && (kind === "image" || kind === "video") ? { resolution: mediaResolution } : {};
const processingField = kind === "video" ? getVideoProcessingField({
part,
warnings
}) : {};
switch (part.data.type) {
case "data": {
const mimeType = resolveFullMediaType({ part });
return {
type: kind,
data: convertToBase64(part.data.data),
mime_type: mimeType,
...resolutionField,
...processingField
};
}
case "url": return {
type: kind,
uri: part.data.url.toString(),
...isFullMediaType(part.mediaType) ? { mime_type: part.mediaType } : {},
...resolutionField,
...processingField
};
case "reference": {
const uri = resolveProviderReference({
reference: part.data.reference,
provider: "google"
});
return {
type: kind,
uri,
...isFullMediaType(part.mediaType) ? { mime_type: part.mediaType } : {},
...resolutionField,
...processingField
};
}
}
}
function getVideoProcessingField({ part, warnings }) {
const processing = (part.providerOptions?.google)?.processing;
if (processing == null) return {};
if (processing === "agentic" || processing === "static") return { processing };
if (typeof processing === "object" && !Array.isArray(processing) && processing.type === "static") {
const config = processing;
return { processing: {
type: "static",
...typeof config.startOffset === "number" ? { start_offset: `${config.startOffset}s` } : {},
...typeof config.endOffset === "number" ? { end_offset: `${config.endOffset}s` } : {},
...typeof config.fps === "number" ? { fps: config.fps } : {}
} };
}
warnings.push({
type: "other",
message: "google.interactions: invalid providerOptions.google.processing on video file part; expected \"agentic\", \"static\", or a static processing configuration. Option dropped."
});
return {};
}
function compactPromptForPreviousInteraction({ prompt, previousInteractionId }) {
const out = [];
const droppedToolCallIds = /* @__PURE__ */ new Set();
for (const message of prompt) {
if (message.role === "assistant") {
if (message.content.some((part) => {
return part.providerOptions?.google?.interactionId === previousInteractionId;
})) {
for (const part of message.content) if (part.type === "tool-call") droppedToolCallIds.add(part.toolCallId);
continue;
}
out.push(message);
continue;
}
if (message.role === "tool") {
const remaining = message.content.filter((part) => {
if (part.type !== "tool-result") return true;
return !droppedToolCallIds.has(part.toolCallId);
});
if (remaining.length === 0) continue;
out.push({
...message,
content: remaining
});
continue;
}
out.push(message);
}
return out;
}
function safeParseToolArgs(input) {
try {
const parsed = secureJsonParse(input);
if (parsed != null && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
return { value: parsed };
} catch {
return { value: input };
}
}
function convertToolResultPart({ toolCallId, toolName, output, signature, warnings }) {
const base = {
type: "function_result",
call_id: toolCallId,
name: toolName,
...signature != null ? { signature } : {}
};
switch (output.type) {
case "text": return {
...base,
result: output.value
};
case "json": return {
...base,
result: JSON.stringify(output.value)
};
case "error-text": return {
...base,
is_error: true,
result: output.value
};
case "error-json": return {
...base,
is_error: true,
result: JSON.stringify(output.value)
};
case "execution-denied": return {
...base,
is_error: true,
result: output.reason ?? "Tool execution denied by user."
};
case "content": {
const blocks = [];
for (const item of output.value) if (item.type === "text") blocks.push({
type: "text",
text: item.text
});
else if (item.type === "file") {
if (getTopLevelMediaType(item.mediaType) !== "image") {
warnings.push({
type: "other",
message: `google.interactions: tool-result file with mediaType "${item.mediaType}" is not supported (Interactions \`function_result.result\` accepts only text and image content); part dropped.`
});
continue;
}
const imageBlock = filePartToImageBlock({
part: item,
warnings
});
if (imageBlock != null) blocks.push(imageBlock);
} else warnings.push({
type: "other",
message: `google.interactions: tool-result content part type "${item.type}" is not supported; part dropped.`
});
return {
...base,
result: blocks
};
}
}
}
function filePartToImageBlock({ part, warnings }) {
switch (part.data.type) {
case "data": {
const mimeType = isFullMediaType(part.mediaType) ? part.mediaType : resolveFullMediaType({ part: {
type: "file",
mediaType: part.mediaType,
data: part.data
} });
return {
type: "image",
data: convertToBase64(part.data.data),
mime_type: mimeType
};
}
case "url": return {
type: "image",
uri: part.data.url.toString(),
...isFullMediaType(part.mediaType) ? { mime_type: part.mediaType } : {}
};
case "reference": return {
type: "image",
uri: resolveProviderReference({
reference: part.data.reference,
provider: "google"
}),
...isFullMediaType(part.mediaType) ? { mime_type: part.mediaType } : {}
};
case "text":
warnings.push({
type: "other",
message: "google.interactions: tool-result image part with `data.type === \"text\"` is not representable as an image; part dropped."
});
return;
}
}
function mergeAdjacentTextContent(content) {
if (content.length < 2) return content;
const result = [];
for (const block of content) {
const last = result[result.length - 1];
if (block.type === "text" && last != null && last.type === "text" && last.annotations == null && block.annotations == null) {
const merged = {
type: "text",
text: `${last.text}\n\n${block.text}`
};
result[result.length - 1] = merged;
continue;
}
result.push(block);
}
return result;
}
//#endregion
//#region src/interactions/google-interactions-api.ts
const tokenByModalitySchema = () => z.object({
modality: z.string().nullish(),
tokens: z.number().nullish()
}).loose();
const usageSchema = () => z.object({
total_input_tokens: z.number().nullish(),
total_output_tokens: z.number().nullish(),
total_thought_tokens: z.number().nullish(),
total_cached_tokens: z.number().nullish(),
total_tool_use_tokens: z.number().nullish(),
total_tokens: z.number().nullish(),
input_tokens_by_modality: z.array(tokenByModalitySchema()).nullish(),
output_tokens_by_modality: z.array(tokenByModalitySchema()).nullish(),
cached_tokens_by_modality: z.array(tokenByModalitySchema()).nullish(),
tool_use_tokens_by_modality: z.array(tokenByModalitySchema()).nullish(),
grounding_tool_count: z.array(z.object({
type: z.string().nullish(),
count: z.number().nullish()
}).loose()).nullish()
}).loose();
const interactionStatusSchema = () => z.enum([
"in_progress",
"requires_action",
"completed",
"failed",
"cancelled",
"incomplete"
]);
const annotationSchema = () => {
const urlCitation = z.object({
type: z.literal("url_citation"),
url: z.string().nullish(),
title: z.string().nullish(),
start_index: z.number().nullish(),
end_index: z.number().nullish()
}).loose();
const fileCitation = z.object({
type: z.literal("file_citation"),
file_name: z.string().nullish(),
document_uri: z.string().nullish(),
url: z.string().nullish(),
page_number: z.number().nullish(),
media_id: z.string().nullish(),
start_index: z.number().nullish(),
end_index: z.number().nullish(),
custom_metadata: z.record(z.string(), z.unknown()).nullish()
}).loose();
const placeCitation = z.object({
type: z.literal("place_citation"),
name: z.string().nullish(),
url: z.string().nullish(),
place_id: z.string().nullish(),
start_index: z.number().nullish(),
end_index: z.number().nullish()
}).loose();
return z.union([
urlCitation,
fileCitation,
placeCitation,
z.object({ type: z.string() }).loose()
]);
};
const thoughtSummaryItemSchema = () => z.object({
type: z.string(),
text: z.string().nullish(),
data: z.string().nullish(),
mime_type: z.string().nullish()
}).loose();
const contentBlockSchema = () => {
const textContent = z.object({
type: z.literal("text"),
text: z.string(),
annotations: z.array(annotationSchema()).nullish()
}).loose();
const imageContent = z.object({
type: z.literal("image"),
data: z.string().nullish(),
mime_type: z.string().nullish(),
resolution: z.enum([
"low",
"medium",
"high",
"ultra_high"
]).nullish(),
uri: z.string().nullish()
}).loose();
const videoContent = z.object({
type: z.literal("video"),
data: z.string().nullish(),
mime_type: z.string().nullish(),
uri: z.string().nullish()
}).loose();
return z.union([
textContent,
imageContent,
videoContent,
z.object({ type: z.string() }).loose()
]);
};
const BUILTIN_TOOL_CALL_STEP_TYPES = [
"google_search_call",
"code_execution_call",
"url_context_call",
"file_search_call",
"google_maps_call",
"mcp_server_tool_call"
];
const BUILTIN_TOOL_RESULT_STEP_TYPES = [
"google_search_result",
"code_execution_result",
"url_context_result",
"file_search_result",
"google_maps_result",
"mcp_server_tool_result"
];
const stepSchema = () => {
const userInputStep = z.object({
type: z.literal("user_input"),
content: z.array(contentBlockSchema()).nullish()
}).loose();
const modelOutputStep = z.object({
type: z.literal("model_output"),
content: z.array(contentBlockSchema()).nullish()
}).loose();
const functionCallStep = z.object({
type: z.literal("function_call"),
id: z.string(),
name: z.string(),
arguments: z.record(z.string(), z.unknown()).nullish(),
signature: z.string().nullish()
}).loose();
const thoughtStep = z.object({
type: z.literal("thought"),
signature: z.string().nullish(),
summary: z.array(thoughtSummaryItemSchema()).nullish()
}).loose();
const processingCallStep = z.object({
type: z.literal("processing_call"),
id: z.string(),
signature: z.string().nullish()
}).loose();
const processingResultStep = z.object({
type: z.literal("processing_result"),
call_id: z.string(),
signature: z.string().nullish()
}).loose();
const builtinToolCallStep = z.object({
type: z.enum(BUILTIN_TOOL_CALL_STEP_TYPES),
id: z.string(),
arguments: z.record(z.string(), z.unknown()).nullish(),
name: z.string().nullish(),
server_name: z.string().nullish(),
search_type: z.string().nullish(),
signature: z.string().nullish()
}).loose();
const builtinToolResultStep = z.object({
type: z.enum(BUILTIN_TOOL_RESULT_STEP_TYPES),
call_id: z.string(),
result: z.unknown().nullish(),
is_error: z.boolean().nullish(),
name: z.string().nullish(),
server_name: z.string().nullish(),
signature: z.string().nullish()
}).loose();
return z.union([
userInputStep,
modelOutputStep,
functionCallStep,
thoughtStep,
processingCallStep,
processingResultStep,
builtinToolCallStep,
builtinToolResultStep,
z.object({ type: z.string() }).loose()
]);
};
const googleInteractionsResponseSchema = lazySchema(() => zodSchema(z.object({
id: z.string().nullish(),
created: z.string().nullish(),
updated: z.string().nullish(),
status: interactionStatusSchema(),
model: z.string().nullish(),
agent: z.string().nullish(),
steps: z.array(stepSchema()).nullish(),
usage: usageSchema().nullish(),
service_tier: z.string().nullish(),
previous_interaction_id: z.string().nullish(),
response_modalities: z.array(z.string()).nullish()
}).loose()));
const googleInteractionsEventSchema = lazySchema(() => zodSchema((() => {
const status = interactionStatusSchema();
const annotation = annotationSchema();
const thoughtSummaryItem = thoughtSummaryItemSchema();
const interactionCreatedEvent = z.object({
event_type: z.literal("interaction.created"),
event_id: z.string().nullish(),
interaction: z.object({
id: z.string().nullish(),
created: z.string().nullish(),
model: z.string().nullish(),
agent: z.string().nullish(),
status: status.nullish()
}).loose()
}).loose();
const stepStartEvent = z.object({
event_type: z.literal("step.start"),
event_id: z.string().nullish(),
index: z.number(),
step: stepSchema()
}).loose();
const stepDeltaText = z.object({
type: z.literal("text"),
text: z.string()
}).loose();
const stepDeltaThoughtSummary = z.object({
type: z.literal("thought_summary"),
content: thoughtSummaryItem.nullish()
}).loose();
const stepDeltaThoughtSignature = z.object({
type: z.literal("thought_signature"),
signature: z.string().nullish()
}).loose();
const stepDeltaArgumentsDelta = z.object({
type: z.literal("arguments_delta"),
arguments: z.string().nullish(),
id: z.string().nullish(),
signature: z.string().nullish()
}).loose();
const stepDeltaTextAnnotation = z.object({
type: z.enum(["text_annotation_delta", "text_annotation"]),
annotations: z.array(annotation).nullish()
}).loose();
const stepDeltaImage = z.object({
type: z.literal("image"),
data: z.string().nullish(),
mime_type: z.string().nullish(),
resolution: z.enum([
"low",
"medium",
"high",
"ultra_high"
]).nullish(),
uri: z.string().nullish()
}).loose();
const stepDeltaVideo = z.object({
type: z.literal("video"),
data: z.string().nullish(),
mime_type: z.string().nullish(),
uri: z.string().nullish()
}).loose();
const stepDeltaBuiltinToolCall = z.object({
type: z.enum(BUILTIN_TOOL_CALL_STEP_TYPES),
id: z.string().nullish(),
arguments: z.record(z.string(), z.unknown()).nullish(),
name: z.string().nullish(),
server_name: z.string().nullish(),
search_type: z.string().nullish(),
signature: z.string().nullish()
}).loose();
const stepDeltaBuiltinToolResult = z.object({
type: z.enum(BUILTIN_TOOL_RESULT_STEP_TYPES),
call_id: z.string().nullish(),
result: z.unknown().nullish(),
is_error: z.boolean().nullish(),
name: z.string().nullish(),
server_name: z.string().nullish(),
signature: z.string().nullish()
}).loose();
const stepDeltaUnknown = z.object({ type: z.string() }).loose();
const stepDeltaUnion = z.union([
stepDeltaText,
stepDeltaImage,
stepDeltaVideo,
stepDeltaThoughtSummary,
stepDeltaThoughtSignature,
stepDeltaArgumentsDelta,
stepDeltaTextAnnotation,
stepDeltaBuiltinToolCall,
stepDeltaBuiltinToolResult,
stepDeltaUnknown
]);
const stepDeltaEvent = z.object({
event_type: z.literal("step.delta"),
event_id: z.string().nullish(),
index: z.number(),
delta: stepDeltaUnion
}).loose();
const stepStopEvent = z.object({
event_type: z.literal("step.stop"),
event_id: z.string().nullish(),
index: z.number()
}).loose();
const interactionStatusUpdateEvent = z.object({
event_type: z.literal("interaction.status_update"),
event_id: z.string().nullish(),
interaction_id: z.string().nullish(),
status: status.nullish()
}).loose();
const interactionInProgressEvent = z.object({
event_type: z.literal("interaction.in_progress"),
event_id: z.string().nullish(),
interaction_id: z.string().nullish(),
status: status.nullish()
}).loose();
const interactionRequiresActionEvent = z.object({
event_type: z.literal("interaction.requires_action"),
event_id: z.string().nullish(),
interaction_id: z.string().nullish(),
status: status.nullish()
}).loose();
const interactionCompletedEvent = z.object({
event_type: z.literal("interaction.completed"),
event_id: z.string().nullish(),
interaction: z.object({
id: z.string().nullish(),
status: status.nullish(),
usage: usageSchema().nullish(),
service_tier: z.string().nullish()
}).loose()
}).loose();
const errorEvent = z.object({
event_type: z.literal("error"),
event_id: z.string().nullish(),
error: z.object({
code: z.string().nullish(),
message: z.string().nullish()
}).loose().nullish()
}).loose();
const unknownEvent = z.object({ event_type: z.string() }).loose();
return z.union([
interactionCreatedEvent,
stepStartEvent,
stepDeltaEvent,
stepStopEvent,
interactionStatusUpdateEvent,
interactionInProgressEvent,
interactionRequiresActionEvent,
interactionCompletedEvent,
errorEvent,
unknownEvent
]);
})()));
//#endregion
//#region src/interactions/google-interactions-language-model-options.ts
/**
* Provider-options schema for `google.interactions(...)` calls. Read from the
* shared `providerOptions.google.*` namespace (per PRD); per-call options that
* the AI SDK doesn't natively expose live here.
*
* All fields are `.nullish()` per the existing google provider convention.
*/
const googleInteractionsLanguageModelOptions = lazySchema(() => zodSchema(z.object({
/** User-defined request metadata, including `safety_identifier`. */
labels: z.record(z.string(), z.string()).nullish(),
previousInteractionId: z.string().nullish(),
store: z.boolean().nullish(),
agent: z.string().nullish(),
agentConfig: z.union([z.object({ type: z.literal("dynamic") }).loose(), z.object({
type: z.literal("deep-research"),
thinkingSummaries: z.enum(["auto", "none"]).nullish(),
visualization: z.enum(["off", "auto"]).nullish(),
collaborativePlanning: z.boolean().nullish()
})]).nullish(),
thinkingLevel: z.enum([
"minimal",
"low",
"medium",
"high",
"xhigh"
]).nullish(),
thinkingSummaries: z.enum(["auto", "none"]).nullish(),
/**
* Output-format entries that map directly to the API's `response_format`
* array. Use this to request image, audio, video, or non-JSON text
* outputs with modality-specific controls.
*
* Entries are sent in order. The AI SDK call-level `responseFormat: {
* type: 'json', schema }` still drives JSON-mode and adds a matching
* text entry automatically; entries listed here are appended.
*/
responseFormat: z.array(z.union([
z.object({
type: z.literal("text"),
mimeType: z.string().nullish(),
schema: z.unknown().nullish()
}).loose(),
z.object({
type: z.literal("image"),
mimeType: z.string().nullish(),
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"
]).nullish(),
imageSize: z.enum([
"1K",
"2K",
"4K",
"512"
]).nullish()
}).loose(),
z.object({
type: z.literal("audio"),
mimeType: z.string().nullish()
}).loose(),
z.object({
type: z.literal("video"),
aspectRatio: z.enum(["16:9", "9:16"]).nullish(),
resolution: z.enum([
"360p",
"720p",
"1080p",
"4k"
]).nullish(),
duration: z.string().nullish(),
delivery: z.enum(["inline", "uri"]).nullish(),
gcsUri: z.string().nullish()
}).loose()
])).nullish(),
/**
* @deprecated Use `responseFormat` with a `{ type: 'image', ... }`
* entry instead. Retained for backwards compatibility; the SDK
* translates it into a matching `response_format` image entry and
* emits a warning when set.
*/
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"
]).nullish(),
imageSize: z.enum([
"1K",
"2K",
"4K",
"512"
]).nullish()
}).nullish(),
mediaResolution: z.enum([
"low",
"medium",
"high",
"ultra_high"
]).nullish(),
responseModalities: z.array(z.enum([
"text",
"image",
"audio",
"video",
"document"
])).nullish(),
serviceTier: z.enum([
"flex",
"standard",
"priority"
]).nullish(),
/**
* Alternative to AI SDK `system` message. If both are set, the AI SDK
* `system` message wins and a warning is emitted.
*/
systemInstruction: z.string().nullish(),
/**
* Per-block signature for round-tripping `thought.signature` and
* `function_call.signature` blocks. Set by the SDK on output reasoning /
* tool-call parts; passed back unchanged on input parts so the API
* accepts the prior turn.
*/
signature: z.string().nullish(),
/**
* Set by the SDK on output assistant messages. The converter uses it to
* decide which messages to drop when compacting under
* `previousInteractionId`.
*/
interactionId: z.string().nullish(),
/**
* Maximum time, in milliseconds, to poll a background interaction (agent
* call) before giving up. Defaults to 30 minutes. Long-running agents
* such as deep research can take tens of minutes — increase if needed.
*/
pollingTimeoutMs: z.number().int().positive().nullish(),
/**
* Run the interaction in the background. Required for agents whose
* server-side workflow cannot complete within a single request/response.
* When `true`, the POST returns with a non-terminal status and the SDK
* polls `GET /interactions/{id}` until the work completes. Some agents
* reject `true`; see the agent's documentation for which mode it
* requires.
*/
background: z.boolean().nullish(),
/**
* Environment configuration for the agent sandbox. Only applies to agent
* calls (`google.interactions({ agent })`); ignored on model-id calls.
*
* - `"remote"`: provision a fresh sandbox for this call.
* - any other string: an existing `environment_id` to reuse.
* - object: provision a fresh sandbox and optionally preload `sources`
* and/or constrain outbound traffic via `network`.
*/
environment: z.union([z.string(), z.object({
type: z.literal("remote"),
sources: z.array(z.union([
z.object({
type: z.literal("gcs"),
source: z.string(),
target: z.string().nullish()
}),
z.object({
type: z.literal("repository"),
source: z.string(),
target: z.string().nullish()
}),
z.object({
type: z.literal("inline"),
content: z.string(),
target: z.string()
})
])).nullish(),
network: z.union([z.literal("disabled"), z.object({ allowlist: z.array(z.object({
domain: z.string(),
transform: z.array(z.record(z.string(), z.string())).nullish()
})) })]).nullish()
})]).nullish()
})));
//#endregion
//#region src/interactions/parse-google-interactions-outputs.ts
function googleProviderMetadata({ signature, interactionId, processingId, processingCallId }) {
const google = {};
if (signature != null) google.signature = signature;
if (interactionId != null) google.interactionId = interactionId;
if (processingId != null) google.processingId = processingId;
if (processingCallId != null) google.processingCallId = processingCallId;
return Object.keys(google).length > 0 ? { providerMetadata: { google } } : {};
}
const BUILTIN_TOOL_CALL_TYPES = /* @__PURE__ */ new Set([
"google_search_call",
"code_execution_call",
"url_context_call",
"file_search_call",
"google_maps_call",
"mcp_server_tool_call"
]);
const BUILTIN_TOOL_RESULT_TYPES = /* @__PURE__ */ new Set([
"google_search_result",
"code_execution_result",
"url_context_result",
"file_search_result",
"google_maps_result",
"mcp_server_tool_result"
]);
function builtinToolNameFromCallType(type) {
return type.replace(/_call$/, "");
}
function builtinToolNameFromResultType(type) {
return type.replace(/_result$/, "");
}
/**
* Walks the `steps[]` array of an Interactions response and emits AI SDK
* `LanguageModelV4Content[]`. Surfaces:
*
* - `model_output` steps: iterates `step.content[]` for `text` (with
* annotations → source parts) and `image` content blocks.
* - `thought` steps: emits a single `reasoning` part from `summary[*]`.
* - `function_call` steps: emits a `tool-call` part directly.
* - Built-in tool `*_call` / `*_result` steps (Google Search, Code Execution,
* URL Context, File Search, Google Maps, MCP Server): emits
* `tool-call`/`tool-result` parts with `providerExecuted: true`.
* - `user_input` steps are skipped (they echo the client's input).
*/
function parseGoogleInteractionsOutputs({ steps, generateId, interactionId }) {
const content = [];
let hasFunctionCall = false;
if (steps == null) return {
content,
hasFunctionCall
};
for (const step of steps) {
if (step == null || typeof step !== "object") continue;
const type = step.type;
if (typeof type !== "string") continue;
switch (type) {
case "user_input": break;
case "model_output": {
const blocks = step.content ?? [];
for (const block of blocks) {
if (block == null || typeof block !== "object") continue;
const blockType = block.type;
if (blockType === "text") {
const text = block.text ?? "";
const annotations = block.annotations;
content.push({
type: "text",
text,
...googleProviderMetadata({ interactionId })
});
const sources = annotationsToSources({
annotations,
generateId
});
for (const source of sources) content.push(source);
} else if (blockType === "image") {
const image = block;
if (image.data != null && image.data.length > 0) content.push({
type: "file",
mediaType: image.mime_type ?? "image/png",
data: {
type: "data",
data: image.data
},
...googleProviderMetadata({ interactionId })
});
else if (image.uri != null && image.uri.length > 0) content.push({
type: "file",
mediaType: image.mime_type ?? "image/png",
data: {
type: "url",
url: new URL(image.uri)
},
...googleProviderMetadata({ interactionId })
});
} else if (blockType === "video") {
const video = block;
if (video.data != null && video.data.length > 0) content.push({
type: "file",
mediaType: video.mime_type ?? "video/mp4",
data: {
type: "data",
data: video.data
},
...googleProviderMetadata({ interactionId })
});
else if (video.uri != null && video.uri.length > 0) content.push({
type: "file",
mediaType: video.mime_type ?? "video/mp4",
data: {
type: "url",
url: new URL(video.uri)
},
...googleProviderMetadata({ interactionId })
});
}
}
break;
}
case "thought": {
const thought = step;
const text = (Array.isArray(thought.summary) ? thought.summary : []).filter((item) => item?.type === "text" && typeof item.text === "string").map((item) => item.text).join("\n");
content.push({
type: "reasoning",
text,
...googleProviderMetadata({
signature: thought.signature,
interactionId
})
});
break;
}
case "processing_call": {
const call = step;
const processingId = call.id || generateId();
content.push({
type: "custom",
kind: "google.processing_call",
...googleProviderMetadata({
signature: call.signature,
interactionId,
processingId
})
});
break;
}
case "processing_result": {
const result = step;
const processingCallId = result.call_id || generateId();
content.push({
type: "custom",
kind: "google.processing_result",
...googleProviderMetadata({
signature: result.signature,
interactionId,
processingCallId
})
});
break;
}
case "function_call": {
hasFunctionCall = true;
const call = step;
content.push({
type: "tool-call",
toolCallId: call.id,
toolName: call.name,
input: JSON.stringify(call.arguments ?? {}),
...googleProviderMetadata({
signature: call.signature,
interactionId
})
});
break;
}
default: if (BUILTIN_TOOL_CALL_TYPES.has(type)) {
const call = step;
const toolName = type === "mcp_server_tool_call" ? call.name ?? "mcp_server_tool" : builtinToolNameFromCallType(type);
const input = JSON.stringify(call.arguments ?? {});
content.push({
type: "tool-call",
toolCallId: call.id || generateId(),
toolName,
input,
providerExecuted: true
});
} else if (BUILTIN_TOOL_RESULT_TYPES.has(type)) {
const result = step;
const toolName = type === "mcp_server_tool_result" ? result.name ?? "mcp_server_tool" : builtinToolNameFromResultType(type);
content.push({
type: "tool-result",
toolCallId: result.call_id || generateId(),
toolName,
result: result.result ?? null
});
const sources = builtinToolResultToSources({
block: step,
generateId
});
for (const source of sources) content.push(source);
}
}
}
return {
content,
hasFunctionCall
};
}
//#endregion
//#region src/interactions/cancel-google-interaction.ts
const getOriginalFetch = () => globalThis.fetch;
/**
* Best-effort `POST /interactions/{id}/cancel` to stop a background interaction
* on Google's side after the caller has aborted locally. Errors and non-2xx
* responses are swallowed so a cancel failure cannot mask the original abort.
*
* Skips the request entirely if `interactionId` is missing/empty -- e.g. when
* the interaction was created with `store: false` and the API did not return an
* id.
*/
async function cancelGoogleInteraction({ baseURL, interactionId, headers, fetch = getOriginalFetch() }) {
if (interactionId == null || interactionId.length === 0) return;
const url = `${baseURL}/interactions/${encodeURIComponent(interactionId)}/cancel`;
try {
const response = await fetch(url, {
method: "POST",
headers: withUserAgentSuffix(combineHeaders({ "Content-Type": "application/json" }, await resolve(headers)), getRuntimeEnvironmentUserAgent()),
body: "{}"
});
try {
await response.text();
} catch {}
} catch {}
}
//#endregion
//#region src/interactions/poll-google-interactions.ts
const TERMINAL_STATUSES = /* @__PURE__ */ new Set([
"completed",
"failed",
"cancelled",
"incomplete"
]);
function isTerminalStatus(status) {
return status != null && TERMINAL_STATUSES.has(status);
}
const DEFAULT_INITIAL_DELAY_MS = 1e3;
const DEFAULT_MAX_DELAY_MS = 1e4;
const DEFAULT_TIMEOUT_MS = 18e5;
/**
* Polls `GET {baseURL}/interactions/{id}` until the response status is
* terminal (`completed` / `failed` / `cancelled` / `incomplete`). Throws if
* the polling loop exceeds `timeoutMs`, the response has no `id` to poll on,
* or the abort signal fires.
*/
async function pollGoogleInteractionUntilTerminal({ baseURL, interactionId, headers, fetch, abortSignal, initialDelayMs = DEFAULT_INITIAL_DELAY_MS, maxDelayMs = DEFAULT_MAX_DELAY_MS, timeoutMs = DEFAULT_TIMEOUT_MS }) {
if (interactionId == null || interactionId.length === 0) throw new Error("google.interactions: cannot poll a background interaction without an id. The POST response did not include an interaction id.");
const startedAt = Date.now();
let nextDelayMs = initialDelayMs;
const url = `${baseURL}/interactions/${encodeURIComponent(interactionId)}`;
const cancelOnServer = () => cancelGoogleInteraction({
baseURL,
interactionId,
headers,
fetch
});
try {
while (true) {
if (abortSignal?.aborted) {
await cancelOnServer();
throw new DOMException("Polling was aborted", "AbortError");
}
if (Date.now() - startedAt > timeoutMs) throw new Error(`google.interactions: timed out polling interaction ${interactionId} after ${timeoutMs}ms.`);
await delay(nextDelayMs, { abortSignal });
const { value: response, rawValue: rawResponse, responseHeaders } = await getFromApi({
url,
validateUrl: false,
headers: await resolve(headers),
failedResponseHandler: googleFailedResponseHandler,
successfulResponseHandler: createJsonResponseHandler(googleInteractionsResponseSchema),
abortSignal,
fetch
});
if (isTerminalStatus(response.status)) return {
response,
rawResponse,
responseHeaders
};
nextDelayMs = Math.min(nextDelayMs * 2, maxDelayMs);
}
} catch (error) {
if (isAbortError(error)) await cancelOnServer();
throw error;
}
}
//#endregion
//#region src/interactions/prepare-google-interactions-tools.ts
/**
* Maps AI SDK tool definitions and `toolChoice` onto the Gemini Interactions
* `tools[]` and `tool_choice` request fields.
*
* AI SDK function tools (`{ type: 'function', name, description, inputSchema }`)
* map to Interactions `{ type: 'function', name, description, parameters }`,
* with `parameters` passed through as plain JSON Schema (per
* `googleapis/js-genai` `samples/interactions_tool_call_with_functions.ts` and
* `src/interactions/resources/interactions.ts` `Function.parameters: unknown`).
*
* Provider-defined tools (`{ type: 'provider', id: 'google.<name>', args }`)
* map to the discriminated `Tool` union. The full set of
* provider-defined tool ids supported here:
*
* - `google.google_search` -> `{ type: 'google_search', search_types? }`
* - `google.code_execution` -> `{ type: 'code_execution' }`
* - `google.url_context` -> `{ type: 'url_context' }`
* - `google.file_search` -> `{ type: 'file_search', file_search_store_names?, top_k?, metadata_filter? }`
* - `google.google_maps` -> `{ type: 'google_maps', latitude?, longitude?, enable_widget? }`
* - `google.computer_use` -> `{ type: 'computer_use', environment?, excludedPredefinedFunctions? }`
* - `google.mcp_server` -> `{ type: 'mcp_server', name?, url?, headers?, allowed_tools? }`
* - `google.retrieval` -> `{ type: 'retrieval', retrieval_types?, vertex_ai_search_config? }`
*
* `toolChoice` shapes:
* - `'auto'` -> `'auto'`
* - `'required'` -> `'any'`
* - `'none'` -> omit all tools and `tool_choice`
* - `{ type: 'tool', toolName }` -> `{ allowed_tools: { mode: 'validated', tools: [name] } }`
* (Interactions `AllowedTools.tools` is an `Array<string>` of function
* names, not tool descriptors -- see `src/interactions/resources/interactions.ts`
* line ~151).
*/
function prepareGoogleInteractionsTools({ tools, toolChoice }) {
const toolWarnings = [];
if (toolChoice?.type === "none") return {
tools: void 0,
toolChoice: void 0,
toolWarnings
};
const normalized = tools?.length ? tools : void 0;
if (normalized == null) return {
tools: void 0,
toolChoice: void 0,
toolWarnings
};
const interactionsTools = [];
for (const tool of normalized) {
if (tool.type === "function") {
interactionsTools.push({
type: "function",
name: tool.name,
description: tool.description ?? "",
parameters: tool.inputSchema
});
continue;
}
if (tool.type === "provider") {
const args = tool.args ?? {};
switch (tool.id) {
case "google.google_search": {
const searchTypesArg = args.searchTypes;
let search_types;
if (searchTypesArg != null && typeof searchTypesArg === "object") {
const list = [];
if (searchTypesArg.webSearch != null) list.push("web_search");
if (searchTypesArg.imageSearch != null) list.push("image_search");
if (list.length > 0) search_types = list;
}
interactionsTools.push({
type: "google_search",
...search_types != null ? { search_types } : {}
});
break;
}
case "google.code_execution":
interactionsTools.push({ type: "code_execution" });
break;
case "google.url_context":
interactionsTools.push({ type: "url_context" });
break;
case "google.file_search":
interactionsTools.push({
type: "file_search",
...args.fileSearchStoreNames != null ? { file_search_store_names: args.fileSearchStoreNames } : {},
...args.topK != null ? { top_k: args.topK } : {},
...args.metadataFilter != null ? { metadata_filter: args.metadataFilter } : {}
});
break;
case "google.google_maps":
interactionsTools.push({
type: "google_maps",
...args.latitude != null ? { latitude: args.latitude } : {},
...args.longitude != null ? { longitude: args.longitude } : {},
...args.enableWidget != null ? { enable_widget: args.enableWidget } : {}
});
break;
case "google.computer_use":
interactionsTools.push({
type: "computer_use",
environment: args.environment ?? "browser",
...args.excludedPredefinedFunctions != null ? { excludedPredefinedFunctions: args.excludedPredefinedFunctions } : {}
});
break;
case "google.mcp_server":
interactionsTools.push({
type: "mcp_server",
...args.name != null ? { name: args.name } : {},
...args.url != null ? { url: args.url } : {},
...args.headers != null ? { headers: args.headers } : {},
...args.allowedTools != null ? { allowed_tools: args.allowedTools } : {}
});
break;
case "google.retrieval": {
const vertexAiSearchConfig = args.vertexAiSearchConfig ?? void 0;
interactionsTools.push({
type: "retrieval",
...args.retrievalTypes != null ? { retrieval_types: args.retrievalTypes } : { retrieval_types: ["vertex_ai_search"] },
...vertexAiSearchConfig != null ? { vertex_ai_search_config: vertexAiSearchConfig } : {}
});
break;
}
default: toolWarnings.push({
type: "unsupported",
feature: `provider-defined tool ${tool.id}`,
details: `provider-defined tool ${tool.id} is not supported by google.interactions; tool dropped.`
});
}
continue;
}
toolWarnings.push({
type: "unsupported",
feature: `tool of type ${tool.type}`,
details: "Only function tools and google.* provider-defined tools are supported by google.interactions; tool dropped."
});
}
const hasFunctionTool = interactionsTools.some((t) => t.type === "function");
let mappedToolChoice;
if (toolChoice != null && hasFunctionTool) switch (toolChoice.type) {
case "auto":
mappedToolChoice = "auto";
break;
case "required":
mappedToolChoice = "any";
break;
case "tool": mappedToolChoice = { allowed_tools: {
mode: "validated",
tools: [toolChoice.toolName]
} };
}
return {
tools: interactionsTools.length > 0 ? interactionsTools : void 0,
toolChoice: mappedToolChoice,
toolWarnings
};
}
//#endregion
//#region src/interactions/stream-google-interactions.ts
const DEFAULT_MAX_RETRIES = 3;
const DEFAULT_RETRY_DELAY_MS = 500;
/**
* Connects to `GET {baseURL}/interactions/{id}?stream=true` and surfaces the
* server-sent events as a `ReadableStream<ParseResult<GoogleInteractionsEvent>>`
* so the existing `buildGoogleInteractionsStreamTransform` can consume them
* unchanged.
*
* The connection can drop mid-run: long-running agents idle for long
* stretches between SSE events and undici's default body timeout terminates
* the request with `UND_ERR_BODY_TIMEOUT`. We track the last seen `event_id`
* and reconnect with `?last_event_id=<id>` on any unexpected end. After
* `maxRetries` consecutive failures the stream errors out so the caller can
* decide whether to fall back to polling.
*
* The stream completes cleanly when an `interaction.complete` event with a
* terminal status arrives, or when an `error` event arrives.
*/
function streamGoogleInteractionEvents({ baseURL, interactionId, headers, fetch, abortSignal, maxRetries = DEFAULT_MAX_RETRIES, retryDelayMs = DEFAULT_RETRY_DELAY_MS }) {
if (interactionId.length === 0) throw new Error("google.interactions: cannot stream a background interaction without an id.");
let lastEventId;
let complete = false;
let attempt = 0;
let receivedAnyEventThisAttempt = false;
let currentReader;
const internalAbort = new AbortController();
const upstreamAbortHandler = () => internalAbort.abort();
if (abortSignal != null) {
if (abortSignal.aborted) internalAbort.abort();
else abortSignal.addEventListener("abort", upstreamAbortHandler, { once: true });
}
const effectiveSignal = internalAbort.signal;
function buildUrl() {
const base = `${baseURL}/interactions/${encodeURIComponent(interactionId)}`;
const params = new URLSearchParams({ stream: "true" });
if (lastEventId != null) params.set("last_event_id", lastEventId);
return `${base}?${params.toString()}`;
}
async function openReader() {
const { value: stream } = await getFromApi({
url: buildUrl(),
validateUrl: false,
headers: {
...await resolve(headers),
accept: "text/event-stream"
},
failedResponseHandler: googleFailedResponseHandler,
successfulResponseHandler: createEventSourceResponseHandler(googleInteractionsEventSchema),
abortSignal: effectiveSignal,
fetch
});
return stream.getReader();
}
return new ReadableStream({
async start(controller) {
try {
while (!complete && !effectiveSignal.aborted) {
if (currentReader == null) try {
currentReader = await openReader();
receivedAnyEventThisAttempt = false;
} catch (error) {
if (isAbortError(error) || effectiveSignal.aborted) {
controller.error(error);
return;
}
attempt++;
if (attempt >= maxRetries) {
controller.error(error);
return;
}
await delay(retryDelayMs * attempt, { abortSignal: effectiveSignal });
continue;
}
try {
const { done, value } = await currentReader.read();
if (done) {
currentReader = void 0;
if (complete) break;
if (!receivedAnyEventThisAttempt) {
attempt++;
if (attempt >= maxRetries) {
controller.error(/* @__PURE__ */ new Error("google.interactions: SSE stream closed without producing any events."));
return;
}
await delay(retryDelayMs * attempt, { abortSignal: effectiveSignal });
} else attempt = 0;
continue;
}
receivedAnyEventThisAttempt = true;
if (value.success) {
const streamEvent = value.value;
if (typeof streamEvent.event_id === "string" && streamEvent.event_id.length > 0) lastEventId = streamEvent.event_id;
if (streamEvent.event_type === "interaction.completed" || streamEvent.event_type === "error") complete = true;
}
controller.enqueue(value);
} catch (error) {
if (isAbortError(error) || effectiveSignal.aborted) {
controller.error(error);
return;
}
currentReader = void 0;
attempt++;
if (attempt >= maxRetries) {
controller.error(error);
return;
}
await delay(retryDelayMs * attempt, { abortSignal: effectiveSignal });
}
}
controller.close();
} catch (error) {
controller.error(error);
} finally {
if (abortSignal != null) abortSignal.removeEventListener("abort", upstreamAbortHandler);
currentReader?.cancel().catch(() => {});
currentReader = void 0;
if (effectiveSignal.aborted && !complete) await cancelGoogleInteraction({
baseURL,
interactionId,
headers,
fetch
});
}
},
cancel() {
internalAbort.abort();
currentReader?.cancel().catch(() => {});
currentReader = void 0;
}
});
}
//#endregion
//#region src/interactions/synthesize-google-interactions-agent-stream.ts
/**
* Synthesizes a `LanguageModelV4StreamPart` stream from a fully-resolved
* Interaction response (i.e. the `response` returned after polling a
* `background: true` agent call to a terminal status).
*
* Agent calls cannot use SSE (`stream: true` is incompatible with
* `background: true`), so we deterministically replay the polled outputs as a
* stream sequence in the same order/shape `buildGoogleInteractionsStreamTransform`
* would produce. Each text/reasoning block is emitted as a single delta — the
* server has already produced the whole block by the time we synthesize.
*/
function synthesizeGoogleInteractionsAgentStream({ response, warnings, generateId, includeRawChunks, headerServiceTier }) {
return new ReadableStream({ start(controller) {
controller.enqueue({
type: "stream-start",
warnings
});
const interactionId = typeof response.id === "string" && response.id.length > 0 ? response.id : void 0;
let timestamp;
const created = response.created;
if (typeof created === "string") {
const parsed = new Date(created);
if (!Number.isNaN(parsed.getTime())) timestamp = parsed;
}
controller.enqueue({
type: "response-metadata",
...interactionId != null ? { id: interactionId } : {},
modelId: response.model ?? void 0,
...timestamp ? { timestamp } : {}
});
if (includeRawChunks) controller.enqueue({
type: "raw",
rawValue: response
});
const { content, hasFunctionCall } = parseGoogleInteractionsOutputs({
steps: response.steps ?? null,
generateId,
interactionId
});
let blockCounter = 0;
const nextBlockId = () => `${interactionId ?? "agent"}:${blockCounter++}`;
for (const part of content) switch (part.type) {
case "text": {
const id = nextBlockId();
const providerMetadata = part.providerMetadata;
controller.enqueue({
type: "text-start",
id
});
if (part.text.length > 0) controller.enqueue({
type: "text-delta",
id,
delta: part.text
});
controller.enqueue({
type: "text-end",
id,
...providerMetadata ? { providerMetadata } : {}
});
break;
}
case "reasoning": {
const id = nextBlockId();
const providerMetadata = part.providerMetadata;
controller.enqueue({
type: "reasoning-start",
id
});
if (part.text.length > 0) controller.enqueue({
type: "reasoning-delta",
id,
delta: part.text
});
controller.enqueue({
type: "reasoning-end",
id,
...providerMetadata ? { providerMetadata } : {}
});
break;
}
case "tool-call": {
const providerMetadata = part.providerMetadata;
controller.enqueue({
type: "tool-input-start",
id: part.toolCallId,
toolName: part.toolName,
...part.providerExecuted ? { providerExecuted: part.providerExecuted } : {}
});
controller.enqueue({
type: "tool-input-delta",
id: part.toolCallId,
delta: part.input
});
controller.enqueue({
type: "tool-input-end",
id: part.toolCallId
});
controller.enqueue({
type: "tool-call",
toolCallId: part.toolCallId,
toolName: part.toolName,
input: part.input,
...part.providerExecuted ? { providerExecuted: part.providerExecuted } : {},
...providerMetadata ? { providerMetadata } : {}
});
break;
}
case "tool-result":
controller.enqueue({
type: "tool-result",
toolCallId: part.toolCallId,
toolName: part.toolName,
result: part.result
});
break;
case "source":
case "file": controller.enqueue(part);
}
const serviceTier = response.service_tier ?? headerServiceTier;
const finishReason = {
unified: mapGoogleInteractionsFinishReason({
status: response.status,
hasFunctionCall
}),
raw: response.status
};
const providerMetadata = { google: {
...interactionId != null ? { interactionId } : {},
...serviceTier != null ? { serviceTier } : {}
} };
controller.enqueue({
type: "finish",
finishReason,
usage: convertGoogleInteractionsUsage(response.usage),
providerMetadata
});
controller.close();
} });
}
//#endregion
//#region src/interactions/google-interactions-language-model.ts
var GoogleInteractionsLanguageModel = class GoogleInteractionsLanguageModel {
static [WORKFLOW_SERIALIZE](model) {
return {
...serializeModelOptions({
modelId: model.modelId,
config: model.config
}),
agent: model.agent
};
}
static [WORKFLOW_DESERIALIZE](options) {
return new GoogleInteractionsLanguageModel(options.agent != null ? { agent: options.agent } : options.modelId, options.config);
}
constructor(modelOrAgent, config) {
this.specificationVersion = "v4";
if (typeof modelOrAgent === "string") {
this.modelId = modelOrAgent;
this.agent = void 0;
} else if ("managedAgent" in modelOrAgent) {
this.modelId = modelOrAgent.managedAgent;
this.agent = modelOrAgent.managedAgent;
} else {
this.modelId = modelOrAgent.agent;
this.agent = modelOrAgent.agent;
}
this.config = config;
}
get provider() {
return this.config.provider;
}
get supportedUrls() {
if (this.config.supportedUrls) return this.config.supportedUrls();
return {
"image/*": [/^https?:\/\/.+/],
"application/pdf": [/^https?:\/\/.+/],
"audio/*": [/^https?:\/\/.+/],
"video/*": [
/^https?:\/\/(www\.)?youtube\.com\/watch\?v=.+/,
/^https?:\/\/youtu\.be\/.+/,
/^gs:\/\/.+/
]
};
}
async getArgs(options) {
const warnings = [];
const googleOptions = await parseProviderOptions({
provider: "google",
providerOptions: options.providerOptions,
schema: googleInteractionsLanguageModelOptions
});
const isAgent = this.agent != null;
if (!isAgent) {
if (options.frequencyPenalty != null) warnings.push({
type: "unsupported",
feature: "frequencyPenalty"
});
if (options.presencePenalty != null) warnings.push({
type: "unsupported",
feature: "presencePenalty"
});
}
const hasTools = options.tools != null && options.tools.length > 0;
let toolsForBody;
let toolChoiceForBody;
if (hasTools) {
const prepared = prepareGoogleInteractionsTools({
tools: options.tools,
toolChoice: options.toolChoice
});
toolsForBody = prepared.tools;
toolChoiceForBody = prepared.toolChoice;
warnings.push(...prepared.toolWarnings);
}
const responseFormatEntries = [];
if (options.responseFormat?.type === "json") {
if (isAgent) warnings.push({
type: "other",
message: "google.interactions: structured output (responseFormat) is not supported when an agent is set; responseFormat will be ignored."
});
else {
const entry = {
type: "text",
mime_type: "application/json",
...options.responseFormat.schema != null ? { schema: options.responseFormat.schema } : {}
};
responseFormatEntries.push(entry);
}
}
if (googleOptions?.responseFormat != null) {
for (const entry of googleOptions.responseFormat) if (entry.type === "text") responseFormatEntries.push(pruneUndefined({
type: "text",
mime_type: entry.mimeType ?? void 0,
schema: entry.schema ?? void 0
}));
else if (entry.type === "image") responseFormatEntries.push(pruneUndefined({
type: "image",
mime_type: entry.mimeType ?? void 0,
aspect_ratio: entry.aspectRatio ?? void 0,
image_size: entry.imageSize ?? void 0
}));
else if (entry.type === "audio") responseFormatEntries.push(pruneUndefined({
type: "audio",
mime_type: entry.mimeType ?? void 0
}));
else if (entry.type === "video") responseFormatEntries.push(pruneUndefined({
type: "video",
aspect_ratio: entry.aspectRatio ?? void 0,
resolution: entry.resolution ?? void 0,
duration: entry.duration ?? void 0,
delivery: entry.delivery ?? void 0,
gcs_uri: entry.gcsUri ?? void 0
}));
}
const { input, systemInstruction: convertedSystemInstruction, warnings: convWarnings } = convertToGoogleInteractionsInput({
prompt: options.prompt,
previousInteractionId: googleOptions?.previousInteractionId ?? void 0,
store: googleOptions?.store ?? void 0,
mediaResolution: googleOptions?.mediaResolution ?? void 0
});
warnings.push(...convWarnings);
let systemInstruction = convertedSystemInstruction;
const optionSystemInstruction = googleOptions?.systemInstruction ?? void 0;
if (systemInstruction != null && optionSystemInstruction != null) warnings.push({
type: "other",
message: "google.interactions: both AI SDK system message and providerOptions.google.systemInstruction were set; using the AI SDK system message."
});
else if (systemInstruction == null && optionSystemInstruction != null) systemInstruction = optionSystemInstruction;
let generationConfig;
if (isAgent) {
const droppedFields = [];
if (options.temperature != null) droppedFields.push("temperature");
if (options.topP != null) droppedFields.push("topP");
if (options.topK != null) droppedFields.push("topK");
if (options.frequencyPenalty != null) droppedFields.push("frequencyPenalty");
if (options.presencePenalty != null) droppedFields.push("presencePenalty");
if (options.seed != null) droppedFields.push("seed");
if (options.stopSequences != null && options.stopSequences.length > 0) droppedFields.push("stopSequences");
if (options.maxOutputTokens != null) droppedFields.push("maxOutputTokens");
if (isCustomReasoning(options.reasoning)) droppedFields.push("reasoning");
if (googleOptions?.thinkingLevel != null) droppedFields.push("thinkingLevel");
if (googleOptions?.thinkingSummaries != null) droppedFields.push("thinkingSummaries");
if (googleOptions?.imageConfig != null) droppedFields.push("imageConfig");
if (droppedFields.length > 0) warnings.push({
type: "other",
message: `google.interactions: ${droppedFields.join(", ")} ${droppedFields.length === 1 ? "is" : "are"} not supported when an agent is set; use providerOptions.google.agentConfig instead. Dropped from the request body.`
});
generationConfig = void 0;
} else {
generationConfig = pruneUndefined({
temperature: options.temperature ?? void 0,
top_p: options.topP ?? void 0,
top_k: options.topK ?? void 0,
seed: options.seed ?? void 0,
stop_sequences: options.stopSequences != null && options.stopSequences.length > 0 ? options.stopSequences : void 0,
max_output_tokens: options.maxOutputTokens ?? void 0,
thinking_level: mapReasoningToProviderEffort({
reasoning: options.reasoning,
providerOptionsEffort: googleOptions?.thinkingLevel,
effortMap: Object.fromEntries((getGoogleLanguageModelEntry(this.modelId).capabilities.language.reasoning.efforts ?? []).map((level) => [level, level])),
warnings
}),
thinking_summaries: googleOptions?.thinkingSummaries ?? void 0,
tool_choice: toolChoiceForBody
});
if (googleOptions?.imageConfig != null) {
const alreadyHasImageEntry = responseFormatEntries.some((entry) => entry.type === "image");
warnings.push({
type: "other",
message: alreadyHasImageEntry ? "google.interactions: providerOptions.google.imageConfig is deprecated and was ignored because providerOptions.google.responseFormat already supplies an image entry. Use responseFormat exclusively." : "google.interactions: providerOptions.google.imageConfig is deprecated. Use providerOptions.google.responseFormat with a { type: \"image\", ... } entry instead."
});
if (!alreadyHasImageEntry) responseFormatEntries.push({
type: "image",
mime_type: "image/png",
...googleOptions.imageConfig.aspectRatio != null ? { aspect_ratio: googleOptions.imageConfig.aspectRatio } : {},
...googleOptions.imageConfig.imageSize != null ? { image_size: googleOptions.imageConfig.imageSize } : {}
});
}
}
let agentConfig;
if (isAgent && googleOptions?.agentConfig != null) {
const agentConfigOptions = googleOptions.agentConfig;
if (agentConfigOptions.type === "deep-research") agentConfig = pruneUndefined({
type: "deep-research",
thinking_summaries: agentConfigOptions.thinkingSummaries ?? void 0,
visualization: agentConfigOptions.visualization ?? void 0,
collaborative_planning: agentConfigOptions.collaborativePlanning ?? void 0
});
else if (agentConfigOptions.type === "dynamic") agentConfig = { type: "dynamic" };
}
let environment;
if (googleOptions?.environment != null) {
if (!isAgent) warnings.push({
type: "other",
message: "google.interactions: environment is only supported when an agent is set; environment will be omitted from the request body."
});
else if (typeof googleOptions.environment === "string") environment = googleOptions.environment;
else {
const environmentOptions = googleOptions.environment;
const sources = environmentOptions.sources?.map((source) => {
if (source.type === "inline") return {
type: "inline",
content: source.content,
target: source.target
};
return pruneUndefined({
type: source.type,
source: source.source,
target: source.target ?? void 0
});
});
let network;
if (environmentOptions.network === "disabled") network = "disabled";
else if (environmentOptions.network != null) network = { allowlist: environmentOptions.network.allowlist.map((entry) => pruneUndefined({
domain: entry.domain,
transform: entry.transform ?? void 0
})) };
environment = pruneUndefined({
type: "remote",
sources: sources != null && sources.length > 0 ? sources : void 0,
network
});
}
}
return {
args: pruneUndefined({
...isAgent ? { agent: this.agent } : { model: this.modelId },
input,
labels: googleOptions?.labels ?? void 0,
system_instruction: systemInstruction,
tools: toolsForBody,
response_format: responseFormatEntries.length > 0 ? responseFormatEntries : void 0,
response_modalities: googleOptions?.responseModalities != null ? googleOptions.responseModalities : void 0,
previous_interaction_id: googleOptions?.previousInteractionId ?? void 0,
service_tier: googleOptions?.serviceTier ?? void 0,
store: googleOptions?.store ?? void 0,
generation_config: generationConfig != null && Object.keys(generationConfig).length > 0 ? generationConfig : void 0,
agent_config: agentConfig,
environment,
background: googleOptions?.background ?? void 0
}),
warnings,
isAgent,
isBackground: googleOptions?.background === true,
pollingTimeoutMs: googleOptions?.pollingTimeoutMs ?? void 0
};
}
async doGenerate(options) {
const { args, warnings, isAgent, isBackground, pollingTimeoutMs } = await this.getArgs(options);
const url = `${this.config.baseURL}/interactions`;
const mergedHeaders = async () => combineHeaders(await resolve(this.config.headers), options.headers);
let { responseHeaders, value: response, rawValue: rawResponse } = await postJsonToApi({
url,
headers: await resolve(mergedHeaders),
body: args,
failedResponseHandler: googleFailedResponseHandler,
successfulResponseHandler: createJsonResponseHandler(googleInteractionsResponseSchema),
abortSignal: options.abortSignal,
fetch: this.config.fetch
});
if ((isAgent || isBackground) && !isTerminalStatus(response.status)) {
const polled = await pollGoogleInteractionUntilTerminal({
baseURL: this.config.baseURL,
interactionId: response.id,
headers: mergedHeaders,
fetch: this.config.fetch,
abortSignal: options.abortSignal,
timeoutMs: pollingTimeoutMs
});
response = polled.response;
rawResponse = polled.rawResponse;
responseHeaders = polled.responseHeaders ?? responseHeaders;
}
const interactionId = typeof response.id === "string" && response.id.length > 0 ? response.id : void 0;
const { content, hasFunctionCall } = parseGoogleInteractionsOutputs({
steps: response.steps ?? null,
generateId: this.config.generateId ?? generateId,
interactionId
});
const finishReason = {
unified: mapGoogleInteractionsFinishReason({
status: response.status,
hasFunctionCall
}),
raw: response.status
};
const serviceTier = response.service_tier ?? responseHeaders?.["x-gemini-service-tier"] ?? void 0;
const outputTokensByModality = getGoogleInteractionsOutputTokensByModality(response.usage);
const providerMetadata = { google: {
...interactionId != null ? { interactionId } : {},
...serviceTier != null ? { serviceTier } : {},
...outputTokensByModality != null ? { outputTokensByModality } : {}
} };
let timestamp;
if (typeof response.created === "string") {
const parsed = new Date(response.created);
if (!Number.isNaN(parsed.getTime())) timestamp = parsed;
}
return {
content,
finishReason,
usage: convertGoogleInteractionsUsage(response.usage),
warnings,
providerMetadata,
request: { body: args },
response: {
headers: responseHeaders,
body: rawResponse,
...interactionId != null ? { id: interactionId } : {},
...timestamp ? { timestamp } : {},
modelId: response.model ?? void 0
}
};
}
async doStream(options) {
const { args, warnings, isBackground, pollingTimeoutMs } = await this.getArgs(options);
const url = `${this.config.baseURL}/interactions`;
const mergedHeaders = async () => combineHeaders(await resolve(this.config.headers), options.headers);
if (isBackground) return this.doStreamBackground({
args,
warnings,
url,
mergedHeaders,
options,
pollingTimeoutMs
});
const body = {
...args,
stream: true
};
const { responseHeaders, value: response } = await postJsonToApi({
url,
headers: await resolve(mergedHeaders),
body,
failedResponseHandler: googleFailedResponseHandler,
successfulResponseHandler: createEventSourceResponseHandler(googleInteractionsEventSchema),
abortSignal: options.abortSignal,
fetch: this.config.fetch
});
const headerServiceTier = responseHeaders?.["x-gemini-service-tier"];
const transform = buildGoogleInteractionsStreamTransform({
warnings,
generateId: this.config.generateId ?? generateId,
includeRawChunks: options.includeRawChunks,
serviceTier: headerServiceTier
});
return {
stream: response.pipeThrough(transform),
request: { body },
response: { headers: responseHeaders }
};
}
async doStreamBackground({ args, warnings, url, mergedHeaders, options, pollingTimeoutMs }) {
const { responseHeaders: postHeaders, value: postResponse } = await postJsonToApi({
url,
headers: await resolve(mergedHeaders),
body: args,
failedResponseHandler: googleFailedResponseHandler,
successfulResponseHandler: createJsonResponseHandler(googleInteractionsResponseSchema),
abortSignal: options.abortSignal,
fetch: this.config.fetch
});
const interactionId = postResponse.id;
if (interactionId == null || interactionId.length === 0) throw new Error("google.interactions: background POST response did not include an interaction id; cannot stream the result.");
const headerServiceTier = postHeaders?.["x-gemini-service-tier"];
if (isTerminalStatus(postResponse.status)) return {
stream: synthesizeGoogleInteractionsAgentStream({
response: postResponse,
warnings,
generateId: this.config.generateId ?? generateId,
includeRawChunks: options.includeRawChunks,
headerServiceTier
}),
request: { body: args },
response: { headers: postHeaders }
};
const events = streamGoogleInteractionEvents({
baseURL: this.config.baseURL,
interactionId,
headers: mergedHeaders,
fetch: this.config.fetch,
abortSignal: options.abortSignal
});
const transform = buildGoogleInteractionsStreamTransform({
warnings,
generateId: this.config.generateId ?? generateId,
includeRawChunks: options.includeRawChunks,
serviceTier: headerServiceTier
});
return {
stream: events.pipeThrough(transform),
request: { body: args },
response: { headers: postHeaders }
};
}
};
function pruneUndefined(obj) {
const result = {};
for (const [key, value] of Object.entries(obj)) {
if (value === void 0) continue;
result[key] = value;
}
return result;
}
//#endregion
export { GoogleInteractionsLanguageModel, GoogleLanguageModel, GoogleSpeechModel, getGoogleLanguageModelEntry, getGoogleSpeechInput, getGroundingMetadataSchema, getUrlContextMetadataSchema, googleProviderOptions, googleTools, responseSchema };
//# sourceMappingURL=index.js.map