@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.
- package/dist/cjs/a2a/agent_to_a2a.js +18 -2
- package/dist/cjs/a2a/event_converter_utils.js +8 -4
- package/dist/cjs/a2a/metadata_converter_utils.js +1 -1
- package/dist/cjs/agents/base_agent.js +39 -0
- package/dist/cjs/agents/functions.js +6 -21
- package/dist/cjs/agents/llm_agent.js +2 -1
- package/dist/cjs/agents/loop_agent.js +15 -3
- package/dist/cjs/agents/processors/content_processor_utils.js +2 -1
- package/dist/cjs/agents/routed_agent.js +31 -0
- package/dist/cjs/agents/run_config.js +3 -2
- package/dist/cjs/artifacts/in_memory_artifact_service.js +7 -0
- package/dist/cjs/common.js +29 -0
- package/dist/cjs/context/anchored_context_compactor.js +127 -0
- package/dist/cjs/context/compaction_utils.js +52 -0
- package/dist/cjs/context/token_based_context_compactor.js +11 -24
- package/dist/cjs/events/compacted_event.js +9 -3
- package/dist/cjs/events/event.js +28 -0
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/integrations/agent_registry/agent_registry_mcp_toolset.js +9 -1
- package/dist/cjs/plugins/global_instruction_plugin.js +97 -0
- package/dist/cjs/runner/runner.js +51 -12
- package/dist/cjs/tools/enterprise_web_search_tool.js +81 -0
- package/dist/cjs/tools/example_tool.js +68 -0
- package/dist/cjs/tools/load_web_page.js +250 -0
- package/dist/cjs/tools/mcp/load_mcp_resource_tool.js +140 -0
- package/dist/cjs/tools/mcp/mcp_toolset.js +63 -0
- package/dist/cjs/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.js +13 -10
- package/dist/cjs/tools/skill/run_skill_inline_script_tool.js +54 -4
- package/dist/cjs/tools/skill/skill_toolset.js +4 -2
- package/dist/cjs/utils/branch_trie.js +41 -0
- package/dist/cjs/utils/streaming_utils.js +5 -5
- package/dist/cjs/version.js +1 -1
- package/dist/esm/a2a/agent_to_a2a.js +18 -2
- package/dist/esm/a2a/event_converter_utils.js +8 -4
- package/dist/esm/a2a/metadata_converter_utils.js +1 -1
- package/dist/esm/agents/base_agent.js +39 -0
- package/dist/esm/agents/functions.js +7 -17
- package/dist/esm/agents/llm_agent.js +4 -3
- package/dist/esm/agents/loop_agent.js +15 -3
- package/dist/esm/agents/processors/content_processor_utils.js +2 -1
- package/dist/esm/agents/routed_agent.js +31 -0
- package/dist/esm/agents/run_config.js +3 -2
- package/dist/esm/artifacts/in_memory_artifact_service.js +7 -0
- package/dist/esm/common.js +25 -1
- package/dist/esm/context/anchored_context_compactor.js +99 -0
- package/dist/esm/context/compaction_utils.js +27 -0
- package/dist/esm/context/token_based_context_compactor.js +11 -24
- package/dist/esm/events/compacted_event.js +7 -2
- package/dist/esm/events/event.js +24 -0
- package/dist/esm/index.js +6 -1
- package/dist/esm/integrations/agent_registry/agent_registry_mcp_toolset.js +9 -1
- package/dist/esm/plugins/global_instruction_plugin.js +69 -0
- package/dist/esm/runner/runner.js +51 -12
- package/dist/esm/tools/enterprise_web_search_tool.js +55 -0
- package/dist/esm/tools/example_tool.js +42 -0
- package/dist/esm/tools/load_web_page.js +221 -0
- package/dist/esm/tools/mcp/load_mcp_resource_tool.js +114 -0
- package/dist/esm/tools/mcp/mcp_toolset.js +63 -0
- package/dist/esm/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.js +13 -10
- package/dist/esm/tools/skill/run_skill_inline_script_tool.js +53 -4
- package/dist/esm/tools/skill/skill_toolset.js +4 -2
- package/dist/esm/utils/branch_trie.js +13 -0
- package/dist/esm/utils/streaming_utils.js +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/types/a2a/a2a_remote_agent.d.ts +5 -1
- package/dist/types/a2a/agent_to_a2a.d.ts +38 -0
- package/dist/types/agents/base_agent.d.ts +29 -1
- package/dist/types/agents/functions.d.ts +1 -9
- package/dist/types/agents/llm_agent.d.ts +5 -1
- package/dist/types/agents/loop_agent.d.ts +2 -2
- package/dist/types/agents/routed_agent.d.ts +24 -1
- package/dist/types/agents/run_config.d.ts +1 -1
- package/dist/types/common.d.ts +9 -2
- package/dist/types/context/anchored_context_compactor.d.ts +35 -0
- package/dist/types/context/compaction_utils.d.ts +15 -0
- package/dist/types/events/compacted_event.d.ts +10 -0
- package/dist/types/events/event.d.ts +13 -0
- package/dist/types/index.d.ts +3 -2
- package/dist/types/plugins/global_instruction_plugin.d.ts +38 -0
- package/dist/types/runner/runner.d.ts +1 -1
- package/dist/types/tools/enterprise_web_search_tool.d.ts +26 -0
- package/dist/types/tools/example_tool.d.ts +21 -0
- package/dist/types/tools/load_web_page.d.ts +33 -0
- package/dist/types/tools/mcp/load_mcp_resource_tool.d.ts +26 -0
- package/dist/types/tools/mcp/mcp_toolset.d.ts +27 -0
- package/dist/types/tools/skill/run_skill_inline_script_tool.d.ts +25 -0
- package/dist/types/tools/skill/skill_toolset.d.ts +9 -0
- package/dist/types/utils/branch_trie.d.ts +14 -0
- package/dist/types/version.d.ts +1 -1
- package/dist/web/a2a/agent_to_a2a.js +18 -2
- package/dist/web/a2a/event_converter_utils.js +8 -4
- package/dist/web/a2a/metadata_converter_utils.js +1 -1
- package/dist/web/agents/base_agent.js +39 -0
- package/dist/web/agents/functions.js +7 -17
- package/dist/web/agents/llm_agent.js +4 -3
- package/dist/web/agents/loop_agent.js +27 -3
- package/dist/web/agents/processors/content_processor_utils.js +2 -1
- package/dist/web/agents/routed_agent.js +31 -0
- package/dist/web/agents/run_config.js +8 -3
- package/dist/web/artifacts/in_memory_artifact_service.js +7 -0
- package/dist/web/common.js +25 -1
- package/dist/web/context/anchored_context_compactor.js +117 -0
- package/dist/web/context/compaction_utils.js +27 -0
- package/dist/web/context/token_based_context_compactor.js +11 -24
- package/dist/web/events/compacted_event.js +7 -2
- package/dist/web/events/event.js +24 -0
- package/dist/web/index.js +6 -1
- package/dist/web/integrations/agent_registry/agent_registry_mcp_toolset.js +9 -1
- package/dist/web/plugins/global_instruction_plugin.js +67 -0
- package/dist/web/runner/runner.js +48 -11
- package/dist/web/tools/enterprise_web_search_tool.js +55 -0
- package/dist/web/tools/example_tool.js +42 -0
- package/dist/web/tools/load_web_page.js +221 -0
- package/dist/web/tools/mcp/load_mcp_resource_tool.js +107 -0
- package/dist/web/tools/mcp/mcp_toolset.js +63 -0
- package/dist/web/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.js +13 -10
- package/dist/web/tools/skill/run_skill_inline_script_tool.js +51 -4
- package/dist/web/tools/skill/skill_toolset.js +4 -2
- package/dist/web/utils/branch_trie.js +13 -0
- package/dist/web/utils/streaming_utils.js +1 -1
- package/dist/web/version.js +1 -1
- package/package.json +1 -1
|
@@ -252,7 +252,7 @@ class Runner {
|
|
|
252
252
|
throw new Error("No parts in the newMessage.");
|
|
253
253
|
}
|
|
254
254
|
if (runConfig.saveInputBlobsAsArtifacts) {
|
|
255
|
-
yield new __await(this.saveArtifacts(
|
|
255
|
+
newMessage = yield new __await(this.saveArtifacts(
|
|
256
256
|
invocationContext.invocationId,
|
|
257
257
|
session.userId,
|
|
258
258
|
session.id,
|
|
@@ -354,7 +354,7 @@ class Runner {
|
|
|
354
354
|
}
|
|
355
355
|
/**
|
|
356
356
|
* Saves artifacts from the message parts and replaces the inline data with
|
|
357
|
-
* a file name placeholder.
|
|
357
|
+
* a file name placeholder and optional file reference.
|
|
358
358
|
*
|
|
359
359
|
* @param invocationId The current invocation ID.
|
|
360
360
|
* @param userId The user ID of the session.
|
|
@@ -364,27 +364,64 @@ class Runner {
|
|
|
364
364
|
async saveArtifacts(invocationId, userId, sessionId, message) {
|
|
365
365
|
var _a2;
|
|
366
366
|
if (!this.artifactService || !((_a2 = message.parts) == null ? void 0 : _a2.length)) {
|
|
367
|
-
return;
|
|
367
|
+
return message;
|
|
368
368
|
}
|
|
369
369
|
const sessionKey = {
|
|
370
370
|
appName: this.appName,
|
|
371
371
|
userId,
|
|
372
372
|
sessionId
|
|
373
373
|
};
|
|
374
|
+
const newParts = [];
|
|
375
|
+
let modified = false;
|
|
374
376
|
for (let i = 0; i < message.parts.length; i++) {
|
|
375
377
|
const part = message.parts[i];
|
|
376
378
|
if (!part.inlineData) {
|
|
379
|
+
newParts.push(part);
|
|
377
380
|
continue;
|
|
378
381
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
382
|
+
try {
|
|
383
|
+
const inlineData = part.inlineData;
|
|
384
|
+
const fileName = inlineData.displayName || "artifact_".concat(invocationId, "_").concat(i);
|
|
385
|
+
const version = await this.artifactService.saveArtifact(__spreadProps(__spreadValues({}, sessionKey), {
|
|
386
|
+
filename: fileName,
|
|
387
|
+
artifact: part
|
|
388
|
+
}));
|
|
389
|
+
newParts.push(createPartFromText('[Uploaded Artifact: "'.concat(fileName, '"]')));
|
|
390
|
+
try {
|
|
391
|
+
const artifactVersion = await this.artifactService.getArtifactVersion(
|
|
392
|
+
__spreadProps(__spreadValues({}, sessionKey), {
|
|
393
|
+
filename: fileName,
|
|
394
|
+
version
|
|
395
|
+
})
|
|
396
|
+
);
|
|
397
|
+
if ((artifactVersion == null ? void 0 : artifactVersion.canonicalUri) && /^(gs|https?):/i.test(artifactVersion.canonicalUri)) {
|
|
398
|
+
newParts.push({
|
|
399
|
+
fileData: {
|
|
400
|
+
fileUri: artifactVersion.canonicalUri,
|
|
401
|
+
mimeType: artifactVersion.mimeType || inlineData.mimeType || "",
|
|
402
|
+
displayName: fileName
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
} catch (error) {
|
|
407
|
+
logger.warn(
|
|
408
|
+
"Failed to resolve artifact version for ".concat(fileName, ":"),
|
|
409
|
+
error
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
modified = true;
|
|
413
|
+
logger.info("Successfully saved artifact: ".concat(fileName));
|
|
414
|
+
} catch (error) {
|
|
415
|
+
logger.error("Failed to save artifact for part ".concat(i, ":"), error);
|
|
416
|
+
newParts.push(part);
|
|
417
|
+
}
|
|
387
418
|
}
|
|
419
|
+
if (!modified) {
|
|
420
|
+
return message;
|
|
421
|
+
}
|
|
422
|
+
return __spreadProps(__spreadValues({}, message), {
|
|
423
|
+
parts: newParts
|
|
424
|
+
});
|
|
388
425
|
}
|
|
389
426
|
/**
|
|
390
427
|
* 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 ".concat(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: ".concat(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 = /* @__PURE__ */ BigInt("0");
|
|
112
|
+
for (const hextet of hextets) {
|
|
113
|
+
value = value << /* @__PURE__ */ BigInt("16") | 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 >> /* @__PURE__ */ BigInt("32") === /* @__PURE__ */ BigInt("0xffff")) {
|
|
135
|
+
return isBlockedIpv4([
|
|
136
|
+
Number(value >> /* @__PURE__ */ BigInt("24") & /* @__PURE__ */ BigInt("0xff")),
|
|
137
|
+
Number(value >> /* @__PURE__ */ BigInt("16") & /* @__PURE__ */ BigInt("0xff")),
|
|
138
|
+
Number(value >> /* @__PURE__ */ BigInt("8") & /* @__PURE__ */ BigInt("0xff")),
|
|
139
|
+
Number(value & /* @__PURE__ */ BigInt("0xff"))
|
|
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: ".concat(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: ".concat(url));
|
|
172
|
+
}
|
|
173
|
+
if (isBlockedHostname(parsed.hostname)) {
|
|
174
|
+
throw new Error("Blocked host: ".concat(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: ".concat(hostname));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function decodeHtmlEntities(text) {
|
|
185
|
+
return text.replace(/ /g, " ").replace(/'/g, "'").replace(/'/g, "'").replace(/"/g, '"').replace(/</g, "<").replace(/>/g, ">").replace(/&/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 (e) {
|
|
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
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
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 { Type } from "@google/genai";
|
|
9
|
+
import { appendInstructions } from "../../models/llm_request.js";
|
|
10
|
+
import { logger } from "../../utils/logger.js";
|
|
11
|
+
import {
|
|
12
|
+
BaseTool
|
|
13
|
+
} from "../base_tool.js";
|
|
14
|
+
class LoadMcpResourceTool extends BaseTool {
|
|
15
|
+
constructor(mcpToolset) {
|
|
16
|
+
super({
|
|
17
|
+
name: "load_mcp_resource",
|
|
18
|
+
description: "Loads resources from the MCP server.\n\nNOTE: Call when you need access to resources."
|
|
19
|
+
});
|
|
20
|
+
this.mcpToolset = mcpToolset;
|
|
21
|
+
}
|
|
22
|
+
_getDeclaration() {
|
|
23
|
+
return {
|
|
24
|
+
name: this.name,
|
|
25
|
+
description: this.description,
|
|
26
|
+
parameters: {
|
|
27
|
+
type: Type.OBJECT,
|
|
28
|
+
properties: {
|
|
29
|
+
resource_names: {
|
|
30
|
+
type: Type.ARRAY,
|
|
31
|
+
items: {
|
|
32
|
+
type: Type.STRING
|
|
33
|
+
},
|
|
34
|
+
description: "The names of the MCP resources to load."
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async runAsync({ args }) {
|
|
41
|
+
const resourceNames = args["resource_names"] || [];
|
|
42
|
+
return {
|
|
43
|
+
resource_names: resourceNames,
|
|
44
|
+
status: "resource contents temporarily inserted and removed. to access these resources, call load_mcp_resource tool again."
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
async processLlmRequest(request) {
|
|
48
|
+
await super.processLlmRequest(request);
|
|
49
|
+
await this.appendResourcesToLlmRequest(request.llmRequest);
|
|
50
|
+
}
|
|
51
|
+
async appendResourcesToLlmRequest(llmRequest) {
|
|
52
|
+
var _a, _b;
|
|
53
|
+
try {
|
|
54
|
+
const availableResourceNames = await this.mcpToolset.listResources();
|
|
55
|
+
if (availableResourceNames.length > 0) {
|
|
56
|
+
appendInstructions(llmRequest, [
|
|
57
|
+
"You have a list of MCP resources:\n".concat(JSON.stringify(
|
|
58
|
+
availableResourceNames
|
|
59
|
+
), "\n\nWhen the user asks questions about any of the resources, you should call the\n`load_mcp_resource` function to load the resource. Always call load_mcp_resource\nbefore answering questions related to the resources.")
|
|
60
|
+
]);
|
|
61
|
+
}
|
|
62
|
+
} catch (e) {
|
|
63
|
+
logger.warn("Failed to list MCP resources: ".concat(e));
|
|
64
|
+
}
|
|
65
|
+
const lastContent = llmRequest.contents.at(-1);
|
|
66
|
+
const functionResponse = (_b = (_a = lastContent == null ? void 0 : lastContent.parts) == null ? void 0 : _a[0]) == null ? void 0 : _b.functionResponse;
|
|
67
|
+
if (!functionResponse || functionResponse.name !== this.name) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const response = functionResponse.response || {};
|
|
71
|
+
const requestedResourceNames = response["resource_names"] || [];
|
|
72
|
+
for (const resourceName of requestedResourceNames) {
|
|
73
|
+
try {
|
|
74
|
+
const resourceContents = await this.mcpToolset.readResource(resourceName);
|
|
75
|
+
for (const content of resourceContents) {
|
|
76
|
+
llmRequest.contents.push({
|
|
77
|
+
role: "user",
|
|
78
|
+
parts: [
|
|
79
|
+
{ text: "Resource ".concat(resourceName, " is:") },
|
|
80
|
+
this.mcpContentToPart(content, resourceName)
|
|
81
|
+
]
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
} catch (e) {
|
|
85
|
+
logger.warn("Failed to read MCP resource '".concat(resourceName, "': ").concat(e));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
mcpContentToPart(content, resourceName) {
|
|
90
|
+
var _a;
|
|
91
|
+
if ("text" in content) {
|
|
92
|
+
return { text: content.text };
|
|
93
|
+
}
|
|
94
|
+
if ("blob" in content) {
|
|
95
|
+
return {
|
|
96
|
+
inlineData: {
|
|
97
|
+
data: content.blob,
|
|
98
|
+
mimeType: (_a = content.mimeType) != null ? _a : "application/octet-stream"
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return { text: "[Unknown content type for ".concat(resourceName, "]") };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export {
|
|
106
|
+
LoadMcpResourceTool
|
|
107
|
+
};
|
|
@@ -66,6 +66,69 @@ class MCPToolset extends BaseToolset {
|
|
|
66
66
|
);
|
|
67
67
|
return tools;
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Lists the names of the resources advertised by the MCP server.
|
|
71
|
+
*
|
|
72
|
+
* @return The resource names available on the server.
|
|
73
|
+
*/
|
|
74
|
+
async listResources() {
|
|
75
|
+
const session = await this.mcpSessionManager.createSession();
|
|
76
|
+
try {
|
|
77
|
+
const result = await session.listResources();
|
|
78
|
+
return result.resources.map((resource) => resource.name);
|
|
79
|
+
} finally {
|
|
80
|
+
await this.mcpSessionManager.closeSession(session);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Returns metadata for the resource whose name matches `name`.
|
|
85
|
+
*
|
|
86
|
+
* @param name The advertised name of the resource.
|
|
87
|
+
* @return The matching MCP `Resource`.
|
|
88
|
+
* @throws If no resource with the given name is advertised by the server.
|
|
89
|
+
*/
|
|
90
|
+
async getResourceInfo(name) {
|
|
91
|
+
const session = await this.mcpSessionManager.createSession();
|
|
92
|
+
let result;
|
|
93
|
+
try {
|
|
94
|
+
result = await session.listResources();
|
|
95
|
+
} finally {
|
|
96
|
+
await this.mcpSessionManager.closeSession(session);
|
|
97
|
+
}
|
|
98
|
+
const resource = result.resources.find(
|
|
99
|
+
(candidate) => candidate.name === name
|
|
100
|
+
);
|
|
101
|
+
if (!resource) {
|
|
102
|
+
throw new Error("Resource with name '".concat(name, "' not found."));
|
|
103
|
+
}
|
|
104
|
+
return resource;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Reads the contents of the named resource from the MCP server.
|
|
108
|
+
*
|
|
109
|
+
* The resource name is resolved to a URI via {@link getResourceInfo} before
|
|
110
|
+
* reading. Binary contents are returned base64-encoded, exactly as provided
|
|
111
|
+
* by the server (never decoded and re-encoded).
|
|
112
|
+
*
|
|
113
|
+
* @param name The advertised name of the resource to read.
|
|
114
|
+
* @return The resource contents (text and/or base64-encoded binary).
|
|
115
|
+
* @throws If the resource is unknown or has no URI.
|
|
116
|
+
*/
|
|
117
|
+
async readResource(name) {
|
|
118
|
+
const resourceInfo = await this.getResourceInfo(name);
|
|
119
|
+
if (!resourceInfo.uri) {
|
|
120
|
+
throw new Error("Resource '".concat(name, "' has no URI."));
|
|
121
|
+
}
|
|
122
|
+
const session = await this.mcpSessionManager.createSession();
|
|
123
|
+
try {
|
|
124
|
+
const result = await session.readResource({
|
|
125
|
+
uri: resourceInfo.uri
|
|
126
|
+
});
|
|
127
|
+
return result.contents;
|
|
128
|
+
} finally {
|
|
129
|
+
await this.mcpSessionManager.closeSession(session);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
69
132
|
async close() {
|
|
70
133
|
const sessions = this.mcpSessionManager.getActiveSessions();
|
|
71
134
|
await Promise.allSettled(
|
|
@@ -62,19 +62,22 @@ let ToolAuthHandler = class {
|
|
|
62
62
|
rawAuthCredential: this.authCredential,
|
|
63
63
|
credentialKey: this.credentialKey || "default_openapi_key"
|
|
64
64
|
};
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
const authResponseCredential = this.context.getAuthResponse(authConfig);
|
|
66
|
+
const credential = authResponseCredential != null ? authResponseCredential : this.authCredential;
|
|
67
|
+
if (!credential) {
|
|
68
|
+
this.context.requestCredential(authConfig);
|
|
69
|
+
return { state: "pending" };
|
|
70
|
+
}
|
|
71
|
+
const exchanger = new AutoAuthCredentialExchanger();
|
|
72
|
+
const result = await exchanger.exchange({
|
|
73
|
+
authScheme: this.authScheme,
|
|
74
|
+
authCredential: credential
|
|
75
|
+
});
|
|
76
|
+
if (authResponseCredential || result.wasExchanged) {
|
|
72
77
|
const key = store.getCredentialKey(this.authScheme);
|
|
73
78
|
store.storeCredential(key, result.credential);
|
|
74
|
-
return { state: "done", authCredential: result.credential };
|
|
75
79
|
}
|
|
76
|
-
|
|
77
|
-
return { state: "pending" };
|
|
80
|
+
return { state: "done", authCredential: result.credential };
|
|
78
81
|
}
|
|
79
82
|
};
|
|
80
83
|
__decorateClass([
|