diff --git a/packages/config-yaml/src/load/unroll.test.ts b/packages/config-yaml/src/load/unroll.test.ts index 3357e9cfc3e..7ad08c68293 100644 --- a/packages/config-yaml/src/load/unroll.test.ts +++ b/packages/config-yaml/src/load/unroll.test.ts @@ -4,6 +4,7 @@ import { getTemplateVariables, parseMarkdownRuleOrAssistantUnrolled, replaceInputsWithSecrets, + unrollBlocks, } from "./unroll.js"; describe("parseMarkdownRuleOrAssistantUnrolled tests", () => { @@ -92,6 +93,41 @@ model: # should be models expect(rule).toHaveProperty("globs", undefined); expect(rule).toHaveProperty("rule", dubiousContent); }); + + it("unrolls markdown files used in prompts as prompts", async () => { + const markdownContent = ` +--- +name: scotty +description: A reusable prompt +--- +Generate a concise repair plan. +`; + + const registry = { + getContent: async () => markdownContent, + }; + + const result = await unrollBlocks( + { + name: "Test Agent", + version: "1.0.0", + prompts: [{ uses: "file://prompts/scotty.md" }], + }, + registry, + undefined, + ); + + expect(result.errors).toBeUndefined(); + expect(result.config?.rules).toBeUndefined(); + expect(result.config?.prompts).toEqual([ + { + name: "scotty", + description: "A reusable prompt", + prompt: "Generate a concise repair plan.", + sourceFile: "prompts/scotty.md", + }, + ]); + }); }); describe("replaceInputsWithSecrets tests", () => { diff --git a/packages/config-yaml/src/load/unroll.ts b/packages/config-yaml/src/load/unroll.ts index 44285d7beb9..39f436bce62 100644 --- a/packages/config-yaml/src/load/unroll.ts +++ b/packages/config-yaml/src/load/unroll.ts @@ -22,6 +22,7 @@ import { blockSchema, ConfigYaml, configYamlSchema, + Prompt, Rule, } from "../schemas/index.js"; import { ConfigResult, ConfigValidationError } from "../validation.js"; @@ -448,6 +449,7 @@ export async function unrollBlocks( blockIdentifier, unrolledBlock.with, registry, + section, ); const block = blockConfigYaml[section]?.[0]; if (block) { @@ -514,6 +516,7 @@ export async function unrollBlocks( decodePackageIdentifier(rule.uses), rule.with, registry, + "rules", ); const block = blockConfigYaml.rules?.[0]; return { index, rule: block || null, error: null }; @@ -731,6 +734,7 @@ export async function resolveBlock( id: PackageIdentifier, inputs: Record | undefined, registry: Registry, + sectionHint?: BlockType, ): Promise { // Retrieve block raw yaml const rawYaml = await registry.getContent(id); @@ -764,7 +768,11 @@ export async function resolveBlock( } // Add source slug for mcp servers - const parsed = parseMarkdownRuleOrAssistantUnrolled(templatedYaml, id); + const parsed = parseMarkdownRuleOrAssistantUnrolled( + templatedYaml, + id, + sectionHint, + ); if ( id.uriType === "slug" && "mcpServers" in parsed && @@ -779,8 +787,14 @@ export async function resolveBlock( export function parseMarkdownRuleOrAssistantUnrolled( content: string, id: PackageIdentifier, + sectionHint?: BlockType, ): AssistantUnrolled { - return parseYamlOrMarkdownRule(content, id, parseBlock); + return parseYamlOrMarkdownRule( + content, + id, + parseBlock, + sectionHint, + ); } function parseMarkdownRuleOrConfigYaml( @@ -790,10 +804,21 @@ function parseMarkdownRuleOrConfigYaml( return parseYamlOrMarkdownRule(content, id, parseConfigYaml); } +function markdownToPrompt(content: string, id: PackageIdentifier): Prompt { + const rule = markdownToRule(content, id); + return { + name: rule.name, + description: rule.description, + prompt: rule.rule, + sourceFile: rule.sourceFile, + }; +} + function parseYamlOrMarkdownRule( content: string, id: PackageIdentifier, parseYamlFn: (content: string) => T, + sectionHint?: BlockType, ): T { let parsedYaml: T; try { @@ -808,9 +833,22 @@ function parseYamlOrMarkdownRule( } // If YAML parsing fails, try parsing as markdown rule try { - const rule = markdownToRule(content, id); - // Convert the rule object to the expected format - parsedYaml = { name: rule.name, version: "1.0.0", rules: [rule] } as T; + if (sectionHint === "prompts") { + const prompt = markdownToPrompt(content, id); + parsedYaml = { + name: prompt.name, + version: "1.0.0", + prompts: [prompt], + } as T; + } else { + const rule = markdownToRule(content, id); + // Convert the rule object to the expected format + parsedYaml = { + name: rule.name, + version: "1.0.0", + rules: [rule], + } as T; + } } catch (markdownError) { // If both fail, throw the original YAML error throw yamlError;