@ai-sdk/google 4.0.0-beta.8 → 4.0.0-beta.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +608 -5
  2. package/README.md +6 -4
  3. package/dist/index.d.ts +297 -54
  4. package/dist/index.js +5409 -640
  5. package/dist/index.js.map +1 -1
  6. package/dist/internal/index.d.ts +97 -26
  7. package/dist/internal/index.js +1653 -453
  8. package/dist/internal/index.js.map +1 -1
  9. package/docs/{15-google-generative-ai.mdx → 15-google.mdx} +784 -69
  10. package/package.json +16 -17
  11. package/src/{convert-google-generative-ai-usage.ts → convert-google-usage.ts} +13 -5
  12. package/src/convert-json-schema-to-openapi-schema.ts +1 -1
  13. package/src/convert-to-google-messages.ts +647 -0
  14. package/src/{google-generative-ai-embedding-options.ts → google-embedding-model-options.ts} +9 -2
  15. package/src/{google-generative-ai-embedding-model.ts → google-embedding-model.ts} +31 -18
  16. package/src/google-error.ts +1 -1
  17. package/src/google-files.ts +225 -0
  18. package/src/google-image-model-options.ts +35 -0
  19. package/src/{google-generative-ai-image-model.ts → google-image-model.ts} +116 -65
  20. package/src/{google-generative-ai-image-settings.ts → google-image-settings.ts} +2 -2
  21. package/src/google-json-accumulator.ts +371 -0
  22. package/src/{google-generative-ai-options.ts → google-language-model-options.ts} +50 -5
  23. package/src/{google-generative-ai-language-model.ts → google-language-model.ts} +691 -217
  24. package/src/google-prepare-tools.ts +72 -12
  25. package/src/google-prompt.ts +86 -0
  26. package/src/google-provider.ts +157 -53
  27. package/src/google-speech-api.ts +36 -0
  28. package/src/google-speech-model-options.ts +48 -0
  29. package/src/google-speech-model.ts +311 -0
  30. package/src/google-video-model-options.ts +43 -0
  31. package/src/{google-generative-ai-video-model.ts → google-video-model.ts} +25 -60
  32. package/src/{google-generative-ai-video-settings.ts → google-video-settings.ts} +2 -1
  33. package/src/index.ts +40 -9
  34. package/src/interactions/build-google-interactions-stream-transform.ts +818 -0
  35. package/src/interactions/cancel-google-interaction.ts +60 -0
  36. package/src/interactions/convert-google-interactions-usage.ts +47 -0
  37. package/src/interactions/convert-to-google-interactions-input.ts +557 -0
  38. package/src/interactions/extract-google-interactions-sources.ts +252 -0
  39. package/src/interactions/google-interactions-agent.ts +15 -0
  40. package/src/interactions/google-interactions-api.ts +530 -0
  41. package/src/interactions/google-interactions-language-model-options.ts +262 -0
  42. package/src/interactions/google-interactions-language-model.ts +776 -0
  43. package/src/interactions/google-interactions-prompt.ts +582 -0
  44. package/src/interactions/google-interactions-provider-metadata.ts +23 -0
  45. package/src/interactions/map-google-interactions-finish-reason.ts +31 -0
  46. package/src/interactions/parse-google-interactions-outputs.ts +252 -0
  47. package/src/interactions/poll-google-interactions.ts +129 -0
  48. package/src/interactions/prepare-google-interactions-tools.ts +245 -0
  49. package/src/interactions/stream-google-interactions.ts +242 -0
  50. package/src/interactions/synthesize-google-interactions-agent-stream.ts +185 -0
  51. package/src/internal/index.ts +3 -2
  52. package/src/{map-google-generative-ai-finish-reason.ts → map-google-finish-reason.ts} +3 -3
  53. package/src/realtime/google-realtime-event-mapper.ts +383 -0
  54. package/src/realtime/google-realtime-model-options.ts +3 -0
  55. package/src/realtime/google-realtime-model.ts +160 -0
  56. package/src/realtime/index.ts +2 -0
  57. package/src/tool/code-execution.ts +2 -2
  58. package/src/tool/enterprise-web-search.ts +9 -3
  59. package/src/tool/file-search.ts +5 -7
  60. package/src/tool/google-maps.ts +3 -2
  61. package/src/tool/google-search.ts +11 -12
  62. package/src/tool/url-context.ts +4 -2
  63. package/src/tool/vertex-rag-store.ts +9 -6
  64. package/dist/index.d.mts +0 -384
  65. package/dist/index.mjs +0 -2519
  66. package/dist/index.mjs.map +0 -1
  67. package/dist/internal/index.d.mts +0 -287
  68. package/dist/internal/index.mjs +0 -1708
  69. package/dist/internal/index.mjs.map +0 -1
  70. package/src/convert-to-google-generative-ai-messages.ts +0 -239
  71. package/src/google-generative-ai-prompt.ts +0 -47
