Skip to content

Commit 48fa3d7

Browse files
committed
fix(core): preserve dollar sequences in prompt template substitutions
1 parent c22137e commit 48fa3d7

2 files changed

Lines changed: 41 additions & 4 deletions

File tree

packages/core/src/prompts/utils.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
} from './utils.js';
1414
import type { Config } from '../config/config.js';
1515
import type { ToolRegistry } from '../tools/tool-registry.js';
16+
import * as snippets from './snippets.js';
1617

1718
vi.mock('../utils/paths.js', () => ({
1819
homedir: vi.fn().mockReturnValue('/mock/home'),
@@ -312,4 +313,40 @@ describe('applySubstitutions', () => {
312313
);
313314
expect(result).toBe('A plain prompt with no variables.');
314315
});
316+
317+
it('should preserve dollar sequences in ${AgentSkills} verbatim', () => {
318+
const result = applySubstitutions(
319+
'Skills: ${AgentSkills} | Tail',
320+
mockConfig,
321+
"echo $'a\\nb' and $$ and $& and $VAR",
322+
);
323+
expect(result).toBe("Skills: echo $'a\\nb' and $$ and $& and $VAR | Tail");
324+
});
325+
326+
it('should preserve dollar sequences in ${SubAgents} verbatim', () => {
327+
vi.mocked(snippets.renderSubAgents).mockReturnValueOnce(
328+
"echo $'a\\nb' and $$ and $&",
329+
);
330+
const result = applySubstitutions(
331+
'Agents: ${SubAgents} | Tail',
332+
mockConfig,
333+
'',
334+
true,
335+
);
336+
expect(result).toBe("Agents: echo $'a\\nb' and $$ and $& | Tail");
337+
});
338+
339+
it('should preserve dollar sequences in ${AvailableTools} verbatim', () => {
340+
(mockConfig as unknown as { toolRegistry: ToolRegistry }).toolRegistry = {
341+
getAllToolNames: vi.fn().mockReturnValue(["echo $'a\\nb'", '$$', '$&']),
342+
getAllTools: vi.fn().mockReturnValue([]),
343+
} as unknown as ToolRegistry;
344+
345+
const result = applySubstitutions(
346+
'Tools: ${AvailableTools} | Tail',
347+
mockConfig,
348+
'',
349+
);
350+
expect(result).toContain("- echo $'a\\nb'\n- $$\n- $& | Tail");
351+
});
315352
});

packages/core/src/prompts/utils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export function applySubstitutions(
6969
): string {
7070
let result = prompt;
7171

72-
result = result.replace(/\${AgentSkills}/g, skillsPrompt);
72+
result = result.replace(/\${AgentSkills}/g, () => skillsPrompt);
7373

7474
const activeSnippets = isGemini3 ? snippets : legacySnippets;
7575
const subAgentsContent = activeSnippets.renderSubAgents(
@@ -82,21 +82,21 @@ export function applySubstitutions(
8282
})),
8383
);
8484

85-
result = result.replace(/\${SubAgents}/g, subAgentsContent);
85+
result = result.replace(/\${SubAgents}/g, () => subAgentsContent);
8686

8787
const toolRegistry = context.toolRegistry;
8888
const allToolNames = toolRegistry.getAllToolNames();
8989
const availableToolsList =
9090
allToolNames.length > 0
9191
? allToolNames.map((name) => `- ${name}`).join('\n')
9292
: 'No tools are currently available.';
93-
result = result.replace(/\${AvailableTools}/g, availableToolsList);
93+
result = result.replace(/\${AvailableTools}/g, () => availableToolsList);
9494

9595
for (const toolName of allToolNames) {
9696
const varName = `${toolName}_ToolName`;
9797
result = result.replace(
9898
new RegExp(`\\\${\\b${varName}\\b}`, 'g'),
99-
toolName,
99+
() => toolName,
100100
);
101101
}
102102

0 commit comments

Comments
 (0)