Studio: prompt variables into prompt editor (#6434)

* add custom and system variable feature in system prompt

* missing function use

* feat: prompt variables editor ux

Co-authored-by: CodeMan62 <175127021+CodeMan62@users.noreply.github.com>

* fix: guard prompt variable defaults

* refine prompt variables editor layout

* fix: harden prompt variable substitution

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Refine prompt variables editor copy and built-in token labels

---------

Co-authored-by: CodeMan62 <sharmahimanshu15082007@gmail.com>
Co-authored-by: CodeMan62 <175127021+CodeMan62@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Leo Borcherding <borchborchmail@gmail.com>
This commit is contained in:
Lee Jackson 2026-06-25 18:09:16 +01:00 committed by GitHub
commit c873ef052d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 322 additions and 16 deletions

View file

@ -185,6 +185,124 @@ function wait(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function parseSystemVariablesMap(raw: string): Record<string, unknown> {
if (!raw.trim()) {
return {};
}
try {
const parsed = JSON.parse(raw) as unknown;
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
return parsed as Record<string, unknown>;
}
} catch {
// Invalid JSON: keep unresolved placeholders in output prompt.
}
return {};
}
function hasOwn(object: object, key: string): boolean {
return Object.prototype.hasOwnProperty.call(object, key);
}
function getNestedValue(
values: Record<string, unknown>,
path: string,
): unknown | undefined {
const parts = path.split(".").map((part) => part.trim()).filter(Boolean);
if (parts.length === 0) {
return undefined;
}
let current: unknown = values;
for (const part of parts) {
if (!current || typeof current !== "object" || Array.isArray(current)) {
return undefined;
}
if (!hasOwn(current, part)) {
return undefined;
}
current = (current as Record<string, unknown>)[part];
}
return current;
}
function padDatePart(value: number): string {
return String(value).padStart(2, "0");
}
function formatLocalDate(now: Date): string {
return [
now.getFullYear(),
padDatePart(now.getMonth() + 1),
padDatePart(now.getDate()),
].join("-");
}
function formatLocalTime(now: Date): string {
return [
padDatePart(now.getHours()),
padDatePart(now.getMinutes()),
padDatePart(now.getSeconds()),
].join(":");
}
function formatTimezoneOffset(now: Date): string {
const offsetMinutes = -now.getTimezoneOffset();
const sign = offsetMinutes >= 0 ? "+" : "-";
const abs = Math.abs(offsetMinutes);
const hours = Math.floor(abs / 60);
const minutes = abs % 60;
return `${sign}${padDatePart(hours)}:${padDatePart(minutes)}`;
}
function stringifyTemplateValue(value: unknown): string {
if (value == null) {
return "";
}
if (typeof value === "string") {
return value;
}
if (typeof value === "number" || typeof value === "boolean") {
return String(value);
}
try {
return JSON.stringify(value);
} catch {
return String(value);
}
}
function resolveSystemPromptVariables(
prompt: string,
customVariablesRaw: string,
): string {
if (!prompt) {
return prompt;
}
const now = new Date();
const localDate = formatLocalDate(now);
const localTime = formatLocalTime(now);
const systemVariables: Record<string, string> = {
$date: localDate,
$time: localTime,
$now: `${localDate}T${localTime}${formatTimezoneOffset(now)}`,
};
const customVariables = parseSystemVariablesMap(customVariablesRaw);
return prompt.replaceAll(
/{{\s*([a-zA-Z_$][a-zA-Z0-9_$.-]*)\s*}}/g,
(full, keyRaw) => {
const key = String(keyRaw).trim();
if (hasOwn(systemVariables, key)) {
return systemVariables[key] ?? full;
}
const resolved = getNestedValue(customVariables, key);
if (resolved === undefined) {
return full;
}
return stringifyTemplateValue(resolved);
},
);
}
export const ThreadAutosaveHandle: ThreadAutosaveHandle = {
registerFirstSave(threadId, promise) {
const trackedPromise = promise.catch(() => {});
@ -1778,7 +1896,14 @@ export function createOpenAIStreamAdapter(): ChatModelAdapter {
}
const safeSystemPrompt =
typeof params.systemPrompt === "string" ? params.systemPrompt : "";
typeof params.systemPrompt === "string"
? resolveSystemPromptVariables(
params.systemPrompt,
typeof params.systemVariables === "string"
? params.systemVariables
: "",
)
: "";
const projectInstructions =
await resolveProjectInstructions(resolvedThreadId);
const combinedSystemPrompt = [