mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-01 14:42:51 -07:00
feat(core): enhance and consolidate prompter library into render-prompt.ts
This commit is contained in:
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
export type ContextResolver<C, O> = O | ((ctx: C) => O | Promise<O>);
|
||||
|
||||
export type PromptSlot = { slot: string; content?: never };
|
||||
|
||||
export type PromptSection<C> = {
|
||||
/** Add a Markdown heading of appropriate level to this section. */
|
||||
heading?: string;
|
||||
/** If supplied, wrap this section in an XML tag. */
|
||||
tag?: string;
|
||||
/** If supplied, add attributes to the XML section tag. */
|
||||
attrs?: Record<string, string>;
|
||||
/** Formatting of the content inside this section. Defaults to 'block'. */
|
||||
format?: 'inline' | 'block';
|
||||
|
||||
/** Condition that must evaluate to true for the section to be rendered. */
|
||||
condition?: (ctx: C) => boolean | Promise<boolean>;
|
||||
content: PromptContent<C>;
|
||||
};
|
||||
|
||||
// The core recursive type.
|
||||
// It wraps your 3 base node shapes (string, section, or array) in the resolver.
|
||||
export type PromptContent<C> = ContextResolver<
|
||||
C,
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| PromptSection<C>
|
||||
| PromptSlot
|
||||
| Array<PromptContent<C>>
|
||||
>;
|
||||
+38
-2
@@ -5,8 +5,8 @@
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { renderPrompt, p } from './prompter.js';
|
||||
import type { PromptContent } from './types.js';
|
||||
import { renderPrompt, p } from './render-prompt.js';
|
||||
import type { PromptContent } from './render-prompt.js';
|
||||
|
||||
type TestContext = { name?: string; shouldRender?: boolean };
|
||||
|
||||
@@ -215,6 +215,42 @@ const tests: TestCase[] = [
|
||||
context: {},
|
||||
expect: 'Prefix: FirstSecond',
|
||||
},
|
||||
{
|
||||
desc: 'conditionally omits items when null or undefined are present',
|
||||
content: [
|
||||
'Item 1',
|
||||
null,
|
||||
'Item 2',
|
||||
undefined,
|
||||
{
|
||||
heading: 'Optional Section',
|
||||
content: null,
|
||||
},
|
||||
'Item 3',
|
||||
],
|
||||
context: {},
|
||||
expect: 'Item 1\n\nItem 2\n\nItem 3',
|
||||
},
|
||||
{
|
||||
desc: 'renders lists with dashes',
|
||||
content: {
|
||||
heading: 'My List',
|
||||
format: 'list',
|
||||
content: ['Apple', 'Banana', null, 'Cherry'],
|
||||
},
|
||||
context: {},
|
||||
expect: '# My List\n\n- Apple\n- Banana\n- Cherry',
|
||||
},
|
||||
{
|
||||
desc: 'supports custom format functions',
|
||||
content: {
|
||||
heading: 'Custom Format',
|
||||
format: (parts) => parts.join(' | '),
|
||||
content: ['A', 'B', undefined, 'C'],
|
||||
},
|
||||
context: {},
|
||||
expect: '# Custom Format\n\nA | B | C',
|
||||
},
|
||||
];
|
||||
|
||||
describe('renderPrompt', () => {
|
||||
+46
-6
@@ -4,15 +4,47 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { SystemPromptOptions } from 'src/prompts/snippets.js';
|
||||
import type { PromptContent, PromptSlot } from './types.js';
|
||||
import type { SystemPromptOptions } from './snippets.js';
|
||||
|
||||
export type ContextResolver<C, O> = O | ((ctx: C) => O | Promise<O>);
|
||||
|
||||
export type PromptSlot = { slot: string; content?: never };
|
||||
|
||||
export type PromptSection<C> = {
|
||||
/** Add a Markdown heading of appropriate level to this section. */
|
||||
heading?: string;
|
||||
/** If supplied, wrap this section in an XML tag. */
|
||||
tag?: string;
|
||||
/** If supplied, add attributes to the XML section tag. */
|
||||
attrs?: Record<string, string>;
|
||||
/** Formatting of the content inside this section. Defaults to 'block'. */
|
||||
format?: 'inline' | 'block' | 'list' | ((parts: string[]) => string);
|
||||
|
||||
/** Condition that must evaluate to true for the section to be rendered. */
|
||||
condition?: (ctx: C) => boolean | Promise<boolean>;
|
||||
content: PromptContent<C>;
|
||||
};
|
||||
|
||||
// The core recursive type.
|
||||
// It wraps your 3 base node shapes (string, section, or array) in the resolver.
|
||||
export type PromptContent<C> = ContextResolver<
|
||||
C,
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
| undefined
|
||||
| PromptSection<C>
|
||||
| PromptSlot
|
||||
| Array<PromptContent<C>>
|
||||
>;
|
||||
|
||||
type BaseContent = string | BaseSection | PromptSlot | BaseContent[];
|
||||
type BaseSection = {
|
||||
heading?: string;
|
||||
tag?: string;
|
||||
attrs?: Record<string, string>;
|
||||
format?: 'inline' | 'block';
|
||||
format?: 'inline' | 'block' | 'list' | ((parts: string[]) => string);
|
||||
content: BaseContent;
|
||||
};
|
||||
|
||||
@@ -68,6 +100,7 @@ export async function renderPrompt<C = SystemPromptOptions>({
|
||||
const resolveToBasic = async (
|
||||
c: PromptContent<C>,
|
||||
): Promise<BaseContent | null> => {
|
||||
if (c === undefined || c === null) return null;
|
||||
if (typeof c === 'function') {
|
||||
const resolved = await c(context);
|
||||
return resolveToBasic(resolved);
|
||||
@@ -133,14 +166,21 @@ export async function renderPrompt<C = SystemPromptOptions>({
|
||||
const formatBasic = (
|
||||
c: BaseContent | null,
|
||||
depth: number,
|
||||
format: 'inline' | 'block',
|
||||
format: 'inline' | 'block' | 'list' | ((parts: string[]) => string),
|
||||
): string => {
|
||||
if (c === null) return '';
|
||||
if (typeof c === 'string') return c;
|
||||
if (Array.isArray(c)) {
|
||||
return c
|
||||
const parts = c
|
||||
.map((item) => formatBasic(item, depth, format))
|
||||
.join(format === 'inline' ? '' : '\n\n');
|
||||
.filter((p) => p !== '');
|
||||
if (typeof format === 'function') {
|
||||
return format(parts);
|
||||
}
|
||||
if (format === 'list') {
|
||||
return parts.map((p) => '- ' + p).join('\n');
|
||||
}
|
||||
return parts.join(format === 'inline' ? '' : '\n\n');
|
||||
}
|
||||
if ('slot' in c) {
|
||||
const slotContributions = resolvedContributions[c.slot];
|
||||
Reference in New Issue
Block a user