/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import * as path from 'node:path'; import { getFolderStructure } from '../utils/getFolderStructure.js'; import type { MessageBus } from '../confirmation-bus/message-bus.js'; import { BaseDeclarativeTool, BaseToolInvocation, Kind, type ToolResult, type ToolCallConfirmationDetails, type ToolInvocation, type ToolConfirmationOutcome, } from './tools.js'; import type { Config } from '../config/config.js'; import { ACTIVATE_SKILL_TOOL_NAME } from './tool-names.js'; import { ToolErrorType } from './tool-error.js'; import { getActivateSkillDefinition } from './definitions/coreTools.js'; import { resolveToolDeclaration } from './definitions/resolver.js'; /** * Parameters for the ActivateSkill tool */ export interface ActivateSkillToolParams { /** * The name of the skill to activate */ name: string; } class ActivateSkillToolInvocation extends BaseToolInvocation< ActivateSkillToolParams, ToolResult > { private cachedFolderStructure: string | undefined; constructor( private config: Config, params: ActivateSkillToolParams, messageBus: MessageBus, _toolName?: string, _toolDisplayName?: string, ) { super(params, messageBus, _toolName, _toolDisplayName); } getDescription(): string { const skillName = this.params.name; const skill = this.config.getSkillManager().getSkill(skillName); if (skill) { return `"${skillName}": ${skill.description}`; } return `"${skillName}" (?) unknown skill`; } private async getOrFetchFolderStructure( skillLocation: string, ): Promise { if (this.cachedFolderStructure === undefined) { this.cachedFolderStructure = await getFolderStructure( path.dirname(skillLocation), ); } return this.cachedFolderStructure; } protected override async getConfirmationDetails( _abortSignal: AbortSignal, ): Promise { if (!this.messageBus) { return false; } const skillName = this.params.name; const skill = this.config.getSkillManager().getSkill(skillName); if (!skill) { return false; } if (skill.isBuiltin) { return false; } const folderStructure = await this.getOrFetchFolderStructure( skill.location, ); const confirmationDetails: ToolCallConfirmationDetails = { type: 'info', title: `Activate Skill: ${skillName}`, prompt: `You are about to enable the specialized agent skill **${skillName}**. **Description:** ${skill.description} **Resources to be shared with the model:** ${folderStructure}`, onConfirm: async (outcome: ToolConfirmationOutcome) => { await this.publishPolicyUpdate(outcome); }, }; return confirmationDetails; } async execute(_signal: AbortSignal): Promise { const skillName = this.params.name; const skillManager = this.config.getSkillManager(); const skill = skillManager.getSkill(skillName); if (!skill) { const skills = skillManager.getSkills(); const availableSkills = skills.map((s) => s.name).join(', '); const errorMessage = `Skill "${skillName}" not found. Available skills are: ${availableSkills}`; return { llmContent: `Error: ${errorMessage}`, returnDisplay: `Error: ${errorMessage}`, error: { message: errorMessage, type: ToolErrorType.INVALID_TOOL_PARAMS, }, }; } skillManager.activateSkill(skillName); // Add the skill's directory to the workspace context so the agent has permission // to read its bundled resources. this.config .getWorkspaceContext() .addDirectory(path.dirname(skill.location)); const folderStructure = await this.getOrFetchFolderStructure( skill.location, ); return { llmContent: ` ${skill.body} ${folderStructure} `, returnDisplay: `Skill **${skillName}** activated. Resources loaded from \`${path.dirname(skill.location)}\`:\n\n${folderStructure}`, }; } } /** * Implementation of the ActivateSkill tool logic */ export class ActivateSkillTool extends BaseDeclarativeTool< ActivateSkillToolParams, ToolResult > { static readonly Name = ACTIVATE_SKILL_TOOL_NAME; constructor( private config: Config, messageBus: MessageBus, ) { const skills = config.getSkillManager().getSkills(); const skillNames = skills.map((s) => s.name); const definition = getActivateSkillDefinition(skillNames); super( ActivateSkillTool.Name, 'Activate Skill', definition.base.description!, Kind.Other, definition.base.parametersJsonSchema, messageBus, true, false, ); } protected createInvocation( params: ActivateSkillToolParams, messageBus: MessageBus, _toolName?: string, _toolDisplayName?: string, ): ToolInvocation { return new ActivateSkillToolInvocation( this.config, params, messageBus, _toolName, _toolDisplayName ?? 'Activate Skill', ); } override getSchema(modelId?: string) { const skills = this.config.getSkillManager().getSkills(); const skillNames = skills.map((s) => s.name); return resolveToolDeclaration( getActivateSkillDefinition(skillNames), modelId, ); } }