fix(coding-agent): preserve runtime provider transport override precedence
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user