fix(coding-agent): preserve runtime provider transport override precedence

This commit is contained in:
Aidan
2026-04-24 17:45:06 -04:00
parent 1dbfa78dac
commit ff1549fd2c
3 changed files with 48 additions and 6 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()`, preserve existing `baseUrl` on subsequent headers-only updates, and clear stale transport overrides when a provider is re-registered under a different extension source
- 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, clear stale transport overrides when a provider is re-registered under a different extension source, and keep runtime transport headers authoritative when `modelOverrides` set overlapping header keys
## [14.2.0] - 2026-04-23
@@ -886,9 +886,8 @@ export class ModelRegistry {
const withConfigModels = this.#mergeCustomModels(resolvedDefaults, this.#customModelOverlays);
// Merge runtime extension models so they survive refresh() cycles
const combined = this.#mergeCustomModels(withConfigModels, this.#runtimeModelOverlays);
const withRuntimeProviderOverrides = this.#applyRuntimeProviderOverrides(combined);
this.#models = this.#applyModelOverrides(withRuntimeProviderOverrides, this.#modelOverrides);
const withModelOverrides = this.#applyModelOverrides(combined, this.#modelOverrides);
this.#models = this.#applyRuntimeProviderOverrides(withModelOverrides);
this.#rebuildCanonicalIndex();
}
@@ -1179,8 +1178,8 @@ export class ModelRegistry {
const withConfigModels = this.#mergeCustomModels(resolved, this.#customModelOverlays);
// Merge runtime extension models so they survive online discovery completion
const combined = this.#mergeCustomModels(withConfigModels, this.#runtimeModelOverlays);
const withRuntimeProviderOverrides = this.#applyRuntimeProviderOverrides(combined);
this.#models = this.#applyModelOverrides(withRuntimeProviderOverrides, this.#modelOverrides);
const withModelOverrides = this.#applyModelOverrides(combined, this.#modelOverrides);
this.#models = this.#applyRuntimeProviderOverrides(withModelOverrides);
this.#rebuildCanonicalIndex();
}
@@ -295,6 +295,49 @@ describe("ModelRegistry runtime provider registration", () => {
expect(registry.find("runtime-provider", modelId)).toBeUndefined();
});
test("runtime headers override modelOverrides headers across refresh cycles", async () => {
const initialRegistry = new ModelRegistry(authStorage, modelsJsonPath);
const targetModel = initialRegistry.getAll().find(model => model.provider === "anthropic");
if (!targetModel) throw new Error("Expected bundled anthropic model");
const modelId = targetModel.id;
const sharedHeader = "X-Shared-Provider-Model-Header";
const configHeaderValue = "config-header";
const runtimeHeaderValue = "runtime-header";
fs.writeFileSync(
modelsJsonPath,
JSON.stringify({
providers: {
anthropic: {
modelOverrides: {
[modelId]: { headers: { [sharedHeader]: configHeaderValue } },
},
},
},
}),
);
const registry = new ModelRegistry(authStorage, modelsJsonPath);
expect(registry.find("anthropic", modelId)?.headers?.[sharedHeader]).toBe(configHeaderValue);
registry.registerProvider(
"anthropic",
{ headers: { [sharedHeader]: runtimeHeaderValue } },
"ext://runtime",
);
expect(registry.find("anthropic", modelId)?.headers?.[sharedHeader]).toBe(runtimeHeaderValue);
await registry.refresh("offline");
expect(registry.find("anthropic", modelId)?.headers?.[sharedHeader]).toBe(runtimeHeaderValue);
await registry.refreshProvider("anthropic", "offline");
expect(registry.find("anthropic", modelId)?.headers?.[sharedHeader]).toBe(runtimeHeaderValue);
registry.clearSourceRegistrations("ext://runtime");
expect(registry.find("anthropic", modelId)?.headers?.[sharedHeader]).toBe(configHeaderValue);
});
test("extension-registered API keys survive refresh cycle for auth resolution", async () => {
const registry = new ModelRegistry(authStorage, modelsJsonPath);