fix(#2852): forward model overrides from categories/agent config to subagents

- fix(switcher): use lastIndexOf for multi-slash model IDs (e.g. aws/anthropic/claude-sonnet-4)
- fix(model-resolution): same lastIndexOf fix in doctor parseProviderModel
- fix(call-omo-agent): resolve model from agent config and forward to both
  background and sync executors via DelegatedModelConfig
- fix(subagent-resolver): inherit category model/variant when agent uses
  category reference without explicit model override
- test: add model override forwarding tests for call-omo-agent
- test: add multi-slash model ID test for switcher
This commit is contained in:
YeonGyu-Kim
2026-03-27 13:59:06 +09:00
parent 40a92138ea
commit 670d8ab175
12 changed files with 185 additions and 17 deletions
+4 -1
View File
@@ -451,7 +451,9 @@ describe("preemptive-compaction", () => {
expect(ctx.client.session.summarize).toHaveBeenCalledTimes(1)
// when - new message with high tokens (context grew after compaction)
// when - advance past the 60s cooldown window, then new message with high tokens
const originalNow = Date.now
Date.now = () => originalNow() + 61_000
await hook.event({
event: {
type: "message.updated",
@@ -480,6 +482,7 @@ describe("preemptive-compaction", () => {
// then - summarize should fire again
expect(ctx.client.session.summarize).toHaveBeenCalledTimes(2)
Date.now = originalNow
})
// #given modelContextLimitsCache has model-specific limit (256k)
+7
View File
@@ -10,6 +10,7 @@ import { createPostCompactionDegradationMonitor } from "./preemptive-compaction-
const PREEMPTIVE_COMPACTION_TIMEOUT_MS = 120_000
const PREEMPTIVE_COMPACTION_THRESHOLD = 0.78
const PREEMPTIVE_COMPACTION_COOLDOWN_MS = 60_000
declare function setTimeout(handler: () => void, timeout?: number): unknown
declare function clearTimeout(timeoutID: unknown): void
@@ -68,6 +69,7 @@ export function createPreemptiveCompactionHook(
) {
const compactionInProgress = new Set<string>()
const compactedSessions = new Set<string>()
const lastCompactionTime = new Map<string, number>()
const tokenCache = new Map<string, CachedCompactionState>()
const postCompactionMonitor = createPostCompactionDegradationMonitor({
@@ -85,6 +87,9 @@ export function createPreemptiveCompactionHook(
const { sessionID } = input
if (compactedSessions.has(sessionID) || compactionInProgress.has(sessionID)) return
const lastTime = lastCompactionTime.get(sessionID)
if (lastTime && Date.now() - lastTime < PREEMPTIVE_COMPACTION_COOLDOWN_MS) return
const cached = tokenCache.get(sessionID)
if (!cached) return
@@ -127,6 +132,7 @@ export function createPreemptiveCompactionHook(
)
compactedSessions.add(sessionID)
lastCompactionTime.set(sessionID, Date.now())
} catch (error) {
log("[preemptive-compaction] Compaction failed", { sessionID, error: String(error) })
} finally {
@@ -142,6 +148,7 @@ export function createPreemptiveCompactionHook(
if (sessionID) {
compactionInProgress.delete(sessionID)
compactedSessions.delete(sessionID)
lastCompactionTime.delete(sessionID)
tokenCache.delete(sessionID)
postCompactionMonitor.clear(sessionID)
}
+13
View File
@@ -146,6 +146,19 @@ describe("think-mode switcher", () => {
expect(getHighVariant("custom-llm/gemini-3.1-pro")).toBe("custom-llm/gemini-3-1-pro-high")
})
it("should handle multi-slash model IDs (#2852)", () => {
// given model IDs with multiple slashes (e.g. aws/anthropic/claude-sonnet-4)
const variant = getHighVariant("aws/anthropic/claude-sonnet-4-6")
// then should split at last slash, preserving full provider prefix
expect(variant).toBe("aws/anthropic/claude-sonnet-4-6-high")
})
it("should return null for multi-slash unknown models", () => {
// given multi-slash model ID without high variant mapping
expect(getHighVariant("aws/anthropic/unknown-model")).toBeNull()
})
it("should return null for prefixed models without high variant mapping", () => {
// given prefixed model IDs without high variant mapping
expect(getHighVariant("vertex_ai/unknown-model")).toBeNull()
+2 -1
View File
@@ -26,9 +26,10 @@ import { normalizeModelID } from "../../shared"
* extractModelPrefix("vertex_ai/claude-sonnet-4-6") // { prefix: "vertex_ai/", base: "claude-sonnet-4-6" }
* extractModelPrefix("claude-sonnet-4-6") // { prefix: "", base: "claude-sonnet-4-6" }
* extractModelPrefix("openai/gpt-5.4") // { prefix: "openai/", base: "gpt-5.4" }
* extractModelPrefix("aws/anthropic/claude-sonnet-4") // { prefix: "aws/anthropic/", base: "claude-sonnet-4" }
*/
function extractModelPrefix(modelID: string): { prefix: string; base: string } {
const slashIndex = modelID.indexOf("/")
const slashIndex = modelID.lastIndexOf("/")
if (slashIndex === -1) {
return { prefix: "", base: modelID }
}