@google/adk 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/dist/cjs/a2a/agent_to_a2a.js +18 -2
  2. package/dist/cjs/a2a/event_converter_utils.js +8 -4
  3. package/dist/cjs/a2a/metadata_converter_utils.js +1 -1
  4. package/dist/cjs/agents/base_agent.js +39 -0
  5. package/dist/cjs/agents/functions.js +6 -21
  6. package/dist/cjs/agents/llm_agent.js +2 -1
  7. package/dist/cjs/agents/loop_agent.js +15 -3
  8. package/dist/cjs/agents/processors/content_processor_utils.js +2 -1
  9. package/dist/cjs/agents/routed_agent.js +31 -0
  10. package/dist/cjs/agents/run_config.js +3 -2
  11. package/dist/cjs/artifacts/in_memory_artifact_service.js +7 -0
  12. package/dist/cjs/common.js +29 -0
  13. package/dist/cjs/context/anchored_context_compactor.js +127 -0
  14. package/dist/cjs/context/compaction_utils.js +52 -0
  15. package/dist/cjs/context/token_based_context_compactor.js +11 -24
  16. package/dist/cjs/events/compacted_event.js +9 -3
  17. package/dist/cjs/events/event.js +28 -0
  18. package/dist/cjs/index.js +4 -0
  19. package/dist/cjs/integrations/agent_registry/agent_registry_mcp_toolset.js +9 -1
  20. package/dist/cjs/plugins/global_instruction_plugin.js +97 -0
  21. package/dist/cjs/runner/runner.js +51 -12
  22. package/dist/cjs/tools/enterprise_web_search_tool.js +81 -0
  23. package/dist/cjs/tools/example_tool.js +68 -0
  24. package/dist/cjs/tools/load_web_page.js +250 -0
  25. package/dist/cjs/tools/mcp/load_mcp_resource_tool.js +140 -0
  26. package/dist/cjs/tools/mcp/mcp_toolset.js +63 -0
  27. package/dist/cjs/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.js +13 -10
  28. package/dist/cjs/tools/skill/run_skill_inline_script_tool.js +54 -4
  29. package/dist/cjs/tools/skill/skill_toolset.js +4 -2
  30. package/dist/cjs/utils/branch_trie.js +41 -0
  31. package/dist/cjs/utils/streaming_utils.js +5 -5
  32. package/dist/cjs/version.js +1 -1
  33. package/dist/esm/a2a/agent_to_a2a.js +18 -2
  34. package/dist/esm/a2a/event_converter_utils.js +8 -4
  35. package/dist/esm/a2a/metadata_converter_utils.js +1 -1
  36. package/dist/esm/agents/base_agent.js +39 -0
  37. package/dist/esm/agents/functions.js +7 -17
  38. package/dist/esm/agents/llm_agent.js +4 -3
  39. package/dist/esm/agents/loop_agent.js +15 -3
  40. package/dist/esm/agents/processors/content_processor_utils.js +2 -1
  41. package/dist/esm/agents/routed_agent.js +31 -0
  42. package/dist/esm/agents/run_config.js +3 -2
  43. package/dist/esm/artifacts/in_memory_artifact_service.js +7 -0
  44. package/dist/esm/common.js +25 -1
  45. package/dist/esm/context/anchored_context_compactor.js +99 -0
  46. package/dist/esm/context/compaction_utils.js +27 -0
  47. package/dist/esm/context/token_based_context_compactor.js +11 -24
  48. package/dist/esm/events/compacted_event.js +7 -2
  49. package/dist/esm/events/event.js +24 -0
  50. package/dist/esm/index.js +6 -1
  51. package/dist/esm/integrations/agent_registry/agent_registry_mcp_toolset.js +9 -1
  52. package/dist/esm/plugins/global_instruction_plugin.js +69 -0
  53. package/dist/esm/runner/runner.js +51 -12
  54. package/dist/esm/tools/enterprise_web_search_tool.js +55 -0
  55. package/dist/esm/tools/example_tool.js +42 -0
  56. package/dist/esm/tools/load_web_page.js +221 -0
  57. package/dist/esm/tools/mcp/load_mcp_resource_tool.js +114 -0
  58. package/dist/esm/tools/mcp/mcp_toolset.js +63 -0
  59. package/dist/esm/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.js +13 -10
  60. package/dist/esm/tools/skill/run_skill_inline_script_tool.js +53 -4
  61. package/dist/esm/tools/skill/skill_toolset.js +4 -2
  62. package/dist/esm/utils/branch_trie.js +13 -0
  63. package/dist/esm/utils/streaming_utils.js +1 -1
  64. package/dist/esm/version.js +1 -1
  65. package/dist/types/a2a/a2a_remote_agent.d.ts +5 -1
  66. package/dist/types/a2a/agent_to_a2a.d.ts +38 -0
  67. package/dist/types/agents/base_agent.d.ts +29 -1
  68. package/dist/types/agents/functions.d.ts +1 -9
  69. package/dist/types/agents/llm_agent.d.ts +5 -1
  70. package/dist/types/agents/loop_agent.d.ts +2 -2
  71. package/dist/types/agents/routed_agent.d.ts +24 -1
  72. package/dist/types/agents/run_config.d.ts +1 -1
  73. package/dist/types/common.d.ts +9 -2
  74. package/dist/types/context/anchored_context_compactor.d.ts +35 -0
  75. package/dist/types/context/compaction_utils.d.ts +15 -0
  76. package/dist/types/events/compacted_event.d.ts +10 -0
  77. package/dist/types/events/event.d.ts +13 -0
  78. package/dist/types/index.d.ts +3 -2
  79. package/dist/types/plugins/global_instruction_plugin.d.ts +38 -0
  80. package/dist/types/runner/runner.d.ts +1 -1
  81. package/dist/types/tools/enterprise_web_search_tool.d.ts +26 -0
  82. package/dist/types/tools/example_tool.d.ts +21 -0
  83. package/dist/types/tools/load_web_page.d.ts +33 -0
  84. package/dist/types/tools/mcp/load_mcp_resource_tool.d.ts +26 -0
  85. package/dist/types/tools/mcp/mcp_toolset.d.ts +27 -0
  86. package/dist/types/tools/skill/run_skill_inline_script_tool.d.ts +25 -0
  87. package/dist/types/tools/skill/skill_toolset.d.ts +9 -0
  88. package/dist/types/utils/branch_trie.d.ts +14 -0
  89. package/dist/types/version.d.ts +1 -1
  90. package/dist/web/a2a/agent_to_a2a.js +18 -2
  91. package/dist/web/a2a/event_converter_utils.js +8 -4
  92. package/dist/web/a2a/metadata_converter_utils.js +1 -1
  93. package/dist/web/agents/base_agent.js +39 -0
  94. package/dist/web/agents/functions.js +7 -17
  95. package/dist/web/agents/llm_agent.js +4 -3
  96. package/dist/web/agents/loop_agent.js +27 -3
  97. package/dist/web/agents/processors/content_processor_utils.js +2 -1
  98. package/dist/web/agents/routed_agent.js +31 -0
  99. package/dist/web/agents/run_config.js +8 -3
  100. package/dist/web/artifacts/in_memory_artifact_service.js +7 -0
  101. package/dist/web/common.js +25 -1
  102. package/dist/web/context/anchored_context_compactor.js +117 -0
  103. package/dist/web/context/compaction_utils.js +27 -0
  104. package/dist/web/context/token_based_context_compactor.js +11 -24
  105. package/dist/web/events/compacted_event.js +7 -2
  106. package/dist/web/events/event.js +24 -0
  107. package/dist/web/index.js +6 -1
  108. package/dist/web/integrations/agent_registry/agent_registry_mcp_toolset.js +9 -1
  109. package/dist/web/plugins/global_instruction_plugin.js +67 -0
  110. package/dist/web/runner/runner.js +48 -11
  111. package/dist/web/tools/enterprise_web_search_tool.js +55 -0
  112. package/dist/web/tools/example_tool.js +42 -0
  113. package/dist/web/tools/load_web_page.js +221 -0
  114. package/dist/web/tools/mcp/load_mcp_resource_tool.js +107 -0
  115. package/dist/web/tools/mcp/mcp_toolset.js +63 -0
  116. package/dist/web/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.js +13 -10
  117. package/dist/web/tools/skill/run_skill_inline_script_tool.js +51 -4
  118. package/dist/web/tools/skill/skill_toolset.js +4 -2
  119. package/dist/web/utils/branch_trie.js +13 -0
  120. package/dist/web/utils/streaming_utils.js +1 -1
  121. package/dist/web/version.js +1 -1
  122. package/package.json +1 -1
