@iqai/adk 0.2.1 → 0.2.3

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