Skip to content
49 linesCodeBlameRaw
1/**
2 * An agent's skills in its instructions (docs.g1t.sh/guides/agent-skills/).
3 * Pure, so it is tested on its own.
4 *
5 * Each foundational skill that is on (@g1t/contracts skills.ts) puts its
6 * playbook in the system prompt, followed by what it can't do here: the
7 * abilities whose tools this turn doesn't offer (code tools in a
8 * conversation whose people can't all read code, say), and the abilities
9 * that are coming. A skill never adds a tool: the tools offered are the
10 * tool box's, decided before this runs.
11 */
12import { type AgentSkill, skillsOn } from "../../../packages/contracts/src/skills.ts";
13
14/** "a, b and c". */
15function list(items: string[]): string {
16 if (items.length <= 1) return items.join("");
17 return `${items.slice(0, -1).join(", ")} and ${items[items.length - 1]}`;
18}
19
20/** What one skill says: its playbook, then what isn't available here and what is coming. */
21export function skillBlock(skill: AgentSkill, offered: ReadonlySet<string>): string {
22 const missingHere = skill.abilities.filter((a) => a.status === "ready" && a.tools.length > 0 && !a.tools.some((tool) => offered.has(tool)));
23 const coming = skill.abilities.filter((a) => a.status === "coming");
24 const lines = [`### ${skill.name}`, "", skill.instructions];
25 if (missingHere.length) {
26 lines.push(`- Not available in this conversation (its tools aren't offered here): ${list(missingHere.map((a) => a.label.toLowerCase()))}. If asked, say you can't do that here.`);
27 }
28 if (coming.length) {
29 lines.push(`- Not yet in g1t: ${list(coming.map((a) => a.label.toLowerCase()))}. If asked, say plainly it isn't available yet and offer what you can do instead; never pretend to have done it.`);
30 }
31 return lines.join("\n");
32}
33
34/**
35 * The "Your skills" section: every skill that is on, for the tools this
36 * turn offers. Null when every skill is off.
37 */
38export function skillsSection(off: readonly string[] | null | undefined, offered: Iterable<string>): string | null {
39 const on = skillsOn(off);
40 if (!on.length) return null;
41 const tools = new Set(offered);
42 return [
43 "## Your skills",
44 "",
45 "Playbooks for the work people ask of you, from g1t. They use only the tools you have; they never add one. When a request matches a skill, follow its playbook and deliver the thing itself.",
46 "",
47 on.map((skill) => skillBlock(skill, tools)).join("\n\n"),
48 ].join("\n");
49}