mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-18 22:10:52 -07:00
refactor(core): remove synchronous rendering from render-prompt
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, expect, it } from 'vitest';
|
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';
|
import type { PromptContent } from './render-prompt.js';
|
||||||
|
|
||||||
type TestContext = { name?: string; shouldRender?: boolean };
|
type TestContext = { name?: string; shouldRender?: boolean };
|
||||||
@@ -350,24 +350,3 @@ describe('renderPrompt', () => {
|
|||||||
expect(result).toBe(test.expect);
|
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -6,17 +6,11 @@
|
|||||||
|
|
||||||
import type { SystemPromptOptions } from './snippets.js';
|
import type { SystemPromptOptions } from './snippets.js';
|
||||||
|
|
||||||
type MaybePromise<T, Sync extends boolean> = Sync extends true
|
export type ContextResolver<C, O> = O | ((ctx: C) => O | Promise<O>);
|
||||||
? T
|
|
||||||
: T | Promise<T>;
|
|
||||||
|
|
||||||
export type ContextResolver<C, O, Sync extends boolean = false> =
|
|
||||||
| O
|
|
||||||
| ((ctx: C) => MaybePromise<O, Sync>);
|
|
||||||
|
|
||||||
export type PromptSlot = { slot: string; content?: never };
|
export type PromptSlot = { slot: string; content?: never };
|
||||||
|
|
||||||
export type PromptSection<C, Sync extends boolean = false> = {
|
export type PromptSection<C> = {
|
||||||
/** Add a Markdown heading of appropriate level to this section. */
|
/** Add a Markdown heading of appropriate level to this section. */
|
||||||
heading?: string;
|
heading?: string;
|
||||||
/** If supplied, wrap this section in an XML tag. */
|
/** If supplied, wrap this section in an XML tag. */
|
||||||
@@ -32,32 +26,28 @@ export type PromptSection<C, Sync extends boolean = false> = {
|
|||||||
| ((parts: string[]) => string);
|
| ((parts: string[]) => string);
|
||||||
|
|
||||||
/** Condition that must evaluate to true for the section to be rendered. */
|
/** Condition that must evaluate to true for the section to be rendered. */
|
||||||
condition?: boolean | ((ctx: C) => MaybePromise<boolean, Sync>);
|
condition?: boolean | ((ctx: C) => boolean | Promise<boolean>);
|
||||||
content: PromptContent<C, Sync>;
|
content: PromptContent<C>;
|
||||||
/** Alternate content to render if the primary content resolves to a falsy value. */
|
/** Alternate content to render if the primary content resolves to a falsy value. */
|
||||||
fallback?: PromptContent<C, Sync>;
|
fallback?: PromptContent<C>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// The core recursive type.
|
// The core recursive type.
|
||||||
// It wraps your 3 base node shapes (string, section, or array) in the resolver.
|
// It wraps your 3 base node shapes (string, section, or array) in the resolver.
|
||||||
export type PromptContent<C, Sync extends boolean = false> = ContextResolver<
|
export type PromptContent<C> = ContextResolver<
|
||||||
C,
|
C,
|
||||||
| string
|
| string
|
||||||
| number
|
| number
|
||||||
| boolean
|
| boolean
|
||||||
| null
|
| null
|
||||||
| undefined
|
| undefined
|
||||||
| PromptSection<C, Sync>
|
| PromptSection<C>
|
||||||
| PromptSlot
|
| PromptSlot
|
||||||
| Array<PromptContent<C, Sync>>,
|
| Array<PromptContent<C>>
|
||||||
Sync
|
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type BaseContent = string | StaticSection | PromptSlot | BaseContent[];
|
type BaseContent = string | StaticSection | PromptSlot | BaseContent[];
|
||||||
type StaticSection = Omit<
|
type StaticSection = Omit<PromptSection<unknown>, 'condition' | 'content'> & {
|
||||||
PromptSection<unknown, boolean>,
|
|
||||||
'condition' | 'content'
|
|
||||||
> & {
|
|
||||||
content: BaseContent;
|
content: BaseContent;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -69,22 +59,22 @@ function renderAttributes(attrs?: Record<string, string>): string {
|
|||||||
.join('');
|
.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function p<C = SystemPromptOptions, Sync extends boolean = false>(
|
export function p<C = SystemPromptOptions>(
|
||||||
strings: TemplateStringsArray,
|
strings: TemplateStringsArray,
|
||||||
...values: Array<PromptContent<C, Sync>>
|
...values: Array<PromptContent<C>>
|
||||||
): PromptContent<C, Sync> {
|
): PromptContent<C> {
|
||||||
const content = strings.reduce<Array<PromptContent<C, Sync>>>(
|
const content = strings.reduce<Array<PromptContent<C>>>(
|
||||||
(acc, str, i) => [...acc, str, values[i] ?? ''],
|
(acc, str, i) => [...acc, str, values[i] ?? ''],
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
return { format: 'inline', content };
|
return { format: 'inline', content };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RenderPromptOptions<C, Sync extends boolean = false> {
|
export interface RenderPromptOptions<C> {
|
||||||
content: PromptContent<C, Sync> | Array<PromptContent<C, Sync>>;
|
content: PromptContent<C> | Array<PromptContent<C>>;
|
||||||
contributions?:
|
contributions?:
|
||||||
| Record<string, PromptContent<C, Sync>>
|
| Record<string, PromptContent<C>>
|
||||||
| Array<Record<string, PromptContent<C, Sync>>>;
|
| Array<Record<string, PromptContent<C>>>;
|
||||||
context: C;
|
context: C;
|
||||||
options?: { depth?: number };
|
options?: { depth?: number };
|
||||||
}
|
}
|
||||||
@@ -163,9 +153,9 @@ export async function renderPrompt<C = SystemPromptOptions>({
|
|||||||
contributions,
|
contributions,
|
||||||
context,
|
context,
|
||||||
options,
|
options,
|
||||||
}: RenderPromptOptions<C, false>): Promise<string> {
|
}: RenderPromptOptions<C>): Promise<string> {
|
||||||
const contents = Array.isArray(content) ? content : [content];
|
const contents = Array.isArray(content) ? content : [content];
|
||||||
const _contributions: Record<string, Array<PromptContent<C, false>>> = {};
|
const _contributions: Record<string, Array<PromptContent<C>>> = {};
|
||||||
|
|
||||||
if (contributions) {
|
if (contributions) {
|
||||||
const batches = Array.isArray(contributions)
|
const batches = Array.isArray(contributions)
|
||||||
@@ -180,7 +170,7 @@ export async function renderPrompt<C = SystemPromptOptions>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const resolveToBasic = async (
|
const resolveToBasic = async (
|
||||||
c: PromptContent<C, false>,
|
c: PromptContent<C>,
|
||||||
): Promise<BaseContent | null> => {
|
): Promise<BaseContent | null> => {
|
||||||
if (c === undefined || c === null) return null;
|
if (c === undefined || c === null) return null;
|
||||||
if (typeof c === 'function') {
|
if (typeof c === 'function') {
|
||||||
@@ -269,128 +259,8 @@ export async function renderPrompt<C = SystemPromptOptions>({
|
|||||||
return normalizeResult(rawResult);
|
return normalizeResult(rawResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderPromptSync<C = SystemPromptOptions>({
|
export function prompt<C = SystemPromptOptions>(
|
||||||
content,
|
...content: Array<PromptContent<C>>
|
||||||
contributions,
|
): PromptContent<C> {
|
||||||
context,
|
|
||||||
options,
|
|
||||||
}: RenderPromptOptions<C, true>): string {
|
|
||||||
const contents = Array.isArray(content) ? content : [content];
|
|
||||||
const _contributions: Record<string, Array<PromptContent<C, true>>> = {};
|
|
||||||
|
|
||||||
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<C, true>,
|
|
||||||
): 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<string, BaseContent[]> = {};
|
|
||||||
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<C = SystemPromptOptions, Sync extends boolean = false>(
|
|
||||||
...content: Array<PromptContent<C, Sync>>
|
|
||||||
): PromptContent<C, Sync> {
|
|
||||||
return content.length === 1 ? content[0] : content;
|
return content.length === 1 ? content[0] : content;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user