-
Notifications
You must be signed in to change notification settings - Fork 276
feat(coding-agent): add template variables to custom system prompts #892
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat(coding-agent): add template variables to custom system prompts #892
Conversation
a4676f6 to
7e06aff
Compare
|
Thanks! This touches a lot of stuff that's been changed in the great refactor, I will integrate this manually once the refactor is complete. |
|
Also need to integrate this #896 |
let me know if you'd like me to tackle that |
|
The Something like this for example: function render(template: string, context: Record<string, any>): string {
// 1. Resolve nested paths (e.g., "user.name")
const resolve = (path: string, ctx: any) =>
path.trim().split('.').reduce((obj, key) => obj?.[key], ctx);
// 2. Handle Logic Blocks: {% if ... %} and {% for ... %}
// This regex matches {% tag condition %} content {% endtag %}
let output = template.replace(
/{%\s+(if|for)\s+(.*?)\s+%}([\s\S]*?){%\s+end\1\s+%}/g,
(_, type, condition, inner) => {
if (type === "if") {
return resolve(condition, context) ? render(inner, context) : "";
} else {
// For loops: expects syntax "item in list"
const [itemKey, , listKey] = condition.split(" ");
const list = resolve(listKey, context);
if (!Array.isArray(list)) return "";
return list
.map((item) => render(inner, { ...context, [itemKey]: item }))
.join("");
}
}
);
// 3. Handle Variable Interpolation: {{ variable }}
return output.replace(/{{(.*?)}}/g, (_, path) => {
const value = resolve(path, context);
return value !== undefined && value !== null ? String(value) : "";
});
}You would use it like this const context = {
cwd: resolvedCwd,
tools: tools.map(t => ({ name: t, desc: toolDescriptions[t] })),
hasSkills: skills.length > 0,
skills: skills,
contextFiles: contextFiles
};
const template = `
You are an expert assistant working in {{ cwd }}.
Available Tools:
{% for tool in tools %}
- {{ tool.name }}: {{ tool.desc }}
{% endfor %}
{% if hasSkills %}
# Skills
{{ skills }}
{% endif %}
# Project Context
{% for file in contextFiles %}
## {{ file.path }}
{{ file.content }}
{% endfor %}
`;
const systemPrompt = render(template, context);Then you move the actual string to a file. Users can even bring custom templates in the future via extensions. |
adds {{tools}}, {{context}}, {{skills}} template variables for SYSTEM.md.
when present, content is injected at variable location.
no template variables = full replacement (no automatic appending).
also tracks injection state so UI only shows 'Loaded context/skills'
when they were actually injected into the system prompt.
7e06aff to
ed90a22
Compare
|
@badlogic rebased/refactored implementation after your resourceloader rework. regarding #896 - i can add that to this pr too, but wanted to check first since it feels like a design choice. currently system prompts aren't stored in sessions at all (no @scutifer yes that approach feels a lot comprehensive, though again design choice. i just want my custom system prompt in there for now, either way works for me. |
|
i'd prefer to have use of nunjucks tbh, means less core churn and more flexibility for end users to extend in their own way |
adds
{{tools}},{{context}},{{skills}}template variables for SYSTEM.md.when present, replaced with dynamic content. no templates = full replacement (current behavior preserved).
also tracks injection metadata through sdk → session → ui, so the welcome screen only shows "loaded context/skills" when actually injected into the system prompt.