fix(coding-agent): evicted resolved completions session URLs

The completions provider stores session state under the request-time resolved base URL, which can differ from the catalog baseUrl for Moonshot, Alibaba Coding Plan, Azure deployments, and similar provider overrides. The model-switch cleanup now evicts the previous provider prefix whenever the switch leaves that completions backend, so those resolved-url keys cannot survive the switch.
This commit is contained in:
roboomp
2026-06-22 13:39:48 +00:00
parent 596a2d1317
commit 45b200cd09
3 changed files with 26 additions and 7 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
### Fixed
- Fixed `openai-completions` provider session state surviving `/model` switches across different providers or base URLs. `AgentSession.#closeProviderSessionsForModelSwitch` only evicted `openai-codex-responses` and `openai-responses:<provider>` keys; entries keyed `openai-completions:<provider>:<baseUrl>:<modelId>` (cached strict-tools disable scopes and reasoning-effort fallbacks for the old transport) lingered indefinitely. Moving away from an `openai-completions` backend now evicts every cached entry sharing the previous `(provider, baseUrl)` pair, while same-backend model toggles keep their cached state ([#3260](https://github.com/can1357/oh-my-pi/issues/3260))
- Fixed `openai-completions` provider session state surviving `/model` switches across different providers or base URLs. `AgentSession.#closeProviderSessionsForModelSwitch` only evicted `openai-codex-responses` and `openai-responses:<provider>` keys; entries keyed `openai-completions:<provider>:<resolvedBaseUrl>:<modelId>` (cached strict-tools disable scopes and reasoning-effort fallbacks for the old transport) lingered indefinitely. Moving away from an `openai-completions` backend now evicts every cached entry for the previous provider, including entries whose base URL was resolved at request time rather than read from the catalog, while same-backend model toggles keep their cached state ([#3260](https://github.com/can1357/oh-my-pi/issues/3260))
## [16.1.14] - 2026-06-22
@@ -9026,18 +9026,18 @@ export class AgentSession {
providerKeys.add(`openai-responses:${nextModel.provider}`);
}
// `openai-completions` sessions are keyed `openai-completions:<provider>:<baseUrl>:<modelId>`
// `openai-completions` sessions are keyed `openai-completions:<provider>:<resolvedBaseUrl>:<modelId>`
// and cache backend-specific decisions (strict-tools disable scopes, reasoning-effort
// fallbacks). When the user moves away from that backend — different `api`, `provider`,
// or `baseUrl` — the cached decisions no longer track the active transport. Evict the
// full `(provider, baseUrl)` prefix; same-backend model toggles keep their state.
// fallbacks). The resolved request base URL can differ from the catalog `model.baseUrl`
// (Moonshot env override, Alibaba Coding Plan enterprise URL, Azure deployment URL),
// so evict by provider prefix when the user moves away from that completions backend.
let completionsPrefixToEvict: string | undefined;
if (currentModel.api === "openai-completions") {
const currentScope = `${currentModel.provider}:${currentModel.baseUrl ?? ""}`;
const nextScope =
nextModel.api === "openai-completions" ? `${nextModel.provider}:${nextModel.baseUrl ?? ""}` : undefined;
if (currentScope !== nextScope) {
completionsPrefixToEvict = `openai-completions:${currentScope}:`;
completionsPrefixToEvict = `openai-completions:${currentModel.provider}:`;
}
}
@@ -92,7 +92,7 @@ describe("AgentSession openai-completions provider session eviction", () => {
expect(session.providerSessionState.has(completionsSessionKey(deepseek))).toBe(false);
});
it("evicts every cached entry under the old (provider, baseUrl) prefix", async () => {
it("evicts every cached entry under the old provider prefix", async () => {
const deepseekPro = completionsModel("deepseek", "deepseek-v4-pro");
const deepseekFlash = completionsModel("deepseek", "deepseek-v4-flash");
const cerebras = completionsModel("cerebras", "llama3.1-8b");
@@ -119,6 +119,25 @@ describe("AgentSession openai-completions provider session eviction", () => {
expect(session.providerSessionState.has(completionsSessionKey(deepseekFlash))).toBe(false);
});
it("evicts entries whose base URL was resolved at request time", async () => {
const moonshot = completionsModel("moonshot", "kimi-k2-thinking");
const cerebras = completionsModel("cerebras", "llama3.1-8b");
authStorage.setRuntimeApiKey(cerebras.provider, "cerebras-test-key");
session = buildSession(moonshot);
const closeSpy = vi.fn();
const resolvedBaseUrlKey = `openai-completions:${moonshot.provider}:https://api.moonshot.cn/v1:${moonshot.id}`;
session.providerSessionState.set(resolvedBaseUrlKey, {
close: closeSpy,
} satisfies ProviderSessionState);
await session.setModel(cerebras);
expect(closeSpy).toHaveBeenCalledTimes(1);
expect(session.providerSessionState.has(resolvedBaseUrlKey)).toBe(false);
});
it("leaves unrelated provider session state untouched", async () => {
const deepseek = completionsModel("deepseek", "deepseek-v4-pro");
const cerebras = completionsModel("cerebras", "llama3.1-8b");