diff --git a/packages/core/src/prompts/render-prompt.test.ts b/packages/core/src/prompts/render-prompt.test.ts index a48ac93fc0..0cd2e9f6cb 100644 --- a/packages/core/src/prompts/render-prompt.test.ts +++ b/packages/core/src/prompts/render-prompt.test.ts @@ -284,6 +284,60 @@ const tests: TestCase[] = [ context: {}, expect: '# Lines List\n\nLine 1\nLine 2\nLine 3', }, + { + desc: 'renders fallback when content is falsy (boolean)', + content: { + heading: 'Fallback Boolean', + content: false, + fallback: 'This is the fallback', + }, + context: {}, + expect: '# Fallback Boolean\n\nThis is the fallback', + }, + { + desc: 'renders fallback when content is falsy (empty string via short-circuit)', + content: { + heading: 'Fallback Empty String', + content: String('') && 'something', + fallback: 'Fallback for empty string', + }, + context: {}, + expect: '# Fallback Empty String\n\nFallback for empty string', + }, + { + desc: 'renders fallback when content is an array that filters down to empty', + content: { + heading: 'Fallback Empty Array', + content: [false, null, '', undefined], + fallback: 'Fallback for empty array', + }, + context: {}, + expect: '# Fallback Empty Array\n\nFallback for empty array', + }, + { + desc: 'renders normal content and ignores fallback when content is present', + content: { + heading: 'No Fallback', + content: 'Primary content', + fallback: 'Should not see this', + }, + context: {}, + expect: '# No Fallback\n\nPrimary content', + }, + { + desc: 'does not render section if both content and fallback are falsy', + content: [ + 'Visible start', + { + heading: 'Double Falsy', + content: '', + fallback: null, + }, + 'Visible end', + ], + context: {}, + expect: 'Visible start\n\nVisible end', + }, ]; describe('renderPrompt', () => { diff --git a/packages/core/src/prompts/render-prompt.ts b/packages/core/src/prompts/render-prompt.ts index da9a7d0523..eaaaa72b5f 100644 --- a/packages/core/src/prompts/render-prompt.ts +++ b/packages/core/src/prompts/render-prompt.ts @@ -34,6 +34,8 @@ export type PromptSection = { /** Condition that must evaluate to true for the section to be rendered. */ condition?: boolean | ((ctx: C) => MaybePromise); content: PromptContent; + /** Alternate content to render if the primary content resolves to a falsy value. */ + fallback?: PromptContent; }; // The core recursive type. @@ -189,7 +191,8 @@ export async function renderPrompt({ return null; } if (typeof c === 'string' || typeof c === 'number') { - return String(c); + const val = String(c); + return val === '' ? null : val; } if (Array.isArray(c)) { const resolved = await Promise.all(c.map((item) => resolveToBasic(item))); @@ -212,7 +215,18 @@ export async function renderPrompt({ : section.condition; if (!shouldRender) return null; } - const resolvedInner = await resolveToBasic(section.content); + let resolvedInner = await resolveToBasic(section.content); + + if ( + resolvedInner === null || + resolvedInner === '' || + (Array.isArray(resolvedInner) && resolvedInner.length === 0) + ) { + if (section.fallback !== undefined) { + resolvedInner = await resolveToBasic(section.fallback); + } + } + if ( resolvedInner === null || resolvedInner === '' || @@ -294,7 +308,8 @@ export function renderPromptSync({ return null; } if (typeof c === 'string' || typeof c === 'number') { - return String(c); + const val = String(c); + return val === '' ? null : val; } if (Array.isArray(c)) { const resolved = c.map((item) => resolveToBasicSync(item)); @@ -324,7 +339,18 @@ export function renderPromptSync({ } if (!shouldRender) return null; } - const resolvedInner = resolveToBasicSync(section.content); + let resolvedInner = resolveToBasicSync(section.content); + + if ( + resolvedInner === null || + resolvedInner === '' || + (Array.isArray(resolvedInner) && resolvedInner.length === 0) + ) { + if (section.fallback !== undefined) { + resolvedInner = resolveToBasicSync(section.fallback); + } + } + if ( resolvedInner === null || resolvedInner === '' || @@ -366,6 +392,5 @@ export function renderPromptSync({ export function prompt( ...content: Array> ): PromptContent { - - return (content.length === 1 ? content[0] : content); + return content.length === 1 ? content[0] : content; }