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:
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user