Merge 934a3ffcba35e074672732d38d46af0b2e3c92af into 8a05c05596ca9ba0735dafd8e359885de4c2c969

This commit is contained in:
johnkhagler 2026-03-21 05:45:11 +00:00 committed by GitHub
commit 2132566141
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 130 additions and 30 deletions

View File

@ -28,7 +28,7 @@ import type { VerboseLevel } from "../thinking.js";
import type { GetReplyOptions } from "../types.js";
import {
buildEmbeddedRunExecutionParams,
resolveModelFallbackOptions,
resolveCompactionModelFallbackOptions,
} from "./agent-runner-utils.js";
import {
hasAlreadyFlushedForCurrentCompaction,
@ -282,13 +282,14 @@ export async function runMemoryFlushIfNeeded(params: {
return sandboxCfg.workspaceAccess === "rw";
})();
const isCli = isCliProvider(params.followupRun.run.provider, params.cfg);
const compactionFallbackOptions = resolveCompactionModelFallbackOptions(params.followupRun.run);
const isCli = isCliProvider(compactionFallbackOptions.provider, params.cfg);
const canAttemptFlush = memoryFlushWritable && !params.isHeartbeat && !isCli;
let entry =
params.sessionEntry ??
(params.sessionKey ? params.sessionStore?.[params.sessionKey] : undefined);
const contextWindowTokens = resolveMemoryFlushContextWindowTokens({
modelId: params.followupRun.run.model ?? params.defaultModel,
modelId: compactionFallbackOptions.model ?? params.defaultModel,
agentCfgContextTokens: params.agentCfgContextTokens,
});
@ -478,7 +479,7 @@ export async function runMemoryFlushIfNeeded(params: {
.join("\n\n");
try {
await runWithModelFallback({
...resolveModelFallbackOptions(params.followupRun.run),
...compactionFallbackOptions,
runId: flushRunId,
run: async (provider, model, runOptions) => {
const { embeddedContext, senderContext, runBaseParams } = buildEmbeddedRunExecutionParams({

View File

@ -1,20 +1,11 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { FollowupRun } from "./queue.js";
const hoisted = vi.hoisted(() => {
const resolveRunModelFallbacksOverrideMock = vi.fn();
return { resolveRunModelFallbacksOverrideMock };
});
vi.mock("../../agents/agent-scope.js", () => ({
resolveRunModelFallbacksOverride: (...args: unknown[]) =>
hoisted.resolveRunModelFallbacksOverrideMock(...args),
}));
const {
buildThreadingToolContext,
buildEmbeddedRunBaseParams,
buildEmbeddedRunContexts,
resolveCompactionModelFallbackOptions,
resolveModelFallbackOptions,
resolveProviderScopedAuthProfile,
} = await import("./agent-runner-utils.js");
@ -45,20 +36,21 @@ function makeRun(overrides: Partial<FollowupRun["run"]> = {}): FollowupRun["run"
describe("agent-runner-utils", () => {
beforeEach(() => {
hoisted.resolveRunModelFallbacksOverrideMock.mockClear();
vi.restoreAllMocks();
});
it("resolves model fallback options from run context", () => {
hoisted.resolveRunModelFallbacksOverrideMock.mockReturnValue(["fallback-model"]);
const run = makeRun();
const run = makeRun({
sessionKey: "agent:agent-1:main",
config: {
agents: {
list: [{ id: "agent-1", model: { fallbacks: ["fallback-model"] } }],
},
},
});
const resolved = resolveModelFallbackOptions(run);
expect(hoisted.resolveRunModelFallbacksOverrideMock).toHaveBeenCalledWith({
cfg: run.config,
agentId: run.agentId,
sessionKey: run.sessionKey,
});
expect(resolved).toEqual({
cfg: run.config,
provider: run.provider,
@ -69,19 +61,93 @@ describe("agent-runner-utils", () => {
});
it("passes through missing agentId for helper-based fallback resolution", () => {
hoisted.resolveRunModelFallbacksOverrideMock.mockReturnValue(["fallback-model"]);
const run = makeRun({ agentId: undefined });
const run = makeRun({
agentId: undefined,
sessionKey: "agent:agent-2:main",
config: {
agents: {
list: [{ id: "agent-2", model: { fallbacks: ["fallback-model"] } }],
},
},
});
const resolved = resolveModelFallbackOptions(run);
expect(hoisted.resolveRunModelFallbacksOverrideMock).toHaveBeenCalledWith({
cfg: run.config,
agentId: undefined,
sessionKey: run.sessionKey,
});
expect(resolved.fallbacksOverride).toEqual(["fallback-model"]);
});
it("uses compaction model override for compaction-scoped fallback resolution", () => {
const run = makeRun({
sessionKey: "agent:agent-1:main",
config: {
agents: {
list: [{ id: "agent-1", model: { fallbacks: ["fallback-model"] } }],
defaults: {
compaction: {
model: "openrouter/anthropic/claude-sonnet-4-5",
},
},
},
},
});
const resolved = resolveCompactionModelFallbackOptions(run);
expect(resolved).toEqual({
cfg: run.config,
provider: "openrouter",
model: "anthropic/claude-sonnet-4-5",
agentDir: run.agentDir,
fallbacksOverride: ["openrouter/fallback-model"],
});
});
it("rebases model-only fallbacks onto the compaction provider", () => {
const run = makeRun({
sessionKey: "agent:agent-1:main",
config: {
agents: {
list: [{ id: "agent-1", model: { fallbacks: ["fallback-model", "anthropic/haiku"] } }],
defaults: {
compaction: {
model: "openrouter/anthropic/claude-sonnet-4-5",
},
},
},
},
});
const resolved = resolveCompactionModelFallbackOptions(run);
expect(resolved.fallbacksOverride).toEqual(["openrouter/fallback-model", "anthropic/haiku"]);
});
it("keeps the primary provider when compaction model override omits a provider", () => {
const run = makeRun({
sessionKey: "agent:agent-1:main",
config: {
agents: {
list: [{ id: "agent-1", model: { fallbacks: ["fallback-model"] } }],
defaults: {
compaction: {
model: "claude-sonnet-4-5",
},
},
},
},
});
const resolved = resolveCompactionModelFallbackOptions(run);
expect(resolved).toEqual({
cfg: run.config,
provider: run.provider,
model: "claude-sonnet-4-5",
agentDir: run.agentDir,
fallbacksOverride: ["fallback-model"],
});
});
it("builds embedded run base params with auth profile and run metadata", () => {
const run = makeRun({ enforceFinalTag: true });
const authProfile = resolveProviderScopedAuthProfile({
@ -191,7 +257,6 @@ describe("agent-runner-utils", () => {
expect(context).toMatchObject({
currentChannelId: "telegram:-1003841603622",
currentThreadTs: "928",
currentMessageId: "2284",
});
});

View File

@ -168,6 +168,40 @@ export function resolveModelFallbackOptions(run: FollowupRun["run"]) {
};
}
export function resolveCompactionModelFallbackOptions(run: FollowupRun["run"]) {
const resolved = resolveModelFallbackOptions(run);
const override = run.config?.agents?.defaults?.compaction?.model?.trim();
if (!override) {
return resolved;
}
const slashIdx = override.indexOf("/");
if (slashIdx > 0) {
const provider = override.slice(0, slashIdx).trim();
const model = override.slice(slashIdx + 1).trim();
if (provider && model) {
const fallbacksOverride = resolved.fallbacksOverride?.map((fallback) => {
const trimmed = fallback.trim();
if (!trimmed || trimmed.includes("/")) {
return trimmed;
}
return `${provider}/${trimmed}`;
});
return {
...resolved,
provider,
model,
fallbacksOverride,
};
}
}
return {
...resolved,
model: override,
};
}
export function buildEmbeddedRunBaseParams(params: {
run: FollowupRun["run"];
provider: string;