feat(sdk): implement support for custom skills (#19031)

This commit is contained in:
Michael Bleigh
2026-02-13 18:09:31 -08:00
committed by GitHub
parent 9fc7b56793
commit a129dbcdd4
10 changed files with 202 additions and 29 deletions

View File

@@ -17,12 +17,15 @@ import {
scheduleAgentTools,
getAuthTypeFromEnv,
type ToolRegistry,
loadSkillsFromDir,
ActivateSkillTool,
} from '@google/gemini-cli-core';
import { type Tool, SdkTool } from './tool.js';
import { SdkAgentFilesystem } from './fs.js';
import { SdkAgentShell } from './shell.js';
import type { SessionContext } from './types.js';
import type { SkillReference } from './skills.js';
export type SystemInstructions =
| string
@@ -32,6 +35,7 @@ export interface GeminiCliAgentOptions {
instructions: SystemInstructions;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
tools?: Array<Tool<any>>;
skills?: SkillReference[];
model?: string;
cwd?: string;
debug?: boolean;
@@ -40,9 +44,10 @@ export interface GeminiCliAgentOptions {
}
export class GeminiCliAgent {
private config: Config;
private readonly config: Config;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private tools: Array<Tool<any>>;
private readonly tools: Array<Tool<any>>;
private readonly skillRefs: SkillReference[];
private readonly instructions: SystemInstructions;
private instructionsLoaded = false;
@@ -50,6 +55,7 @@ export class GeminiCliAgent {
this.instructions = options.instructions;
const cwd = options.cwd || process.cwd();
this.tools = options.tools || [];
this.skillRefs = options.skills || [];
const initialMemory =
typeof this.instructions === 'string' ? this.instructions : '';
@@ -67,6 +73,8 @@ export class GeminiCliAgent {
extensionsEnabled: false,
recordResponses: options.recordResponses,
fakeResponses: options.fakeResponses,
skillsSupport: true,
adminSkillsEnabled: true,
};
this.config = new Config(configParams);
@@ -83,6 +91,45 @@ export class GeminiCliAgent {
await this.config.refreshAuth(authType);
await this.config.initialize();
// Load additional skills from options
if (this.skillRefs.length > 0) {
const skillManager = this.config.getSkillManager();
const loadPromises = this.skillRefs.map(async (ref) => {
try {
if (ref.type === 'dir') {
return await loadSkillsFromDir(ref.path);
}
} catch (e) {
// eslint-disable-next-line no-console
console.error(`Failed to load skills from ${ref.path}:`, e);
}
return [];
});
const loadedSkills = (await Promise.all(loadPromises)).flat();
if (loadedSkills.length > 0) {
skillManager.addSkills(loadedSkills);
}
}
// Re-register ActivateSkillTool if we have skills (either built-in/workspace or manually loaded)
// This is required because ActivateSkillTool captures the set of available skills at construction time.
const skillManager = this.config.getSkillManager();
if (skillManager.getSkills().length > 0) {
const registry = this.config.getToolRegistry();
const toolName = ActivateSkillTool.Name;
// Config.initialize already registers it, but we might have added more skills.
// Re-registering updates the schema with new skills.
if (registry.getTool(toolName)) {
registry.unregisterTool(toolName);
}
registry.registerTool(
new ActivateSkillTool(this.config, this.config.getMessageBus()),
);
}
// Register tools now that registry exists
const registry = this.config.getToolRegistry();
const messageBus = this.config.getMessageBus();