Files
openclaw/src/auto-reply/reply/response-prefix-template.ts
Gustavo Madeira Santana 4629054403 chore: apply local workspace updates (#9911)
* chore: apply local workspace updates

* fix: resolve prep findings after rebase (#9898) (thanks @gumadeiras)

* refactor: centralize model allowlist normalization (#9898) (thanks @gumadeiras)

* fix: guard model allowlist initialization (#9911)

* docs: update changelog scope for #9911

* docs: remove model names from changelog entry (#9911)

* fix: satisfy type-aware lint in model allowlist (#9911)
2026-02-05 16:54:44 -05:00

102 lines
3.1 KiB
TypeScript

/**
* Template interpolation for response prefix.
*
* Supports variables like `{model}`, `{provider}`, `{thinkingLevel}`, etc.
* Variables are case-insensitive and unresolved ones remain as literal text.
*/
export type ResponsePrefixContext = {
/** Short model name (e.g., "gpt-5.2", "claude-opus-4-6") */
model?: string;
/** Full model ID including provider (e.g., "openai-codex/gpt-5.2") */
modelFull?: string;
/** Provider name (e.g., "openai-codex", "anthropic") */
provider?: string;
/** Current thinking level (e.g., "high", "low", "off") */
thinkingLevel?: string;
/** Agent identity name */
identityName?: string;
};
// Regex pattern for template variables: {variableName} or {variable.name}
const TEMPLATE_VAR_PATTERN = /\{([a-zA-Z][a-zA-Z0-9.]*)\}/g;
/**
* Interpolate template variables in a response prefix string.
*
* @param template - The template string with `{variable}` placeholders
* @param context - Context object with values for interpolation
* @returns The interpolated string, or undefined if template is undefined
*
* @example
* resolveResponsePrefixTemplate("[{model} | think:{thinkingLevel}]", {
* model: "gpt-5.2",
* thinkingLevel: "high"
* })
* // Returns: "[gpt-5.2 | think:high]"
*/
export function resolveResponsePrefixTemplate(
template: string | undefined,
context: ResponsePrefixContext,
): string | undefined {
if (!template) {
return undefined;
}
return template.replace(TEMPLATE_VAR_PATTERN, (match, varName: string) => {
const normalizedVar = varName.toLowerCase();
switch (normalizedVar) {
case "model":
return context.model ?? match;
case "modelfull":
return context.modelFull ?? match;
case "provider":
return context.provider ?? match;
case "thinkinglevel":
case "think":
return context.thinkingLevel ?? match;
case "identity.name":
case "identityname":
return context.identityName ?? match;
default:
// Leave unrecognized variables as-is
return match;
}
});
}
/**
* Extract short model name from a full model string.
*
* Strips:
* - Provider prefix (e.g., "openai/" from "openai/gpt-5.2")
* - Date suffixes (e.g., "-20260205" from "claude-opus-4-6-20260205")
* - Common version suffixes (e.g., "-latest")
*
* @example
* extractShortModelName("openai-codex/gpt-5.2") // "gpt-5.2"
* extractShortModelName("claude-opus-4-6-20260205") // "claude-opus-4-6"
* extractShortModelName("gpt-5.2-latest") // "gpt-5.2"
*/
export function extractShortModelName(fullModel: string): string {
// Strip provider prefix
const slash = fullModel.lastIndexOf("/");
const modelPart = slash >= 0 ? fullModel.slice(slash + 1) : fullModel;
// Strip date suffixes (YYYYMMDD format)
return modelPart.replace(/-\d{8}$/, "").replace(/-latest$/, "");
}
/**
* Check if a template string contains any template variables.
*/
export function hasTemplateVariables(template: string | undefined): boolean {
if (!template) {
return false;
}
// Reset lastIndex since we're using a global regex
TEMPLATE_VAR_PATTERN.lastIndex = 0;
return TEMPLATE_VAR_PATTERN.test(template);
}