diff --git a/packages/core/src/prompts/render-prompt.test.ts b/packages/core/src/prompts/render-prompt.test.ts index 0cd2e9f6cb..270e5976a2 100644 --- a/packages/core/src/prompts/render-prompt.test.ts +++ b/packages/core/src/prompts/render-prompt.test.ts @@ -5,7 +5,7 @@ */ import { describe, expect, it } from 'vitest'; -import { renderPrompt, renderPromptSync, p } from './render-prompt.js'; +import { renderPrompt, p } from './render-prompt.js'; import type { PromptContent } from './render-prompt.js'; type TestContext = { name?: string; shouldRender?: boolean }; @@ -350,24 +350,3 @@ describe('renderPrompt', () => { expect(result).toBe(test.expect); }); }); - -describe('renderPromptSync', () => { - const syncTests = tests.filter( - (t) => - !t.desc.includes('async') && - !t.desc.includes('Promise') && - !t.desc.includes('resolves recursive async functions') && - !t.desc.includes('async condition'), - ); - - it.each(syncTests)('$desc', (test) => { - const result = renderPromptSync({ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - content: test.content as any, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - contributions: test.contributions as any, - context: test.context, - }); - expect(result).toBe(test.expect); - }); -}); diff --git a/packages/core/src/prompts/render-prompt.ts b/packages/core/src/prompts/render-prompt.ts index eaaaa72b5f..70fb7c773d 100644 --- a/packages/core/src/prompts/render-prompt.ts +++ b/packages/core/src/prompts/render-prompt.ts @@ -6,17 +6,11 @@ import type { SystemPromptOptions } from './snippets.js'; -type MaybePromise = Sync extends true - ? T - : T | Promise; - -export type ContextResolver = - | O - | ((ctx: C) => MaybePromise); +export type ContextResolver = O | ((ctx: C) => O | Promise); export type PromptSlot = { slot: string; content?: never }; -export type PromptSection = { +export type PromptSection = { /** Add a Markdown heading of appropriate level to this section. */ heading?: string; /** If supplied, wrap this section in an XML tag. */ @@ -32,32 +26,28 @@ export type PromptSection = { | ((parts: string[]) => string); /** Condition that must evaluate to true for the section to be rendered. */ - condition?: boolean | ((ctx: C) => MaybePromise); - content: PromptContent; + condition?: boolean | ((ctx: C) => boolean | Promise); + content: PromptContent; /** Alternate content to render if the primary content resolves to a falsy value. */ - fallback?: PromptContent; + fallback?: PromptContent; }; // The core recursive type. // It wraps your 3 base node shapes (string, section, or array) in the resolver. -export type PromptContent = ContextResolver< +export type PromptContent = ContextResolver< C, | string | number | boolean | null | undefined - | PromptSection + | PromptSection | PromptSlot - | Array>, - Sync + | Array> >; type BaseContent = string | StaticSection | PromptSlot | BaseContent[]; -type StaticSection = Omit< - PromptSection, - 'condition' | 'content' -> & { +type StaticSection = Omit, 'condition' | 'content'> & { content: BaseContent; }; @@ -69,22 +59,22 @@ function renderAttributes(attrs?: Record): string { .join(''); } -export function p( +export function p( strings: TemplateStringsArray, - ...values: Array> -): PromptContent { - const content = strings.reduce>>( + ...values: Array> +): PromptContent { + const content = strings.reduce>>( (acc, str, i) => [...acc, str, values[i] ?? ''], [], ); return { format: 'inline', content }; } -export interface RenderPromptOptions { - content: PromptContent | Array>; +export interface RenderPromptOptions { + content: PromptContent | Array>; contributions?: - | Record> - | Array>>; + | Record> + | Array>>; context: C; options?: { depth?: number }; } @@ -163,9 +153,9 @@ export async function renderPrompt({ contributions, context, options, -}: RenderPromptOptions): Promise { +}: RenderPromptOptions): Promise { const contents = Array.isArray(content) ? content : [content]; - const _contributions: Record>> = {}; + const _contributions: Record>> = {}; if (contributions) { const batches = Array.isArray(contributions) @@ -180,7 +170,7 @@ export async function renderPrompt({ } const resolveToBasic = async ( - c: PromptContent, + c: PromptContent, ): Promise => { if (c === undefined || c === null) return null; if (typeof c === 'function') { @@ -269,128 +259,8 @@ export async function renderPrompt({ return normalizeResult(rawResult); } -export function renderPromptSync({ - content, - contributions, - context, - options, -}: RenderPromptOptions): string { - const contents = Array.isArray(content) ? content : [content]; - const _contributions: Record>> = {}; - - if (contributions) { - const batches = Array.isArray(contributions) - ? contributions - : [contributions]; - for (const batch of batches) { - for (const [slot, c] of Object.entries(batch)) { - _contributions[slot] = _contributions[slot] || []; - _contributions[slot].push(c); - } - } - } - - const resolveToBasicSync = ( - c: PromptContent, - ): BaseContent | null => { - if (c === undefined || c === null) return null; - if (typeof c === 'function') { - const resolved = c(context); - // Extra safety check at runtime for JS users - if (resolved instanceof Promise) { - throw new Error( - 'renderPromptSync encountered a Promise from a resolver function.', - ); - } - return resolveToBasicSync(resolved); - } - if (typeof c === 'boolean') { - return null; - } - if (typeof c === 'string' || typeof c === 'number') { - const val = String(c); - return val === '' ? null : val; - } - if (Array.isArray(c)) { - const resolved = c.map((item) => resolveToBasicSync(item)); - const filtered = resolved.filter( - (item): item is BaseContent => item !== null, - ); - if (filtered.length === 0) return null; - return filtered; - } - if (typeof c === 'object' && c !== null) { - if ('slot' in c) { - return c; - } - - const section = c; - if (section.condition !== undefined) { - let shouldRender; - if (typeof section.condition === 'function') { - shouldRender = section.condition(context); - if ((shouldRender as unknown) instanceof Promise) { - throw new Error( - 'renderPromptSync encountered a Promise from a condition function.', - ); - } - } else { - shouldRender = section.condition; - } - if (!shouldRender) return null; - } - 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 === '' || - (Array.isArray(resolvedInner) && resolvedInner.length === 0) - ) { - return null; - } - return { - heading: section.heading, - tag: section.tag, - attrs: section.attrs, - format: section.format, - content: resolvedInner, - }; - } - return null; - }; - - const resolvedContents = contents.map((c) => resolveToBasicSync(c)); - - const resolvedContributions: Record = {}; - for (const [slot, slotContributions] of Object.entries(_contributions)) { - const resolved = slotContributions.map((c) => resolveToBasicSync(c)); - resolvedContributions[slot] = resolved.filter( - (c): c is BaseContent => c !== null, - ); - } - - const parts = resolvedContents - .map((c) => - formatBasic(c, options?.depth ?? 1, 'block', resolvedContributions), - ) - .filter((p) => p !== null && p !== ''); - - const rawResult = parts.join('\n\n').trim(); - return normalizeResult(rawResult); -} - -export function prompt( - ...content: Array> -): PromptContent { +export function prompt( + ...content: Array> +): PromptContent { return content.length === 1 ? content[0] : content; }