fix(auto-reply): preserve OpenRouter @preset model directives (#23769)

* Auto-reply: preserve OpenRouter @preset model directives

* Changelog: move OpenRouter preset fix into 2026.2.22 unreleased
This commit is contained in:
Vincent Koc
2026-02-22 12:46:04 -05:00
committed by GitHub
parent 91944ede4c
commit 24fd8cbdc8
3 changed files with 26 additions and 6 deletions

View File

@@ -28,14 +28,14 @@ Docs: https://docs.openclaw.ai
### Fixes
- OpenRouter/Anthropic: inject `cache_control` on system prompts for OpenRouter Anthropic models to improve prompt-cache reuse. (#17473) Thanks @rrenamed.
- Providers/OpenRouter: inject `cache_control` on system prompts for OpenRouter Anthropic models to improve prompt-cache reuse. (#17473) Thanks @rrenamed.
- Providers/OpenRouter: allow pass-through OpenRouter and Opencode model IDs in live model filtering so custom routed model IDs are treated as modern refs. (#14312) Thanks @Joly0.
- Providers/OpenRouter: default reasoning to enabled when the selected model advertises `reasoning: true` and no session/directive override is set. (#22513) Thanks @zwffff.
- Providers/OpenRouter: map `/think` levels to `reasoning.effort` in embedded runs while preserving explicit `reasoning.max_tokens` payloads. (#17236) Thanks @robbyczgw-cla.
- Providers/OpenRouter: preserve stored session provider when model IDs are vendor-prefixed (for example, `anthropic/...`) so follow-up turns do not incorrectly route to direct provider APIs. (#22753) Thanks @dndodson.
- Providers/OpenRouter: preserve the required `openrouter/` prefix for OpenRouter-native model IDs during model-ref normalization. (#12942) Thanks @omair445.
- Providers/OpenRouter: pass through provider routing parameters from model params.provider to OpenRouter request payloads for provider selection controls. (#17148) Thanks @carrotRakko.
- Providers/OpenRouter: preserve model allowlist entries containing OpenRouter preset paths (for example `openrouter/@preset/...`) by treating `/model ...@profile` auth-profile parsing as a suffix-only override. (#14120) Thanks @NotMainstream.
- Cron/Auth: propagate auth-profile resolution to isolated cron sessions so provider API keys are resolved the same way as main sessions, fixing 401 errors when using providers configured via auth-profiles. (#20689) Thanks @lailoo.
- Telegram/Webhook: keep webhook monitors alive until gateway abort signals fire, preventing false channel exits and immediate webhook auto-restart loops.
- Telegram/Polling: retry recoverable setup-time network failures in monitor startup and await runner teardown before retry to avoid overlapping polling sessions.

View File

@@ -36,6 +36,20 @@ describe("extractModelDirective", () => {
expect(result.rawProfile).toBe("myprofile");
});
it("keeps OpenRouter preset paths that include @ in the model name", () => {
const result = extractModelDirective("/model openrouter/@preset/kimi-2-5");
expect(result.hasDirective).toBe(true);
expect(result.rawModel).toBe("openrouter/@preset/kimi-2-5");
expect(result.rawProfile).toBeUndefined();
});
it("still allows profile overrides after OpenRouter preset paths", () => {
const result = extractModelDirective("/model openrouter/@preset/kimi-2-5@work");
expect(result.hasDirective).toBe(true);
expect(result.rawModel).toBe("openrouter/@preset/kimi-2-5");
expect(result.rawProfile).toBe("work");
});
it("returns no directive for plain text", () => {
const result = extractModelDirective("hello world");
expect(result.hasDirective).toBe(false);

View File

@@ -33,10 +33,16 @@ export function extractModelDirective(
let rawModel = raw;
let rawProfile: string | undefined;
if (raw?.includes("@")) {
const parts = raw.split("@");
rawModel = parts[0]?.trim();
rawProfile = parts.slice(1).join("@").trim() || undefined;
if (raw) {
const atIndex = raw.lastIndexOf("@");
if (atIndex > 0) {
const candidateModel = raw.slice(0, atIndex).trim();
const candidateProfile = raw.slice(atIndex + 1).trim();
if (candidateModel && candidateProfile && !candidateProfile.includes("/")) {
rawModel = candidateModel;
rawProfile = candidateProfile;
}
}
}
const cleaned = match ? body.replace(match[0], " ").replace(/\s+/g, " ").trim() : body.trim();