@@ -7,6 +7,7 @@ const require = topLevelCreateRequire(import.meta.url);
7
7
  */
8
8
  import { getContents } from "../agents/processors/content_processor_utils.js";
9
9
  import { isCompactedEvent } from "../events/compacted_event.js";
10
+ import { calculateRetainStartIndex } from "./compaction_utils.js";
10
11
  const CHARS_PER_TOKEN = 4;
11
12
  class TokenBasedContextCompactor {
12
13
  constructor(options) {
@@ -39,6 +40,13 @@ class TokenBasedContextCompactor {
39
40
  if (rawEvents.length <= this.eventRetentionSize) {
40
41
  return false;
41
42
  }
43
+ const retainStartIndex = calculateRetainStartIndex(
44
+ rawEvents,
45
+ this.eventRetentionSize
46
+ );
47
+ if (retainStartIndex === 0) {
48
+ return false;
49
+ }
42
50
  const promptTokenCount = latestPromptTokenCount(
43
51
  activeEvents,
44
52
  invocationContext
@@ -55,19 +63,10 @@ class TokenBasedContextCompactor {
55
63
  if (rawEvents.length <= this.eventRetentionSize) {
56
64
  return;
57
65
  }
58
- let retainStartIndex = Math.max(
59
- 0,
60
- rawEvents.length - this.eventRetentionSize
66
+ const retainStartIndex = calculateRetainStartIndex(
67
+ rawEvents,
68
+ this.eventRetentionSize
61
69
  );
62
- while (retainStartIndex > 0) {
63
- const eventToRetain = rawEvents[retainStartIndex];
64
- const previousEvent = rawEvents[retainStartIndex - 1];
65
- if (hasFunctionResponse(eventToRetain) && hasFunctionCall(previousEvent)) {
66
- retainStartIndex--;
67
- } else {
68
- break;
69
- }
70
- }
71
70
  if (retainStartIndex === 0) {
72
71
  return;
73
72
  }
@@ -122,18 +121,6 @@ function estimatePromptTokenCount(activeEvents, invocationContext) {
122
121
  }
123
122
  return Math.ceil(totalChars / CHARS_PER_TOKEN);
124
123
  }
125
- function hasFunctionCall(event) {
126
- var _a, _b;
127
- return !!((_b = (_a = event.content) == null ? void 0 : _a.parts) == null ? void 0 : _b.some(
128
- (part) => part.functionCall !== void 0
129
- ));
130
- }
131
- function hasFunctionResponse(event) {
132
- var _a, _b;
133
- return !!((_b = (_a = event.content) == null ? void 0 : _a.parts) == null ? void 0 : _b.some(
134
- (part) => part.functionResponse !== void 0
135
- ));
136
- }
137
124
  export {
138
125
  TokenBasedContextCompactor
139
126
  };
@@ -9,16 +9,21 @@ import { createEvent } from "./event.js";
9
9
  function isCompactedEvent(event) {
10
10
  return "isCompacted" in event && event.isCompacted === true;
11
11
  }
12
+ function isScratchpadEvent(event) {
13
+ return isCompactedEvent(event) && event.isScratchpad === true;
14
+ }
12
15
  function createCompactedEvent(params = {}) {
13
16
  return {
14
17
  ...createEvent(params),
15
18
  isCompacted: params.isCompacted || true,
16
19
  startTime: params.startTime,
17
20
  endTime: params.endTime,
18
- compactedContent: params.compactedContent
21
+ compactedContent: params.compactedContent,
22
+ isScratchpad: params.isScratchpad
19
23
  };
20
24
  }
21
25
  export {
22
26
  createCompactedEvent,
23
- isCompactedEvent
27
+ isCompactedEvent,
28
+ isScratchpadEvent
24
29
  };
@@ -5,6 +5,7 @@ const require = topLevelCreateRequire(import.meta.url);
5
5
  * Copyright 2026 Google LLC
6
6
  * SPDX-License-Identifier: Apache-2.0
7
7
  */
8
+ import { randomUUID } from "../utils/env_aware_utils.js";
8
9
  import { toCamelCase, toSnakeCase } from "../utils/object_notation_utils.js";
9
10
  import { createEventActions } from "./event_actions.js";
10
11
  function createEvent(params = {}) {
@@ -36,6 +37,17 @@ function getFunctionCalls(event) {
36
37
  }
37
38
  return funcCalls;
38
39
  }
40
+ const AF_FUNCTION_CALL_ID_PREFIX = "adk-";
41
+ function generateClientFunctionCallId() {
42
+ return `${AF_FUNCTION_CALL_ID_PREFIX}${randomUUID()}`;
43
+ }
44
+ function populateClientFunctionCallId(modelResponseEvent) {
45
+ for (const functionCall of getFunctionCalls(modelResponseEvent)) {
46
+ if (!functionCall.id) {
47
+ functionCall.id = generateClientFunctionCallId();
48
+ }
49
+ }
50
+ }
39
51
  function getFunctionResponses(event) {
40
52
  const funcResponses = [];
41
53
  if (event.content && event.content.parts) {
@@ -65,6 +77,14 @@ function stringifyContent(event) {
65
77
  return (_a2 = part.text) != null ? _a2 : "";
66
78
  }).join("");
67
79
  }
80
+ function getEventTokens(event) {
81
+ var _a;
82
+ if (((_a = event.usageMetadata) == null ? void 0 : _a.promptTokenCount) !== void 0) {
83
+ return event.usageMetadata.promptTokenCount;
84
+ }
85
+ const contentStr = stringifyContent(event);
86
+ return Math.ceil(contentStr.length / 4);
87
+ }
68
88
  function hasThoughts(event) {
69
89
  var _a, _b;
70
90
  return !!((_b = (_a = event.content) == null ? void 0 : _a.parts) == null ? void 0 : _b.some((part) => part.thought === true));
@@ -120,13 +140,17 @@ function transformToSnakeCaseEvent(event) {
120
140
  return toSnakeCase(event, PRESERVE_KEYS_CAMEL_CASE);
121
141
  }
122
142
  export {
143
+ AF_FUNCTION_CALL_ID_PREFIX,
123
144
  createEvent,
124
145
  createNewEventId,
146
+ generateClientFunctionCallId,
147
+ getEventTokens,
125
148
  getFunctionCalls,
126
149
  getFunctionResponses,
127
150
  hasThoughts,
128
151
  hasTrailingCodeExecutionResult,
129
152
  isFinalResponse,
153
+ populateClientFunctionCallId,
130
154
  pruneThoughts,
131
155
  stringifyContent,
132
156
  transformToCamelCaseEvent,
package/dist/esm/index.js CHANGED
@@ -29,11 +29,15 @@ import {
29
29
  loadSkillFromDir,
30
30
  validateSkillDir
31
31
  } from "./skills/loader.js";
32
- import { RunSkillInlineScriptTool } from "./tools/skill/run_skill_inline_script_tool.js";
32
+ import {
33
+ RunSkillInlineScriptErrorCode,
34
+ RunSkillInlineScriptTool
35
+ } from "./tools/skill/run_skill_inline_script_tool.js";
33
36
  import { RunSkillScriptTool } from "./tools/skill/run_skill_script_tool.js";
34
37
  export * from "./integrations/agent_registry/agent_registry.js";
35
38
  export * from "./telemetry/google_cloud.js";
36
39
  export * from "./telemetry/setup.js";
40
+ export * from "./tools/mcp/load_mcp_resource_tool.js";
37
41
  export * from "./tools/mcp/mcp_session_manager.js";
38
42
  export * from "./tools/mcp/mcp_tool.js";
39
43
  export * from "./tools/mcp/mcp_toolset.js";
@@ -47,6 +51,7 @@ export {
47
51
  GcsArtifactService,
48
52
  InvocationContext,
49
53
  RemoteA2AAgent,
54
+ RunSkillInlineScriptErrorCode,
50
55
  RunSkillInlineScriptTool,
51
56
  RunSkillScriptTool,
52
57
  UnsafeLocalCodeExecutor,
@@ -10,6 +10,7 @@ import {
10
10
  MCPSessionManager
11
11
  } from "../../tools/mcp/mcp_session_manager.js";
12
12
  import { MCPTool } from "../../tools/mcp/mcp_tool.js";
13
+ import { logger } from "../../utils/logger.js";
13
14
  import { GCP_MCP_SERVER_DESTINATION_ID } from "./types.js";
14
15
  class AgentRegistrySingleMCPToolset extends BaseToolset {
15
16
  /**
@@ -65,7 +66,14 @@ class AgentRegistrySingleMCPToolset extends BaseToolset {
65
66
  };
66
67
  const sessionManager = new MCPSessionManager(connectionParamsCopy);
67
68
  const session = await sessionManager.createSession();
68
- const listResult = await session.listTools();
69
+ let listResult;
70
+ try {
71
+ listResult = await session.listTools();
72
+ } finally {
73
+ await sessionManager.closeSession(session).catch((e) => {
74
+ logger.warn("Failed to close MCP discovery session", e);
75
+ });
76
+ }
69
77
  const tools = listResult.tools.map((tool) => {
70
78
  const prefixedName = this.prefix ? `${this.prefix}_${tool.name}` : tool.name;
71
79
  const mcpTool = new MCPTool(
@@ -0,0 +1,69 @@
1
+ import {createRequire as topLevelCreateRequire} from 'module';
2
+ const require = topLevelCreateRequire(import.meta.url);
3
+ /**
4
+ * @license
5
+ * Copyright 2026 Google LLC
6
+ * SPDX-License-Identifier: Apache-2.0
7
+ */
8
+ import { injectSessionState } from "../agents/instructions.js";
9
+ import { ReadonlyContext } from "../agents/readonly_context.js";
10
+ import { BasePlugin } from "./base_plugin.js";
11
+ class GlobalInstructionPlugin extends BasePlugin {
12
+ /**
13
+ * Initializes the GlobalInstructionPlugin.
14
+ *
15
+ * @param globalInstruction The instruction to apply globally. Can be a string
16
+ * or an InstructionProvider function that takes ReadonlyContext and
17
+ * returns a string (sync or async).
18
+ * @param name The name of the plugin (defaults to 'global_instruction').
19
+ */
20
+ constructor(globalInstruction, name = "global_instruction") {
21
+ super(name);
22
+ this.globalInstruction = globalInstruction;
23
+ }
24
+ async beforeModelCallback(params) {
25
+ if (!this.globalInstruction) {
26
+ return;
27
+ }
28
+ const readonlyContext = new ReadonlyContext(
29
+ params.callbackContext.invocationContext
30
+ );
31
+ const finalGlobalInstruction = await this.resolveGlobalInstruction(readonlyContext);
32
+ if (!finalGlobalInstruction) {
33
+ return;
34
+ }
35
+ if (!params.llmRequest.config) {
36
+ params.llmRequest.config = {};
37
+ }
38
+ const existingInstruction = params.llmRequest.config.systemInstruction;
39
+ if (!existingInstruction || Array.isArray(existingInstruction) && existingInstruction.length === 0) {
40
+ params.llmRequest.config.systemInstruction = finalGlobalInstruction;
41
+ return;
42
+ }
43
+ if (typeof existingInstruction === "string") {
44
+ params.llmRequest.config.systemInstruction = `${finalGlobalInstruction}
45
+
46
+ ${existingInstruction}`;
47
+ } else if (Array.isArray(existingInstruction)) {
48
+ params.llmRequest.config.systemInstruction = [
49
+ finalGlobalInstruction,
50
+ ...existingInstruction
51
+ ];
52
+ } else {
53
+ params.llmRequest.config.systemInstruction = [
54
+ finalGlobalInstruction,
55
+ existingInstruction
56
+ ];
57
+ }
58
+ return;
59
+ }
60
+ async resolveGlobalInstruction(readonlyContext) {
61
+ if (typeof this.globalInstruction === "string") {
62
+ return await injectSessionState(this.globalInstruction, readonlyContext);
63
+ }
64
+ return await this.globalInstruction(readonlyContext);
65
+ }
66
+ }
67
+ export {
68
+ GlobalInstructionPlugin
69
+ };
@@ -181,7 +181,7 @@ class Runner {
181
181
  throw new Error("No parts in the newMessage.");
182
182
  }
183
183
  if (runConfig.saveInputBlobsAsArtifacts) {
184
- await this.saveArtifacts(
184
+ newMessage = await this.saveArtifacts(
185
185
  invocationContext.invocationId,
186
186
  session.userId,
187
187
  session.id,
@@ -269,7 +269,7 @@ class Runner {
269
269
  }
270
270
  /**
271
271
  * Saves artifacts from the message parts and replaces the inline data with
272
- * a file name placeholder.
272
+ * a file name placeholder and optional file reference.
273
273
  *
274
274
  * @param invocationId The current invocation ID.
275
275
  * @param userId The user ID of the session.
@@ -279,28 +279,67 @@ class Runner {
279
279
  async saveArtifacts(invocationId, userId, sessionId, message) {
280
280
  var _a2;
281
281
  if (!this.artifactService || !((_a2 = message.parts) == null ? void 0 : _a2.length)) {
282
- return;
282
+ return message;
283
283
  }
284
284
  const sessionKey = {
285
285
  appName: this.appName,
286
286
  userId,
287
287
  sessionId
288
288
  };
289
+ const newParts = [];
290
+ let modified = false;
289
291
  for (let i = 0; i < message.parts.length; i++) {
290
292
  const part = message.parts[i];
291
293
  if (!part.inlineData) {
294
+ newParts.push(part);
292
295
  continue;
293
296
  }
294
- const fileName = `artifact_${invocationId}_${i}`;
295
- await this.artifactService.saveArtifact({
296
- ...sessionKey,
297
- filename: fileName,
298
- artifact: part
299
- });
300
- message.parts[i] = createPartFromText(
301
- `Uploaded file: ${fileName}. It is saved into artifacts`
302
- );
297
+ try {
298
+ const inlineData = part.inlineData;
299
+ const fileName = inlineData.displayName || `artifact_${invocationId}_${i}`;
300
+ const version = await this.artifactService.saveArtifact({
301
+ ...sessionKey,
302
+ filename: fileName,
303
+ artifact: part
304
+ });
305
+ newParts.push(createPartFromText(`[Uploaded Artifact: "${fileName}"]`));
306
+ try {
307
+ const artifactVersion = await this.artifactService.getArtifactVersion(
308
+ {
309
+ ...sessionKey,
310
+ filename: fileName,
311
+ version
312
+ }
313
+ );
314
+ if ((artifactVersion == null ? void 0 : artifactVersion.canonicalUri) && /^(gs|https?):/i.test(artifactVersion.canonicalUri)) {
315
+ newParts.push({
316
+ fileData: {
317
+ fileUri: artifactVersion.canonicalUri,
318
+ mimeType: artifactVersion.mimeType || inlineData.mimeType || "",
319
+ displayName: fileName
320
+ }
321
+ });
322
+ }
323
+ } catch (error) {
324
+ logger.warn(
325
+ `Failed to resolve artifact version for ${fileName}:`,
326
+ error
327
+ );
328
+ }
329
+ modified = true;
330
+ logger.info(`Successfully saved artifact: ${fileName}`);
331
+ } catch (error) {
332
+ logger.error(`Failed to save artifact for part ${i}:`, error);
333
+ newParts.push(part);
334
+ }
335
+ }
336
+ if (!modified) {
337
+ return message;
303
338
  }
339
+ return {
340
+ ...message,
341
+ parts: newParts
342
+ };
304
343
  }
305
344
  /**
306
345
  * Determines the next agent to run to continue the session. This is primarily
@@ -0,0 +1,55 @@
1
+ import {createRequire as topLevelCreateRequire} from 'module';
2
+ const require = topLevelCreateRequire(import.meta.url);
3
+ /**
4
+ * @license
5
+ * Copyright 2026 Google LLC
6
+ * SPDX-License-Identifier: Apache-2.0
7
+ */
8
+ import {
9
+ isGemini1Model,
10
+ isGeminiModel,
11
+ isGeminiModelIdCheckDisabled
12
+ } from "../utils/model_name.js";
13
+ import { BaseTool } from "./base_tool.js";
14
+ function applyEnterpriseWebSearch(llmRequest) {
15
+ if (!llmRequest.model) {
16
+ return;
17
+ }
18
+ const modelCheckDisabled = isGeminiModelIdCheckDisabled();
19
+ llmRequest.config = llmRequest.config || {};
20
+ llmRequest.config.tools = llmRequest.config.tools || [];
21
+ if (isGeminiModel(llmRequest.model) || modelCheckDisabled) {
22
+ if (isGemini1Model(llmRequest.model) && llmRequest.config.tools.length > 0) {
23
+ throw new Error(
24
+ "Enterprise Web Search tool cannot be used with other tools in Gemini 1.x."
25
+ );
26
+ }
27
+ llmRequest.config.tools.push({ enterpriseWebSearch: {} });
28
+ return;
29
+ }
30
+ throw new Error(
31
+ `Enterprise Web Search tool is not supported for model ${llmRequest.model}`
32
+ );
33
+ }
34
+ class EnterpriseWebSearchTool extends BaseTool {
35
+ constructor() {
36
+ super({
37
+ name: "enterprise_web_search",
38
+ description: "Enterprise Web Search Tool"
39
+ });
40
+ }
41
+ runAsync() {
42
+ return Promise.resolve();
43
+ }
44
+ async processLlmRequest({
45
+ llmRequest
46
+ }) {
47
+ applyEnterpriseWebSearch(llmRequest);
48
+ }
49
+ }
50
+ const ENTERPRISE_WEB_SEARCH = new EnterpriseWebSearchTool();
51
+ export {
52
+ ENTERPRISE_WEB_SEARCH,
53
+ EnterpriseWebSearchTool,
54
+ applyEnterpriseWebSearch
55
+ };
@@ -0,0 +1,42 @@
1
+ import {createRequire as topLevelCreateRequire} from 'module';
2
+ const require = topLevelCreateRequire(import.meta.url);
3
+ /**
4
+ * @license
5
+ * Copyright 2026 Google LLC
6
+ * SPDX-License-Identifier: Apache-2.0
7
+ */
8
+ import { buildExampleSi } from "../examples/example_util.js";
9
+ import { appendInstructions } from "../models/llm_request.js";
10
+ import {
11
+ BaseTool
12
+ } from "./base_tool.js";
13
+ class ExampleTool extends BaseTool {
14
+ constructor(examples) {
15
+ super({
16
+ // Name and description are not used because this tool only changes
17
+ // llmRequest.
18
+ name: "example_tool",
19
+ description: "example tool"
20
+ });
21
+ this.examples = examples;
22
+ }
23
+ async runAsync(_request) {
24
+ throw new Error("ExampleTool should not be called by model");
25
+ }
26
+ async processLlmRequest({
27
+ toolContext,
28
+ llmRequest
29
+ }) {
30
+ var _a, _b;
31
+ const parts = (_a = toolContext.userContent) == null ? void 0 : _a.parts;
32
+ if (!parts || !((_b = parts[0]) == null ? void 0 : _b.text)) {
33
+ return;
34
+ }
35
+ appendInstructions(llmRequest, [
36
+ buildExampleSi(this.examples, parts[0].text, llmRequest.model)
37
+ ]);
38
+ }
39
+ }
40
+ export {
41
+ ExampleTool
42
+ };
@@ -0,0 +1,221 @@
1
+ import {createRequire as topLevelCreateRequire} from 'module';
2
+ const require = topLevelCreateRequire(import.meta.url);
3
+ /**
4
+ * @license
5
+ * Copyright 2026 Google LLC
6
+ * SPDX-License-Identifier: Apache-2.0
7
+ */
8
+ import { lookup } from "node:dns/promises";
9
+ import { isIP } from "node:net";
10
+ import { z } from "zod";
11
+ import { FunctionTool } from "./function_tool.js";
12
+ const ALLOWED_SCHEMES = /* @__PURE__ */ new Set(["http:", "https:"]);
13
+ const DEFAULT_TIMEOUT_MS = 3e4;
14
+ const BLOCKED_IPV4_CIDRS = [
15
+ "0.0.0.0/8",
16
+ // "this host on this network"
17
+ "10.0.0.0/8",
18
+ // private
19
+ "100.64.0.0/10",
20
+ // shared address space / CGNAT
21
+ "127.0.0.0/8",
22
+ // loopback
23
+ "169.254.0.0/16",
24
+ // link-local (includes GCP metadata 169.254.169.254)
25
+ "172.16.0.0/12",
26
+ // private
27
+ "192.0.0.0/24",
28
+ // IETF protocol assignments
29
+ "192.0.2.0/24",
30
+ // TEST-NET-1 (documentation)
31
+ "192.88.99.0/24",
32
+ // 6to4 relay anycast (deprecated)
33
+ "192.168.0.0/16",
34
+ // private
35
+ "198.18.0.0/15",
36
+ // benchmarking
37
+ "198.51.100.0/24",
38
+ // TEST-NET-2 (documentation)
39
+ "203.0.113.0/24",
40
+ // TEST-NET-3 (documentation)
41
+ "224.0.0.0/4",
42
+ // multicast
43
+ "240.0.0.0/4"
44
+ // reserved / future use (includes 255.255.255.255)
45
+ ].map(parseIpv4Cidr);
46
+ const BLOCKED_IPV6_CIDRS = [
47
+ "::/128",
48
+ // unspecified
49
+ "::1/128",
50
+ // loopback
51
+ "64:ff9b:1::/48",
52
+ // local NAT64
53
+ "100::/64",
54
+ // discard-only
55
+ "2001:db8::/32",
56
+ // documentation
57
+ "fc00::/7",
58
+ // unique-local (ULA, private)
59
+ "fe80::/10",
60
+ // link-local
61
+ "ff00::/8"
62
+ // multicast
63
+ ].map(parseIpv6Cidr);
64
+ function failedToFetchMessage(url) {
65
+ return `Failed to fetch url: ${url}`;
66
+ }
67
+ function isBlockedHostname(hostname) {
68
+ const normalized = hostname.replace(/\.+$/, "").toLowerCase();
69
+ return normalized === "localhost" || normalized.endsWith(".localhost");
70
+ }
71
+ function normalizeHost(hostname) {
72
+ return hostname.startsWith("[") ? hostname.slice(1, -1) : hostname;
73
+ }
74
+ function parseIpv4(address) {
75
+ const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(address);
76
+ if (!match) {
77
+ return null;
78
+ }
79
+ const octets = match.slice(1).map(Number);
80
+ if (octets.some((octet) => octet > 255)) {
81
+ return null;
82
+ }
83
+ return octets;
84
+ }
85
+ function parseIpv6(address) {
86
+ if (isIP(address) !== 6) {
87
+ return null;
88
+ }
89
+ const [head, tail] = address.split("::");
90
+ const highGroups = head ? expandHextets(head) : [];
91
+ const lowGroups = tail ? expandHextets(tail) : [];
92
+ const compressed = address.includes("::") ? new Array(8 - highGroups.length - lowGroups.length).fill(0) : [];
93
+ return [...highGroups, ...compressed, ...lowGroups];
94
+ }
95
+ function expandHextets(fragment) {
96
+ const hextets = [];
97
+ for (const group of fragment.split(":")) {
98
+ if (group.includes(".")) {
99
+ const octets = parseIpv4(group);
100
+ hextets.push(octets[0] << 8 | octets[1], octets[2] << 8 | octets[3]);
101
+ } else {
102
+ hextets.push(parseInt(group, 16));
103
+ }
104
+ }
105
+ return hextets;
106
+ }
107
+ function ipv4ToInt(octets) {
108
+ return (octets[0] << 24 | octets[1] << 16 | octets[2] << 8 | octets[3]) >>> 0;
109
+ }
110
+ function hextetsToBigInt(hextets) {
111
+ let value = 0n;
112
+ for (const hextet of hextets) {
113
+ value = value << 16n | BigInt(hextet);
114
+ }
115
+ return value;
116
+ }
117
+ function parseIpv4Cidr(cidr) {
118
+ const [address, prefix] = cidr.split("/");
119
+ const mask = 4294967295 << 32 - Number(prefix) >>> 0;
120
+ return { base: (ipv4ToInt(parseIpv4(address)) & mask) >>> 0, mask };
121
+ }
122
+ function parseIpv6Cidr(cidr) {
123
+ const [address, prefix] = cidr.split("/");
124
+ return { base: hextetsToBigInt(parseIpv6(address)), prefix: Number(prefix) };
125
+ }
126
+ function isBlockedIpv4(octets) {
127
+ const value = ipv4ToInt(octets);
128
+ return BLOCKED_IPV4_CIDRS.some(
129
+ ({ base, mask }) => (value & mask) >>> 0 === base
130
+ );
131
+ }
132
+ function isBlockedIpv6(hextets) {
133
+ const value = hextetsToBigInt(hextets);
134
+ if (value >> 32n === 0xffffn) {
135
+ return isBlockedIpv4([
136
+ Number(value >> 24n & 0xffn),
137
+ Number(value >> 16n & 0xffn),
138
+ Number(value >> 8n & 0xffn),
139
+ Number(value & 0xffn)
140
+ ]);
141
+ }
142
+ return BLOCKED_IPV6_CIDRS.some(
143
+ ({ base, prefix }) => value >> BigInt(128 - prefix) === base >> BigInt(128 - prefix)
144
+ );
145
+ }
146
+ function isBlockedAddress(address) {
147
+ const octets = parseIpv4(address);
148
+ if (octets) {
149
+ return isBlockedIpv4(octets);
150
+ }
151
+ const hextets = parseIpv6(address);
152
+ if (hextets) {
153
+ return isBlockedIpv6(hextets);
154
+ }
155
+ return true;
156
+ }
157
+ async function resolveHostAddresses(hostname) {
158
+ if (isIP(hostname) !== 0) {
159
+ return [hostname];
160
+ }
161
+ const records = await lookup(hostname, { all: true });
162
+ const addresses = [...new Set(records.map((record) => record.address))];
163
+ if (addresses.length === 0) {
164
+ throw new Error(`Unable to resolve host: ${hostname}`);
165
+ }
166
+ return addresses;
167
+ }
168
+ function assertUrlAllowed(url) {
169
+ const parsed = new URL(url);
170
+ if (!ALLOWED_SCHEMES.has(parsed.protocol)) {
171
+ throw new Error(`Unsupported url scheme: ${url}`);
172
+ }
173
+ if (isBlockedHostname(parsed.hostname)) {
174
+ throw new Error(`Blocked host: ${parsed.hostname}`);
175
+ }
176
+ return parsed;
177
+ }
178
+ async function validateResolvedAddresses(hostname) {
179
+ const addresses = await resolveHostAddresses(hostname);
180
+ if (addresses.some(isBlockedAddress)) {
181
+ throw new Error(`Blocked host: ${hostname}`);
182
+ }
183
+ }
184
+ function decodeHtmlEntities(text) {
185
+ return text.replace(/&nbsp;/g, " ").replace(/&#39;/g, "'").replace(/&#x27;/g, "'").replace(/&quot;/g, '"').replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
186
+ }
187
+ function htmlToText(html) {
188
+ const withoutCode = html.replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<!--[\s\S]*?-->/g, " ");
189
+ const text = decodeHtmlEntities(withoutCode.replace(/<[^>]+>/g, "\n"));
190
+ return text.split("\n").map((line) => line.trim()).filter((line) => line.split(/\s+/).filter(Boolean).length > 3).join("\n");
191
+ }
192
+ async function loadWebPage(url, options) {
193
+ var _a;
194
+ const timeoutMs = (_a = options == null ? void 0 : options.timeoutMs) != null ? _a : DEFAULT_TIMEOUT_MS;
195
+ try {
196
+ const parsed = assertUrlAllowed(url);
197
+ await validateResolvedAddresses(normalizeHost(parsed.hostname));
198
+ const response = await fetch(url, {
199
+ redirect: "manual",
200
+ signal: AbortSignal.timeout(timeoutMs)
201
+ });
202
+ if (response.status !== 200) {
203
+ return failedToFetchMessage(url);
204
+ }
205
+ return htmlToText(await response.text());
206
+ } catch {
207
+ return failedToFetchMessage(url);
208
+ }
209
+ }
210
+ const LOAD_WEB_PAGE = new FunctionTool({
211
+ name: "load_web_page",
212
+ description: "Fetches the content at the given URL and returns the readable text extracted from the page.",
213
+ parameters: z.object({
214
+ url: z.string().describe("The URL to fetch and extract text from.")
215
+ }),
216
+ execute: ({ url }) => loadWebPage(url)
217
+ });
218
+ export {
219
+ LOAD_WEB_PAGE,
220
+ loadWebPage
221
+ };