mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-26 09:02:11 +03:00
Validated in a combined 3-PR batch worktree off release/v3.8.51 tip (a sibling PR from the same author, #11495, was held out — a typecheck error in zai-web.ts only reproduced with this PR + #11495 boarded together, and cleared without #11495; isolated this PR alone confirmed clean on its own too, so the interaction belonged to #11495's side — see its comment). - Golden lock: executor-map-golden.test.ts — passes byte-identical (same keys, classes, provider identities, dispatch guards) - Focused tests part of batch's 94/94 node:test run - typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK - Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff Thanks for the measured, careful methodology here — the golden-lock contract plus the isolated DATA_DIR benchmarking make this an easy PR to trust despite the wide surface (72 files).
70 lines
2.6 KiB
TypeScript
70 lines
2.6 KiB
TypeScript
/**
|
|
* CLIProxyAPI model-mapping application (#6876).
|
|
*
|
|
* The dashboard persists `cliproxyapiModelMapping` per provider
|
|
* (`upstream_proxy_config.cliproxyapi_model_mapping`) but nothing at
|
|
* request-dispatch time ever consulted it — the configured alias was
|
|
* silently dropped and the original model was forwarded verbatim to
|
|
* CLIProxyAPI. This module applies the mapping exactly once, at the
|
|
* executor boundary, so it only affects requests that actually reach
|
|
* CLIProxyAPI (the `cliproxyapi` passthrough leg and the CLIProxyAPI retry
|
|
* leg of `fallback` mode) and never the native leg of `fallback` mode.
|
|
*/
|
|
|
|
type ExecutorInput = {
|
|
model: string;
|
|
body: unknown;
|
|
};
|
|
|
|
// No index signature: executors (BaseExecutor subclasses) must satisfy this
|
|
// structurally, and class instances don't carry index signatures.
|
|
type ExecutorLike = {
|
|
execute: (input: ExecutorInput) => Promise<unknown>;
|
|
};
|
|
|
|
export type CliproxyapiModelMapping = Record<string, unknown> | null | undefined;
|
|
|
|
function resolveMappedModel(model: string, mapping: CliproxyapiModelMapping): string | null {
|
|
if (!mapping || typeof mapping !== "object") return null;
|
|
const mapped = (mapping as Record<string, unknown>)[model];
|
|
return typeof mapped === "string" && mapped.trim() ? mapped : null;
|
|
}
|
|
|
|
/**
|
|
* Rewrites `input.model` (and `input.body.model` when body is a plain
|
|
* object) to the mapped model, if one is configured for `input.model`.
|
|
* Returns the original input unchanged when no mapping applies.
|
|
*/
|
|
export function applyCliproxyapiModelMapping(
|
|
input: ExecutorInput,
|
|
mapping: CliproxyapiModelMapping
|
|
): ExecutorInput {
|
|
const mappedModel = resolveMappedModel(input.model, mapping);
|
|
if (!mappedModel) return input;
|
|
|
|
const body =
|
|
input.body && typeof input.body === "object" && !Array.isArray(input.body)
|
|
? { ...(input.body as Record<string, unknown>), model: mappedModel }
|
|
: input.body;
|
|
|
|
return { ...input, model: mappedModel, body };
|
|
}
|
|
|
|
/**
|
|
* Wraps an executor so every `execute()` call has the CLIProxyAPI model
|
|
* mapping applied first. Returns the executor unchanged when no mapping is
|
|
* configured (empty/absent mapping is a no-op, matching prior behavior).
|
|
*/
|
|
export function wrapExecutorWithCliproxyapiModelMapping<T extends ExecutorLike>(
|
|
executor: T,
|
|
mapping: CliproxyapiModelMapping
|
|
): T {
|
|
if (!mapping || typeof mapping !== "object" || Object.keys(mapping).length === 0) {
|
|
return executor;
|
|
}
|
|
const wrapped = Object.create(executor) as T;
|
|
wrapped.execute = (input: ExecutorInput) =>
|
|
executor.execute(applyCliproxyapiModelMapping(input, mapping));
|
|
return wrapped;
|
|
}
|