Fix runtime provider override leakage across source handoff

This commit is contained in:
Aidan
2026-04-24 16:58:03 -04:00
parent a5d4a0058e
commit 1dbfa78dac
3 changed files with 50 additions and 1 deletions
+1 -1
View File
@@ -25,7 +25,7 @@
- Fixed edit streaming preview updates to cancel obsolete in-flight computations and avoid rendering stale previews as args change
- Fixed Mermaid fenced markdown rendering in assistant messages on terminals without image protocol support ([#650](https://github.com/can1357/oh-my-pi/issues/650))
- Fixed SQLite `read` helper queries to reject `where=` clauses with SQL control syntax that could override the structured selector's pagination; raw SQL remains available through `q=SELECT ...`
- Fixed `models` provider transport overrides so `headers`-only entries apply without requiring `baseUrl`, including runtime `registerProvider()` overrides that now persist across `refresh()` / `refreshProvider()` and preserve existing `baseUrl` on subsequent headers-only updates
- Fixed `models` provider transport overrides so `headers`-only entries apply without requiring `baseUrl`, including runtime `registerProvider()` overrides that now persist across `refresh()` / `refreshProvider()`, preserve existing `baseUrl` on subsequent headers-only updates, and clear stale transport overrides when a provider is re-registered under a different extension source
## [14.2.0] - 2026-04-23
@@ -2035,6 +2035,7 @@ export class ModelRegistry {
if (previousProviders && previousProviders.size === 0) {
this.#runtimeProvidersBySource.delete(previousSourceId);
}
this.#runtimeProviderOverrides.delete(providerName);
}
const sourceProviders = this.#runtimeProvidersBySource.get(sourceId) ?? new Set<string>();
sourceProviders.add(providerName);
@@ -382,6 +382,54 @@ describe("ModelRegistry runtime provider registration", () => {
expect(modelAfterProviderRefresh?.headers?.[runtimeHeader]).toBe("runtime-header");
});
test("provider source handoff does not retain previous source transport overrides", async () => {
const registry = new ModelRegistry(authStorage, modelsJsonPath);
const providerName = "shared-runtime-provider";
const leakedHeader = "X-Old-Source-Header";
const sourceBBaseUrl = "https://source-b.example.com/v1";
registry.registerProvider(
providerName,
{
baseUrl: "https://source-a.example.com/v1",
apiKey: "KEY_A",
api: "openai-completions",
models: [{ ...baseModel, id: "model-a" }],
},
"ext://a",
);
registry.registerProvider(
providerName,
{ baseUrl: "https://override-a.example.com/v1", headers: { [leakedHeader]: "from-source-a" } },
"ext://a",
);
registry.registerProvider(
providerName,
{
baseUrl: sourceBBaseUrl,
apiKey: "KEY_B",
api: "openai-completions",
models: [{ ...baseModel, id: "model-b" }],
},
"ext://b",
);
expect(registry.find(providerName, "model-a")).toBeUndefined();
const modelAfterHandoff = registry.find(providerName, "model-b");
expect(modelAfterHandoff?.baseUrl).toBe(sourceBBaseUrl);
expect(modelAfterHandoff?.headers?.[leakedHeader]).toBeUndefined();
await registry.refresh("offline");
const modelAfterRefresh = registry.find(providerName, "model-b");
expect(modelAfterRefresh?.baseUrl).toBe(sourceBBaseUrl);
expect(modelAfterRefresh?.headers?.[leakedHeader]).toBeUndefined();
await registry.refreshProvider(providerName, "offline");
const modelAfterProviderRefresh = registry.find(providerName, "model-b");
expect(modelAfterProviderRefresh?.baseUrl).toBe(sourceBBaseUrl);
expect(modelAfterProviderRefresh?.headers?.[leakedHeader]).toBeUndefined();
});
test("multiple extension providers survive refresh independently", async () => {
const registry = new ModelRegistry(authStorage, modelsJsonPath);