mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 07:11:39 +00:00
feat: add dynamic template variables to messages.responsePrefix (#923)
Adds support for template variables in `messages.responsePrefix` that
resolve dynamically at runtime with the actual model used (including
after fallback).
Supported variables (case-insensitive):
- {model} - short model name (e.g., "claude-opus-4-5", "gpt-4o")
- {modelFull} - full model identifier (e.g., "anthropic/claude-opus-4-5")
- {provider} - provider name (e.g., "anthropic", "openai")
- {thinkingLevel} or {think} - thinking level ("high", "low", "off")
- {identity.name} or {identityName} - agent identity name
Example: "[{model} | think:{thinkingLevel}]" → "[claude-opus-4-5 | think:high]"
Variables show the actual model used after fallback, not the intended
model. Unresolved variables remain as literal text.
Implementation:
- New module: src/auto-reply/reply/response-prefix-template.ts
- Template interpolation in normalize-reply.ts via context provider
- onModelSelected callback in agent-runner-execution.ts
- Updated all 6 provider message handlers (web, signal, discord,
telegram, slack, imessage)
- 27 unit tests covering all variables and edge cases
- Documentation in docs/gateway/configuration.md and JSDoc
Fixes #923
This commit is contained in:
97
src/auto-reply/reply/response-prefix-template.ts
Normal file
97
src/auto-reply/reply/response-prefix-template.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* 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-5") */
|
||||
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., "-20251101" from "claude-opus-4-5-20251101")
|
||||
* - Common version suffixes (e.g., "-latest")
|
||||
*
|
||||
* @example
|
||||
* extractShortModelName("openai-codex/gpt-5.2") // "gpt-5.2"
|
||||
* extractShortModelName("claude-opus-4-5-20251101") // "claude-opus-4-5"
|
||||
* 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);
|
||||
}
|
||||
Reference in New Issue
Block a user