Files
tf_code/packages/opencode/src/tool/skill.ts
2025-12-27 21:20:00 -06:00

106 lines
3.4 KiB
TypeScript

import path from "path"
import z from "zod"
import { Tool } from "./tool"
import { Skill } from "../skill"
import { Agent } from "../agent/agent"
import { Permission } from "../permission"
import { Wildcard } from "../util/wildcard"
import { ConfigMarkdown } from "../config/markdown"
const parameters = z.object({
name: z.string().describe("The skill identifier from available_skills (e.g., 'code-review')"),
})
export const SkillTool: Tool.Info<typeof parameters> = {
id: "skill",
async init(ctx) {
const skills = await Skill.all()
// Filter skills by agent permissions if agent provided
let accessibleSkills = skills
if (ctx?.agent) {
const permissions = ctx.agent.permission.skill
accessibleSkills = skills.filter((skill) => {
const action = Wildcard.all(skill.name, permissions)
return action !== "deny"
})
}
const description =
accessibleSkills.length === 0
? "Load a skill to get detailed instructions for a specific task. No skills are currently available."
: [
"Load a skill to get detailed instructions for a specific task.",
"Skills provide specialized knowledge and step-by-step guidance.",
"Use this when a task matches an available skill's description.",
"<available_skills>",
...accessibleSkills.flatMap((skill) => [
` <skill>`,
` <name>${skill.name}</name>`,
` <description>${skill.description}</description>`,
` </skill>`,
]),
"</available_skills>",
].join(" ")
return {
description,
parameters,
async execute(params, ctx) {
const agent = await Agent.get(ctx.agent)
const skill = await Skill.get(params.name)
if (!skill) {
const available = await Skill.all().then((x) => x.map((s) => s.name).join(", "))
throw new Error(`Skill "${params.name}" not found. Available skills: ${available || "none"}`)
}
// Check permission using Wildcard.all on the skill name
const permissions = agent.permission.skill
const action = Wildcard.all(params.name, permissions)
if (action === "deny") {
throw new Permission.RejectedError(
ctx.sessionID,
"skill",
ctx.callID,
{ skill: params.name },
`Access to skill "${params.name}" is denied for agent "${agent.name}".`,
)
}
if (action === "ask") {
await Permission.ask({
type: "skill",
pattern: params.name,
sessionID: ctx.sessionID,
messageID: ctx.messageID,
callID: ctx.callID,
title: `Load skill: ${skill.name}`,
metadata: { name: skill.name, description: skill.description },
})
}
// Load and parse skill content
const parsed = await ConfigMarkdown.parse(skill.location)
const dir = path.dirname(skill.location)
// Format output similar to plugin pattern
const output = [`## Skill: ${skill.name}`, "", `**Base directory**: ${dir}`, "", parsed.content.trim()].join(
"\n",
)
return {
title: `Loaded skill: ${skill.name}`,
output,
metadata: {
name: skill.name,
dir,
},
}
},
}
},
}