mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-20 22:02:19 +03:00
`BaseExecutor.buildHeaders(credentials, stream?, clientHeaders?, model?, health?)` was shadowed in three executors by same-named helpers with unrelated signatures: hailuo-web private buildHeaders(token: string, yy: string) lmarena protected buildHeaders(_model: string, credentials: unknown, _body: unknown) qwen-web private buildHeaders(token: string, cookieHeader: string, chatId?: string) Name collisions, not overrides — each reported TS2416. They are renamed to `buildStreamHeaders` / `buildRequestHeaders` / `buildApiHeaders`; the two lmarena test files that called the helper directly are updated with them. Worth stating precisely, because the shadow sat on a live dispatch path without being a live bug: `BaseExecutor.countTokens()` calls `this.buildHeaders(credentials, false)`, and all three inherit `countTokens()`. It is unreachable today only because `buildCountTokensUrl()` returns null unless `config.format === "claude"` and the URL carries `/messages` — hailuo-web and qwen-web set no format, lmarena sets `"openai"` — so `countTokens()` returns at the guard above. Latent, not live; one `format` change away from passing a credentials object where a token string is expected. Two more, surfaced by clearing the above: * `lmarena` declared `buildUrl` and `transformRequest` `protected` while both are public on BaseExecutor (TS2415 — a subclass may widen visibility, never narrow it). Both were masked behind the buildHeaders TS2416 and appeared one at a time as it cleared. Runtime is unaffected; JavaScript has no member visibility. * `GithubExecutor.refreshCredentials` had no declared return type, so TypeScript inferred the union of its four literal returns. `GheCopilotExecutor` legitimately overrides it with a wider `providerSpecificData` (it also records the enterprise proxy URL) and no `expiresIn`, which is not assignable to that inferred union. Declared as `RefreshedCopilotCredentials | null` — same shape of fix as #8489, on a different method. Validation: full tsc error-set diff against the base config — 335 -> 331, zero new errors (line-number-agnostic). `typecheck:core` clean; the 15 existing test files importing a touched executor pass, including lmarena's 44 across the two updated files. `plan3-p0.test.ts` fails identically with and without this change (it reads the developer's real ~/.omniroute DB rather than a test-scoped DATA_DIR). The new test pins that the inherited method is no longer shadowed — verified to fail on the base, where all three prototypes still carry their own `buildHeaders` — and that the `countTokens()` early return which kept it harmless still holds.
91 lines
4.0 KiB
TypeScript
91 lines
4.0 KiB
TypeScript
/**
|
|
* Guards the executor override signatures fixed for TS 7 readiness.
|
|
*
|
|
* Three executors declared a *private/protected* `buildHeaders()` helper whose signature
|
|
* has nothing to do with `BaseExecutor.buildHeaders(credentials, stream?, clientHeaders?,
|
|
* model?, health?)`:
|
|
*
|
|
* hailuo-web (token: string, yy: string)
|
|
* lmarena (_model: string, credentials: unknown, _body: unknown)
|
|
* qwen-web (token: string, cookieHeader: string, chatId?: string)
|
|
*
|
|
* They were name collisions, not overrides — each shadowed the inherited member with an
|
|
* incompatible signature (TS2416). `BaseExecutor` calls `this.buildHeaders(credentials,
|
|
* false)` from `countTokens()`, so the shadow sat on a live dispatch path; it was never
|
|
* reached only because `buildCountTokensUrl()` returns null unless `config.format` is
|
|
* `"claude"`, and none of these three is. Latent rather than live — but one `format`
|
|
* change away from passing a credentials object where a token string was expected.
|
|
*
|
|
* The helpers are renamed, so these assertions pin both halves: the inherited method is
|
|
* no longer shadowed, and the early return that kept it harmless still holds.
|
|
*/
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import { BaseExecutor } from "../../open-sse/executors/base.ts";
|
|
import { HailuoWebExecutor } from "../../open-sse/executors/hailuo-web.ts";
|
|
import { LMArenaExecutor } from "../../open-sse/executors/lmarena.ts";
|
|
import { QwenWebExecutor } from "../../open-sse/executors/qwen-web.ts";
|
|
|
|
const CASES = [
|
|
{ name: "hailuo-web", make: () => new HailuoWebExecutor(), helper: "buildStreamHeaders" },
|
|
{ name: "lmarena", make: () => new LMArenaExecutor(), helper: "buildRequestHeaders" },
|
|
{ name: "qwen-web", make: () => new QwenWebExecutor(), helper: "buildApiHeaders" },
|
|
];
|
|
|
|
for (const { name, make, helper } of CASES) {
|
|
test(`${name}: buildHeaders resolves to BaseExecutor, not a local helper`, () => {
|
|
const executor = make() as unknown as Record<string, unknown>;
|
|
|
|
assert.equal(
|
|
executor.buildHeaders,
|
|
BaseExecutor.prototype.buildHeaders,
|
|
`${name} must not shadow BaseExecutor.buildHeaders — countTokens() dispatches through it`
|
|
);
|
|
});
|
|
|
|
test(`${name}: its own header helper is still present under the renamed key`, () => {
|
|
const executor = make() as unknown as Record<string, unknown>;
|
|
|
|
assert.equal(
|
|
typeof executor[helper],
|
|
"function",
|
|
`${name} should keep its provider-specific header builder as ${helper}()`
|
|
);
|
|
assert.notEqual(
|
|
executor[helper],
|
|
BaseExecutor.prototype.buildHeaders,
|
|
"the renamed helper must be the provider's own function, not the inherited one"
|
|
);
|
|
});
|
|
|
|
test(`${name}: countTokens() short-circuits before reaching buildHeaders`, async () => {
|
|
const executor = make();
|
|
|
|
// buildCountTokensUrl() returns null unless config.format === "claude" and the URL
|
|
// carries /messages. That early return is what kept the old shadow unreachable; if it
|
|
// ever changes, the inherited buildHeaders must be the one that runs.
|
|
const result = await executor.countTokens({
|
|
model: "whatever",
|
|
body: { messages: [] },
|
|
credentials: {},
|
|
signal: new AbortController().signal,
|
|
log: null,
|
|
});
|
|
|
|
assert.equal(result, null, `${name} does not support the Anthropic count_tokens endpoint`);
|
|
});
|
|
}
|
|
|
|
test("LMArenaExecutor does not narrow visibility of inherited members", () => {
|
|
// TS2415: a subclass may widen a member's visibility but never narrow it. `buildUrl` and
|
|
// `transformRequest` were `protected` here while public on BaseExecutor — masked behind
|
|
// the buildHeaders TS2416 until that cleared. Runtime has no visibility, so this asserts
|
|
// the members are reachable, which is what the type change encodes.
|
|
const executor = new LMArenaExecutor() as unknown as Record<string, unknown>;
|
|
|
|
for (const member of ["buildUrl", "transformRequest"]) {
|
|
assert.equal(typeof executor[member], "function", `${member} must stay callable`);
|
|
}
|
|
});
|