@iqai/adk 0.2.0 → 0.2.1

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/index.mjs CHANGED
@@ -3879,6 +3879,7 @@ __export(tools_exports, {
3879
3879
  McpAbi: () => McpAbi,
3880
3880
  McpAtp: () => McpAtp,
3881
3881
  McpBamm: () => McpBamm,
3882
+ McpClientService: () => McpClientService,
3882
3883
  McpCoinGecko: () => McpCoinGecko,
3883
3884
  McpDiscord: () => McpDiscord,
3884
3885
  McpError: () => McpError,
@@ -3894,6 +3895,7 @@ __export(tools_exports, {
3894
3895
  McpSamplingHandler: () => McpSamplingHandler,
3895
3896
  McpTelegram: () => McpTelegram,
3896
3897
  McpToolset: () => McpToolset,
3898
+ McpUpbit: () => McpUpbit,
3897
3899
  ToolContext: () => ToolContext,
3898
3900
  TransferToAgentTool: () => TransferToAgentTool,
3899
3901
  UserInteractionTool: () => UserInteractionTool,
@@ -3907,956 +3909,953 @@ __export(tools_exports, {
3907
3909
  mcpSchemaToParameters: () => mcpSchemaToParameters,
3908
3910
  normalizeJsonSchema: () => normalizeJsonSchema
3909
3911
  });
3910
- init_base_tool();
3911
3912
 
3912
- // src/tools/base/create-tool.ts
3913
- init_base_tool();
3914
- import * as z from "zod";
3915
- import { zodToJsonSchema } from "zod-to-json-schema";
3916
- var CreatedTool = class extends BaseTool {
3917
- func;
3918
- schema;
3919
- functionDeclaration;
3920
- constructor(config) {
3921
- super({
3922
- name: config.name,
3923
- description: config.description,
3924
- isLongRunning: config.isLongRunning ?? false,
3925
- shouldRetryOnFailure: config.shouldRetryOnFailure ?? false,
3926
- maxRetryAttempts: config.maxRetryAttempts ?? 3
3927
- });
3928
- this.func = config.fn;
3929
- this.schema = config.schema ?? z.object({});
3930
- this.functionDeclaration = this.buildDeclaration();
3913
+ // src/tools/mcp/client.ts
3914
+ init_logger();
3915
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3916
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
3917
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3918
+ import { CreateMessageRequestSchema as CreateMessageRequestSchema2 } from "@modelcontextprotocol/sdk/types.js";
3919
+
3920
+ // src/tools/mcp/sampling-handler.ts
3921
+ init_logger();
3922
+ import {
3923
+ CreateMessageRequestSchema,
3924
+ CreateMessageResultSchema
3925
+ } from "@modelcontextprotocol/sdk/types.js";
3926
+
3927
+ // src/tools/mcp/types.ts
3928
+ var McpErrorType = /* @__PURE__ */ ((McpErrorType2) => {
3929
+ McpErrorType2["CONNECTION_ERROR"] = "connection_error";
3930
+ McpErrorType2["TOOL_EXECUTION_ERROR"] = "tool_execution_error";
3931
+ McpErrorType2["RESOURCE_CLOSED_ERROR"] = "resource_closed_error";
3932
+ McpErrorType2["TIMEOUT_ERROR"] = "timeout_error";
3933
+ McpErrorType2["INVALID_SCHEMA_ERROR"] = "invalid_schema_error";
3934
+ McpErrorType2["SAMPLING_ERROR"] = "SAMPLING_ERROR";
3935
+ McpErrorType2["INVALID_REQUEST_ERROR"] = "INVALID_REQUEST_ERROR";
3936
+ return McpErrorType2;
3937
+ })(McpErrorType || {});
3938
+ var McpError = class extends Error {
3939
+ type;
3940
+ originalError;
3941
+ constructor(message, type, originalError) {
3942
+ super(message);
3943
+ this.name = "McpError";
3944
+ this.type = type;
3945
+ this.originalError = originalError;
3946
+ }
3947
+ };
3948
+
3949
+ // src/tools/mcp/sampling-handler.ts
3950
+ var McpSamplingHandler = class {
3951
+ logger = new Logger({ name: "McpSamplingHandler" });
3952
+ samplingHandler;
3953
+ constructor(samplingHandler) {
3954
+ this.samplingHandler = samplingHandler;
3931
3955
  }
3932
3956
  /**
3933
- * Executes the tool function with validation
3957
+ * Handle MCP sampling request and convert between formats
3934
3958
  */
3935
- async runAsync(args, context4) {
3959
+ async handleSamplingRequest(request) {
3936
3960
  try {
3937
- const validatedArgs = this.schema.parse(args);
3938
- const result = await Promise.resolve(this.func(validatedArgs, context4));
3939
- return result ?? {};
3961
+ if (request.method !== "sampling/createMessage") {
3962
+ this.logger.error(
3963
+ `Invalid method for sampling handler: ${request.method}. Expected: sampling/createMessage`
3964
+ );
3965
+ throw new McpError(
3966
+ `Invalid method: ${request.method}. This handler only processes sampling/createMessage requests.`,
3967
+ "INVALID_REQUEST_ERROR" /* INVALID_REQUEST_ERROR */
3968
+ );
3969
+ }
3970
+ const validationResult = CreateMessageRequestSchema.safeParse(request);
3971
+ if (!validationResult.success) {
3972
+ this.logger.error(
3973
+ "Invalid MCP sampling request:",
3974
+ validationResult.error
3975
+ );
3976
+ throw new McpError(
3977
+ `Invalid sampling request: ${validationResult.error.message}`,
3978
+ "INVALID_REQUEST_ERROR" /* INVALID_REQUEST_ERROR */
3979
+ );
3980
+ }
3981
+ const mcpParams = request.params;
3982
+ if (!mcpParams.messages || !Array.isArray(mcpParams.messages)) {
3983
+ throw new McpError(
3984
+ "Invalid sampling request: messages array is required",
3985
+ "INVALID_REQUEST_ERROR" /* INVALID_REQUEST_ERROR */
3986
+ );
3987
+ }
3988
+ if (!mcpParams.maxTokens || mcpParams.maxTokens <= 0) {
3989
+ throw new McpError(
3990
+ "Invalid sampling request: maxTokens must be a positive number",
3991
+ "INVALID_REQUEST_ERROR" /* INVALID_REQUEST_ERROR */
3992
+ );
3993
+ }
3994
+ this.logger.debug("Converting MCP request to ADK format");
3995
+ const adkContents = this.convertMcpMessagesToADK(
3996
+ mcpParams.messages,
3997
+ mcpParams.systemPrompt
3998
+ );
3999
+ const requestModel = mcpParams.model || "gemini-2.0-flash";
4000
+ const adkRequest = new LlmRequest({
4001
+ model: requestModel,
4002
+ contents: adkContents,
4003
+ config: {
4004
+ temperature: mcpParams.temperature,
4005
+ maxOutputTokens: mcpParams.maxTokens
4006
+ }
4007
+ });
4008
+ this.logger.debug("Calling ADK sampling handler");
4009
+ const adkResponse = await this.samplingHandler(adkRequest);
4010
+ this.logger.debug("Converting ADK response to MCP format");
4011
+ const mcpResponse = this.convertADKResponseToMcp(
4012
+ adkResponse,
4013
+ requestModel
4014
+ );
4015
+ const responseValidation = CreateMessageResultSchema.safeParse(mcpResponse);
4016
+ if (!responseValidation.success) {
4017
+ this.logger.error(
4018
+ "Invalid MCP response generated:",
4019
+ responseValidation.error
4020
+ );
4021
+ throw new McpError(
4022
+ `Invalid response generated: ${responseValidation.error.message}`,
4023
+ "SAMPLING_ERROR" /* SAMPLING_ERROR */
4024
+ );
4025
+ }
4026
+ return mcpResponse;
3940
4027
  } catch (error) {
3941
- if (error instanceof z.ZodError) {
3942
- return {
3943
- error: `Invalid arguments for ${this.name}: ${error.message}`
3944
- };
4028
+ this.logger.error("Error handling sampling request:", error);
4029
+ if (error instanceof McpError) {
4030
+ throw error;
3945
4031
  }
3946
- return {
3947
- error: `Error executing ${this.name}: ${error instanceof Error ? error.message : String(error)}`
3948
- };
4032
+ throw new McpError(
4033
+ `Sampling request failed: ${error instanceof Error ? error.message : String(error)}`,
4034
+ "SAMPLING_ERROR" /* SAMPLING_ERROR */,
4035
+ error instanceof Error ? error : void 0
4036
+ );
3949
4037
  }
3950
4038
  }
3951
4039
  /**
3952
- * Returns the function declaration for this tool
4040
+ * Convert MCP messages to ADK Content format
3953
4041
  */
3954
- getDeclaration() {
3955
- return this.functionDeclaration;
4042
+ convertMcpMessagesToADK(mcpMessages, systemPrompt) {
4043
+ const contents = [];
4044
+ if (systemPrompt) {
4045
+ contents.push({
4046
+ role: "user",
4047
+ // System messages are typically sent as user role in content
4048
+ parts: [{ text: systemPrompt }]
4049
+ });
4050
+ }
4051
+ const transformedMessages = mcpMessages.map(
4052
+ (mcpMessage) => this.convertSingleMcpMessageToADK(mcpMessage)
4053
+ );
4054
+ contents.push(...transformedMessages);
4055
+ return contents;
3956
4056
  }
3957
4057
  /**
3958
- * Builds the function declaration from the Zod schema
4058
+ * Convert a single MCP message to ADK Content format
3959
4059
  */
3960
- buildDeclaration() {
3961
- const rawParameters = zodToJsonSchema(this.schema, {
3962
- target: "jsonSchema7",
3963
- $refStrategy: "none"
3964
- });
3965
- const { $schema, ...parameters } = rawParameters;
3966
- return {
3967
- name: this.name,
3968
- description: this.description,
3969
- parameters
4060
+ convertSingleMcpMessageToADK(mcpMessage) {
4061
+ const adkRole = mcpMessage.role === "assistant" ? "model" : "user";
4062
+ const adkParts = this.convertMcpContentToADKParts(mcpMessage.content);
4063
+ const adkContent = {
4064
+ role: adkRole,
4065
+ parts: adkParts
3970
4066
  };
4067
+ this.logger.debug(
4068
+ `Converted MCP message - role: ${mcpMessage.role} -> ${adkRole}, content type: ${mcpMessage.content.type}`
4069
+ );
4070
+ return adkContent;
3971
4071
  }
3972
- };
3973
- function createTool(config) {
3974
- return new CreatedTool(config);
3975
- }
3976
-
3977
- // src/tools/common/agent-tool.ts
3978
- init_logger();
3979
- import { Type } from "@google/genai";
3980
- import { v4 as uuidv42 } from "uuid";
3981
-
3982
- // src/agents/invocation-context.ts
3983
- var LlmCallsLimitExceededError = class extends Error {
3984
- constructor(message) {
3985
- super(message);
3986
- this.name = "LlmCallsLimitExceededError";
3987
- }
3988
- };
3989
- var InvocationCostManager = class {
3990
4072
  /**
3991
- * A counter that keeps track of number of llm calls made.
3992
- */
3993
- _numberOfLlmCalls = 0;
3994
- /**
3995
- * Increments _numberOfLlmCalls and enforces the limit.
4073
+ * Convert MCP message content to ADK parts format
3996
4074
  */
3997
- incrementAndEnforceLlmCallsLimit(runConfig) {
3998
- this._numberOfLlmCalls += 1;
3999
- if (runConfig && runConfig.maxLlmCalls > 0 && this._numberOfLlmCalls > runConfig.maxLlmCalls) {
4000
- throw new LlmCallsLimitExceededError(
4001
- `Max number of llm calls limit of \`${runConfig.maxLlmCalls}\` exceeded`
4002
- );
4075
+ convertMcpContentToADKParts(mcpContent) {
4076
+ if (mcpContent.type === "text") {
4077
+ const textContent = mcpContent.text || "";
4078
+ return [{ text: textContent }];
4003
4079
  }
4080
+ if (mcpContent.type === "image") {
4081
+ const parts = [];
4082
+ if (mcpContent.text && typeof mcpContent.text === "string") {
4083
+ parts.push({ text: mcpContent.text });
4084
+ }
4085
+ if (mcpContent.data && typeof mcpContent.data === "string") {
4086
+ const mimeType = mcpContent.mimeType || "image/jpeg";
4087
+ parts.push({
4088
+ inlineData: {
4089
+ data: mcpContent.data,
4090
+ mimeType
4091
+ }
4092
+ });
4093
+ }
4094
+ return parts.length > 0 ? parts : [{ text: "" }];
4095
+ }
4096
+ this.logger.warn(`Unknown MCP content type: ${mcpContent.type}`);
4097
+ const fallbackText = typeof mcpContent.data === "string" ? mcpContent.data : "";
4098
+ return [{ text: fallbackText }];
4004
4099
  }
4005
- };
4006
- function newInvocationContextId() {
4007
- return `e-${crypto.randomUUID()}`;
4008
- }
4009
- var InvocationContext = class _InvocationContext {
4010
- artifactService;
4011
- sessionService;
4012
- memoryService;
4013
4100
  /**
4014
- * The id of this invocation context. Readonly.
4101
+ * Convert ADK response to MCP response format
4015
4102
  */
4016
- invocationId;
4017
- /**
4018
- * The branch of the invocation context.
4019
- *
4020
- * The format is like agent_1.agent_2.agent_3, where agent_1 is the parent of
4021
- * agent_2, and agent_2 is the parent of agent_3.
4022
- *
4023
- * Branch is used when multiple sub-agents shouldn't see their peer agents'
4024
- * conversation history.
4025
- */
4026
- branch;
4027
- /**
4028
- * The current agent of this invocation context. Readonly.
4029
- */
4030
- agent;
4031
- /**
4032
- * The user content that started this invocation. Readonly.
4033
- */
4034
- userContent;
4035
- /**
4036
- * The current session of this invocation context. Readonly.
4037
- */
4038
- session;
4039
- /**
4040
- * Whether to end this invocation.
4041
- *
4042
- * Set to True in callbacks or tools to terminate this invocation.
4043
- */
4044
- endInvocation = false;
4045
- /**
4046
- * The queue to receive live requests.
4047
- */
4048
- liveRequestQueue;
4049
- /**
4050
- * The running streaming tools of this invocation.
4051
- */
4052
- activeStreamingTools;
4053
- /**
4054
- * Caches necessary, data audio or contents, that are needed by transcription.
4055
- */
4056
- transcriptionCache;
4057
- /**
4058
- * Configurations for live agents under this invocation.
4059
- */
4060
- runConfig;
4061
- /**
4062
- * A container to keep track of different kinds of costs incurred as a part
4063
- * of this invocation.
4064
- */
4065
- _invocationCostManager = new InvocationCostManager();
4066
- /**
4067
- * Constructor for InvocationContext
4068
- */
4069
- constructor(options) {
4070
- this.artifactService = options.artifactService;
4071
- this.sessionService = options.sessionService;
4072
- this.memoryService = options.memoryService;
4073
- this.invocationId = options.invocationId || newInvocationContextId();
4074
- this.branch = options.branch;
4075
- this.agent = options.agent;
4076
- this.userContent = options.userContent;
4077
- this.session = options.session;
4078
- this.endInvocation = options.endInvocation || false;
4079
- this.liveRequestQueue = options.liveRequestQueue;
4080
- this.activeStreamingTools = options.activeStreamingTools;
4081
- this.transcriptionCache = options.transcriptionCache;
4082
- this.runConfig = options.runConfig;
4083
- }
4084
- /**
4085
- * App name from the session
4086
- */
4087
- get appName() {
4088
- return this.session.appName;
4089
- }
4090
- /**
4091
- * User ID from the session
4092
- */
4093
- get userId() {
4094
- return this.session.userId;
4095
- }
4096
- /**
4097
- * Tracks number of llm calls made.
4098
- *
4099
- * @throws {LlmCallsLimitExceededError} If number of llm calls made exceed the set threshold.
4100
- */
4101
- incrementLlmCallCount() {
4102
- this._invocationCostManager.incrementAndEnforceLlmCallsLimit(
4103
- this.runConfig
4104
- );
4103
+ convertADKResponseToMcp(adkResponse, model) {
4104
+ let responseText = "";
4105
+ if (typeof adkResponse === "string") {
4106
+ responseText = adkResponse;
4107
+ } else {
4108
+ if (adkResponse.content) {
4109
+ if (typeof adkResponse.content === "string") {
4110
+ responseText = adkResponse.content;
4111
+ } else if (adkResponse.content.parts) {
4112
+ responseText = adkResponse.content.parts.map((part) => {
4113
+ return typeof part.text === "string" ? part.text : "";
4114
+ }).join("");
4115
+ }
4116
+ }
4117
+ }
4118
+ const mcpResponse = {
4119
+ model,
4120
+ // Use the model from the request
4121
+ role: "assistant",
4122
+ // ADK responses are always from assistant
4123
+ content: {
4124
+ type: "text",
4125
+ text: responseText
4126
+ }
4127
+ };
4128
+ this.logger.debug(`Received content: ${responseText}`);
4129
+ return mcpResponse;
4105
4130
  }
4106
4131
  /**
4107
- * Creates a child invocation context for a sub-agent
4132
+ * Update the ADK handler
4108
4133
  */
4109
- createChildContext(agent) {
4110
- return new _InvocationContext({
4111
- artifactService: this.artifactService,
4112
- sessionService: this.sessionService,
4113
- memoryService: this.memoryService,
4114
- invocationId: this.invocationId,
4115
- // Keep same invocation ID
4116
- branch: this.branch ? `${this.branch}.${agent.name}` : agent.name,
4117
- // Update branch
4118
- agent,
4119
- // Update to the new agent
4120
- userContent: this.userContent,
4121
- session: this.session,
4122
- endInvocation: this.endInvocation,
4123
- liveRequestQueue: this.liveRequestQueue,
4124
- activeStreamingTools: this.activeStreamingTools,
4125
- transcriptionCache: this.transcriptionCache,
4126
- runConfig: this.runConfig
4127
- });
4134
+ updateHandler(handler) {
4135
+ this.samplingHandler = handler;
4136
+ this.logger.debug("ADK sampling handler updated");
4128
4137
  }
4129
4138
  };
4139
+ function createSamplingHandler(handler) {
4140
+ return handler;
4141
+ }
4130
4142
 
4131
- // src/tools/common/agent-tool.ts
4132
- init_base_tool();
4133
- function isLlmAgent(agent) {
4134
- return true;
4143
+ // src/tools/mcp/utils.ts
4144
+ function withRetry(fn, instance, reinitMethod, maxRetries = 1) {
4145
+ return async (...args) => {
4146
+ let attempt = 0;
4147
+ while (attempt <= maxRetries) {
4148
+ try {
4149
+ return await fn.apply(instance, args);
4150
+ } catch (error) {
4151
+ const isClosedResourceError = error instanceof Error && (error.message.includes("closed") || error.message.includes("ECONNRESET") || error.message.includes("socket hang up"));
4152
+ if (!isClosedResourceError || attempt >= maxRetries) {
4153
+ throw error;
4154
+ }
4155
+ console.warn(
4156
+ `Resource closed, reinitializing (attempt ${attempt + 1}/${maxRetries + 1})...`
4157
+ );
4158
+ try {
4159
+ await reinitMethod(instance);
4160
+ } catch (reinitError) {
4161
+ console.error("Error reinitializing resources:", reinitError);
4162
+ throw new Error(`Failed to reinitialize resources: ${reinitError}`);
4163
+ }
4164
+ attempt++;
4165
+ }
4166
+ }
4167
+ throw new Error("Unexpected end of retry loop");
4168
+ };
4135
4169
  }
4136
- var AgentTool = class extends BaseTool {
4137
- /**
4138
- * The agent used by this tool
4139
- */
4140
- agent;
4141
- /**
4142
- * The function declaration schema
4143
- */
4144
- functionDeclaration;
4145
- /**
4146
- * The key to store the tool output in the state
4147
- */
4148
- outputKey;
4149
- /**
4150
- * Whether to skip summarization of the agent's response
4151
- */
4152
- skipSummarization;
4153
- logger = new Logger({ name: "AgentTool" });
4154
- /**
4155
- * Create a new agent tool
4156
- */
4170
+
4171
+ // src/tools/mcp/client.ts
4172
+ var McpClientService = class {
4173
+ config;
4174
+ client = null;
4175
+ transport = null;
4176
+ isClosing = false;
4177
+ mcpSamplingHandler = null;
4178
+ logger = new Logger({ name: "McpClientService" });
4157
4179
  constructor(config) {
4158
- super({
4159
- name: config.name,
4160
- description: config.description || config.agent.description,
4161
- isLongRunning: config.isLongRunning || false,
4162
- shouldRetryOnFailure: config.shouldRetryOnFailure || false,
4163
- maxRetryAttempts: config.maxRetryAttempts || 3
4164
- });
4165
- this.agent = config.agent;
4166
- this.functionDeclaration = config.functionDeclaration;
4167
- this.outputKey = config.outputKey;
4168
- this.skipSummarization = config.skipSummarization || false;
4180
+ this.config = config;
4181
+ if (config.samplingHandler) {
4182
+ this.mcpSamplingHandler = new McpSamplingHandler(config.samplingHandler);
4183
+ }
4169
4184
  }
4170
4185
  /**
4171
- * Get the function declaration for the tool
4186
+ * Initializes and returns an MCP client based on configuration.
4187
+ * Will create a new client if one doesn't exist yet.
4172
4188
  */
4173
- getDeclaration() {
4174
- if (this.functionDeclaration) {
4175
- return this.functionDeclaration;
4189
+ async initialize() {
4190
+ if (this.isClosing) {
4191
+ throw new McpError(
4192
+ "Cannot initialize a client that is being closed",
4193
+ "resource_closed_error" /* RESOURCE_CLOSED_ERROR */
4194
+ );
4176
4195
  }
4177
- const description = isLlmAgent(this.agent) ? typeof this.agent.instruction === "string" ? this.agent.instruction : this.description : this.description;
4178
- return {
4179
- name: this.name,
4180
- description,
4181
- parameters: {
4182
- type: Type.OBJECT,
4183
- properties: {
4184
- input: {
4185
- type: Type.STRING,
4186
- description: "The input to provide to the agent"
4187
- }
4196
+ if (this.client) {
4197
+ return this.client;
4198
+ }
4199
+ try {
4200
+ if (!this.transport) {
4201
+ this.transport = await this.createTransport();
4202
+ }
4203
+ const client = new Client(
4204
+ {
4205
+ name: this.config.name,
4206
+ version: "0.0.1"
4188
4207
  },
4189
- required: ["input"]
4208
+ {
4209
+ capabilities: {
4210
+ prompts: {},
4211
+ resources: {},
4212
+ tools: {},
4213
+ sampling: {}
4214
+ // Enable sampling capability
4215
+ }
4216
+ }
4217
+ );
4218
+ const connectPromise = client.connect(this.transport);
4219
+ if (this.config.timeout) {
4220
+ const timeoutPromise = new Promise((_, reject) => {
4221
+ setTimeout(() => {
4222
+ reject(
4223
+ new McpError(
4224
+ `MCP client connection timed out after ${this.config.timeout}ms`,
4225
+ "timeout_error" /* TIMEOUT_ERROR */
4226
+ )
4227
+ );
4228
+ }, this.config.timeout);
4229
+ });
4230
+ await Promise.race([connectPromise, timeoutPromise]);
4231
+ } else {
4232
+ await connectPromise;
4190
4233
  }
4191
- };
4234
+ await this.setupSamplingHandler(client);
4235
+ this.logger.debug("\u2705 MCP client connected successfully");
4236
+ this.client = client;
4237
+ return client;
4238
+ } catch (error) {
4239
+ await this.cleanupResources();
4240
+ if (!(error instanceof McpError)) {
4241
+ this.logger.error("Failed to initialize MCP client:", error);
4242
+ throw new McpError(
4243
+ `Failed to initialize MCP client: ${error instanceof Error ? error.message : String(error)}`,
4244
+ "connection_error" /* CONNECTION_ERROR */,
4245
+ error instanceof Error ? error : void 0
4246
+ );
4247
+ }
4248
+ throw error;
4249
+ }
4192
4250
  }
4193
4251
  /**
4194
- * Execute the tool by running the agent with the provided input
4252
+ * Creates a transport based on the configuration.
4195
4253
  */
4196
- async runAsync(params, context4) {
4254
+ async createTransport() {
4197
4255
  try {
4198
- const input = params.input || Object.values(params)[0];
4199
- if (!isLlmAgent(this.agent)) {
4200
- throw new Error(
4201
- `Agent ${this.name} does not support running as a tool`
4256
+ if (this.config.transport.mode === "sse") {
4257
+ this.logger.debug(
4258
+ "\u{1F680} Initializing MCP client in SSE mode",
4259
+ this.config.transport.serverUrl
4260
+ );
4261
+ const headers = {
4262
+ ...this.config.transport.headers || {},
4263
+ ...this.config.headers || {}
4264
+ };
4265
+ return new SSEClientTransport(
4266
+ new URL(this.config.transport.serverUrl),
4267
+ {
4268
+ requestInit: {
4269
+ headers,
4270
+ ...this.config.timeout ? { timeout: this.config.timeout } : {}
4271
+ }
4272
+ }
4202
4273
  );
4203
4274
  }
4204
- const parentInvocation = context4._invocationContext;
4205
- const childInvocationContext = new InvocationContext({
4206
- invocationId: uuidv42(),
4207
- agent: this.agent,
4208
- session: parentInvocation.session,
4209
- artifactService: parentInvocation.artifactService,
4210
- sessionService: parentInvocation.sessionService,
4211
- memoryService: parentInvocation.memoryService,
4212
- runConfig: parentInvocation.runConfig,
4213
- userContent: {
4214
- role: "user",
4215
- parts: [{ text: String(input) }]
4216
- },
4217
- branch: parentInvocation.branch ? `${parentInvocation.branch}.${this.agent.name}` : this.agent.name
4275
+ this.logger.debug(
4276
+ "\u{1F680} Initializing MCP client in STDIO mode",
4277
+ this.config.transport.command
4278
+ );
4279
+ return new StdioClientTransport({
4280
+ command: this.config.transport.command,
4281
+ args: this.config.transport.args,
4282
+ env: this.config.transport.env
4218
4283
  });
4219
- let lastEvent = null;
4220
- for await (const event of this.agent.runAsync(childInvocationContext)) {
4221
- if (!event.partial) {
4222
- await childInvocationContext.sessionService.appendEvent(
4223
- childInvocationContext.session,
4224
- event
4225
- );
4226
- }
4227
- if (event.content && event.author === this.agent.name) {
4228
- lastEvent = event;
4229
- }
4230
- }
4231
- if (!lastEvent || !lastEvent.content || !lastEvent.content.parts) {
4232
- return "";
4233
- }
4234
- const mergedText = lastEvent.content.parts.filter((part) => part.text !== void 0 && part.text !== null).map((part) => part.text).join("\n");
4235
- let toolResult;
4236
- try {
4237
- toolResult = JSON.parse(mergedText);
4238
- } catch {
4239
- toolResult = mergedText;
4240
- }
4241
- if (this.outputKey && context4?.state) {
4242
- context4.state[this.outputKey] = toolResult;
4243
- }
4244
- return toolResult;
4245
4284
  } catch (error) {
4246
- this.logger.error(`Error executing agent tool ${this.name}:`, error);
4247
- throw new Error(
4248
- `Agent tool execution failed: ${error instanceof Error ? error.message : String(error)}`
4285
+ throw new McpError(
4286
+ `Failed to create transport: ${error instanceof Error ? error.message : String(error)}`,
4287
+ "connection_error" /* CONNECTION_ERROR */,
4288
+ error instanceof Error ? error : void 0
4249
4289
  );
4250
4290
  }
4251
4291
  }
4252
- };
4253
-
4254
- // src/tools/tool-context.ts
4255
- var ToolContext = class extends CallbackContext {
4256
4292
  /**
4257
- * The function call id of the current tool call. This id was
4258
- * returned in the function call event from LLM to identify a function call.
4259
- * If LLM didn't return this id, ADK will assign one to it. This id is used
4260
- * to map function call response to the original function call.
4261
- */
4262
- functionCallId;
4263
- /**
4264
- * Constructor for ToolContext
4265
- */
4266
- constructor(invocationContext, options = {}) {
4267
- super(invocationContext, { eventActions: options.eventActions });
4268
- this.functionCallId = options.functionCallId;
4269
- }
4270
- /**
4271
- * Gets the event actions of the current tool call
4293
+ * Re-initializes the MCP client when a session is closed.
4294
+ * Used by the retry mechanism.
4272
4295
  */
4273
- get actions() {
4274
- return this.eventActions;
4296
+ async reinitialize() {
4297
+ this.logger.debug("\u{1F504} Reinitializing MCP client after closed connection");
4298
+ await this.cleanupResources();
4299
+ this.client = null;
4300
+ this.transport = null;
4301
+ await this.initialize();
4275
4302
  }
4276
4303
  /**
4277
- * Lists the filenames of the artifacts attached to the current session
4304
+ * Cleans up resources associated with this client service.
4305
+ * Similar to Python's AsyncExitStack.aclose() functionality.
4278
4306
  */
4279
- async listArtifacts() {
4280
- if (!this._invocationContext.artifactService) {
4281
- throw new Error("Artifact service is not initialized.");
4307
+ async cleanupResources() {
4308
+ try {
4309
+ this.isClosing = true;
4310
+ if (this.client) {
4311
+ try {
4312
+ if (typeof this.client.close === "function") {
4313
+ await this.client.close();
4314
+ }
4315
+ } catch (err) {
4316
+ }
4317
+ }
4318
+ if (this.transport && typeof this.transport.close === "function") {
4319
+ await this.transport.close();
4320
+ }
4321
+ this.logger.debug("\u{1F9F9} Cleaned up MCP client resources");
4322
+ } catch (error) {
4323
+ this.logger.error("Error cleaning up MCP resources:", error);
4324
+ } finally {
4325
+ this.client = null;
4326
+ this.transport = null;
4327
+ this.isClosing = false;
4282
4328
  }
4283
- return await this._invocationContext.artifactService.listArtifactKeys({
4284
- appName: this._invocationContext.appName,
4285
- userId: this._invocationContext.userId,
4286
- sessionId: this._invocationContext.session.id
4287
- });
4288
4329
  }
4289
4330
  /**
4290
- * Searches the memory of the current user
4291
- */
4292
- async searchMemory(query) {
4293
- if (!this._invocationContext.memoryService) {
4294
- throw new Error("Memory service is not available.");
4295
- }
4296
- return await this._invocationContext.memoryService.searchMemory({
4297
- query,
4298
- appName: this._invocationContext.appName,
4299
- userId: this._invocationContext.userId
4300
- });
4301
- }
4302
- };
4303
-
4304
- // src/tools/index.ts
4305
- init_function_tool();
4306
-
4307
- // src/tools/function/index.ts
4308
- init_function_tool();
4309
- init_function_utils();
4310
- function createFunctionTool(func, options) {
4311
- const { FunctionTool: FunctionTool2 } = (init_function_tool(), __toCommonJS(function_tool_exports));
4312
- return new FunctionTool2(func, options);
4313
- }
4314
-
4315
- // src/tools/index.ts
4316
- init_function_utils();
4317
-
4318
- // src/tools/common/google-search.ts
4319
- init_logger();
4320
- init_base_tool();
4321
- import { Type as Type3 } from "@google/genai";
4322
- var GoogleSearch = class extends BaseTool {
4323
- logger = new Logger({ name: "GoogleSearch" });
4324
- /**
4325
- * Constructor for GoogleSearch
4326
- */
4327
- constructor() {
4328
- super({
4329
- name: "google_search",
4330
- description: "Search the web using Google"
4331
- });
4332
- }
4333
- /**
4334
- * Get the function declaration for the tool
4331
+ * Call an MCP tool with retry capability if the session is closed.
4335
4332
  */
4336
- getDeclaration() {
4337
- return {
4338
- name: this.name,
4339
- description: this.description,
4340
- parameters: {
4341
- type: Type3.OBJECT,
4342
- properties: {
4343
- query: {
4344
- type: Type3.STRING,
4345
- description: "The search query to execute"
4346
- },
4347
- num_results: {
4348
- type: Type3.INTEGER,
4349
- description: "Number of results to return (max 10)",
4350
- default: 5
4351
- }
4333
+ async callTool(name, args) {
4334
+ try {
4335
+ const wrappedCall = withRetry(
4336
+ async function() {
4337
+ const client = await this.initialize();
4338
+ return client.callTool({
4339
+ name,
4340
+ arguments: args
4341
+ });
4352
4342
  },
4353
- required: ["query"]
4343
+ this,
4344
+ async (instance) => await instance.reinitialize(),
4345
+ this.config.retryOptions?.maxRetries || 2
4346
+ );
4347
+ return await wrappedCall();
4348
+ } catch (error) {
4349
+ if (!(error instanceof McpError)) {
4350
+ throw new McpError(
4351
+ `Error calling tool "${name}": ${error instanceof Error ? error.message : String(error)}`,
4352
+ "tool_execution_error" /* TOOL_EXECUTION_ERROR */,
4353
+ error instanceof Error ? error : void 0
4354
+ );
4354
4355
  }
4355
- };
4356
+ throw error;
4357
+ }
4356
4358
  }
4357
4359
  /**
4358
- * Execute the search
4359
- * This is a simplified implementation that doesn't actually search, just returns mock results
4360
+ * Closes and cleans up all resources.
4361
+ * Should be called when the service is no longer needed.
4362
+ * Similar to Python's close() method.
4360
4363
  */
4361
- async runAsync(args, _context) {
4362
- this.logger.debug(
4363
- `[GoogleSearch] Executing Google search for: ${args.query}`
4364
- );
4365
- return {
4366
- results: [
4367
- {
4368
- title: `Result 1 for ${args.query}`,
4369
- link: "https://example.com/1",
4370
- snippet: `This is a sample result for the query "${args.query}".`
4371
- },
4372
- {
4373
- title: `Result 2 for ${args.query}`,
4374
- link: "https://example.com/2",
4375
- snippet: `Another sample result for "${args.query}".`
4376
- }
4377
- ]
4378
- };
4379
- }
4380
- };
4381
-
4382
- // src/tools/common/http-request-tool.ts
4383
- init_base_tool();
4384
- import { Type as Type4 } from "@google/genai";
4385
- var HttpRequestTool = class extends BaseTool {
4386
- constructor() {
4387
- super({
4388
- name: "http_request",
4389
- description: "Make HTTP requests to external APIs and web services"
4390
- });
4364
+ async close() {
4365
+ this.logger.debug("\u{1F51A} Closing MCP client service");
4366
+ await this.cleanupResources();
4391
4367
  }
4392
4368
  /**
4393
- * Get the function declaration for the tool
4369
+ * Checks if the client is currently connected
4394
4370
  */
4395
- getDeclaration() {
4396
- return {
4397
- name: this.name,
4398
- description: this.description,
4399
- parameters: {
4400
- type: Type4.OBJECT,
4401
- properties: {
4402
- url: {
4403
- type: Type4.STRING,
4404
- description: "The URL to send the request to"
4405
- },
4406
- method: {
4407
- type: Type4.STRING,
4408
- description: "The HTTP method to use (GET, POST, PUT, DELETE, etc.)",
4409
- enum: ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"],
4410
- default: "GET"
4411
- },
4412
- headers: {
4413
- type: Type4.OBJECT,
4414
- description: "Request headers to include"
4415
- },
4416
- body: {
4417
- type: Type4.STRING,
4418
- description: "Request body content (as string, typically JSON)"
4419
- },
4420
- params: {
4421
- type: Type4.OBJECT,
4422
- description: "URL query parameters to include"
4423
- },
4424
- timeout: {
4425
- type: Type4.INTEGER,
4426
- description: "Request timeout in milliseconds",
4427
- default: 1e4
4371
+ isConnected() {
4372
+ return !!this.client && !this.isClosing;
4373
+ }
4374
+ async setupSamplingHandler(client) {
4375
+ if (!this.mcpSamplingHandler) {
4376
+ this.logger.debug(
4377
+ "\u26A0\uFE0F No sampling handler provided - sampling requests will be rejected"
4378
+ );
4379
+ return;
4380
+ }
4381
+ try {
4382
+ client.setRequestHandler(
4383
+ CreateMessageRequestSchema2,
4384
+ async (request) => {
4385
+ try {
4386
+ this.logger.debug("Received sampling request:", request);
4387
+ const response = await this.mcpSamplingHandler.handleSamplingRequest(request);
4388
+ this.logger.debug("\u2705 Sampling request completed successfully");
4389
+ return response;
4390
+ } catch (error) {
4391
+ this.logger.error("\u274C Error handling sampling request:", error);
4392
+ if (error instanceof McpError) {
4393
+ throw error;
4394
+ }
4395
+ throw new McpError(
4396
+ `Sampling request failed: ${error instanceof Error ? error.message : String(error)}`,
4397
+ "SAMPLING_ERROR" /* SAMPLING_ERROR */,
4398
+ error instanceof Error ? error : void 0
4399
+ );
4428
4400
  }
4429
- },
4430
- required: ["url"]
4431
- }
4432
- };
4401
+ }
4402
+ );
4403
+ this.logger.debug("\u{1F3AF} Sampling handler registered successfully");
4404
+ } catch (error) {
4405
+ this.logger.error("Failed to setup sampling handler:", error);
4406
+ this.logger.debug(
4407
+ "\u26A0\uFE0F Sampling handler registration failed, continuing without sampling support"
4408
+ );
4409
+ }
4433
4410
  }
4434
4411
  /**
4435
- * Execute the HTTP request
4412
+ * Set a new ADK sampling handler
4436
4413
  */
4437
- async runAsync(args, _context) {
4438
- try {
4439
- const {
4440
- url,
4441
- method = "GET",
4442
- headers = {},
4443
- body,
4444
- params,
4445
- timeout = 1e4
4446
- } = args;
4447
- const urlObj = new URL(url);
4448
- if (params) {
4449
- Object.entries(params).forEach(([key, value]) => {
4450
- urlObj.searchParams.append(key, value);
4451
- });
4452
- }
4453
- const requestHeaders = { ...headers };
4454
- if (body && !requestHeaders["Content-Type"] && this.isValidJson(body)) {
4455
- requestHeaders["Content-Type"] = "application/json";
4456
- }
4457
- const options = {
4458
- method,
4459
- headers: requestHeaders,
4460
- body,
4461
- signal: AbortSignal.timeout(timeout)
4462
- };
4463
- const response = await fetch(urlObj.toString(), options);
4464
- const responseHeaders = {};
4465
- response.headers.forEach((value, key) => {
4466
- responseHeaders[key] = value;
4414
+ setSamplingHandler(handler) {
4415
+ this.mcpSamplingHandler = new McpSamplingHandler(handler);
4416
+ if (this.client) {
4417
+ this.setupSamplingHandler(this.client).catch((error) => {
4418
+ this.logger.error("Failed to update ADK sampling handler:", error);
4467
4419
  });
4468
- const responseBody = await response.text();
4469
- return {
4470
- statusCode: response.status,
4471
- headers: responseHeaders,
4472
- body: responseBody
4473
- };
4474
- } catch (error) {
4475
- return {
4476
- statusCode: 0,
4477
- headers: {},
4478
- body: "",
4479
- error: error instanceof Error ? error.message : String(error)
4480
- };
4481
4420
  }
4482
4421
  }
4483
4422
  /**
4484
- * Check if a string is valid JSON
4423
+ * Remove the sampling handler
4485
4424
  */
4486
- isValidJson(str) {
4487
- try {
4488
- JSON.parse(str);
4489
- return true;
4490
- } catch (e) {
4491
- return false;
4425
+ removeSamplingHandler() {
4426
+ this.mcpSamplingHandler = null;
4427
+ if (this.client) {
4428
+ try {
4429
+ this.client.removeRequestHandler?.("sampling/createMessage");
4430
+ } catch (error) {
4431
+ this.logger.error("Failed to remove sampling handler:", error);
4432
+ }
4492
4433
  }
4493
4434
  }
4494
4435
  };
4495
4436
 
4496
- // src/tools/common/file-operations-tool.ts
4437
+ // src/tools/index.ts
4497
4438
  init_base_tool();
4498
- import fs from "fs/promises";
4499
- import path from "path";
4500
- import { Type as Type5 } from "@google/genai";
4501
- var FileOperationsTool = class extends BaseTool {
4502
- basePath;
4503
- constructor(options) {
4439
+
4440
+ // src/tools/base/create-tool.ts
4441
+ init_base_tool();
4442
+ import * as z from "zod";
4443
+ import { zodToJsonSchema } from "zod-to-json-schema";
4444
+ var CreatedTool = class extends BaseTool {
4445
+ func;
4446
+ schema;
4447
+ functionDeclaration;
4448
+ constructor(config) {
4504
4449
  super({
4505
- name: "file_operations",
4506
- description: "Perform file system operations like reading, writing, and managing files"
4507
- });
4508
- this.basePath = options?.basePath || process.cwd();
4509
- }
4510
- /**
4511
- * Get the function declaration for the tool
4512
- */
4513
- getDeclaration() {
4514
- return {
4515
- name: this.name,
4516
- description: this.description,
4517
- parameters: {
4518
- type: Type5.OBJECT,
4519
- properties: {
4520
- operation: {
4521
- type: Type5.STRING,
4522
- description: "The file operation to perform",
4523
- enum: [
4524
- "read",
4525
- "write",
4526
- "append",
4527
- "delete",
4528
- "exists",
4529
- "list",
4530
- "mkdir"
4531
- ]
4532
- },
4533
- filepath: {
4534
- type: Type5.STRING,
4535
- description: "Path to the file or directory (relative to the base path)"
4536
- },
4537
- content: {
4538
- type: Type5.STRING,
4539
- description: "Content to write to the file (for write and append operations)"
4540
- },
4541
- encoding: {
4542
- type: Type5.STRING,
4543
- description: "File encoding to use",
4544
- default: "utf8"
4545
- }
4546
- },
4547
- required: ["operation", "filepath"]
4548
- }
4549
- };
4450
+ name: config.name,
4451
+ description: config.description,
4452
+ isLongRunning: config.isLongRunning ?? false,
4453
+ shouldRetryOnFailure: config.shouldRetryOnFailure ?? false,
4454
+ maxRetryAttempts: config.maxRetryAttempts ?? 3
4455
+ });
4456
+ this.func = config.fn;
4457
+ this.schema = config.schema ?? z.object({});
4458
+ this.functionDeclaration = this.buildDeclaration();
4550
4459
  }
4551
4460
  /**
4552
- * Execute the file operation
4461
+ * Executes the tool function with validation
4553
4462
  */
4554
- async runAsync(args, _context) {
4463
+ async runAsync(args, context4) {
4555
4464
  try {
4556
- const resolvedPath = this.resolvePath(args.filepath);
4557
- this.validatePath(resolvedPath);
4558
- const encoding = args.encoding || "utf8";
4559
- switch (args.operation) {
4560
- case "read":
4561
- return await this.readFile(resolvedPath, encoding);
4562
- case "write":
4563
- return await this.writeFile(
4564
- resolvedPath,
4565
- args.content || "",
4566
- encoding
4567
- );
4568
- case "append":
4569
- return await this.appendFile(
4570
- resolvedPath,
4571
- args.content || "",
4572
- encoding
4573
- );
4574
- case "delete":
4575
- return await this.deleteFile(resolvedPath);
4576
- case "exists":
4577
- return await this.fileExists(resolvedPath);
4578
- case "list":
4579
- return await this.listDirectory(resolvedPath);
4580
- case "mkdir":
4581
- return await this.makeDirectory(resolvedPath);
4582
- default:
4583
- throw new Error(`Unsupported operation: ${args.operation}`);
4584
- }
4465
+ const validatedArgs = this.schema.parse(args);
4466
+ const result = await Promise.resolve(this.func(validatedArgs, context4));
4467
+ return result ?? {};
4585
4468
  } catch (error) {
4469
+ if (error instanceof z.ZodError) {
4470
+ return {
4471
+ error: `Invalid arguments for ${this.name}: ${error.message}`
4472
+ };
4473
+ }
4586
4474
  return {
4587
- success: false,
4588
- error: error instanceof Error ? error.message : String(error)
4475
+ error: `Error executing ${this.name}: ${error instanceof Error ? error.message : String(error)}`
4589
4476
  };
4590
4477
  }
4591
4478
  }
4592
4479
  /**
4593
- * Resolve a file path relative to the base path
4480
+ * Returns the function declaration for this tool
4594
4481
  */
4595
- resolvePath(filepath) {
4596
- return path.isAbsolute(filepath) ? filepath : path.resolve(this.basePath, filepath);
4482
+ getDeclaration() {
4483
+ return this.functionDeclaration;
4597
4484
  }
4598
4485
  /**
4599
- * Validate that a path is within the base path for security
4486
+ * Builds the function declaration from the Zod schema
4600
4487
  */
4601
- validatePath(filepath) {
4602
- const normalizedPath = path.normalize(filepath);
4603
- const normalizedBasePath = path.normalize(this.basePath);
4604
- if (!normalizedPath.startsWith(normalizedBasePath)) {
4605
- throw new Error(
4606
- `Access denied: Can't access paths outside the base directory`
4607
- );
4608
- }
4488
+ buildDeclaration() {
4489
+ const rawParameters = zodToJsonSchema(this.schema, {
4490
+ target: "jsonSchema7",
4491
+ $refStrategy: "none"
4492
+ });
4493
+ const { $schema, ...parameters } = rawParameters;
4494
+ return {
4495
+ name: this.name,
4496
+ description: this.description,
4497
+ parameters
4498
+ };
4499
+ }
4500
+ };
4501
+ function createTool(config) {
4502
+ return new CreatedTool(config);
4503
+ }
4504
+
4505
+ // src/tools/common/agent-tool.ts
4506
+ init_logger();
4507
+ import { Type } from "@google/genai";
4508
+ import { v4 as uuidv42 } from "uuid";
4509
+
4510
+ // src/agents/invocation-context.ts
4511
+ var LlmCallsLimitExceededError = class extends Error {
4512
+ constructor(message) {
4513
+ super(message);
4514
+ this.name = "LlmCallsLimitExceededError";
4609
4515
  }
4516
+ };
4517
+ var InvocationCostManager = class {
4610
4518
  /**
4611
- * Read a file
4519
+ * A counter that keeps track of number of llm calls made.
4612
4520
  */
4613
- async readFile(filepath, encoding) {
4614
- try {
4615
- const content = await fs.readFile(filepath, { encoding });
4616
- return {
4617
- success: true,
4618
- data: content
4619
- };
4620
- } catch (error) {
4621
- return {
4622
- success: false,
4623
- error: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`
4624
- };
4625
- }
4626
- }
4521
+ _numberOfLlmCalls = 0;
4627
4522
  /**
4628
- * Write to a file
4523
+ * Increments _numberOfLlmCalls and enforces the limit.
4629
4524
  */
4630
- async writeFile(filepath, content, encoding) {
4631
- try {
4632
- const dir = path.dirname(filepath);
4633
- await fs.mkdir(dir, { recursive: true });
4634
- await fs.writeFile(filepath, content, { encoding });
4635
- return {
4636
- success: true
4637
- };
4638
- } catch (error) {
4639
- return {
4640
- success: false,
4641
- error: `Failed to write to file: ${error instanceof Error ? error.message : String(error)}`
4642
- };
4525
+ incrementAndEnforceLlmCallsLimit(runConfig) {
4526
+ this._numberOfLlmCalls += 1;
4527
+ if (runConfig && runConfig.maxLlmCalls > 0 && this._numberOfLlmCalls > runConfig.maxLlmCalls) {
4528
+ throw new LlmCallsLimitExceededError(
4529
+ `Max number of llm calls limit of \`${runConfig.maxLlmCalls}\` exceeded`
4530
+ );
4643
4531
  }
4644
4532
  }
4533
+ };
4534
+ function newInvocationContextId() {
4535
+ return `e-${crypto.randomUUID()}`;
4536
+ }
4537
+ var InvocationContext = class _InvocationContext {
4538
+ artifactService;
4539
+ sessionService;
4540
+ memoryService;
4645
4541
  /**
4646
- * Append to a file
4542
+ * The id of this invocation context. Readonly.
4647
4543
  */
4648
- async appendFile(filepath, content, encoding) {
4649
- try {
4650
- const dir = path.dirname(filepath);
4651
- await fs.mkdir(dir, { recursive: true });
4652
- await fs.appendFile(filepath, content, { encoding });
4653
- return {
4654
- success: true
4655
- };
4656
- } catch (error) {
4657
- return {
4658
- success: false,
4659
- error: `Failed to append to file: ${error instanceof Error ? error.message : String(error)}`
4660
- };
4661
- }
4662
- }
4544
+ invocationId;
4663
4545
  /**
4664
- * Delete a file
4546
+ * The branch of the invocation context.
4547
+ *
4548
+ * The format is like agent_1.agent_2.agent_3, where agent_1 is the parent of
4549
+ * agent_2, and agent_2 is the parent of agent_3.
4550
+ *
4551
+ * Branch is used when multiple sub-agents shouldn't see their peer agents'
4552
+ * conversation history.
4665
4553
  */
4666
- async deleteFile(filepath) {
4667
- try {
4668
- await fs.unlink(filepath);
4669
- return {
4670
- success: true
4671
- };
4672
- } catch (error) {
4673
- return {
4674
- success: false,
4675
- error: `Failed to delete file: ${error instanceof Error ? error.message : String(error)}`
4676
- };
4677
- }
4554
+ branch;
4555
+ /**
4556
+ * The current agent of this invocation context. Readonly.
4557
+ */
4558
+ agent;
4559
+ /**
4560
+ * The user content that started this invocation. Readonly.
4561
+ */
4562
+ userContent;
4563
+ /**
4564
+ * The current session of this invocation context. Readonly.
4565
+ */
4566
+ session;
4567
+ /**
4568
+ * Whether to end this invocation.
4569
+ *
4570
+ * Set to True in callbacks or tools to terminate this invocation.
4571
+ */
4572
+ endInvocation = false;
4573
+ /**
4574
+ * The queue to receive live requests.
4575
+ */
4576
+ liveRequestQueue;
4577
+ /**
4578
+ * The running streaming tools of this invocation.
4579
+ */
4580
+ activeStreamingTools;
4581
+ /**
4582
+ * Caches necessary, data audio or contents, that are needed by transcription.
4583
+ */
4584
+ transcriptionCache;
4585
+ /**
4586
+ * Configurations for live agents under this invocation.
4587
+ */
4588
+ runConfig;
4589
+ /**
4590
+ * A container to keep track of different kinds of costs incurred as a part
4591
+ * of this invocation.
4592
+ */
4593
+ _invocationCostManager = new InvocationCostManager();
4594
+ /**
4595
+ * Constructor for InvocationContext
4596
+ */
4597
+ constructor(options) {
4598
+ this.artifactService = options.artifactService;
4599
+ this.sessionService = options.sessionService;
4600
+ this.memoryService = options.memoryService;
4601
+ this.invocationId = options.invocationId || newInvocationContextId();
4602
+ this.branch = options.branch;
4603
+ this.agent = options.agent;
4604
+ this.userContent = options.userContent;
4605
+ this.session = options.session;
4606
+ this.endInvocation = options.endInvocation || false;
4607
+ this.liveRequestQueue = options.liveRequestQueue;
4608
+ this.activeStreamingTools = options.activeStreamingTools;
4609
+ this.transcriptionCache = options.transcriptionCache;
4610
+ this.runConfig = options.runConfig;
4678
4611
  }
4679
4612
  /**
4680
- * Check if a file exists
4613
+ * App name from the session
4681
4614
  */
4682
- async fileExists(filepath) {
4683
- try {
4684
- await fs.access(filepath);
4685
- return {
4686
- success: true,
4687
- data: true
4688
- };
4689
- } catch {
4690
- return {
4691
- success: true,
4692
- data: false
4693
- };
4694
- }
4615
+ get appName() {
4616
+ return this.session.appName;
4617
+ }
4618
+ /**
4619
+ * User ID from the session
4620
+ */
4621
+ get userId() {
4622
+ return this.session.userId;
4695
4623
  }
4696
4624
  /**
4697
- * List directory contents
4625
+ * Tracks number of llm calls made.
4626
+ *
4627
+ * @throws {LlmCallsLimitExceededError} If number of llm calls made exceed the set threshold.
4698
4628
  */
4699
- async listDirectory(dirpath) {
4700
- try {
4701
- const entries = await fs.readdir(dirpath, { withFileTypes: true });
4702
- const results = await Promise.all(
4703
- entries.map(async (entry) => {
4704
- const entryPath = path.join(dirpath, entry.name);
4705
- const stats = await fs.stat(entryPath);
4706
- return {
4707
- name: entry.name,
4708
- path: entryPath,
4709
- isFile: entry.isFile(),
4710
- isDirectory: entry.isDirectory(),
4711
- size: stats.size,
4712
- created: stats.birthtime,
4713
- modified: stats.mtime
4714
- };
4715
- })
4716
- );
4717
- return {
4718
- success: true,
4719
- data: results
4720
- };
4721
- } catch (error) {
4722
- return {
4723
- success: false,
4724
- error: `Failed to list directory: ${error instanceof Error ? error.message : String(error)}`
4725
- };
4726
- }
4629
+ incrementLlmCallCount() {
4630
+ this._invocationCostManager.incrementAndEnforceLlmCallsLimit(
4631
+ this.runConfig
4632
+ );
4727
4633
  }
4728
4634
  /**
4729
- * Create a directory
4635
+ * Creates a child invocation context for a sub-agent
4730
4636
  */
4731
- async makeDirectory(dirpath) {
4732
- try {
4733
- await fs.mkdir(dirpath, { recursive: true });
4734
- return {
4735
- success: true
4736
- };
4737
- } catch (error) {
4738
- return {
4739
- success: false,
4740
- error: `Failed to create directory: ${error instanceof Error ? error.message : String(error)}`
4741
- };
4742
- }
4637
+ createChildContext(agent) {
4638
+ return new _InvocationContext({
4639
+ artifactService: this.artifactService,
4640
+ sessionService: this.sessionService,
4641
+ memoryService: this.memoryService,
4642
+ invocationId: this.invocationId,
4643
+ // Keep same invocation ID
4644
+ branch: this.branch ? `${this.branch}.${agent.name}` : agent.name,
4645
+ // Update branch
4646
+ agent,
4647
+ // Update to the new agent
4648
+ userContent: this.userContent,
4649
+ session: this.session,
4650
+ endInvocation: this.endInvocation,
4651
+ liveRequestQueue: this.liveRequestQueue,
4652
+ activeStreamingTools: this.activeStreamingTools,
4653
+ transcriptionCache: this.transcriptionCache,
4654
+ runConfig: this.runConfig
4655
+ });
4743
4656
  }
4744
4657
  };
4745
4658
 
4746
- // src/tools/common/user-interaction-tool.ts
4659
+ // src/tools/common/agent-tool.ts
4747
4660
  init_base_tool();
4748
- import { Type as Type6 } from "@google/genai";
4749
- var UserInteractionTool = class extends BaseTool {
4750
- constructor() {
4661
+ function isLlmAgent(agent) {
4662
+ return true;
4663
+ }
4664
+ var AgentTool = class extends BaseTool {
4665
+ /**
4666
+ * The agent used by this tool
4667
+ */
4668
+ agent;
4669
+ /**
4670
+ * The function declaration schema
4671
+ */
4672
+ functionDeclaration;
4673
+ /**
4674
+ * The key to store the tool output in the state
4675
+ */
4676
+ outputKey;
4677
+ /**
4678
+ * Whether to skip summarization of the agent's response
4679
+ */
4680
+ skipSummarization;
4681
+ logger = new Logger({ name: "AgentTool" });
4682
+ /**
4683
+ * Create a new agent tool
4684
+ */
4685
+ constructor(config) {
4751
4686
  super({
4752
- name: "user_interaction",
4753
- description: "Prompt the user for input during agent execution",
4754
- isLongRunning: true
4687
+ name: config.name,
4688
+ description: config.description || config.agent.description,
4689
+ isLongRunning: config.isLongRunning || false,
4690
+ shouldRetryOnFailure: config.shouldRetryOnFailure || false,
4691
+ maxRetryAttempts: config.maxRetryAttempts || 3
4755
4692
  });
4693
+ this.agent = config.agent;
4694
+ this.functionDeclaration = config.functionDeclaration;
4695
+ this.outputKey = config.outputKey;
4696
+ this.skipSummarization = config.skipSummarization || false;
4756
4697
  }
4757
4698
  /**
4758
4699
  * Get the function declaration for the tool
4759
4700
  */
4760
4701
  getDeclaration() {
4702
+ if (this.functionDeclaration) {
4703
+ return this.functionDeclaration;
4704
+ }
4705
+ const description = isLlmAgent(this.agent) ? typeof this.agent.instruction === "string" ? this.agent.instruction : this.description : this.description;
4761
4706
  return {
4762
4707
  name: this.name,
4763
- description: this.description,
4708
+ description,
4764
4709
  parameters: {
4765
- type: Type6.OBJECT,
4710
+ type: Type.OBJECT,
4766
4711
  properties: {
4767
- prompt: {
4768
- type: Type6.STRING,
4769
- description: "The prompt message to display to the user"
4770
- },
4771
- options: {
4772
- type: Type6.ARRAY,
4773
- description: "Optional array of choices to present to the user",
4774
- items: {
4775
- type: Type6.STRING
4776
- }
4777
- },
4778
- defaultValue: {
4779
- type: Type6.STRING,
4780
- description: "Optional default value for the input field"
4712
+ input: {
4713
+ type: Type.STRING,
4714
+ description: "The input to provide to the agent"
4781
4715
  }
4782
4716
  },
4783
- required: ["prompt"]
4717
+ required: ["input"]
4784
4718
  }
4785
4719
  };
4786
4720
  }
4787
4721
  /**
4788
- * Execute the user interaction
4722
+ * Execute the tool by running the agent with the provided input
4789
4723
  */
4790
- async runAsync(args, context4) {
4724
+ async runAsync(params, context4) {
4791
4725
  try {
4792
- const actions = context4.actions;
4793
- if (!actions || !actions.promptUser) {
4794
- return {
4795
- success: false,
4796
- error: "User interaction is not supported in the current environment"
4797
- };
4798
- }
4799
- if (actions.skipSummarization) {
4800
- actions.skipSummarization(true);
4726
+ const input = params.input || Object.values(params)[0];
4727
+ if (!isLlmAgent(this.agent)) {
4728
+ throw new Error(
4729
+ `Agent ${this.name} does not support running as a tool`
4730
+ );
4801
4731
  }
4802
- const promptOptions = args.options && args.options.length > 0 ? {
4803
- choices: args.options
4804
- } : void 0;
4805
- const response = await actions.promptUser({
4806
- prompt: args.prompt,
4807
- defaultValue: args.defaultValue,
4808
- options: promptOptions
4732
+ const parentInvocation = context4._invocationContext;
4733
+ const childInvocationContext = new InvocationContext({
4734
+ invocationId: uuidv42(),
4735
+ agent: this.agent,
4736
+ session: parentInvocation.session,
4737
+ artifactService: parentInvocation.artifactService,
4738
+ sessionService: parentInvocation.sessionService,
4739
+ memoryService: parentInvocation.memoryService,
4740
+ runConfig: parentInvocation.runConfig,
4741
+ userContent: {
4742
+ role: "user",
4743
+ parts: [{ text: String(input) }]
4744
+ },
4745
+ branch: parentInvocation.branch ? `${parentInvocation.branch}.${this.agent.name}` : this.agent.name
4809
4746
  });
4810
- return {
4811
- success: true,
4812
- userInput: response
4813
- };
4747
+ let lastEvent = null;
4748
+ for await (const event of this.agent.runAsync(childInvocationContext)) {
4749
+ if (!event.partial) {
4750
+ await childInvocationContext.sessionService.appendEvent(
4751
+ childInvocationContext.session,
4752
+ event
4753
+ );
4754
+ }
4755
+ if (event.content && event.author === this.agent.name) {
4756
+ lastEvent = event;
4757
+ }
4758
+ }
4759
+ if (!lastEvent || !lastEvent.content || !lastEvent.content.parts) {
4760
+ return "";
4761
+ }
4762
+ const mergedText = lastEvent.content.parts.filter((part) => part.text !== void 0 && part.text !== null).map((part) => part.text).join("\n");
4763
+ let toolResult;
4764
+ try {
4765
+ toolResult = JSON.parse(mergedText);
4766
+ } catch {
4767
+ toolResult = mergedText;
4768
+ }
4769
+ if (this.outputKey && context4?.state) {
4770
+ context4.state[this.outputKey] = toolResult;
4771
+ }
4772
+ return toolResult;
4814
4773
  } catch (error) {
4815
- return {
4816
- success: false,
4817
- error: error instanceof Error ? error.message : String(error)
4818
- };
4774
+ this.logger.error(`Error executing agent tool ${this.name}:`, error);
4775
+ throw new Error(
4776
+ `Agent tool execution failed: ${error instanceof Error ? error.message : String(error)}`
4777
+ );
4819
4778
  }
4820
4779
  }
4821
4780
  };
4822
4781
 
4823
- // src/tools/common/exit-loop-tool.ts
4824
- init_logger();
4825
- init_base_tool();
4826
- var ExitLoopTool = class extends BaseTool {
4827
- logger = new Logger({ name: "ExitLoopTool" });
4782
+ // src/tools/tool-context.ts
4783
+ var ToolContext = class extends CallbackContext {
4828
4784
  /**
4829
- * Constructor for ExitLoopTool
4785
+ * The function call id of the current tool call. This id was
4786
+ * returned in the function call event from LLM to identify a function call.
4787
+ * If LLM didn't return this id, ADK will assign one to it. This id is used
4788
+ * to map function call response to the original function call.
4830
4789
  */
4831
- constructor() {
4832
- super({
4833
- name: "exit_loop",
4834
- description: "Exits the loop. Call this function only when you are instructed to do so."
4790
+ functionCallId;
4791
+ /**
4792
+ * Constructor for ToolContext
4793
+ */
4794
+ constructor(invocationContext, options = {}) {
4795
+ super(invocationContext, { eventActions: options.eventActions });
4796
+ this.functionCallId = options.functionCallId;
4797
+ }
4798
+ /**
4799
+ * Gets the event actions of the current tool call
4800
+ */
4801
+ get actions() {
4802
+ return this.eventActions;
4803
+ }
4804
+ /**
4805
+ * Lists the filenames of the artifacts attached to the current session
4806
+ */
4807
+ async listArtifacts() {
4808
+ if (!this._invocationContext.artifactService) {
4809
+ throw new Error("Artifact service is not initialized.");
4810
+ }
4811
+ return await this._invocationContext.artifactService.listArtifactKeys({
4812
+ appName: this._invocationContext.appName,
4813
+ userId: this._invocationContext.userId,
4814
+ sessionId: this._invocationContext.session.id
4835
4815
  });
4836
4816
  }
4837
4817
  /**
4838
- * Execute the exit loop action
4818
+ * Searches the memory of the current user
4839
4819
  */
4840
- async runAsync(_args, context4) {
4841
- this.logger.debug("Executing exit loop tool");
4842
- context4.actions.escalate = true;
4820
+ async searchMemory(query) {
4821
+ if (!this._invocationContext.memoryService) {
4822
+ throw new Error("Memory service is not available.");
4823
+ }
4824
+ return await this._invocationContext.memoryService.searchMemory({
4825
+ query,
4826
+ appName: this._invocationContext.appName,
4827
+ userId: this._invocationContext.userId
4828
+ });
4843
4829
  }
4844
4830
  };
4845
4831
 
4846
- // src/tools/common/get-user-choice-tool.ts
4832
+ // src/tools/index.ts
4833
+ init_function_tool();
4834
+
4835
+ // src/tools/function/index.ts
4836
+ init_function_tool();
4837
+ init_function_utils();
4838
+ function createFunctionTool(func, options) {
4839
+ const { FunctionTool: FunctionTool2 } = (init_function_tool(), __toCommonJS(function_tool_exports));
4840
+ return new FunctionTool2(func, options);
4841
+ }
4842
+
4843
+ // src/tools/index.ts
4844
+ init_function_utils();
4845
+
4846
+ // src/tools/common/google-search.ts
4847
4847
  init_logger();
4848
4848
  init_base_tool();
4849
- import { Type as Type7 } from "@google/genai";
4850
- var GetUserChoiceTool = class extends BaseTool {
4851
- logger = new Logger({ name: "GetUserChoiceTool" });
4849
+ import { Type as Type3 } from "@google/genai";
4850
+ var GoogleSearch = class extends BaseTool {
4851
+ logger = new Logger({ name: "GoogleSearch" });
4852
4852
  /**
4853
- * Constructor for GetUserChoiceTool
4853
+ * Constructor for GoogleSearch
4854
4854
  */
4855
4855
  constructor() {
4856
4856
  super({
4857
- name: "get_user_choice",
4858
- description: "This tool provides the options to the user and asks them to choose one. Use this tool when you need the user to make a selection between multiple options. Do not list options in your response - use this tool instead.",
4859
- isLongRunning: true
4857
+ name: "google_search",
4858
+ description: "Search the web using Google"
4860
4859
  });
4861
4860
  }
4862
4861
  /**
@@ -4867,54 +4866,55 @@ var GetUserChoiceTool = class extends BaseTool {
4867
4866
  name: this.name,
4868
4867
  description: this.description,
4869
4868
  parameters: {
4870
- type: Type7.OBJECT,
4869
+ type: Type3.OBJECT,
4871
4870
  properties: {
4872
- options: {
4873
- type: Type7.ARRAY,
4874
- description: "List of options for the user to choose from",
4875
- items: {
4876
- type: Type7.STRING
4877
- }
4871
+ query: {
4872
+ type: Type3.STRING,
4873
+ description: "The search query to execute"
4878
4874
  },
4879
- question: {
4880
- type: Type7.STRING,
4881
- description: "The question or prompt to show the user before presenting options"
4875
+ num_results: {
4876
+ type: Type3.INTEGER,
4877
+ description: "Number of results to return (max 10)",
4878
+ default: 5
4882
4879
  }
4883
4880
  },
4884
- required: ["options"]
4881
+ required: ["query"]
4885
4882
  }
4886
4883
  };
4887
4884
  }
4888
4885
  /**
4889
- * Execute the user choice action
4890
- * This is a long running operation that will return null initially
4891
- * and the actual choice will be provided asynchronously
4886
+ * Execute the search
4887
+ * This is a simplified implementation that doesn't actually search, just returns mock results
4892
4888
  */
4893
- async runAsync(args, context4) {
4889
+ async runAsync(args, _context) {
4894
4890
  this.logger.debug(
4895
- `Executing get_user_choice with options: ${args.options.join(", ")}`
4891
+ `[GoogleSearch] Executing Google search for: ${args.query}`
4896
4892
  );
4897
- if (args.question) {
4898
- this.logger.debug(`Question: ${args.question}`);
4899
- }
4900
- context4.actions.skipSummarization = true;
4901
- return null;
4893
+ return {
4894
+ results: [
4895
+ {
4896
+ title: `Result 1 for ${args.query}`,
4897
+ link: "https://example.com/1",
4898
+ snippet: `This is a sample result for the query "${args.query}".`
4899
+ },
4900
+ {
4901
+ title: `Result 2 for ${args.query}`,
4902
+ link: "https://example.com/2",
4903
+ snippet: `Another sample result for "${args.query}".`
4904
+ }
4905
+ ]
4906
+ };
4902
4907
  }
4903
4908
  };
4904
4909
 
4905
- // src/tools/common/transfer-to-agent-tool.ts
4906
- init_logger();
4910
+ // src/tools/common/http-request-tool.ts
4907
4911
  init_base_tool();
4908
- import { Type as Type8 } from "@google/genai";
4909
- var TransferToAgentTool = class extends BaseTool {
4910
- logger = new Logger({ name: "TransferToAgentTool" });
4911
- /**
4912
- * Constructor for TransferToAgentTool
4913
- */
4912
+ import { Type as Type4 } from "@google/genai";
4913
+ var HttpRequestTool = class extends BaseTool {
4914
4914
  constructor() {
4915
4915
  super({
4916
- name: "transfer_to_agent",
4917
- description: "Transfer the question to another agent when it's more suitable to answer the user's question according to the agent's description. Use this function when you determine that another agent in the system would be better equipped to handle the user's request based on their specialized capabilities and expertise areas."
4916
+ name: "http_request",
4917
+ description: "Make HTTP requests to external APIs and web services"
4918
4918
  });
4919
4919
  }
4920
4920
  /**
@@ -4925,40 +4925,115 @@ var TransferToAgentTool = class extends BaseTool {
4925
4925
  name: this.name,
4926
4926
  description: this.description,
4927
4927
  parameters: {
4928
- type: Type8.OBJECT,
4928
+ type: Type4.OBJECT,
4929
4929
  properties: {
4930
- agent_name: {
4931
- type: Type8.STRING,
4932
- description: "The name of the agent to transfer control to"
4930
+ url: {
4931
+ type: Type4.STRING,
4932
+ description: "The URL to send the request to"
4933
+ },
4934
+ method: {
4935
+ type: Type4.STRING,
4936
+ description: "The HTTP method to use (GET, POST, PUT, DELETE, etc.)",
4937
+ enum: ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"],
4938
+ default: "GET"
4939
+ },
4940
+ headers: {
4941
+ type: Type4.OBJECT,
4942
+ description: "Request headers to include"
4943
+ },
4944
+ body: {
4945
+ type: Type4.STRING,
4946
+ description: "Request body content (as string, typically JSON)"
4947
+ },
4948
+ params: {
4949
+ type: Type4.OBJECT,
4950
+ description: "URL query parameters to include"
4951
+ },
4952
+ timeout: {
4953
+ type: Type4.INTEGER,
4954
+ description: "Request timeout in milliseconds",
4955
+ default: 1e4
4933
4956
  }
4934
4957
  },
4935
- required: ["agent_name"]
4958
+ required: ["url"]
4936
4959
  }
4937
4960
  };
4938
4961
  }
4939
4962
  /**
4940
- * Execute the transfer to agent action
4963
+ * Execute the HTTP request
4941
4964
  */
4942
- async runAsync(args, context4) {
4943
- this.logger.debug(`Executing transfer to agent: ${args.agent_name}`);
4944
- context4.actions.transferToAgent = args.agent_name;
4965
+ async runAsync(args, _context) {
4966
+ try {
4967
+ const {
4968
+ url,
4969
+ method = "GET",
4970
+ headers = {},
4971
+ body,
4972
+ params,
4973
+ timeout = 1e4
4974
+ } = args;
4975
+ const urlObj = new URL(url);
4976
+ if (params) {
4977
+ Object.entries(params).forEach(([key, value]) => {
4978
+ urlObj.searchParams.append(key, value);
4979
+ });
4980
+ }
4981
+ const requestHeaders = { ...headers };
4982
+ if (body && !requestHeaders["Content-Type"] && this.isValidJson(body)) {
4983
+ requestHeaders["Content-Type"] = "application/json";
4984
+ }
4985
+ const options = {
4986
+ method,
4987
+ headers: requestHeaders,
4988
+ body,
4989
+ signal: AbortSignal.timeout(timeout)
4990
+ };
4991
+ const response = await fetch(urlObj.toString(), options);
4992
+ const responseHeaders = {};
4993
+ response.headers.forEach((value, key) => {
4994
+ responseHeaders[key] = value;
4995
+ });
4996
+ const responseBody = await response.text();
4997
+ return {
4998
+ statusCode: response.status,
4999
+ headers: responseHeaders,
5000
+ body: responseBody
5001
+ };
5002
+ } catch (error) {
5003
+ return {
5004
+ statusCode: 0,
5005
+ headers: {},
5006
+ body: "",
5007
+ error: error instanceof Error ? error.message : String(error)
5008
+ };
5009
+ }
5010
+ }
5011
+ /**
5012
+ * Check if a string is valid JSON
5013
+ */
5014
+ isValidJson(str) {
5015
+ try {
5016
+ JSON.parse(str);
5017
+ return true;
5018
+ } catch (e) {
5019
+ return false;
5020
+ }
4945
5021
  }
4946
5022
  };
4947
5023
 
4948
- // src/tools/common/load-memory-tool.ts
4949
- init_logger();
5024
+ // src/tools/common/file-operations-tool.ts
4950
5025
  init_base_tool();
4951
- import { Type as Type9 } from "@google/genai";
4952
- var LoadMemoryTool = class extends BaseTool {
4953
- logger = new Logger({ name: "LoadMemoryTool" });
4954
- /**
4955
- * Constructor for LoadMemoryTool
4956
- */
4957
- constructor() {
5026
+ import fs from "fs/promises";
5027
+ import path from "path";
5028
+ import { Type as Type5 } from "@google/genai";
5029
+ var FileOperationsTool = class extends BaseTool {
5030
+ basePath;
5031
+ constructor(options) {
4958
5032
  super({
4959
- name: "load_memory",
4960
- description: "Loads the memory for the current user based on a query."
5033
+ name: "file_operations",
5034
+ description: "Perform file system operations like reading, writing, and managing files"
4961
5035
  });
5036
+ this.basePath = options?.basePath || process.cwd();
4962
5037
  }
4963
5038
  /**
4964
5039
  * Get the function declaration for the tool
@@ -4968,672 +5043,601 @@ var LoadMemoryTool = class extends BaseTool {
4968
5043
  name: this.name,
4969
5044
  description: this.description,
4970
5045
  parameters: {
4971
- type: Type9.OBJECT,
5046
+ type: Type5.OBJECT,
4972
5047
  properties: {
4973
- query: {
4974
- type: Type9.STRING,
4975
- description: "The query to load memories for"
5048
+ operation: {
5049
+ type: Type5.STRING,
5050
+ description: "The file operation to perform",
5051
+ enum: [
5052
+ "read",
5053
+ "write",
5054
+ "append",
5055
+ "delete",
5056
+ "exists",
5057
+ "list",
5058
+ "mkdir"
5059
+ ]
5060
+ },
5061
+ filepath: {
5062
+ type: Type5.STRING,
5063
+ description: "Path to the file or directory (relative to the base path)"
5064
+ },
5065
+ content: {
5066
+ type: Type5.STRING,
5067
+ description: "Content to write to the file (for write and append operations)"
5068
+ },
5069
+ encoding: {
5070
+ type: Type5.STRING,
5071
+ description: "File encoding to use",
5072
+ default: "utf8"
4976
5073
  }
4977
5074
  },
4978
- required: ["query"]
5075
+ required: ["operation", "filepath"]
4979
5076
  }
4980
5077
  };
4981
5078
  }
4982
5079
  /**
4983
- * Execute the memory loading action
5080
+ * Execute the file operation
4984
5081
  */
4985
- async runAsync(args, context4) {
4986
- this.logger.debug(`Executing load_memory with query: ${args.query}`);
5082
+ async runAsync(args, _context) {
4987
5083
  try {
4988
- const searchResult = await context4.searchMemory(args.query);
4989
- return {
4990
- memories: searchResult.memories || [],
4991
- count: searchResult.memories?.length || 0
4992
- };
5084
+ const resolvedPath = this.resolvePath(args.filepath);
5085
+ this.validatePath(resolvedPath);
5086
+ const encoding = args.encoding || "utf8";
5087
+ switch (args.operation) {
5088
+ case "read":
5089
+ return await this.readFile(resolvedPath, encoding);
5090
+ case "write":
5091
+ return await this.writeFile(
5092
+ resolvedPath,
5093
+ args.content || "",
5094
+ encoding
5095
+ );
5096
+ case "append":
5097
+ return await this.appendFile(
5098
+ resolvedPath,
5099
+ args.content || "",
5100
+ encoding
5101
+ );
5102
+ case "delete":
5103
+ return await this.deleteFile(resolvedPath);
5104
+ case "exists":
5105
+ return await this.fileExists(resolvedPath);
5106
+ case "list":
5107
+ return await this.listDirectory(resolvedPath);
5108
+ case "mkdir":
5109
+ return await this.makeDirectory(resolvedPath);
5110
+ default:
5111
+ throw new Error(`Unsupported operation: ${args.operation}`);
5112
+ }
4993
5113
  } catch (error) {
4994
- console.error("Error searching memory:", error);
4995
5114
  return {
4996
- error: "Memory search failed",
4997
- message: error instanceof Error ? error.message : String(error)
5115
+ success: false,
5116
+ error: error instanceof Error ? error.message : String(error)
4998
5117
  };
4999
5118
  }
5000
5119
  }
5001
- };
5002
-
5003
- // src/tools/common/load-artifacts-tool.ts
5004
- init_base_tool();
5005
- import { Type as Type10 } from "@google/genai";
5006
- var LoadArtifactsTool = class extends BaseTool {
5007
- constructor() {
5008
- super({
5009
- name: "load_artifacts",
5010
- description: "Loads the artifacts and adds them to the session."
5011
- });
5012
- }
5013
5120
  /**
5014
- * Get the function declaration for the tool
5121
+ * Resolve a file path relative to the base path
5015
5122
  */
5016
- getDeclaration() {
5017
- return {
5018
- name: this.name,
5019
- description: this.description,
5020
- parameters: {
5021
- type: Type10.OBJECT,
5022
- properties: {
5023
- artifact_names: {
5024
- type: Type10.ARRAY,
5025
- items: {
5026
- type: Type10.STRING
5027
- },
5028
- description: "List of artifact names to load"
5029
- }
5030
- },
5031
- required: []
5032
- }
5033
- };
5123
+ resolvePath(filepath) {
5124
+ return path.isAbsolute(filepath) ? filepath : path.resolve(this.basePath, filepath);
5034
5125
  }
5035
5126
  /**
5036
- * Execute the load artifacts operation
5127
+ * Validate that a path is within the base path for security
5037
5128
  */
5038
- async runAsync(args, context4) {
5039
- const artifactNames = args.artifact_names || [];
5040
- return { artifact_names: artifactNames };
5129
+ validatePath(filepath) {
5130
+ const normalizedPath = path.normalize(filepath);
5131
+ const normalizedBasePath = path.normalize(this.basePath);
5132
+ if (!normalizedPath.startsWith(normalizedBasePath)) {
5133
+ throw new Error(
5134
+ `Access denied: Can't access paths outside the base directory`
5135
+ );
5136
+ }
5041
5137
  }
5042
5138
  /**
5043
- * Processes the outgoing LLM request for this tool.
5139
+ * Read a file
5044
5140
  */
5045
- async processLlmRequest(toolContext, llmRequest) {
5046
- await super.processLlmRequest(toolContext, llmRequest);
5047
- await this.appendArtifactsToLlmRequest(toolContext, llmRequest);
5141
+ async readFile(filepath, encoding) {
5142
+ try {
5143
+ const content = await fs.readFile(filepath, { encoding });
5144
+ return {
5145
+ success: true,
5146
+ data: content
5147
+ };
5148
+ } catch (error) {
5149
+ return {
5150
+ success: false,
5151
+ error: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`
5152
+ };
5153
+ }
5048
5154
  }
5049
5155
  /**
5050
- * Appends artifacts information to the LLM request
5156
+ * Write to a file
5051
5157
  */
5052
- async appendArtifactsToLlmRequest(toolContext, llmRequest) {
5158
+ async writeFile(filepath, content, encoding) {
5053
5159
  try {
5054
- const artifactNames = await toolContext.listArtifacts();
5055
- if (!artifactNames || artifactNames.length === 0) {
5056
- return;
5057
- }
5058
- const instructions = [
5059
- `You have a list of artifacts:
5060
- ${JSON.stringify(artifactNames)}
5061
-
5062
- When the user asks questions about any of the artifacts, you should call the
5063
- \`load_artifacts\` function to load the artifact. Do not generate any text other
5064
- than the function call.
5065
- `
5066
- ];
5067
- if (llmRequest.appendInstructions) {
5068
- llmRequest.appendInstructions(instructions);
5069
- }
5070
- if (llmRequest.contents && llmRequest.contents.length > 0) {
5071
- const lastContent = llmRequest.contents[llmRequest.contents.length - 1];
5072
- if (lastContent.parts && lastContent.parts.length > 0) {
5073
- const firstPart = lastContent.parts[0];
5074
- const functionResponse = this.extractFunctionResponse(firstPart);
5075
- if (functionResponse && functionResponse.name === "load_artifacts") {
5076
- const requestedArtifactNames = functionResponse.response.artifact_names || [];
5077
- for (const artifactName of requestedArtifactNames) {
5078
- try {
5079
- const artifact = await toolContext.loadArtifact(artifactName);
5080
- if (artifact) {
5081
- llmRequest.contents.push({
5082
- role: "user",
5083
- parts: [
5084
- {
5085
- text: `Artifact ${artifactName} is:`
5086
- },
5087
- artifact
5088
- ]
5089
- });
5090
- }
5091
- } catch (error) {
5092
- console.error(
5093
- `Failed to load artifact ${artifactName}:`,
5094
- error
5095
- );
5096
- }
5097
- }
5098
- }
5099
- }
5100
- }
5160
+ const dir = path.dirname(filepath);
5161
+ await fs.mkdir(dir, { recursive: true });
5162
+ await fs.writeFile(filepath, content, { encoding });
5163
+ return {
5164
+ success: true
5165
+ };
5101
5166
  } catch (error) {
5102
- console.error("Error appending artifacts to LLM request:", error);
5167
+ return {
5168
+ success: false,
5169
+ error: `Failed to write to file: ${error instanceof Error ? error.message : String(error)}`
5170
+ };
5103
5171
  }
5104
5172
  }
5105
5173
  /**
5106
- * Extracts function response from a part if it exists
5174
+ * Append to a file
5107
5175
  */
5108
- extractFunctionResponse(part) {
5109
- if ("functionResponse" in part && part.functionResponse) {
5110
- return part.functionResponse;
5176
+ async appendFile(filepath, content, encoding) {
5177
+ try {
5178
+ const dir = path.dirname(filepath);
5179
+ await fs.mkdir(dir, { recursive: true });
5180
+ await fs.appendFile(filepath, content, { encoding });
5181
+ return {
5182
+ success: true
5183
+ };
5184
+ } catch (error) {
5185
+ return {
5186
+ success: false,
5187
+ error: `Failed to append to file: ${error instanceof Error ? error.message : String(error)}`
5188
+ };
5111
5189
  }
5112
- return null;
5113
5190
  }
5114
- };
5115
-
5116
- // src/tools/mcp/client.ts
5117
- init_logger();
5118
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
5119
- import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
5120
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
5121
- import { CreateMessageRequestSchema as CreateMessageRequestSchema2 } from "@modelcontextprotocol/sdk/types.js";
5122
-
5123
- // src/tools/mcp/sampling-handler.ts
5124
- init_logger();
5125
- import {
5126
- CreateMessageRequestSchema,
5127
- CreateMessageResultSchema
5128
- } from "@modelcontextprotocol/sdk/types.js";
5129
-
5130
- // src/tools/mcp/types.ts
5131
- var McpErrorType = /* @__PURE__ */ ((McpErrorType2) => {
5132
- McpErrorType2["CONNECTION_ERROR"] = "connection_error";
5133
- McpErrorType2["TOOL_EXECUTION_ERROR"] = "tool_execution_error";
5134
- McpErrorType2["RESOURCE_CLOSED_ERROR"] = "resource_closed_error";
5135
- McpErrorType2["TIMEOUT_ERROR"] = "timeout_error";
5136
- McpErrorType2["INVALID_SCHEMA_ERROR"] = "invalid_schema_error";
5137
- McpErrorType2["SAMPLING_ERROR"] = "SAMPLING_ERROR";
5138
- McpErrorType2["INVALID_REQUEST_ERROR"] = "INVALID_REQUEST_ERROR";
5139
- return McpErrorType2;
5140
- })(McpErrorType || {});
5141
- var McpError = class extends Error {
5142
- type;
5143
- originalError;
5144
- constructor(message, type, originalError) {
5145
- super(message);
5146
- this.name = "McpError";
5147
- this.type = type;
5148
- this.originalError = originalError;
5191
+ /**
5192
+ * Delete a file
5193
+ */
5194
+ async deleteFile(filepath) {
5195
+ try {
5196
+ await fs.unlink(filepath);
5197
+ return {
5198
+ success: true
5199
+ };
5200
+ } catch (error) {
5201
+ return {
5202
+ success: false,
5203
+ error: `Failed to delete file: ${error instanceof Error ? error.message : String(error)}`
5204
+ };
5205
+ }
5149
5206
  }
5150
- };
5151
-
5152
- // src/tools/mcp/sampling-handler.ts
5153
- var McpSamplingHandler = class {
5154
- logger = new Logger({ name: "McpSamplingHandler" });
5155
- samplingHandler;
5156
- constructor(samplingHandler) {
5157
- this.samplingHandler = samplingHandler;
5207
+ /**
5208
+ * Check if a file exists
5209
+ */
5210
+ async fileExists(filepath) {
5211
+ try {
5212
+ await fs.access(filepath);
5213
+ return {
5214
+ success: true,
5215
+ data: true
5216
+ };
5217
+ } catch {
5218
+ return {
5219
+ success: true,
5220
+ data: false
5221
+ };
5222
+ }
5158
5223
  }
5159
5224
  /**
5160
- * Handle MCP sampling request and convert between formats
5225
+ * List directory contents
5161
5226
  */
5162
- async handleSamplingRequest(request) {
5227
+ async listDirectory(dirpath) {
5163
5228
  try {
5164
- if (request.method !== "sampling/createMessage") {
5165
- this.logger.error(
5166
- `Invalid method for sampling handler: ${request.method}. Expected: sampling/createMessage`
5167
- );
5168
- throw new McpError(
5169
- `Invalid method: ${request.method}. This handler only processes sampling/createMessage requests.`,
5170
- "INVALID_REQUEST_ERROR" /* INVALID_REQUEST_ERROR */
5171
- );
5172
- }
5173
- const validationResult = CreateMessageRequestSchema.safeParse(request);
5174
- if (!validationResult.success) {
5175
- this.logger.error(
5176
- "Invalid MCP sampling request:",
5177
- validationResult.error
5178
- );
5179
- throw new McpError(
5180
- `Invalid sampling request: ${validationResult.error.message}`,
5181
- "INVALID_REQUEST_ERROR" /* INVALID_REQUEST_ERROR */
5182
- );
5183
- }
5184
- const mcpParams = request.params;
5185
- if (!mcpParams.messages || !Array.isArray(mcpParams.messages)) {
5186
- throw new McpError(
5187
- "Invalid sampling request: messages array is required",
5188
- "INVALID_REQUEST_ERROR" /* INVALID_REQUEST_ERROR */
5189
- );
5190
- }
5191
- if (!mcpParams.maxTokens || mcpParams.maxTokens <= 0) {
5192
- throw new McpError(
5193
- "Invalid sampling request: maxTokens must be a positive number",
5194
- "INVALID_REQUEST_ERROR" /* INVALID_REQUEST_ERROR */
5195
- );
5196
- }
5197
- this.logger.debug("Converting MCP request to ADK format");
5198
- const adkContents = this.convertMcpMessagesToADK(
5199
- mcpParams.messages,
5200
- mcpParams.systemPrompt
5201
- );
5202
- const requestModel = mcpParams.model || "gemini-2.0-flash";
5203
- const adkRequest = new LlmRequest({
5204
- model: requestModel,
5205
- contents: adkContents,
5206
- config: {
5207
- temperature: mcpParams.temperature,
5208
- maxOutputTokens: mcpParams.maxTokens
5209
- }
5210
- });
5211
- this.logger.debug("Calling ADK sampling handler");
5212
- const adkResponse = await this.samplingHandler(adkRequest);
5213
- this.logger.debug("Converting ADK response to MCP format");
5214
- const mcpResponse = this.convertADKResponseToMcp(
5215
- adkResponse,
5216
- requestModel
5229
+ const entries = await fs.readdir(dirpath, { withFileTypes: true });
5230
+ const results = await Promise.all(
5231
+ entries.map(async (entry) => {
5232
+ const entryPath = path.join(dirpath, entry.name);
5233
+ const stats = await fs.stat(entryPath);
5234
+ return {
5235
+ name: entry.name,
5236
+ path: entryPath,
5237
+ isFile: entry.isFile(),
5238
+ isDirectory: entry.isDirectory(),
5239
+ size: stats.size,
5240
+ created: stats.birthtime,
5241
+ modified: stats.mtime
5242
+ };
5243
+ })
5217
5244
  );
5218
- const responseValidation = CreateMessageResultSchema.safeParse(mcpResponse);
5219
- if (!responseValidation.success) {
5220
- this.logger.error(
5221
- "Invalid MCP response generated:",
5222
- responseValidation.error
5223
- );
5224
- throw new McpError(
5225
- `Invalid response generated: ${responseValidation.error.message}`,
5226
- "SAMPLING_ERROR" /* SAMPLING_ERROR */
5227
- );
5228
- }
5229
- return mcpResponse;
5245
+ return {
5246
+ success: true,
5247
+ data: results
5248
+ };
5230
5249
  } catch (error) {
5231
- this.logger.error("Error handling sampling request:", error);
5232
- if (error instanceof McpError) {
5233
- throw error;
5234
- }
5235
- throw new McpError(
5236
- `Sampling request failed: ${error instanceof Error ? error.message : String(error)}`,
5237
- "SAMPLING_ERROR" /* SAMPLING_ERROR */,
5238
- error instanceof Error ? error : void 0
5239
- );
5250
+ return {
5251
+ success: false,
5252
+ error: `Failed to list directory: ${error instanceof Error ? error.message : String(error)}`
5253
+ };
5240
5254
  }
5241
5255
  }
5242
5256
  /**
5243
- * Convert MCP messages to ADK Content format
5257
+ * Create a directory
5244
5258
  */
5245
- convertMcpMessagesToADK(mcpMessages, systemPrompt) {
5246
- const contents = [];
5247
- if (systemPrompt) {
5248
- contents.push({
5249
- role: "user",
5250
- // System messages are typically sent as user role in content
5251
- parts: [{ text: systemPrompt }]
5252
- });
5259
+ async makeDirectory(dirpath) {
5260
+ try {
5261
+ await fs.mkdir(dirpath, { recursive: true });
5262
+ return {
5263
+ success: true
5264
+ };
5265
+ } catch (error) {
5266
+ return {
5267
+ success: false,
5268
+ error: `Failed to create directory: ${error instanceof Error ? error.message : String(error)}`
5269
+ };
5253
5270
  }
5254
- const transformedMessages = mcpMessages.map(
5255
- (mcpMessage) => this.convertSingleMcpMessageToADK(mcpMessage)
5256
- );
5257
- contents.push(...transformedMessages);
5258
- return contents;
5271
+ }
5272
+ };
5273
+
5274
+ // src/tools/common/user-interaction-tool.ts
5275
+ init_base_tool();
5276
+ import { Type as Type6 } from "@google/genai";
5277
+ var UserInteractionTool = class extends BaseTool {
5278
+ constructor() {
5279
+ super({
5280
+ name: "user_interaction",
5281
+ description: "Prompt the user for input during agent execution",
5282
+ isLongRunning: true
5283
+ });
5259
5284
  }
5260
5285
  /**
5261
- * Convert a single MCP message to ADK Content format
5286
+ * Get the function declaration for the tool
5262
5287
  */
5263
- convertSingleMcpMessageToADK(mcpMessage) {
5264
- const adkRole = mcpMessage.role === "assistant" ? "model" : "user";
5265
- const adkParts = this.convertMcpContentToADKParts(mcpMessage.content);
5266
- const adkContent = {
5267
- role: adkRole,
5268
- parts: adkParts
5288
+ getDeclaration() {
5289
+ return {
5290
+ name: this.name,
5291
+ description: this.description,
5292
+ parameters: {
5293
+ type: Type6.OBJECT,
5294
+ properties: {
5295
+ prompt: {
5296
+ type: Type6.STRING,
5297
+ description: "The prompt message to display to the user"
5298
+ },
5299
+ options: {
5300
+ type: Type6.ARRAY,
5301
+ description: "Optional array of choices to present to the user",
5302
+ items: {
5303
+ type: Type6.STRING
5304
+ }
5305
+ },
5306
+ defaultValue: {
5307
+ type: Type6.STRING,
5308
+ description: "Optional default value for the input field"
5309
+ }
5310
+ },
5311
+ required: ["prompt"]
5312
+ }
5269
5313
  };
5270
- this.logger.debug(
5271
- `Converted MCP message - role: ${mcpMessage.role} -> ${adkRole}, content type: ${mcpMessage.content.type}`
5272
- );
5273
- return adkContent;
5274
5314
  }
5275
5315
  /**
5276
- * Convert MCP message content to ADK parts format
5316
+ * Execute the user interaction
5277
5317
  */
5278
- convertMcpContentToADKParts(mcpContent) {
5279
- if (mcpContent.type === "text") {
5280
- const textContent = mcpContent.text || "";
5281
- return [{ text: textContent }];
5282
- }
5283
- if (mcpContent.type === "image") {
5284
- const parts = [];
5285
- if (mcpContent.text && typeof mcpContent.text === "string") {
5286
- parts.push({ text: mcpContent.text });
5318
+ async runAsync(args, context4) {
5319
+ try {
5320
+ const actions = context4.actions;
5321
+ if (!actions || !actions.promptUser) {
5322
+ return {
5323
+ success: false,
5324
+ error: "User interaction is not supported in the current environment"
5325
+ };
5287
5326
  }
5288
- if (mcpContent.data && typeof mcpContent.data === "string") {
5289
- const mimeType = mcpContent.mimeType || "image/jpeg";
5290
- parts.push({
5291
- inlineData: {
5292
- data: mcpContent.data,
5293
- mimeType
5294
- }
5295
- });
5327
+ if (actions.skipSummarization) {
5328
+ actions.skipSummarization(true);
5296
5329
  }
5297
- return parts.length > 0 ? parts : [{ text: "" }];
5330
+ const promptOptions = args.options && args.options.length > 0 ? {
5331
+ choices: args.options
5332
+ } : void 0;
5333
+ const response = await actions.promptUser({
5334
+ prompt: args.prompt,
5335
+ defaultValue: args.defaultValue,
5336
+ options: promptOptions
5337
+ });
5338
+ return {
5339
+ success: true,
5340
+ userInput: response
5341
+ };
5342
+ } catch (error) {
5343
+ return {
5344
+ success: false,
5345
+ error: error instanceof Error ? error.message : String(error)
5346
+ };
5298
5347
  }
5299
- this.logger.warn(`Unknown MCP content type: ${mcpContent.type}`);
5300
- const fallbackText = typeof mcpContent.data === "string" ? mcpContent.data : "";
5301
- return [{ text: fallbackText }];
5302
5348
  }
5349
+ };
5350
+
5351
+ // src/tools/common/exit-loop-tool.ts
5352
+ init_logger();
5353
+ init_base_tool();
5354
+ var ExitLoopTool = class extends BaseTool {
5355
+ logger = new Logger({ name: "ExitLoopTool" });
5356
+ /**
5357
+ * Constructor for ExitLoopTool
5358
+ */
5359
+ constructor() {
5360
+ super({
5361
+ name: "exit_loop",
5362
+ description: "Exits the loop. Call this function only when you are instructed to do so."
5363
+ });
5364
+ }
5365
+ /**
5366
+ * Execute the exit loop action
5367
+ */
5368
+ async runAsync(_args, context4) {
5369
+ this.logger.debug("Executing exit loop tool");
5370
+ context4.actions.escalate = true;
5371
+ }
5372
+ };
5373
+
5374
+ // src/tools/common/get-user-choice-tool.ts
5375
+ init_logger();
5376
+ init_base_tool();
5377
+ import { Type as Type7 } from "@google/genai";
5378
+ var GetUserChoiceTool = class extends BaseTool {
5379
+ logger = new Logger({ name: "GetUserChoiceTool" });
5303
5380
  /**
5304
- * Convert ADK response to MCP response format
5381
+ * Constructor for GetUserChoiceTool
5305
5382
  */
5306
- convertADKResponseToMcp(adkResponse, model) {
5307
- let responseText = "";
5308
- if (typeof adkResponse === "string") {
5309
- responseText = adkResponse;
5310
- } else {
5311
- if (adkResponse.content) {
5312
- if (typeof adkResponse.content === "string") {
5313
- responseText = adkResponse.content;
5314
- } else if (adkResponse.content.parts) {
5315
- responseText = adkResponse.content.parts.map((part) => {
5316
- return typeof part.text === "string" ? part.text : "";
5317
- }).join("");
5318
- }
5319
- }
5320
- }
5321
- const mcpResponse = {
5322
- model,
5323
- // Use the model from the request
5324
- role: "assistant",
5325
- // ADK responses are always from assistant
5326
- content: {
5327
- type: "text",
5328
- text: responseText
5383
+ constructor() {
5384
+ super({
5385
+ name: "get_user_choice",
5386
+ description: "This tool provides the options to the user and asks them to choose one. Use this tool when you need the user to make a selection between multiple options. Do not list options in your response - use this tool instead.",
5387
+ isLongRunning: true
5388
+ });
5389
+ }
5390
+ /**
5391
+ * Get the function declaration for the tool
5392
+ */
5393
+ getDeclaration() {
5394
+ return {
5395
+ name: this.name,
5396
+ description: this.description,
5397
+ parameters: {
5398
+ type: Type7.OBJECT,
5399
+ properties: {
5400
+ options: {
5401
+ type: Type7.ARRAY,
5402
+ description: "List of options for the user to choose from",
5403
+ items: {
5404
+ type: Type7.STRING
5405
+ }
5406
+ },
5407
+ question: {
5408
+ type: Type7.STRING,
5409
+ description: "The question or prompt to show the user before presenting options"
5410
+ }
5411
+ },
5412
+ required: ["options"]
5329
5413
  }
5330
5414
  };
5331
- this.logger.debug(`Received content: ${responseText}`);
5332
- return mcpResponse;
5333
5415
  }
5334
5416
  /**
5335
- * Update the ADK handler
5417
+ * Execute the user choice action
5418
+ * This is a long running operation that will return null initially
5419
+ * and the actual choice will be provided asynchronously
5336
5420
  */
5337
- updateHandler(handler) {
5338
- this.samplingHandler = handler;
5339
- this.logger.debug("ADK sampling handler updated");
5421
+ async runAsync(args, context4) {
5422
+ this.logger.debug(
5423
+ `Executing get_user_choice with options: ${args.options.join(", ")}`
5424
+ );
5425
+ if (args.question) {
5426
+ this.logger.debug(`Question: ${args.question}`);
5427
+ }
5428
+ context4.actions.skipSummarization = true;
5429
+ return null;
5340
5430
  }
5341
5431
  };
5342
- function createSamplingHandler(handler) {
5343
- return handler;
5344
- }
5345
-
5346
- // src/tools/mcp/utils.ts
5347
- function withRetry(fn, instance, reinitMethod, maxRetries = 1) {
5348
- return async (...args) => {
5349
- let attempt = 0;
5350
- while (attempt <= maxRetries) {
5351
- try {
5352
- return await fn.apply(instance, args);
5353
- } catch (error) {
5354
- const isClosedResourceError = error instanceof Error && (error.message.includes("closed") || error.message.includes("ECONNRESET") || error.message.includes("socket hang up"));
5355
- if (!isClosedResourceError || attempt >= maxRetries) {
5356
- throw error;
5357
- }
5358
- console.warn(
5359
- `Resource closed, reinitializing (attempt ${attempt + 1}/${maxRetries + 1})...`
5360
- );
5361
- try {
5362
- await reinitMethod(instance);
5363
- } catch (reinitError) {
5364
- console.error("Error reinitializing resources:", reinitError);
5365
- throw new Error(`Failed to reinitialize resources: ${reinitError}`);
5366
- }
5367
- attempt++;
5368
- }
5369
- }
5370
- throw new Error("Unexpected end of retry loop");
5371
- };
5372
- }
5373
5432
 
5374
- // src/tools/mcp/client.ts
5375
- var McpClientService = class {
5376
- config;
5377
- client = null;
5378
- transport = null;
5379
- isClosing = false;
5380
- mcpSamplingHandler = null;
5381
- logger = new Logger({ name: "McpClientService" });
5382
- constructor(config) {
5383
- this.config = config;
5384
- if (config.samplingHandler) {
5385
- this.mcpSamplingHandler = new McpSamplingHandler(config.samplingHandler);
5386
- }
5387
- }
5433
+ // src/tools/common/transfer-to-agent-tool.ts
5434
+ init_logger();
5435
+ init_base_tool();
5436
+ import { Type as Type8 } from "@google/genai";
5437
+ var TransferToAgentTool = class extends BaseTool {
5438
+ logger = new Logger({ name: "TransferToAgentTool" });
5388
5439
  /**
5389
- * Initializes and returns an MCP client based on configuration.
5390
- * Will create a new client if one doesn't exist yet.
5440
+ * Constructor for TransferToAgentTool
5391
5441
  */
5392
- async initialize() {
5393
- if (this.isClosing) {
5394
- throw new McpError(
5395
- "Cannot initialize a client that is being closed",
5396
- "resource_closed_error" /* RESOURCE_CLOSED_ERROR */
5397
- );
5398
- }
5399
- if (this.client) {
5400
- return this.client;
5401
- }
5402
- try {
5403
- if (!this.transport) {
5404
- this.transport = await this.createTransport();
5405
- }
5406
- const client = new Client(
5407
- {
5408
- name: this.config.name,
5409
- version: "0.0.1"
5410
- },
5411
- {
5412
- capabilities: {
5413
- prompts: {},
5414
- resources: {},
5415
- tools: {},
5416
- sampling: {}
5417
- // Enable sampling capability
5418
- }
5419
- }
5420
- );
5421
- const connectPromise = client.connect(this.transport);
5422
- if (this.config.timeout) {
5423
- const timeoutPromise = new Promise((_, reject) => {
5424
- setTimeout(() => {
5425
- reject(
5426
- new McpError(
5427
- `MCP client connection timed out after ${this.config.timeout}ms`,
5428
- "timeout_error" /* TIMEOUT_ERROR */
5429
- )
5430
- );
5431
- }, this.config.timeout);
5432
- });
5433
- await Promise.race([connectPromise, timeoutPromise]);
5434
- } else {
5435
- await connectPromise;
5436
- }
5437
- await this.setupSamplingHandler(client);
5438
- this.logger.debug("\u2705 MCP client connected successfully");
5439
- this.client = client;
5440
- return client;
5441
- } catch (error) {
5442
- await this.cleanupResources();
5443
- if (!(error instanceof McpError)) {
5444
- this.logger.error("Failed to initialize MCP client:", error);
5445
- throw new McpError(
5446
- `Failed to initialize MCP client: ${error instanceof Error ? error.message : String(error)}`,
5447
- "connection_error" /* CONNECTION_ERROR */,
5448
- error instanceof Error ? error : void 0
5449
- );
5450
- }
5451
- throw error;
5452
- }
5442
+ constructor() {
5443
+ super({
5444
+ name: "transfer_to_agent",
5445
+ description: "Transfer the question to another agent when it's more suitable to answer the user's question according to the agent's description. Use this function when you determine that another agent in the system would be better equipped to handle the user's request based on their specialized capabilities and expertise areas."
5446
+ });
5453
5447
  }
5454
5448
  /**
5455
- * Creates a transport based on the configuration.
5449
+ * Get the function declaration for the tool
5456
5450
  */
5457
- async createTransport() {
5458
- try {
5459
- if (this.config.transport.mode === "sse") {
5460
- this.logger.debug(
5461
- "\u{1F680} Initializing MCP client in SSE mode",
5462
- this.config.transport.serverUrl
5463
- );
5464
- const headers = {
5465
- ...this.config.transport.headers || {},
5466
- ...this.config.headers || {}
5467
- };
5468
- return new SSEClientTransport(
5469
- new URL(this.config.transport.serverUrl),
5470
- {
5471
- requestInit: {
5472
- headers,
5473
- ...this.config.timeout ? { timeout: this.config.timeout } : {}
5474
- }
5451
+ getDeclaration() {
5452
+ return {
5453
+ name: this.name,
5454
+ description: this.description,
5455
+ parameters: {
5456
+ type: Type8.OBJECT,
5457
+ properties: {
5458
+ agent_name: {
5459
+ type: Type8.STRING,
5460
+ description: "The name of the agent to transfer control to"
5475
5461
  }
5476
- );
5462
+ },
5463
+ required: ["agent_name"]
5477
5464
  }
5478
- this.logger.debug(
5479
- "\u{1F680} Initializing MCP client in STDIO mode",
5480
- this.config.transport.command
5481
- );
5482
- return new StdioClientTransport({
5483
- command: this.config.transport.command,
5484
- args: this.config.transport.args,
5485
- env: this.config.transport.env
5486
- });
5487
- } catch (error) {
5488
- throw new McpError(
5489
- `Failed to create transport: ${error instanceof Error ? error.message : String(error)}`,
5490
- "connection_error" /* CONNECTION_ERROR */,
5491
- error instanceof Error ? error : void 0
5492
- );
5493
- }
5465
+ };
5466
+ }
5467
+ /**
5468
+ * Execute the transfer to agent action
5469
+ */
5470
+ async runAsync(args, context4) {
5471
+ this.logger.debug(`Executing transfer to agent: ${args.agent_name}`);
5472
+ context4.actions.transferToAgent = args.agent_name;
5494
5473
  }
5474
+ };
5475
+
5476
+ // src/tools/common/load-memory-tool.ts
5477
+ init_logger();
5478
+ init_base_tool();
5479
+ import { Type as Type9 } from "@google/genai";
5480
+ var LoadMemoryTool = class extends BaseTool {
5481
+ logger = new Logger({ name: "LoadMemoryTool" });
5495
5482
  /**
5496
- * Re-initializes the MCP client when a session is closed.
5497
- * Used by the retry mechanism.
5483
+ * Constructor for LoadMemoryTool
5498
5484
  */
5499
- async reinitialize() {
5500
- this.logger.debug("\u{1F504} Reinitializing MCP client after closed connection");
5501
- await this.cleanupResources();
5502
- this.client = null;
5503
- this.transport = null;
5504
- await this.initialize();
5485
+ constructor() {
5486
+ super({
5487
+ name: "load_memory",
5488
+ description: "Loads the memory for the current user based on a query."
5489
+ });
5505
5490
  }
5506
5491
  /**
5507
- * Cleans up resources associated with this client service.
5508
- * Similar to Python's AsyncExitStack.aclose() functionality.
5492
+ * Get the function declaration for the tool
5509
5493
  */
5510
- async cleanupResources() {
5511
- try {
5512
- this.isClosing = true;
5513
- if (this.client) {
5514
- try {
5515
- if (typeof this.client.close === "function") {
5516
- await this.client.close();
5494
+ getDeclaration() {
5495
+ return {
5496
+ name: this.name,
5497
+ description: this.description,
5498
+ parameters: {
5499
+ type: Type9.OBJECT,
5500
+ properties: {
5501
+ query: {
5502
+ type: Type9.STRING,
5503
+ description: "The query to load memories for"
5517
5504
  }
5518
- } catch (err) {
5519
- }
5520
- }
5521
- if (this.transport && typeof this.transport.close === "function") {
5522
- await this.transport.close();
5505
+ },
5506
+ required: ["query"]
5523
5507
  }
5524
- this.logger.debug("\u{1F9F9} Cleaned up MCP client resources");
5508
+ };
5509
+ }
5510
+ /**
5511
+ * Execute the memory loading action
5512
+ */
5513
+ async runAsync(args, context4) {
5514
+ this.logger.debug(`Executing load_memory with query: ${args.query}`);
5515
+ try {
5516
+ const searchResult = await context4.searchMemory(args.query);
5517
+ return {
5518
+ memories: searchResult.memories || [],
5519
+ count: searchResult.memories?.length || 0
5520
+ };
5525
5521
  } catch (error) {
5526
- this.logger.error("Error cleaning up MCP resources:", error);
5527
- } finally {
5528
- this.client = null;
5529
- this.transport = null;
5530
- this.isClosing = false;
5522
+ console.error("Error searching memory:", error);
5523
+ return {
5524
+ error: "Memory search failed",
5525
+ message: error instanceof Error ? error.message : String(error)
5526
+ };
5531
5527
  }
5532
5528
  }
5529
+ };
5530
+
5531
+ // src/tools/common/load-artifacts-tool.ts
5532
+ init_base_tool();
5533
+ import { Type as Type10 } from "@google/genai";
5534
+ var LoadArtifactsTool = class extends BaseTool {
5535
+ constructor() {
5536
+ super({
5537
+ name: "load_artifacts",
5538
+ description: "Loads the artifacts and adds them to the session."
5539
+ });
5540
+ }
5533
5541
  /**
5534
- * Call an MCP tool with retry capability if the session is closed.
5542
+ * Get the function declaration for the tool
5535
5543
  */
5536
- async callTool(name, args) {
5537
- try {
5538
- const wrappedCall = withRetry(
5539
- async function() {
5540
- const client = await this.initialize();
5541
- return client.callTool({
5542
- name,
5543
- arguments: args
5544
- });
5544
+ getDeclaration() {
5545
+ return {
5546
+ name: this.name,
5547
+ description: this.description,
5548
+ parameters: {
5549
+ type: Type10.OBJECT,
5550
+ properties: {
5551
+ artifact_names: {
5552
+ type: Type10.ARRAY,
5553
+ items: {
5554
+ type: Type10.STRING
5555
+ },
5556
+ description: "List of artifact names to load"
5557
+ }
5545
5558
  },
5546
- this,
5547
- async (instance) => await instance.reinitialize(),
5548
- this.config.retryOptions?.maxRetries || 2
5549
- );
5550
- return await wrappedCall();
5551
- } catch (error) {
5552
- if (!(error instanceof McpError)) {
5553
- throw new McpError(
5554
- `Error calling tool "${name}": ${error instanceof Error ? error.message : String(error)}`,
5555
- "tool_execution_error" /* TOOL_EXECUTION_ERROR */,
5556
- error instanceof Error ? error : void 0
5557
- );
5559
+ required: []
5558
5560
  }
5559
- throw error;
5560
- }
5561
+ };
5561
5562
  }
5562
5563
  /**
5563
- * Closes and cleans up all resources.
5564
- * Should be called when the service is no longer needed.
5565
- * Similar to Python's close() method.
5564
+ * Execute the load artifacts operation
5566
5565
  */
5567
- async close() {
5568
- this.logger.debug("\u{1F51A} Closing MCP client service");
5569
- await this.cleanupResources();
5566
+ async runAsync(args, context4) {
5567
+ const artifactNames = args.artifact_names || [];
5568
+ return { artifact_names: artifactNames };
5570
5569
  }
5571
5570
  /**
5572
- * Checks if the client is currently connected
5571
+ * Processes the outgoing LLM request for this tool.
5573
5572
  */
5574
- isConnected() {
5575
- return !!this.client && !this.isClosing;
5573
+ async processLlmRequest(toolContext, llmRequest) {
5574
+ await super.processLlmRequest(toolContext, llmRequest);
5575
+ await this.appendArtifactsToLlmRequest(toolContext, llmRequest);
5576
5576
  }
5577
- async setupSamplingHandler(client) {
5578
- if (!this.mcpSamplingHandler) {
5579
- this.logger.debug(
5580
- "\u26A0\uFE0F No sampling handler provided - sampling requests will be rejected"
5581
- );
5582
- return;
5583
- }
5577
+ /**
5578
+ * Appends artifacts information to the LLM request
5579
+ */
5580
+ async appendArtifactsToLlmRequest(toolContext, llmRequest) {
5584
5581
  try {
5585
- client.setRequestHandler(
5586
- CreateMessageRequestSchema2,
5587
- async (request) => {
5588
- try {
5589
- this.logger.debug("Received sampling request:", request);
5590
- const response = await this.mcpSamplingHandler.handleSamplingRequest(request);
5591
- this.logger.debug("\u2705 Sampling request completed successfully");
5592
- return response;
5593
- } catch (error) {
5594
- this.logger.error("\u274C Error handling sampling request:", error);
5595
- if (error instanceof McpError) {
5596
- throw error;
5582
+ const artifactNames = await toolContext.listArtifacts();
5583
+ if (!artifactNames || artifactNames.length === 0) {
5584
+ return;
5585
+ }
5586
+ const instructions = [
5587
+ `You have a list of artifacts:
5588
+ ${JSON.stringify(artifactNames)}
5589
+
5590
+ When the user asks questions about any of the artifacts, you should call the
5591
+ \`load_artifacts\` function to load the artifact. Do not generate any text other
5592
+ than the function call.
5593
+ `
5594
+ ];
5595
+ if (llmRequest.appendInstructions) {
5596
+ llmRequest.appendInstructions(instructions);
5597
+ }
5598
+ if (llmRequest.contents && llmRequest.contents.length > 0) {
5599
+ const lastContent = llmRequest.contents[llmRequest.contents.length - 1];
5600
+ if (lastContent.parts && lastContent.parts.length > 0) {
5601
+ const firstPart = lastContent.parts[0];
5602
+ const functionResponse = this.extractFunctionResponse(firstPart);
5603
+ if (functionResponse && functionResponse.name === "load_artifacts") {
5604
+ const requestedArtifactNames = functionResponse.response.artifact_names || [];
5605
+ for (const artifactName of requestedArtifactNames) {
5606
+ try {
5607
+ const artifact = await toolContext.loadArtifact(artifactName);
5608
+ if (artifact) {
5609
+ llmRequest.contents.push({
5610
+ role: "user",
5611
+ parts: [
5612
+ {
5613
+ text: `Artifact ${artifactName} is:`
5614
+ },
5615
+ artifact
5616
+ ]
5617
+ });
5618
+ }
5619
+ } catch (error) {
5620
+ console.error(
5621
+ `Failed to load artifact ${artifactName}:`,
5622
+ error
5623
+ );
5624
+ }
5597
5625
  }
5598
- throw new McpError(
5599
- `Sampling request failed: ${error instanceof Error ? error.message : String(error)}`,
5600
- "SAMPLING_ERROR" /* SAMPLING_ERROR */,
5601
- error instanceof Error ? error : void 0
5602
- );
5603
5626
  }
5604
5627
  }
5605
- );
5606
- this.logger.debug("\u{1F3AF} Sampling handler registered successfully");
5628
+ }
5607
5629
  } catch (error) {
5608
- this.logger.error("Failed to setup sampling handler:", error);
5609
- this.logger.debug(
5610
- "\u26A0\uFE0F Sampling handler registration failed, continuing without sampling support"
5611
- );
5612
- }
5613
- }
5614
- /**
5615
- * Set a new ADK sampling handler
5616
- */
5617
- setSamplingHandler(handler) {
5618
- this.mcpSamplingHandler = new McpSamplingHandler(handler);
5619
- if (this.client) {
5620
- this.setupSamplingHandler(this.client).catch((error) => {
5621
- this.logger.error("Failed to update ADK sampling handler:", error);
5622
- });
5630
+ console.error("Error appending artifacts to LLM request:", error);
5623
5631
  }
5624
5632
  }
5625
5633
  /**
5626
- * Remove the sampling handler
5634
+ * Extracts function response from a part if it exists
5627
5635
  */
5628
- removeSamplingHandler() {
5629
- this.mcpSamplingHandler = null;
5630
- if (this.client) {
5631
- try {
5632
- this.client.removeRequestHandler?.("sampling/createMessage");
5633
- } catch (error) {
5634
- this.logger.error("Failed to remove sampling handler:", error);
5635
- }
5636
+ extractFunctionResponse(part) {
5637
+ if ("functionResponse" in part && part.functionResponse) {
5638
+ return part.functionResponse;
5636
5639
  }
5640
+ return null;
5637
5641
  }
5638
5642
  };
5639
5643
 
@@ -6028,6 +6032,14 @@ function McpCoinGecko(config = {}) {
6028
6032
  );
6029
6033
  return new McpToolset(mcpConfig);
6030
6034
  }
6035
+ function McpUpbit(config = {}) {
6036
+ const mcpConfig = createMcpConfig(
6037
+ "Upbit MCP Client",
6038
+ "@iqai/mcp-upbit",
6039
+ config
6040
+ );
6041
+ return new McpToolset(mcpConfig);
6042
+ }
6031
6043
  function McpFilesystem(config = {}) {
6032
6044
  const mcpConfig = createMcpConfig(
6033
6045
  "Filesystem MCP Client",
@@ -13221,6 +13233,7 @@ export {
13221
13233
  McpAbi,
13222
13234
  McpAtp,
13223
13235
  McpBamm,
13236
+ McpClientService,
13224
13237
  McpCoinGecko,
13225
13238
  McpDiscord,
13226
13239
  McpError,
@@ -13236,6 +13249,7 @@ export {
13236
13249
  McpSamplingHandler,
13237
13250
  McpTelegram,
13238
13251
  McpToolset,
13252
+ McpUpbit,
13239
13253
  memory_exports as Memory,
13240
13254
  models_exports as Models,
13241
13255
  OAuth2Credential,