refactor(agent): make resource invocation explicit
This commit is contained in:
@@ -5,6 +5,7 @@ import type { AgentEvent, AgentMessage, AgentTool, ThinkingLevel } from "../type
|
|||||||
import { collectEntriesForBranchSummary, generateBranchSummary } from "./compaction/branch-summarization.js";
|
import { collectEntriesForBranchSummary, generateBranchSummary } from "./compaction/branch-summarization.js";
|
||||||
import { compact, DEFAULT_COMPACTION_SETTINGS, prepareCompaction } from "./compaction/compaction.js";
|
import { compact, DEFAULT_COMPACTION_SETTINGS, prepareCompaction } from "./compaction/compaction.js";
|
||||||
import { expandPromptTemplate } from "./prompt-templates.js";
|
import { expandPromptTemplate } from "./prompt-templates.js";
|
||||||
|
import { expandSkillCommand } from "./skills.js";
|
||||||
import type {
|
import type {
|
||||||
AbortResult,
|
AbortResult,
|
||||||
AgentHarnessContext,
|
AgentHarnessContext,
|
||||||
@@ -18,7 +19,6 @@ import type {
|
|||||||
ExecutionEnv,
|
ExecutionEnv,
|
||||||
NavigateTreeResult,
|
NavigateTreeResult,
|
||||||
Session,
|
Session,
|
||||||
Skill,
|
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
|
|
||||||
function createUserMessage(text: string, images?: ImageContent[]): AgentMessage {
|
function createUserMessage(text: string, images?: ImageContent[]): AgentMessage {
|
||||||
@@ -297,11 +297,7 @@ export class AgentHarness {
|
|||||||
this.operation.idle = false;
|
this.operation.idle = false;
|
||||||
this.operation.liveOperationId = randomUUID();
|
this.operation.liveOperationId = randomUUID();
|
||||||
const resources = await this.resolveResources(this.agent.signal);
|
const resources = await this.resolveResources(this.agent.signal);
|
||||||
const expanded = this.expandSkillCommand(
|
let messages: AgentMessage[] = [createUserMessage(text, options?.images)];
|
||||||
expandPromptTemplate(text, resources.promptTemplates ?? []),
|
|
||||||
resources.skills ?? [],
|
|
||||||
);
|
|
||||||
let messages: AgentMessage[] = [createUserMessage(expanded, options?.images)];
|
|
||||||
if (this.conversation.nextTurnQueue.length > 0) {
|
if (this.conversation.nextTurnQueue.length > 0) {
|
||||||
messages = [messages[0]!, ...this.conversation.nextTurnQueue];
|
messages = [messages[0]!, ...this.conversation.nextTurnQueue];
|
||||||
this.conversation.nextTurnQueue = [];
|
this.conversation.nextTurnQueue = [];
|
||||||
@@ -312,7 +308,7 @@ export class AgentHarness {
|
|||||||
"before_agent_start",
|
"before_agent_start",
|
||||||
{
|
{
|
||||||
type: "before_agent_start",
|
type: "before_agent_start",
|
||||||
prompt: expanded,
|
prompt: text,
|
||||||
images: options?.images,
|
images: options?.images,
|
||||||
systemPrompt: this.agent.state.systemPrompt,
|
systemPrompt: this.agent.state.systemPrompt,
|
||||||
resources,
|
resources,
|
||||||
@@ -335,12 +331,18 @@ export class AgentHarness {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
async skill(name: string, args?: string): Promise<AssistantMessage> {
|
async skill(name: string, additionalInstructions?: string): Promise<AssistantMessage> {
|
||||||
const resources = await this.resolveResources();
|
const resources = await this.resolveResources();
|
||||||
const skill = (resources.skills ?? []).find((candidate) => candidate.name === name);
|
const skill = (resources.skills ?? []).find((candidate) => candidate.name === name);
|
||||||
if (!skill) throw new Error(`Unknown skill: ${name}`);
|
if (!skill) throw new Error(`Unknown skill: ${name}`);
|
||||||
const prompt = args ? `${skill.content}\n\n${args}` : skill.content;
|
return await this.prompt(expandSkillCommand(skill, additionalInstructions));
|
||||||
return await this.prompt(prompt);
|
}
|
||||||
|
|
||||||
|
async promptFromTemplate(name: string, args: string[] = []): Promise<AssistantMessage> {
|
||||||
|
const resources = await this.resolveResources();
|
||||||
|
const template = (resources.promptTemplates ?? []).find((candidate) => candidate.name === name);
|
||||||
|
if (!template) throw new Error(`Unknown prompt template: ${name}`);
|
||||||
|
return await this.prompt(expandPromptTemplate(template, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
steer(message: AgentMessage): void {
|
steer(message: AgentMessage): void {
|
||||||
@@ -601,14 +603,4 @@ export class AgentHarness {
|
|||||||
handlers.add(handler as any);
|
handlers.add(handler as any);
|
||||||
return () => handlers!.delete(handler as any);
|
return () => handlers!.delete(handler as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
private expandSkillCommand(text: string, skills: Skill[]): string {
|
|
||||||
if (!text.startsWith("/skill:")) return text;
|
|
||||||
const spaceIndex = text.indexOf(" ");
|
|
||||||
const skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);
|
|
||||||
const args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim();
|
|
||||||
const skill = skills.find((candidate) => candidate.name === skillName);
|
|
||||||
if (!skill) return text;
|
|
||||||
return args ? `${skill.content}\n\n${args}` : skill.content;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,13 +211,7 @@ export function substituteArgs(content: string, args: string[]): string {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Expand `/template args` text using the matching prompt template, or return the original text when no template matches. */
|
/** Expand a prompt template with positional command arguments. */
|
||||||
export function expandPromptTemplate(text: string, templates: PromptTemplate[]): string {
|
export function expandPromptTemplate(template: PromptTemplate, args: string[] = []): string {
|
||||||
if (!text.startsWith("/")) return text;
|
return substituteArgs(template.content, args);
|
||||||
const spaceIndex = text.indexOf(" ");
|
|
||||||
const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
|
|
||||||
const argsString = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1);
|
|
||||||
const template = templates.find((candidate) => candidate.name === commandName);
|
|
||||||
if (!template) return text;
|
|
||||||
return substituteArgs(template.content, parseCommandArgs(argsString));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ interface SkillFrontmatter {
|
|||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Expand a skill into a prompt, optionally appending additional user instructions. */
|
||||||
|
export function expandSkillCommand(skill: Skill, additionalInstructions?: string): string {
|
||||||
|
const skillBlock = `<skill name="${skill.name}" location="${skill.filePath}">\nReferences are relative to ${dirnameEnvPath(skill.filePath)}.\n\n${skill.content}\n</skill>`;
|
||||||
|
return additionalInstructions ? `${skillBlock}\n\n${additionalInstructions}` : skillBlock;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load skills from one or more directories.
|
* Load skills from one or more directories.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ describe("loadPromptTemplates", () => {
|
|||||||
describe("expandPromptTemplate", () => {
|
describe("expandPromptTemplate", () => {
|
||||||
it("substitutes command arguments", () => {
|
it("substitutes command arguments", () => {
|
||||||
const content = "$1 $" + "{@:2} $ARGUMENTS";
|
const content = "$1 $" + "{@:2} $ARGUMENTS";
|
||||||
expect(expandPromptTemplate('/one "hello world" test', [{ name: "one", content }])).toBe(
|
expect(expandPromptTemplate({ name: "one", content }, ["hello world", "test"])).toBe(
|
||||||
"hello world test hello world test",
|
"hello world test hello world test",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
24
packages/agent/test/harness/resource-expansion.test.ts
Normal file
24
packages/agent/test/harness/resource-expansion.test.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { expandPromptTemplate } from "../../src/harness/prompt-templates.js";
|
||||||
|
import { expandSkillCommand } from "../../src/harness/skills.js";
|
||||||
|
|
||||||
|
describe("resource expansion helpers", () => {
|
||||||
|
it("expands skills with additional instructions", () => {
|
||||||
|
const skill = {
|
||||||
|
name: "inspect",
|
||||||
|
description: "Inspect things",
|
||||||
|
content: "Use inspection tools.",
|
||||||
|
filePath: "/project/.pi/skills/inspect/SKILL.md",
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(expandSkillCommand(skill, "Check errors.")).toBe(
|
||||||
|
'<skill name="inspect" location="/project/.pi/skills/inspect/SKILL.md">\nReferences are relative to /project/.pi/skills/inspect.\n\nUse inspection tools.\n</skill>\n\nCheck errors.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("expands prompt templates with positional arguments", () => {
|
||||||
|
expect(expandPromptTemplate({ name: "review", content: "Review $1 with $ARGUMENTS" }, ["a.ts", "care"])).toBe(
|
||||||
|
"Review a.ts with a.ts care",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user