@ai-sdk/google 3.0.66 → 3.0.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/index.d.mts +90 -1
- package/dist/index.d.ts +90 -1
- package/dist/index.js +2383 -49
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2353 -1
- package/dist/index.mjs.map +1 -1
- package/docs/15-google-generative-ai.mdx +396 -0
- package/package.json +2 -2
- package/src/google-provider.ts +34 -0
- package/src/index.ts +6 -0
- package/src/interactions/build-google-interactions-stream-transform.ts +711 -0
- package/src/interactions/convert-google-interactions-usage.ts +47 -0
- package/src/interactions/convert-to-google-interactions-input.ts +630 -0
- package/src/interactions/extract-google-interactions-sources.ts +245 -0
- package/src/interactions/google-interactions-agent.ts +16 -0
- package/src/interactions/google-interactions-api.ts +466 -0
- package/src/interactions/google-interactions-language-model-options.ts +136 -0
- package/src/interactions/google-interactions-language-model.ts +609 -0
- package/src/interactions/google-interactions-prompt.ts +457 -0
- package/src/interactions/google-interactions-provider-metadata.ts +23 -0
- package/src/interactions/map-google-interactions-finish-reason.ts +33 -0
- package/src/interactions/parse-google-interactions-outputs.ts +257 -0
- package/src/interactions/poll-google-interactions.ts +110 -0
- package/src/interactions/prepare-google-interactions-tools.ts +245 -0
- package/src/interactions/synthesize-google-interactions-agent-stream.ts +185 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import type { JSONValue, LanguageModelV3Content } from '@ai-sdk/provider';
|
|
2
|
+
import type { GoogleInteractionsContentBlock } 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<LanguageModelV3Content>;
|
|
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-block
|
|
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
|
+
/**
|
|
41
|
+
* Set of built-in tool *call* discriminators emitted by the Interactions API.
|
|
42
|
+
*/
|
|
43
|
+
const BUILTIN_TOOL_CALL_TYPES = new Set([
|
|
44
|
+
'google_search_call',
|
|
45
|
+
'code_execution_call',
|
|
46
|
+
'url_context_call',
|
|
47
|
+
'file_search_call',
|
|
48
|
+
'google_maps_call',
|
|
49
|
+
'mcp_server_tool_call',
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Set of built-in tool *result* discriminators.
|
|
54
|
+
*/
|
|
55
|
+
const BUILTIN_TOOL_RESULT_TYPES = new Set([
|
|
56
|
+
'google_search_result',
|
|
57
|
+
'code_execution_result',
|
|
58
|
+
'url_context_result',
|
|
59
|
+
'file_search_result',
|
|
60
|
+
'google_maps_result',
|
|
61
|
+
'mcp_server_tool_result',
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
function builtinToolNameFromCallType(type: string): string {
|
|
65
|
+
return type.replace(/_call$/, '');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function builtinToolNameFromResultType(type: string): string {
|
|
69
|
+
return type.replace(/_result$/, '');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Walks the `outputs[]` array of an Interaction response and emits AI SDK
|
|
74
|
+
* `LanguageModelV3Content[]`. Surfaces:
|
|
75
|
+
*
|
|
76
|
+
* - `text` blocks (with annotations -> source parts)
|
|
77
|
+
* - `thought` blocks (reasoning)
|
|
78
|
+
* - `function_call` blocks (client-executed tool calls)
|
|
79
|
+
* - Built-in tool `*_call` / `*_result` blocks (Google Search, Code Execution,
|
|
80
|
+
* URL Context, File Search, Google Maps, MCP Server) as
|
|
81
|
+
* `tool-call`/`tool-result` parts with `providerExecuted: true`.
|
|
82
|
+
* - `text_annotation`-derived sources from the URL / file / place citations
|
|
83
|
+
* carried on text blocks.
|
|
84
|
+
*/
|
|
85
|
+
export function parseGoogleInteractionsOutputs({
|
|
86
|
+
outputs,
|
|
87
|
+
generateId,
|
|
88
|
+
interactionId,
|
|
89
|
+
}: {
|
|
90
|
+
outputs: Array<GoogleInteractionsContentBlock> | null | undefined;
|
|
91
|
+
generateId: () => string;
|
|
92
|
+
/**
|
|
93
|
+
* Top-level `Interaction.id` on the response. Stamped onto each output
|
|
94
|
+
* part's `providerMetadata.google.interactionId` so the converter can drop
|
|
95
|
+
* matching assistant turns when `previousInteractionId` is used on the
|
|
96
|
+
* next turn (compaction).
|
|
97
|
+
*/
|
|
98
|
+
interactionId?: string;
|
|
99
|
+
}): ParseGoogleInteractionsOutputsResult {
|
|
100
|
+
const content: Array<LanguageModelV3Content> = [];
|
|
101
|
+
let hasFunctionCall = false;
|
|
102
|
+
|
|
103
|
+
if (outputs == null) {
|
|
104
|
+
return { content, hasFunctionCall };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const block of outputs) {
|
|
108
|
+
if (block == null || typeof block !== 'object') continue;
|
|
109
|
+
const type = (block as { type?: string }).type;
|
|
110
|
+
if (typeof type !== 'string') continue;
|
|
111
|
+
|
|
112
|
+
switch (type) {
|
|
113
|
+
case 'text': {
|
|
114
|
+
const text = (block as { text?: string }).text ?? '';
|
|
115
|
+
const annotations = (
|
|
116
|
+
block as { annotations?: Array<GoogleInteractionsAnnotation> }
|
|
117
|
+
).annotations;
|
|
118
|
+
content.push({
|
|
119
|
+
type: 'text',
|
|
120
|
+
text,
|
|
121
|
+
...googleProviderMetadata({ interactionId }),
|
|
122
|
+
});
|
|
123
|
+
const sources = annotationsToSources({ annotations, generateId });
|
|
124
|
+
for (const source of sources) {
|
|
125
|
+
content.push(source);
|
|
126
|
+
}
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case 'thought': {
|
|
130
|
+
const thought = block as {
|
|
131
|
+
signature?: string;
|
|
132
|
+
summary?: Array<{ type: string; text?: string }>;
|
|
133
|
+
};
|
|
134
|
+
const summary = Array.isArray(thought.summary) ? thought.summary : [];
|
|
135
|
+
const text = summary
|
|
136
|
+
.filter(
|
|
137
|
+
item => item?.type === 'text' && typeof item.text === 'string',
|
|
138
|
+
)
|
|
139
|
+
.map(item => item.text as string)
|
|
140
|
+
.join('\n');
|
|
141
|
+
content.push({
|
|
142
|
+
type: 'reasoning',
|
|
143
|
+
text,
|
|
144
|
+
...googleProviderMetadata({
|
|
145
|
+
signature: thought.signature,
|
|
146
|
+
interactionId,
|
|
147
|
+
}),
|
|
148
|
+
});
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case 'image': {
|
|
152
|
+
const image = block as {
|
|
153
|
+
data?: string;
|
|
154
|
+
mime_type?: string;
|
|
155
|
+
uri?: string;
|
|
156
|
+
};
|
|
157
|
+
if (image.data != null && image.data.length > 0) {
|
|
158
|
+
content.push({
|
|
159
|
+
type: 'file',
|
|
160
|
+
mediaType: image.mime_type ?? 'image/png',
|
|
161
|
+
data: image.data,
|
|
162
|
+
...googleProviderMetadata({ interactionId }),
|
|
163
|
+
});
|
|
164
|
+
} else if (image.uri != null && image.uri.length > 0) {
|
|
165
|
+
/*
|
|
166
|
+
* V3 `LanguageModelV3File` only supports inline data (`string` /
|
|
167
|
+
* `Uint8Array`). URL-only image outputs cannot be represented as a
|
|
168
|
+
* file content part on the v3 spec; surface the URI through provider
|
|
169
|
+
* metadata so callers can still recover it.
|
|
170
|
+
*/
|
|
171
|
+
content.push({
|
|
172
|
+
type: 'file',
|
|
173
|
+
mediaType: image.mime_type ?? 'image/png',
|
|
174
|
+
data: '',
|
|
175
|
+
providerMetadata: {
|
|
176
|
+
google: {
|
|
177
|
+
...(interactionId != null ? { interactionId } : {}),
|
|
178
|
+
imageUri: image.uri,
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
case 'function_call': {
|
|
186
|
+
hasFunctionCall = true;
|
|
187
|
+
const call = block as {
|
|
188
|
+
id: string;
|
|
189
|
+
name: string;
|
|
190
|
+
arguments?: Record<string, unknown> | null;
|
|
191
|
+
signature?: string | null;
|
|
192
|
+
};
|
|
193
|
+
content.push({
|
|
194
|
+
type: 'tool-call',
|
|
195
|
+
toolCallId: call.id,
|
|
196
|
+
toolName: call.name,
|
|
197
|
+
input: JSON.stringify(call.arguments ?? {}),
|
|
198
|
+
...googleProviderMetadata({
|
|
199
|
+
signature: call.signature,
|
|
200
|
+
interactionId,
|
|
201
|
+
}),
|
|
202
|
+
});
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
default: {
|
|
206
|
+
if (BUILTIN_TOOL_CALL_TYPES.has(type)) {
|
|
207
|
+
const call = block as {
|
|
208
|
+
id?: string;
|
|
209
|
+
arguments?: Record<string, unknown>;
|
|
210
|
+
name?: string;
|
|
211
|
+
server_name?: string;
|
|
212
|
+
};
|
|
213
|
+
const toolName =
|
|
214
|
+
type === 'mcp_server_tool_call'
|
|
215
|
+
? (call.name ?? 'mcp_server_tool')
|
|
216
|
+
: builtinToolNameFromCallType(type);
|
|
217
|
+
const input = JSON.stringify(call.arguments ?? {});
|
|
218
|
+
content.push({
|
|
219
|
+
type: 'tool-call',
|
|
220
|
+
toolCallId: call.id ?? generateId(),
|
|
221
|
+
toolName,
|
|
222
|
+
input,
|
|
223
|
+
providerExecuted: true,
|
|
224
|
+
});
|
|
225
|
+
} else if (BUILTIN_TOOL_RESULT_TYPES.has(type)) {
|
|
226
|
+
const result = block as {
|
|
227
|
+
call_id?: string;
|
|
228
|
+
result?: unknown;
|
|
229
|
+
is_error?: boolean;
|
|
230
|
+
name?: string;
|
|
231
|
+
};
|
|
232
|
+
const toolName =
|
|
233
|
+
type === 'mcp_server_tool_result'
|
|
234
|
+
? (result.name ?? 'mcp_server_tool')
|
|
235
|
+
: builtinToolNameFromResultType(type);
|
|
236
|
+
content.push({
|
|
237
|
+
type: 'tool-result',
|
|
238
|
+
toolCallId: result.call_id ?? generateId(),
|
|
239
|
+
toolName,
|
|
240
|
+
result: (result.result ?? null) as NonNullable<JSONValue>,
|
|
241
|
+
});
|
|
242
|
+
const sources = builtinToolResultToSources({
|
|
243
|
+
block:
|
|
244
|
+
block as unknown as GoogleInteractionsBuiltinToolResultContent,
|
|
245
|
+
generateId,
|
|
246
|
+
});
|
|
247
|
+
for (const source of sources) {
|
|
248
|
+
content.push(source);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return { content, hasFunctionCall };
|
|
257
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createJsonResponseHandler,
|
|
3
|
+
delay,
|
|
4
|
+
getFromApi,
|
|
5
|
+
type FetchFunction,
|
|
6
|
+
} from '@ai-sdk/provider-utils';
|
|
7
|
+
import { googleFailedResponseHandler } from '../google-error';
|
|
8
|
+
import {
|
|
9
|
+
googleInteractionsResponseSchema,
|
|
10
|
+
type GoogleInteractionsResponse,
|
|
11
|
+
} from './google-interactions-api';
|
|
12
|
+
import type { GoogleInteractionsStatus } from './google-interactions-prompt';
|
|
13
|
+
|
|
14
|
+
const TERMINAL_STATUSES: ReadonlySet<GoogleInteractionsStatus | string> =
|
|
15
|
+
new Set(['completed', 'failed', 'cancelled', 'incomplete']);
|
|
16
|
+
|
|
17
|
+
export function isTerminalStatus(
|
|
18
|
+
status: GoogleInteractionsStatus | string | null | undefined,
|
|
19
|
+
): boolean {
|
|
20
|
+
return status != null && TERMINAL_STATUSES.has(status);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* Default polling cadence for background interactions. Starts at 1 s, doubles
|
|
25
|
+
* each tick up to a 10 s ceiling, and gives up after 30 minutes -- agent runs
|
|
26
|
+
* such as deep research can take tens of minutes server-side, so we err on
|
|
27
|
+
* the long side rather than truncate a real run. Override per-call via
|
|
28
|
+
* `providerOptions.google.pollingTimeoutMs`.
|
|
29
|
+
*/
|
|
30
|
+
const DEFAULT_INITIAL_DELAY_MS = 1000;
|
|
31
|
+
const DEFAULT_MAX_DELAY_MS = 10000;
|
|
32
|
+
const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
33
|
+
|
|
34
|
+
export type PollGoogleInteractionResult = {
|
|
35
|
+
response: GoogleInteractionsResponse;
|
|
36
|
+
rawResponse: unknown;
|
|
37
|
+
responseHeaders: Record<string, string> | undefined;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Polls `GET {baseURL}/interactions/{id}` until the response status is
|
|
42
|
+
* terminal (`completed` / `failed` / `cancelled` / `incomplete`). Throws if
|
|
43
|
+
* the polling loop exceeds `timeoutMs`, the response has no `id` to poll on,
|
|
44
|
+
* or the abort signal fires.
|
|
45
|
+
*/
|
|
46
|
+
export async function pollGoogleInteractionUntilTerminal({
|
|
47
|
+
baseURL,
|
|
48
|
+
interactionId,
|
|
49
|
+
headers,
|
|
50
|
+
fetch,
|
|
51
|
+
abortSignal,
|
|
52
|
+
initialDelayMs = DEFAULT_INITIAL_DELAY_MS,
|
|
53
|
+
maxDelayMs = DEFAULT_MAX_DELAY_MS,
|
|
54
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
55
|
+
}: {
|
|
56
|
+
baseURL: string;
|
|
57
|
+
interactionId: string | null | undefined;
|
|
58
|
+
headers: Record<string, string | undefined>;
|
|
59
|
+
fetch?: FetchFunction;
|
|
60
|
+
abortSignal?: AbortSignal;
|
|
61
|
+
initialDelayMs?: number;
|
|
62
|
+
maxDelayMs?: number;
|
|
63
|
+
timeoutMs?: number;
|
|
64
|
+
}): Promise<PollGoogleInteractionResult> {
|
|
65
|
+
if (interactionId == null || interactionId.length === 0) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
'google.interactions: cannot poll a background interaction without an id. ' +
|
|
68
|
+
'The POST response did not include an interaction id.',
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const startedAt = Date.now();
|
|
73
|
+
let nextDelayMs = initialDelayMs;
|
|
74
|
+
const url = `${baseURL}/interactions/${encodeURIComponent(interactionId)}`;
|
|
75
|
+
|
|
76
|
+
while (true) {
|
|
77
|
+
if (abortSignal?.aborted) {
|
|
78
|
+
throw new DOMException('Polling was aborted', 'AbortError');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (Date.now() - startedAt > timeoutMs) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`google.interactions: timed out polling interaction ${interactionId} after ${timeoutMs}ms.`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
await delay(nextDelayMs, { abortSignal });
|
|
88
|
+
|
|
89
|
+
const {
|
|
90
|
+
value: response,
|
|
91
|
+
rawValue: rawResponse,
|
|
92
|
+
responseHeaders,
|
|
93
|
+
} = await getFromApi({
|
|
94
|
+
url,
|
|
95
|
+
headers,
|
|
96
|
+
failedResponseHandler: googleFailedResponseHandler,
|
|
97
|
+
successfulResponseHandler: createJsonResponseHandler(
|
|
98
|
+
googleInteractionsResponseSchema,
|
|
99
|
+
),
|
|
100
|
+
abortSignal,
|
|
101
|
+
fetch,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (isTerminalStatus(response.status)) {
|
|
105
|
+
return { response, rawResponse, responseHeaders };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
nextDelayMs = Math.min(nextDelayMs * 2, maxDelayMs);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
LanguageModelV3CallOptions,
|
|
3
|
+
SharedV3Warning,
|
|
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<SharedV3Warning>;
|
|
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 (TASK-7). 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: LanguageModelV3CallOptions['tools'];
|
|
53
|
+
toolChoice?: LanguageModelV3CallOptions['toolChoice'];
|
|
54
|
+
}): PrepareGoogleInteractionsToolsResult {
|
|
55
|
+
const toolWarnings: Array<SharedV3Warning> = [];
|
|
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
|
+
}
|