@@ -0,0 +1,252 @@
1
+ import type { JSONValue, LanguageModelV4Content } from '@ai-sdk/provider';
2
+ import type { GoogleInteractionsStep } from './google-interactions-api';
3
+ import {
4
+ annotationsToSources,
5
+ builtinToolResultToSources,
6
+ } from './extract-google-interactions-sources';
7
+ import type {
8
+ GoogleInteractionsAnnotation,
9
+ GoogleInteractionsBuiltinToolResultContent,
10
+ } from './google-interactions-prompt';
11
+
12
+ export type ParseGoogleInteractionsOutputsResult = {
13
+ content: Array<LanguageModelV4Content>;
14
+ hasFunctionCall: boolean;
15
+ };
16
+
17
+ /*
18
+ * Builds a `providerMetadata.google` payload for an output part so the
19
+ * Interactions converter on the next turn can read both the per-step
20
+ * `signature` (round-trip) and the parent `interactionId` (history compaction
21
+ * under `previousInteractionId`).
22
+ */
23
+ function googleProviderMetadata({
24
+ signature,
25
+ interactionId,
26
+ }: {
27
+ signature?: string | null;
28
+ interactionId?: string;
29
+ }): { providerMetadata: { google: Record<string, string> } } | object {
30
+ const google: Record<string, string> = {};
31
+ if (signature != null) {
32
+ google.signature = signature;
33
+ }
34
+ if (interactionId != null) {
35
+ google.interactionId = interactionId;
36
+ }
37
+ return Object.keys(google).length > 0 ? { providerMetadata: { google } } : {};
38
+ }
39
+
40
+ const BUILTIN_TOOL_CALL_TYPES = new Set([
41
+ 'google_search_call',
42
+ 'code_execution_call',
43
+ 'url_context_call',
44
+ 'file_search_call',
45
+ 'google_maps_call',
46
+ 'mcp_server_tool_call',
47
+ ]);
48
+
49
+ const BUILTIN_TOOL_RESULT_TYPES = new Set([
50
+ 'google_search_result',
51
+ 'code_execution_result',
52
+ 'url_context_result',
53
+ 'file_search_result',
54
+ 'google_maps_result',
55
+ 'mcp_server_tool_result',
56
+ ]);
57
+
58
+ function builtinToolNameFromCallType(type: string): string {
59
+ return type.replace(/_call$/, '');
60
+ }
61
+
62
+ function builtinToolNameFromResultType(type: string): string {
63
+ return type.replace(/_result$/, '');
64
+ }
65
+
66
+ /**
67
+ * Walks the `steps[]` array of an Interactions response and emits AI SDK
68
+ * `LanguageModelV4Content[]`. Surfaces:
69
+ *
70
+ * - `model_output` steps: iterates `step.content[]` for `text` (with
71
+ * annotations → source parts) and `image` content blocks.
72
+ * - `thought` steps: emits a single `reasoning` part from `summary[*]`.
73
+ * - `function_call` steps: emits a `tool-call` part directly.
74
+ * - Built-in tool `*_call` / `*_result` steps (Google Search, Code Execution,
75
+ * URL Context, File Search, Google Maps, MCP Server): emits
76
+ * `tool-call`/`tool-result` parts with `providerExecuted: true`.
77
+ * - `user_input` steps are skipped (they echo the client's input).
78
+ */
79
+ export function parseGoogleInteractionsOutputs({
80
+ steps,
81
+ generateId,
82
+ interactionId,
83
+ }: {
84
+ steps: Array<GoogleInteractionsStep> | null | undefined;
85
+ generateId: () => string;
86
+ /**
87
+ * Top-level `Interaction.id` on the response. Stamped onto each output
88
+ * part's `providerMetadata.google.interactionId` so the converter can drop
89
+ * matching assistant turns when `previousInteractionId` is used on the
90
+ * next turn (compaction).
91
+ */
92
+ interactionId?: string;
93
+ }): ParseGoogleInteractionsOutputsResult {
94
+ const content: Array<LanguageModelV4Content> = [];
95
+ let hasFunctionCall = false;
96
+
97
+ if (steps == null) {
98
+ return { content, hasFunctionCall };
99
+ }
100
+
101
+ for (const step of steps) {
102
+ if (step == null || typeof step !== 'object') continue;
103
+ const type = (step as { type?: string }).type;
104
+ if (typeof type !== 'string') continue;
105
+
106
+ switch (type) {
107
+ case 'user_input': {
108
+ break;
109
+ }
110
+ case 'model_output': {
111
+ const blocks =
112
+ (step as { content?: Array<{ type?: string; [k: string]: unknown }> })
113
+ .content ?? [];
114
+ for (const block of blocks) {
115
+ if (block == null || typeof block !== 'object') continue;
116
+ const blockType = block.type;
117
+ if (blockType === 'text') {
118
+ const text = (block as { text?: string }).text ?? '';
119
+ const annotations = (
120
+ block as {
121
+ annotations?: Array<GoogleInteractionsAnnotation>;
122
+ }
123
+ ).annotations;
124
+ content.push({
125
+ type: 'text',
126
+ text,
127
+ ...googleProviderMetadata({ interactionId }),
128
+ });
129
+ const sources = annotationsToSources({ annotations, generateId });
130
+ for (const source of sources) {
131
+ content.push(source);
132
+ }
133
+ } else if (blockType === 'image') {
134
+ const image = block as {
135
+ data?: string;
136
+ mime_type?: string;
137
+ uri?: string;
138
+ };
139
+ if (image.data != null && image.data.length > 0) {
140
+ content.push({
141
+ type: 'file',
142
+ mediaType: image.mime_type ?? 'image/png',
143
+ data: { type: 'data', data: image.data },
144
+ ...googleProviderMetadata({ interactionId }),
145
+ });
146
+ } else if (image.uri != null && image.uri.length > 0) {
147
+ content.push({
148
+ type: 'file',
149
+ mediaType: image.mime_type ?? 'image/png',
150
+ data: { type: 'url', url: new URL(image.uri) },
151
+ ...googleProviderMetadata({ interactionId }),
152
+ });
153
+ }
154
+ }
155
+ }
156
+ break;
157
+ }
158
+ case 'thought': {
159
+ const thought = step as {
160
+ signature?: string;
161
+ summary?: Array<{ type: string; text?: string }>;
162
+ };
163
+ const summary = Array.isArray(thought.summary) ? thought.summary : [];
164
+ const text = summary
165
+ .filter(
166
+ item => item?.type === 'text' && typeof item.text === 'string',
167
+ )
168
+ .map(item => item.text as string)
169
+ .join('\n');
170
+ content.push({
171
+ type: 'reasoning',
172
+ text,
173
+ ...googleProviderMetadata({
174
+ signature: thought.signature,
175
+ interactionId,
176
+ }),
177
+ });
178
+ break;
179
+ }
180
+ case 'function_call': {
181
+ hasFunctionCall = true;
182
+ const call = step as {
183
+ id: string;
184
+ name: string;
185
+ arguments?: Record<string, unknown> | null;
186
+ signature?: string | null;
187
+ };
188
+ content.push({
189
+ type: 'tool-call',
190
+ toolCallId: call.id,
191
+ toolName: call.name,
192
+ input: JSON.stringify(call.arguments ?? {}),
193
+ ...googleProviderMetadata({
194
+ signature: call.signature,
195
+ interactionId,
196
+ }),
197
+ });
198
+ break;
199
+ }
200
+ default: {
201
+ if (BUILTIN_TOOL_CALL_TYPES.has(type)) {
202
+ const call = step as {
203
+ id?: string;
204
+ arguments?: Record<string, unknown>;
205
+ name?: string;
206
+ server_name?: string;
207
+ };
208
+ const toolName =
209
+ type === 'mcp_server_tool_call'
210
+ ? (call.name ?? 'mcp_server_tool')
211
+ : builtinToolNameFromCallType(type);
212
+ const input = JSON.stringify(call.arguments ?? {});
213
+ content.push({
214
+ type: 'tool-call',
215
+ toolCallId: call.id ?? generateId(),
216
+ toolName,
217
+ input,
218
+ providerExecuted: true,
219
+ });
220
+ } else if (BUILTIN_TOOL_RESULT_TYPES.has(type)) {
221
+ const result = step as {
222
+ call_id?: string;
223
+ result?: unknown;
224
+ is_error?: boolean;
225
+ name?: string;
226
+ };
227
+ const toolName =
228
+ type === 'mcp_server_tool_result'
229
+ ? (result.name ?? 'mcp_server_tool')
230
+ : builtinToolNameFromResultType(type);
231
+ content.push({
232
+ type: 'tool-result',
233
+ toolCallId: result.call_id ?? generateId(),
234
+ toolName,
235
+ result: (result.result ?? null) as NonNullable<JSONValue>,
236
+ });
237
+ const sources = builtinToolResultToSources({
238
+ block:
239
+ step as unknown as GoogleInteractionsBuiltinToolResultContent,
240
+ generateId,
241
+ });
242
+ for (const source of sources) {
243
+ content.push(source);
244
+ }
245
+ }
246
+ break;
247
+ }
248
+ }
249
+ }
250
+
251
+ return { content, hasFunctionCall };
252
+ }
@@ -0,0 +1,129 @@
1
+ import {
2
+ createJsonResponseHandler,
3
+ delay,
4
+ getFromApi,
5
+ isAbortError,
6
+ type FetchFunction,
7
+ } from '@ai-sdk/provider-utils';
8
+ import { googleFailedResponseHandler } from '../google-error';
9
+ import { cancelGoogleInteraction } from './cancel-google-interaction';
10
+ import {
11
+ googleInteractionsResponseSchema,
12
+ type GoogleInteractionsResponse,
13
+ } from './google-interactions-api';
14
+ import type { GoogleInteractionsStatus } from './google-interactions-prompt';
15
+
16
+ const TERMINAL_STATUSES: ReadonlySet<GoogleInteractionsStatus | string> =
17
+ new Set(['completed', 'failed', 'cancelled', 'incomplete']);
18
+
19
+ export function isTerminalStatus(
20
+ status: GoogleInteractionsStatus | string | null | undefined,
21
+ ): boolean {
22
+ return status != null && TERMINAL_STATUSES.has(status);
23
+ }
24
+
25
+ /*
26
+ * Default polling cadence for background interactions. Starts at 1 s, doubles
27
+ * each tick up to a 10 s ceiling, and gives up after 30 minutes -- agent runs
28
+ * such as deep research can take tens of minutes server-side, so we err on
29
+ * the long side rather than truncate a real run. Override per-call via
30
+ * `providerOptions.google.pollingTimeoutMs`.
31
+ */
32
+ const DEFAULT_INITIAL_DELAY_MS = 1000;
33
+ const DEFAULT_MAX_DELAY_MS = 10000;
34
+ const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
35
+
36
+ export type PollGoogleInteractionResult = {
37
+ response: GoogleInteractionsResponse;
38
+ rawResponse: unknown;
39
+ responseHeaders: Record<string, string> | undefined;
40
+ };
41
+
42
+ /**
43
+ * Polls `GET {baseURL}/interactions/{id}` until the response status is
44
+ * terminal (`completed` / `failed` / `cancelled` / `incomplete`). Throws if
45
+ * the polling loop exceeds `timeoutMs`, the response has no `id` to poll on,
46
+ * or the abort signal fires.
47
+ */
48
+ export async function pollGoogleInteractionUntilTerminal({
49
+ baseURL,
50
+ interactionId,
51
+ headers,
52
+ fetch,
53
+ abortSignal,
54
+ initialDelayMs = DEFAULT_INITIAL_DELAY_MS,
55
+ maxDelayMs = DEFAULT_MAX_DELAY_MS,
56
+ timeoutMs = DEFAULT_TIMEOUT_MS,
57
+ }: {
58
+ baseURL: string;
59
+ interactionId: string | null | undefined;
60
+ headers: Record<string, string | undefined>;
61
+ fetch?: FetchFunction;
62
+ abortSignal?: AbortSignal;
63
+ initialDelayMs?: number;
64
+ maxDelayMs?: number;
65
+ timeoutMs?: number;
66
+ }): Promise<PollGoogleInteractionResult> {
67
+ if (interactionId == null || interactionId.length === 0) {
68
+ throw new Error(
69
+ 'google.interactions: cannot poll a background interaction without an id. ' +
70
+ 'The POST response did not include an interaction id.',
71
+ );
72
+ }
73
+
74
+ const startedAt = Date.now();
75
+ let nextDelayMs = initialDelayMs;
76
+ const url = `${baseURL}/interactions/${encodeURIComponent(interactionId)}`;
77
+
78
+ /*
79
+ * When the caller aborts, fire a best-effort `POST /interactions/{id}/cancel`
80
+ * so the run stops billing on Google's side. Wrap every exit path that's
81
+ * triggered by an abort -- the explicit `abortSignal.aborted` check, the
82
+ * AbortError thrown by `delay()`, and any AbortError thrown by `getFromApi`.
83
+ */
84
+ const cancelOnServer = () =>
85
+ cancelGoogleInteraction({ baseURL, interactionId, headers, fetch });
86
+
87
+ try {
88
+ while (true) {
89
+ if (abortSignal?.aborted) {
90
+ await cancelOnServer();
91
+ throw new DOMException('Polling was aborted', 'AbortError');
92
+ }
93
+
94
+ if (Date.now() - startedAt > timeoutMs) {
95
+ throw new Error(
96
+ `google.interactions: timed out polling interaction ${interactionId} after ${timeoutMs}ms.`,
97
+ );
98
+ }
99
+
100
+ await delay(nextDelayMs, { abortSignal });
101
+
102
+ const {
103
+ value: response,
104
+ rawValue: rawResponse,
105
+ responseHeaders,
106
+ } = await getFromApi({
107
+ url,
108
+ headers,
109
+ failedResponseHandler: googleFailedResponseHandler,
110
+ successfulResponseHandler: createJsonResponseHandler(
111
+ googleInteractionsResponseSchema,
112
+ ),
113
+ abortSignal,
114
+ fetch,
115
+ });
116
+
117
+ if (isTerminalStatus(response.status)) {
118
+ return { response, rawResponse, responseHeaders };
119
+ }
120
+
121
+ nextDelayMs = Math.min(nextDelayMs * 2, maxDelayMs);
122
+ }
123
+ } catch (error) {
124
+ if (isAbortError(error)) {
125
+ await cancelOnServer();
126
+ }
127
+ throw error;
128
+ }
129
+ }
@@ -0,0 +1,245 @@
1
+ import type {
2
+ LanguageModelV4CallOptions,
3
+ SharedV4Warning,
4
+ } from '@ai-sdk/provider';
5
+ import type {
6
+ GoogleInteractionsTool,
7
+ GoogleInteractionsToolChoice,
8
+ } from './google-interactions-prompt';
9
+
10
+ export type PrepareGoogleInteractionsToolsResult = {
11
+ tools: Array<GoogleInteractionsTool> | undefined;
12
+ toolChoice: GoogleInteractionsToolChoice | undefined;
13
+ toolWarnings: Array<SharedV4Warning>;
14
+ };
15
+
16
+ /**
17
+ * Maps AI SDK tool definitions and `toolChoice` onto the Gemini Interactions
18
+ * `tools[]` and `tool_choice` request fields.
19
+ *
20
+ * AI SDK function tools (`{ type: 'function', name, description, inputSchema }`)
21
+ * map to Interactions `{ type: 'function', name, description, parameters }`,
22
+ * with `parameters` passed through as plain JSON Schema (per
23
+ * `googleapis/js-genai` `samples/interactions_tool_call_with_functions.ts` and
24
+ * `src/interactions/resources/interactions.ts` `Function.parameters: unknown`).
25
+ *
26
+ * Provider-defined tools (`{ type: 'provider', id: 'google.<name>', args }`)
27
+ * map to the discriminated `Tool` union. The full set of
28
+ * provider-defined tool ids supported here:
29
+ *
30
+ * - `google.google_search` -> `{ type: 'google_search', search_types? }`
31
+ * - `google.code_execution` -> `{ type: 'code_execution' }`
32
+ * - `google.url_context` -> `{ type: 'url_context' }`
33
+ * - `google.file_search` -> `{ type: 'file_search', file_search_store_names?, top_k?, metadata_filter? }`
34
+ * - `google.google_maps` -> `{ type: 'google_maps', latitude?, longitude?, enable_widget? }`
35
+ * - `google.computer_use` -> `{ type: 'computer_use', environment?, excludedPredefinedFunctions? }`
36
+ * - `google.mcp_server` -> `{ type: 'mcp_server', name?, url?, headers?, allowed_tools? }`
37
+ * - `google.retrieval` -> `{ type: 'retrieval', retrieval_types?, vertex_ai_search_config? }`
38
+ *
39
+ * `toolChoice` shapes:
40
+ * - `'auto'` -> `'auto'`
41
+ * - `'required'` -> `'any'`
42
+ * - `'none'` -> `'none'`
43
+ * - `{ type: 'tool', toolName }` -> `{ allowed_tools: { mode: 'validated', tools: [name] } }`
44
+ * (Interactions `AllowedTools.tools` is an `Array<string>` of function
45
+ * names, not tool descriptors -- see `src/interactions/resources/interactions.ts`
46
+ * line ~151).
47
+ */
48
+ export function prepareGoogleInteractionsTools({
49
+ tools,
50
+ toolChoice,
51
+ }: {
52
+ tools: LanguageModelV4CallOptions['tools'];
53
+ toolChoice?: LanguageModelV4CallOptions['toolChoice'];
54
+ }): PrepareGoogleInteractionsToolsResult {
55
+ const toolWarnings: Array<SharedV4Warning> = [];
56
+
57
+ const normalized = tools?.length ? tools : undefined;
58
+
59
+ if (normalized == null) {
60
+ return { tools: undefined, toolChoice: undefined, toolWarnings };
61
+ }
62
+
63
+ const interactionsTools: Array<GoogleInteractionsTool> = [];
64
+
65
+ for (const tool of normalized) {
66
+ if (tool.type === 'function') {
67
+ interactionsTools.push({
68
+ type: 'function',
69
+ name: tool.name,
70
+ description: tool.description ?? '',
71
+ parameters: tool.inputSchema,
72
+ });
73
+ continue;
74
+ }
75
+
76
+ if (tool.type === 'provider') {
77
+ const args = (tool.args ?? {}) as Record<string, unknown>;
78
+ switch (tool.id) {
79
+ case 'google.google_search': {
80
+ const searchTypesArg = args.searchTypes as
81
+ | { webSearch?: unknown; imageSearch?: unknown }
82
+ | undefined;
83
+ let search_types:
84
+ | Array<'web_search' | 'image_search' | 'enterprise_web_search'>
85
+ | undefined;
86
+ if (searchTypesArg != null && typeof searchTypesArg === 'object') {
87
+ const list: Array<
88
+ 'web_search' | 'image_search' | 'enterprise_web_search'
89
+ > = [];
90
+ if (searchTypesArg.webSearch != null) list.push('web_search');
91
+ if (searchTypesArg.imageSearch != null) list.push('image_search');
92
+ if (list.length > 0) {
93
+ search_types = list;
94
+ }
95
+ }
96
+ interactionsTools.push({
97
+ type: 'google_search',
98
+ ...(search_types != null ? { search_types } : {}),
99
+ });
100
+ break;
101
+ }
102
+ case 'google.code_execution': {
103
+ interactionsTools.push({ type: 'code_execution' });
104
+ break;
105
+ }
106
+ case 'google.url_context': {
107
+ interactionsTools.push({ type: 'url_context' });
108
+ break;
109
+ }
110
+ case 'google.file_search': {
111
+ interactionsTools.push({
112
+ type: 'file_search',
113
+ ...(args.fileSearchStoreNames != null
114
+ ? {
115
+ file_search_store_names:
116
+ args.fileSearchStoreNames as Array<string>,
117
+ }
118
+ : {}),
119
+ ...(args.topK != null ? { top_k: args.topK as number } : {}),
120
+ ...(args.metadataFilter != null
121
+ ? { metadata_filter: args.metadataFilter as string }
122
+ : {}),
123
+ });
124
+ break;
125
+ }
126
+ case 'google.google_maps': {
127
+ interactionsTools.push({
128
+ type: 'google_maps',
129
+ ...(args.latitude != null
130
+ ? { latitude: args.latitude as number }
131
+ : {}),
132
+ ...(args.longitude != null
133
+ ? { longitude: args.longitude as number }
134
+ : {}),
135
+ ...(args.enableWidget != null
136
+ ? { enable_widget: args.enableWidget as boolean }
137
+ : {}),
138
+ });
139
+ break;
140
+ }
141
+ case 'google.computer_use': {
142
+ interactionsTools.push({
143
+ type: 'computer_use',
144
+ environment:
145
+ (args.environment as 'browser' | undefined) ?? 'browser',
146
+ ...(args.excludedPredefinedFunctions != null
147
+ ? {
148
+ excludedPredefinedFunctions:
149
+ args.excludedPredefinedFunctions as Array<string>,
150
+ }
151
+ : {}),
152
+ });
153
+ break;
154
+ }
155
+ case 'google.mcp_server': {
156
+ interactionsTools.push({
157
+ type: 'mcp_server',
158
+ ...(args.name != null ? { name: args.name as string } : {}),
159
+ ...(args.url != null ? { url: args.url as string } : {}),
160
+ ...(args.headers != null
161
+ ? { headers: args.headers as Record<string, string> }
162
+ : {}),
163
+ ...(args.allowedTools != null
164
+ ? { allowed_tools: args.allowedTools as Array<unknown> }
165
+ : {}),
166
+ });
167
+ break;
168
+ }
169
+ case 'google.retrieval': {
170
+ const vertexAiSearchConfig =
171
+ (args.vertexAiSearchConfig as
172
+ | { datastores?: Array<string>; engine?: string }
173
+ | undefined) ?? undefined;
174
+ interactionsTools.push({
175
+ type: 'retrieval',
176
+ ...(args.retrievalTypes != null
177
+ ? {
178
+ retrieval_types:
179
+ args.retrievalTypes as Array<'vertex_ai_search'>,
180
+ }
181
+ : { retrieval_types: ['vertex_ai_search'] }),
182
+ ...(vertexAiSearchConfig != null
183
+ ? { vertex_ai_search_config: vertexAiSearchConfig }
184
+ : {}),
185
+ });
186
+ break;
187
+ }
188
+ default: {
189
+ toolWarnings.push({
190
+ type: 'unsupported',
191
+ feature: `provider-defined tool ${tool.id}`,
192
+ details: `provider-defined tool ${tool.id} is not supported by google.interactions; tool dropped.`,
193
+ });
194
+ break;
195
+ }
196
+ }
197
+ continue;
198
+ }
199
+
200
+ toolWarnings.push({
201
+ type: 'unsupported',
202
+ feature: `tool of type ${(tool as { type: string }).type}`,
203
+ details:
204
+ 'Only function tools and google.* provider-defined tools are supported by google.interactions; tool dropped.',
205
+ });
206
+ }
207
+
208
+ /*
209
+ * `tool_choice` on the Interactions API governs function calling only -- the
210
+ * API rejects requests with `tool_choice` set when no `function` tools are
211
+ * present (`{"error":{"message":"Function calling config is set without
212
+ * function_declarations."}}`). Drop `tool_choice` when the resolved tool
213
+ * list is empty or contains no function tools.
214
+ */
215
+ const hasFunctionTool = interactionsTools.some(t => t.type === 'function');
216
+
217
+ let mappedToolChoice: GoogleInteractionsToolChoice | undefined;
218
+ if (toolChoice != null && hasFunctionTool) {
219
+ switch (toolChoice.type) {
220
+ case 'auto':
221
+ mappedToolChoice = 'auto';
222
+ break;
223
+ case 'required':
224
+ mappedToolChoice = 'any';
225
+ break;
226
+ case 'none':
227
+ mappedToolChoice = 'none';
228
+ break;
229
+ case 'tool':
230
+ mappedToolChoice = {
231
+ allowed_tools: {
232
+ mode: 'validated',
233
+ tools: [toolChoice.toolName],
234
+ },
235
+ };
236
+ break;
237
+ }
238
+ }
239
+
240
+ return {
241
+ tools: interactionsTools.length > 0 ? interactionsTools : undefined,
242
+ toolChoice: mappedToolChoice,
243
+ toolWarnings,
244
+ };
245
+ }