2026-01-02 11:15:06 -08:00
/ * *
* @license
* Copyright 2025 Google LLC
* SPDX - License - Identifier : Apache - 2.0
* /
import { z } from 'zod' ;
import { zodToJsonSchema } from 'zod-to-json-schema' ;
import * as path from 'node:path' ;
import { getFolderStructure } from '../utils/getFolderStructure.js' ;
import type { MessageBus } from '../confirmation-bus/message-bus.js' ;
import type {
ToolResult ,
ToolCallConfirmationDetails ,
ToolInvocation ,
ToolConfirmationOutcome ,
} from './tools.js' ;
import { BaseDeclarativeTool , BaseToolInvocation , Kind } from './tools.js' ;
import type { Config } from '../config/config.js' ;
import { ACTIVATE_SKILL_TOOL_NAME } from './tool-names.js' ;
2026-01-06 17:10:00 -08:00
import { ToolErrorType } from './tool-error.js' ;
2026-01-02 11:15:06 -08:00
/ * *
* 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 ,
2026-01-04 17:11:43 -05:00
messageBus : MessageBus ,
2026-01-02 11:15:06 -08:00
_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 } ` ;
}
2026-01-06 17:10:00 -08:00
return ` " ${ skillName } " (?) unknown skill ` ;
2026-01-02 11:15:06 -08:00
}
private async getOrFetchFolderStructure (
skillLocation : string ,
) : Promise < string > {
if ( this . cachedFolderStructure === undefined ) {
this . cachedFolderStructure = await getFolderStructure (
path . dirname ( skillLocation ) ,
) ;
}
return this . cachedFolderStructure ;
}
protected override async getConfirmationDetails (
_abortSignal : AbortSignal ,
) : Promise < ToolCallConfirmationDetails | false > {
if ( ! this . messageBus ) {
return false ;
}
const skillName = this . params . name ;
const skill = this . config . getSkillManager ( ) . getSkill ( skillName ) ;
if ( ! skill ) {
return false ;
}
2026-01-09 22:26:58 -08:00
if ( skill . isBuiltin ) {
return false ;
}
2026-01-02 11:15:06 -08:00
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 < ToolResult > {
const skillName = this . params . name ;
const skillManager = this . config . getSkillManager ( ) ;
const skill = skillManager . getSkill ( skillName ) ;
if ( ! skill ) {
const skills = skillManager . getSkills ( ) ;
2026-01-06 17:10:00 -08:00
const availableSkills = skills . map ( ( s ) = > s . name ) . join ( ', ' ) ;
const errorMessage = ` Skill " ${ skillName } " not found. Available skills are: ${ availableSkills } ` ;
2026-01-02 11:15:06 -08:00
return {
2026-01-06 17:10:00 -08:00
llmContent : ` Error: ${ errorMessage } ` ,
returnDisplay : ` Error: ${ errorMessage } ` ,
error : {
message : errorMessage ,
type : ToolErrorType . INVALID_TOOL_PARAMS ,
} ,
2026-01-02 11:15:06 -08:00
} ;
}
skillManager . activateSkill ( skillName ) ;
2026-01-06 11:24:37 -08:00
// 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 ) ) ;
2026-01-02 11:15:06 -08:00
const folderStructure = await this . getOrFetchFolderStructure (
skill . location ,
) ;
return {
2026-01-06 17:10:00 -08:00
llmContent : ` <activated_skill name=" ${ skillName } ">
< instructions >
2026-01-02 11:15:06 -08:00
$ { skill . body }
2026-01-06 17:10:00 -08:00
< / instructions >
2026-01-02 11:15:06 -08:00
2026-01-06 17:10:00 -08:00
< available_resources >
2026-01-02 11:15:06 -08:00
$ { folderStructure }
2026-01-06 17:10:00 -08:00
< / available_resources >
< / activated_skill > ` ,
2026-01-02 11:15:06 -08:00
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 ,
2026-01-04 17:11:43 -05:00
messageBus : MessageBus ,
2026-01-02 11:15:06 -08:00
) {
const skills = config . getSkillManager ( ) . getSkills ( ) ;
const skillNames = skills . map ( ( s ) = > s . name ) ;
let schema : z.ZodTypeAny ;
if ( skillNames . length === 0 ) {
schema = z . object ( {
name : z.string ( ) . describe ( 'No skills are currently available.' ) ,
} ) ;
} else {
schema = z . object ( {
name : z
2026-02-10 00:10:15 +00:00
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2026-01-02 11:15:06 -08:00
. enum ( skillNames as [ string , . . . string [ ] ] )
. describe ( 'The name of the skill to activate.' ) ,
} ) ;
}
2026-01-06 17:10:00 -08:00
const availableSkillsHint =
skillNames . length > 0
? ` (Available: ${ skillNames . map ( ( n ) = > ` ' ${ n } ' ` ) . join ( ', ' ) } ) `
: '' ;
2026-01-02 11:15:06 -08:00
super (
ActivateSkillTool . Name ,
'Activate Skill' ,
2026-01-06 17:10:00 -08:00
` Activates a specialized agent skill by name ${ availableSkillsHint } . Returns the skill's instructions wrapped in \` <activated_skill> \` tags. These provide specialized guidance for the current task. Use this when you identify a task that matches a skill's description. ONLY use names exactly as they appear in the \` <available_skills> \` section. ` ,
2026-01-02 11:15:06 -08:00
Kind . Other ,
zodToJsonSchema ( schema ) ,
2026-01-04 17:11:43 -05:00
messageBus ,
2026-01-02 11:15:06 -08:00
true ,
false ,
) ;
}
protected createInvocation (
params : ActivateSkillToolParams ,
2026-01-04 17:11:43 -05:00
messageBus : MessageBus ,
2026-01-02 11:15:06 -08:00
_toolName? : string ,
_toolDisplayName? : string ,
) : ToolInvocation < ActivateSkillToolParams , ToolResult > {
return new ActivateSkillToolInvocation (
this . config ,
params ,
messageBus ,
_toolName ,
_toolDisplayName ? ? 'Activate Skill' ,
) ;
}
}