feat: Skills omni-tool (#102)
This commit is contained in:
131
src/agent/context.ts
Normal file
131
src/agent/context.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Agent context module - provides global access to current agent state
|
||||
* This allows tools to access the current agent ID and client
|
||||
*/
|
||||
|
||||
import type Letta from "@letta-ai/letta-client";
|
||||
|
||||
interface AgentContext {
|
||||
agentId: string | null;
|
||||
client: Letta | null;
|
||||
skillsDirectory: string | null;
|
||||
hasLoadedSkills: boolean;
|
||||
}
|
||||
|
||||
// Use globalThis to ensure singleton across bundle
|
||||
// This prevents Bun's bundler from creating duplicate instances of the context
|
||||
const CONTEXT_KEY = Symbol.for("@letta/agentContext");
|
||||
|
||||
type GlobalWithContext = typeof globalThis & {
|
||||
[key: symbol]: AgentContext;
|
||||
};
|
||||
|
||||
function getContext(): AgentContext {
|
||||
const global = globalThis as GlobalWithContext;
|
||||
if (!global[CONTEXT_KEY]) {
|
||||
global[CONTEXT_KEY] = {
|
||||
agentId: null,
|
||||
client: null,
|
||||
skillsDirectory: null,
|
||||
hasLoadedSkills: false,
|
||||
};
|
||||
}
|
||||
return global[CONTEXT_KEY];
|
||||
}
|
||||
|
||||
const context = getContext();
|
||||
|
||||
/**
|
||||
* Set the current agent context
|
||||
* @param agentId - The agent ID
|
||||
* @param client - The Letta client instance
|
||||
* @param skillsDirectory - Optional skills directory path
|
||||
*/
|
||||
export function setAgentContext(
|
||||
agentId: string,
|
||||
client: Letta,
|
||||
skillsDirectory?: string,
|
||||
): void {
|
||||
context.agentId = agentId;
|
||||
context.client = client;
|
||||
context.skillsDirectory = skillsDirectory || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current agent ID
|
||||
* @throws Error if no agent context is set
|
||||
*/
|
||||
export function getCurrentAgentId(): string {
|
||||
if (!context.agentId) {
|
||||
throw new Error("No agent context set. Agent ID is required.");
|
||||
}
|
||||
return context.agentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current Letta client
|
||||
* @throws Error if no agent context is set
|
||||
*/
|
||||
export function getCurrentClient(): Letta {
|
||||
if (!context.client) {
|
||||
throw new Error("No agent context set. Client is required.");
|
||||
}
|
||||
return context.client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the skills directory path
|
||||
* @returns The skills directory path or null if not set
|
||||
*/
|
||||
export function getSkillsDirectory(): string | null {
|
||||
return context.skillsDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if skills are currently loaded (cached state)
|
||||
* @returns true if skills are loaded, false otherwise
|
||||
*/
|
||||
export function hasLoadedSkills(): boolean {
|
||||
return context.hasLoadedSkills;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the loaded skills state (called by Skill tool)
|
||||
* @param loaded - Whether skills are currently loaded
|
||||
*/
|
||||
export function setHasLoadedSkills(loaded: boolean): void {
|
||||
context.hasLoadedSkills = loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the loaded skills flag by checking the block
|
||||
* Should be called after setAgentContext to sync the cached state
|
||||
*/
|
||||
export async function initializeLoadedSkillsFlag(): Promise<void> {
|
||||
if (!context.client || !context.agentId) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const loadedSkillsBlock = await context.client.agents.blocks.retrieve(
|
||||
"loaded_skills",
|
||||
{ agent_id: context.agentId },
|
||||
);
|
||||
const value = loadedSkillsBlock?.value?.trim() || "";
|
||||
// Consider empty or placeholder as no skills loaded
|
||||
context.hasLoadedSkills = value !== "" && value !== "[CURRENTLY EMPTY]";
|
||||
} catch {
|
||||
// Block doesn't exist, no skills loaded
|
||||
context.hasLoadedSkills = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the agent context (useful for cleanup)
|
||||
*/
|
||||
export function clearAgentContext(): void {
|
||||
context.agentId = null;
|
||||
context.client = null;
|
||||
context.skillsDirectory = null;
|
||||
context.hasLoadedSkills = false;
|
||||
}
|
||||
@@ -43,7 +43,13 @@ function parseMdxFrontmatter(content: string): {
|
||||
async function loadMemoryBlocksFromMdx(): Promise<CreateBlock[]> {
|
||||
const memoryBlocks: CreateBlock[] = [];
|
||||
|
||||
const mdxFiles = ["persona.mdx", "human.mdx", "project.mdx", "skills.mdx"];
|
||||
const mdxFiles = [
|
||||
"persona.mdx",
|
||||
"human.mdx",
|
||||
"project.mdx",
|
||||
"skills.mdx",
|
||||
"loaded_skills.mdx",
|
||||
];
|
||||
// const mdxFiles = ["persona.mdx", "human.mdx", "style.mdx"];
|
||||
// const mdxFiles = ["persona_kawaii.mdx", "human.mdx", "style.mdx"];
|
||||
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
import humanPrompt from "./prompts/human.mdx";
|
||||
|
||||
import loadedSkillsPrompt from "./prompts/loaded_skills.mdx";
|
||||
import personaPrompt from "./prompts/persona.mdx";
|
||||
import personaKawaiiPrompt from "./prompts/persona_kawaii.mdx";
|
||||
import planModeReminder from "./prompts/plan_mode_reminder.txt";
|
||||
import projectPrompt from "./prompts/project.mdx";
|
||||
import skillUnloadReminder from "./prompts/skill_unload_reminder.txt";
|
||||
import skillsPrompt from "./prompts/skills.mdx";
|
||||
import stylePrompt from "./prompts/style.mdx";
|
||||
import systemPrompt from "./prompts/system_prompt.txt";
|
||||
|
||||
export const SYSTEM_PROMPT = systemPrompt;
|
||||
export const PLAN_MODE_REMINDER = planModeReminder;
|
||||
export const SKILL_UNLOAD_REMINDER = skillUnloadReminder;
|
||||
|
||||
export const MEMORY_PROMPTS: Record<string, string> = {
|
||||
"persona.mdx": personaPrompt,
|
||||
"human.mdx": humanPrompt,
|
||||
"project.mdx": projectPrompt,
|
||||
"skills.mdx": skillsPrompt,
|
||||
"loaded_skills.mdx": loadedSkillsPrompt,
|
||||
"style.mdx": stylePrompt,
|
||||
"persona_kawaii.mdx": personaKawaiiPrompt,
|
||||
};
|
||||
|
||||
6
src/agent/prompts/loaded_skills.mdx
Normal file
6
src/agent/prompts/loaded_skills.mdx
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
label: loaded_skills
|
||||
description: A memory block to store the full instructions and capabilities from each loaded SKILL.md file in this block.
|
||||
---
|
||||
|
||||
[CURRENTLY EMPTY]
|
||||
6
src/agent/prompts/skill_unload_reminder.txt
Normal file
6
src/agent/prompts/skill_unload_reminder.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
<system-reminder>
|
||||
The `loaded_skills` block has at least one skill loaded. You should:
|
||||
1. Check if loaded skills are relevant for the current task.
|
||||
2. For any skills that are irrelevant, unload them using the `memory` tool.
|
||||
If the block will be empty after unloading, add a "[CURRENTLY EMPTY]" tag.
|
||||
</system-reminder>
|
||||
@@ -25,15 +25,17 @@ Memory blocks are used to modulate and augment your base behavior, follow them c
|
||||
They are the foundation which makes you *you*.
|
||||
|
||||
# Skills
|
||||
You have access to Skills—folders of instructions, scripts, and resources that you can load dynamically to improve performance on specialized tasks. Skills teach you how to complete specific tasks in a repeatable way. Skills work through progressive disclosure—you should determine which Skills are relevant to complete a task and load them, helping to prevent context window overload.
|
||||
You have access to Skills—folders of instructions, scripts, and resources that you can load dynamically to improve performance on specialized tasks. Skills teach you how to complete specific tasks in a repeatable way. Skills work through progressive disclosure—you should determine which skills are relevant to complete a task and load them, helping to prevent context window overload.
|
||||
Each Skill directory includes:
|
||||
- `SKILL.md` file that starts with YAML frontmatter containing required metadata: name and description
|
||||
- Additional files within the Skill directory referenced by name from `SKILL.md`. These additional linked files should be navigated and discovered only as needed.
|
||||
- `SKILL.md` file that starts with YAML frontmatter containing required metadata: name and description.
|
||||
- Additional files within the skill directory referenced by name from `SKILL.md`. These additional linked files should be navigated and discovered only as needed.
|
||||
How to store Skills:
|
||||
- Skills directory and any available skills are stored in the `skills` memory block.
|
||||
- Currently loaded skills are available in the `loaded_skills` memory block.
|
||||
How to use Skills:
|
||||
- Skills are automatically discovered on bootup. The Skills directory and any available Skills are stored in the `skills` memory block.
|
||||
- Review available Skills from the `skills` block when you are asked to complete a task.
|
||||
- If a Skill is relevant, first load the Skill by reading its full `SKILL.md` into context.
|
||||
- Skills are automatically discovered on bootup.
|
||||
- Review available skills from the `skills` block and loaded skills from the `loaded_skills` block when you are asked to complete a task.
|
||||
- If any skill is relevant, load it using the `Skill` tool.
|
||||
- Then, navigate and discover additional linked files in its directory as needed. Don't load additional files immediately, only load them when needed.
|
||||
- When you load / use Skills, explicitly mention which Skills are loaded / used.
|
||||
- When the task is completed or when you load new Skills, unload irrelevant Skills by removing them from the context.
|
||||
Remember to always keep only the full `SKILL.md` into context for all Skills relevant to the current task. Use additional files as needed.
|
||||
- When the task is completed, unload irrelevant skills from the `loaded_skills` block.
|
||||
IMPORTANT: Always remove irrelevant skills using memory management tools from the `loaded_skills` block.
|
||||
@@ -96,6 +96,16 @@ function getPlanModeReminder(): string {
|
||||
return PLAN_MODE_REMINDER;
|
||||
}
|
||||
|
||||
// Get skill unload reminder if skills are loaded (using cached flag)
|
||||
function getSkillUnloadReminder(): string {
|
||||
const { hasLoadedSkills } = require("../agent/context");
|
||||
if (hasLoadedSkills()) {
|
||||
const { SKILL_UNLOAD_REMINDER } = require("../agent/promptAssets");
|
||||
return SKILL_UNLOAD_REMINDER;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// Items that have finished rendering and no longer change
|
||||
type StaticItem =
|
||||
| {
|
||||
@@ -1353,14 +1363,17 @@ export default function App({
|
||||
|
||||
// Prepend plan mode reminder if in plan mode
|
||||
const planModeReminder = getPlanModeReminder();
|
||||
|
||||
// Prepend skill unload reminder if skills are loaded (using cached flag)
|
||||
const skillUnloadReminder = getSkillUnloadReminder();
|
||||
|
||||
// Combine reminders with content (plan mode first, then skill unload)
|
||||
const allReminders = planModeReminder + skillUnloadReminder;
|
||||
const messageContent =
|
||||
planModeReminder && typeof contentParts === "string"
|
||||
? planModeReminder + contentParts
|
||||
: Array.isArray(contentParts) && planModeReminder
|
||||
? [
|
||||
{ type: "text" as const, text: planModeReminder },
|
||||
...contentParts,
|
||||
]
|
||||
allReminders && typeof contentParts === "string"
|
||||
? allReminders + contentParts
|
||||
: Array.isArray(contentParts) && allReminders
|
||||
? [{ type: "text" as const, text: allReminders }, ...contentParts]
|
||||
: contentParts;
|
||||
|
||||
// Append the user message to transcript IMMEDIATELY (optimistic update)
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { ApprovalCreate } from "@letta-ai/letta-client/resources/agents/mes
|
||||
import type { StopReasonType } from "@letta-ai/letta-client/resources/runs/runs";
|
||||
import type { ApprovalResult } from "./agent/approval-execution";
|
||||
import { getClient } from "./agent/client";
|
||||
import { initializeLoadedSkillsFlag, setAgentContext } from "./agent/context";
|
||||
import { createAgent } from "./agent/create";
|
||||
import { sendMessageStream } from "./agent/message";
|
||||
import { getModelUpdateArgs } from "./agent/model";
|
||||
@@ -155,6 +156,10 @@ export async function handleHeadlessCommand(
|
||||
settingsManager.updateLocalProjectSettings({ lastAgent: agent.id });
|
||||
settingsManager.updateSettings({ lastAgent: agent.id });
|
||||
|
||||
// Set agent context for tools that need it (e.g., Skill tool)
|
||||
setAgentContext(agent.id, client, skillsDirectory);
|
||||
await initializeLoadedSkillsFlag();
|
||||
|
||||
// Validate output format
|
||||
const outputFormat =
|
||||
(values["output-format"] as string | undefined) || "text";
|
||||
@@ -305,14 +310,26 @@ export async function handleHeadlessCommand(
|
||||
// Clear any pending approvals before starting a new turn
|
||||
await resolveAllPendingApprovals();
|
||||
|
||||
// Get plan mode reminder if in plan mode
|
||||
// Build message content with reminders (plan mode first, then skill unload)
|
||||
const { permissionMode } = await import("./permissions/mode");
|
||||
let messageContent = prompt;
|
||||
const { hasLoadedSkills } = await import("./agent/context");
|
||||
let messageContent = "";
|
||||
|
||||
// Add plan mode reminder if in plan mode (highest priority)
|
||||
if (permissionMode.getMode() === "plan") {
|
||||
const { PLAN_MODE_REMINDER } = await import("./agent/promptAssets");
|
||||
messageContent = PLAN_MODE_REMINDER + prompt;
|
||||
messageContent += PLAN_MODE_REMINDER;
|
||||
}
|
||||
|
||||
// Add skill unload reminder if skills are loaded (using cached flag)
|
||||
if (hasLoadedSkills()) {
|
||||
const { SKILL_UNLOAD_REMINDER } = await import("./agent/promptAssets");
|
||||
messageContent += SKILL_UNLOAD_REMINDER;
|
||||
}
|
||||
|
||||
// Add user prompt
|
||||
messageContent += prompt;
|
||||
|
||||
// Start with the user message
|
||||
let currentInput: Array<MessageCreate | ApprovalCreate> = [
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ import { parseArgs } from "node:util";
|
||||
import type { AgentState } from "@letta-ai/letta-client/resources/agents/agents";
|
||||
import { getResumeData, type ResumeData } from "./agent/check-approval";
|
||||
import { getClient } from "./agent/client";
|
||||
import { initializeLoadedSkillsFlag, setAgentContext } from "./agent/context";
|
||||
import { permissionMode } from "./permissions/mode";
|
||||
import { settingsManager } from "./settings-manager";
|
||||
import { loadTools, upsertToolsToServer } from "./tools/manager";
|
||||
@@ -556,6 +557,10 @@ async function main() {
|
||||
settingsManager.updateLocalProjectSettings({ lastAgent: agent.id });
|
||||
settingsManager.updateSettings({ lastAgent: agent.id });
|
||||
|
||||
// Set agent context for tools that need it (e.g., Skill tool)
|
||||
setAgentContext(agent.id, client, skillsDirectory);
|
||||
await initializeLoadedSkillsFlag();
|
||||
|
||||
// Check if we're resuming an existing agent
|
||||
const localProjectSettings = settingsManager.getLocalProjectSettings();
|
||||
const isResumingProject =
|
||||
|
||||
@@ -102,6 +102,14 @@ export function checkPermission(
|
||||
}
|
||||
}
|
||||
|
||||
// Always allow Skill tool (read-only operation that loads skills from potentially external directories)
|
||||
if (toolName === "Skill") {
|
||||
return {
|
||||
decision: "allow",
|
||||
reason: "Skill tool is always allowed (read-only)",
|
||||
};
|
||||
}
|
||||
|
||||
// After checking CLI overrides, check if Read/Glob/Grep within working directory
|
||||
if (WORKING_DIRECTORY_TOOLS.includes(toolName)) {
|
||||
const filePath = extractFilePath(toolArgs);
|
||||
|
||||
29
src/tools/descriptions/Skill.md
Normal file
29
src/tools/descriptions/Skill.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Skill
|
||||
|
||||
Load a skill into the system prompt within the main conversation
|
||||
|
||||
<skills_instructions>
|
||||
When users ask you to perform tasks, check if any of the available skills can help complete the task more effectively. Skills provide specialized capabilities and domain knowledge.
|
||||
|
||||
How to use skills:
|
||||
- Invoke skills using this tool with the skill name only (no arguments)
|
||||
- When you invoke a skill, the SKILL.md content will be loaded into the `loaded_skills` memory block
|
||||
- The skill's prompt will provide detailed instructions on how to complete the task
|
||||
- Examples:
|
||||
- `skill: "data-analysis"` - invoke the data-analysis skill
|
||||
- `skill: "web-scraper"` - invoke the web-scraper skill
|
||||
|
||||
Important:
|
||||
- Only load skills that are available in the `skills` memory block
|
||||
- Skills remain loaded until you unload them
|
||||
- Unload skills when done to free up context space
|
||||
- Do not invoke a skill that is already loaded
|
||||
- You can check what skills are currently loaded in the `loaded_skills` memory block
|
||||
</skills_instructions>
|
||||
|
||||
Usage notes:
|
||||
- The `skill` parameter is required and should be the skill ID (e.g., "data-analysis")
|
||||
- Skills are loaded from the skills directory specified in the `skills` memory block
|
||||
- Skills remain loaded in the `loaded_skills` memory block until explicitly unloaded
|
||||
- Only use skill IDs that appear in the `skills` memory block
|
||||
- Each skill provides specialized instructions and capabilities for specific tasks
|
||||
136
src/tools/impl/Skill.ts
Normal file
136
src/tools/impl/Skill.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
getCurrentAgentId,
|
||||
getCurrentClient,
|
||||
getSkillsDirectory,
|
||||
setHasLoadedSkills,
|
||||
} from "../../agent/context";
|
||||
import { SKILLS_DIR } from "../../agent/skills";
|
||||
import { validateRequiredParams } from "./validation.js";
|
||||
|
||||
interface SkillArgs {
|
||||
skill: string;
|
||||
}
|
||||
|
||||
interface SkillResult {
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse loaded_skills block content to extract skill IDs
|
||||
*/
|
||||
function parseLoadedSkills(value: string): string[] {
|
||||
const skillRegex = /# Skill: ([^\n]+)/g;
|
||||
const skills: string[] = [];
|
||||
let match: RegExpExecArray | null = skillRegex.exec(value);
|
||||
|
||||
while (match !== null) {
|
||||
const skillId = match[1]?.trim();
|
||||
if (skillId) {
|
||||
skills.push(skillId);
|
||||
}
|
||||
match = skillRegex.exec(value);
|
||||
}
|
||||
|
||||
return skills;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts skills directory from skills block value
|
||||
*/
|
||||
function extractSkillsDir(skillsBlockValue: string): string | null {
|
||||
const match = skillsBlockValue.match(/Skills Directory: (.+)/);
|
||||
return match ? match[1]?.trim() || null : null;
|
||||
}
|
||||
|
||||
export async function skill(args: SkillArgs): Promise<SkillResult> {
|
||||
validateRequiredParams(args, ["skill"], "Skill");
|
||||
const { skill: skillId } = args;
|
||||
|
||||
try {
|
||||
// Get current agent context
|
||||
const client = getCurrentClient();
|
||||
const agentId = getCurrentAgentId();
|
||||
|
||||
// Retrieve the loaded_skills block directly
|
||||
let loadedSkillsBlock: Awaited<
|
||||
ReturnType<typeof client.agents.blocks.retrieve>
|
||||
>;
|
||||
try {
|
||||
loadedSkillsBlock = await client.agents.blocks.retrieve("loaded_skills", {
|
||||
agent_id: agentId,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Error: loaded_skills block not found. This block is required for the Skill tool to work.\nAgent ID: ${agentId}\nError: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Determine skills directory
|
||||
let skillsDir = getSkillsDirectory();
|
||||
|
||||
if (!skillsDir) {
|
||||
// Try to extract from skills block
|
||||
try {
|
||||
const skillsBlock = await client.agents.blocks.retrieve("skills", {
|
||||
agent_id: agentId,
|
||||
});
|
||||
if (skillsBlock?.value) {
|
||||
skillsDir = extractSkillsDir(skillsBlock.value);
|
||||
}
|
||||
} catch {
|
||||
// Skills block doesn't exist, will fall back to default
|
||||
}
|
||||
}
|
||||
|
||||
if (!skillsDir) {
|
||||
// Fall back to default .skills directory in cwd
|
||||
skillsDir = join(process.cwd(), SKILLS_DIR);
|
||||
}
|
||||
|
||||
// Construct path to SKILL.md
|
||||
const skillPath = join(skillsDir, skillId, "SKILL.md");
|
||||
|
||||
// Read the skill file directly
|
||||
const skillContent = await readFile(skillPath, "utf-8");
|
||||
|
||||
// Parse current loaded_skills block value
|
||||
let currentValue = loadedSkillsBlock.value?.trim() || "";
|
||||
const loadedSkills = parseLoadedSkills(currentValue);
|
||||
|
||||
// Check if skill is already loaded
|
||||
if (loadedSkills.includes(skillId)) {
|
||||
return {
|
||||
message: `Skill "${skillId}" is already loaded`,
|
||||
};
|
||||
}
|
||||
|
||||
// Replace placeholder if this is the first skill
|
||||
if (currentValue === "[CURRENTLY EMPTY]") {
|
||||
currentValue = "";
|
||||
}
|
||||
|
||||
// Append new skill to loaded_skills block
|
||||
const separator = currentValue ? "\n\n---\n\n" : "";
|
||||
const newValue = `${currentValue}${separator}# Skill: ${skillId}\n${skillContent}`;
|
||||
|
||||
// Update the block
|
||||
await client.agents.blocks.update("loaded_skills", {
|
||||
agent_id: agentId,
|
||||
value: newValue,
|
||||
});
|
||||
|
||||
// Update the cached flag to indicate skills are loaded
|
||||
setHasLoadedSkills(true);
|
||||
|
||||
return {
|
||||
message: `Skill "${skillId}" loaded successfully`,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
throw error;
|
||||
}
|
||||
throw new Error(`Failed to load skill: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,7 @@ export const ANTHROPIC_DEFAULT_TOOLS: ToolName[] = [
|
||||
"LS",
|
||||
"MultiEdit",
|
||||
"Read",
|
||||
"Skill",
|
||||
"TodoWrite",
|
||||
"Write",
|
||||
];
|
||||
@@ -69,6 +70,7 @@ export const OPENAI_DEFAULT_TOOLS: ToolName[] = [
|
||||
"grep_files",
|
||||
"apply_patch",
|
||||
"update_plan",
|
||||
"Skill",
|
||||
];
|
||||
|
||||
export const GEMINI_DEFAULT_TOOLS: ToolName[] = [
|
||||
@@ -81,6 +83,7 @@ export const GEMINI_DEFAULT_TOOLS: ToolName[] = [
|
||||
"write_file_gemini",
|
||||
"write_todos",
|
||||
"read_many_files",
|
||||
"Skill",
|
||||
];
|
||||
|
||||
// Tool permissions configuration
|
||||
@@ -95,6 +98,7 @@ const TOOL_PERMISSIONS: Record<ToolName, { requiresApproval: boolean }> = {
|
||||
LS: { requiresApproval: false },
|
||||
MultiEdit: { requiresApproval: true },
|
||||
Read: { requiresApproval: false },
|
||||
Skill: { requiresApproval: false },
|
||||
TodoWrite: { requiresApproval: false },
|
||||
Write: { requiresApproval: true },
|
||||
shell_command: { requiresApproval: true },
|
||||
|
||||
12
src/tools/schemas/Skill.json
Normal file
12
src/tools/schemas/Skill.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"skill": {
|
||||
"type": "string",
|
||||
"description": "The skill name/id (e.g., \"data-analysis\", \"web-scraper\")"
|
||||
}
|
||||
},
|
||||
"required": ["skill"],
|
||||
"additionalProperties": false,
|
||||
"$schema": "http://json-schema.org/draft-07/schema#"
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import RunShellCommandGeminiDescription from "./descriptions/RunShellCommandGemi
|
||||
import SearchFileContentGeminiDescription from "./descriptions/SearchFileContentGemini.md";
|
||||
import ShellDescription from "./descriptions/Shell.md";
|
||||
import ShellCommandDescription from "./descriptions/ShellCommand.md";
|
||||
import SkillDescription from "./descriptions/Skill.md";
|
||||
import TodoWriteDescription from "./descriptions/TodoWrite.md";
|
||||
import UpdatePlanDescription from "./descriptions/UpdatePlan.md";
|
||||
import WriteDescription from "./descriptions/Write.md";
|
||||
@@ -51,6 +52,7 @@ import { run_shell_command } from "./impl/RunShellCommandGemini";
|
||||
import { search_file_content } from "./impl/SearchFileContentGemini";
|
||||
import { shell } from "./impl/Shell";
|
||||
import { shell_command } from "./impl/ShellCommand";
|
||||
import { skill } from "./impl/Skill";
|
||||
import { todo_write } from "./impl/TodoWrite";
|
||||
import { update_plan } from "./impl/UpdatePlan";
|
||||
import { write } from "./impl/Write";
|
||||
@@ -80,6 +82,7 @@ import RunShellCommandGeminiSchema from "./schemas/RunShellCommandGemini.json";
|
||||
import SearchFileContentGeminiSchema from "./schemas/SearchFileContentGemini.json";
|
||||
import ShellSchema from "./schemas/Shell.json";
|
||||
import ShellCommandSchema from "./schemas/ShellCommand.json";
|
||||
import SkillSchema from "./schemas/Skill.json";
|
||||
import TodoWriteSchema from "./schemas/TodoWrite.json";
|
||||
import UpdatePlanSchema from "./schemas/UpdatePlan.json";
|
||||
import WriteSchema from "./schemas/Write.json";
|
||||
@@ -145,6 +148,11 @@ const toolDefinitions = {
|
||||
description: ReadDescription.trim(),
|
||||
impl: read as unknown as ToolImplementation,
|
||||
},
|
||||
Skill: {
|
||||
schema: SkillSchema,
|
||||
description: SkillDescription.trim(),
|
||||
impl: skill as unknown as ToolImplementation,
|
||||
},
|
||||
TodoWrite: {
|
||||
schema: TodoWriteSchema,
|
||||
description: TodoWriteDescription.trim(),
|
||||
|
||||
Reference in New Issue
Block a user