routeModel + conservative v0 allowlist (dependabot/lint → cheap tier) wired into execution/planning/validation lanes in model-resolution.ts; governance (isPermitted) and column-agent override are absolute, OFF by default. Routing decisions (with counterfactual) emit via the U1 usage_events seam.
263 lines
7.6 KiB
TypeScript
263 lines
7.6 KiB
TypeScript
import type { Settings } from "./types.js";
|
|
import type {
|
|
ModelGovernancePredicate,
|
|
RouterDecision,
|
|
RouterLane,
|
|
RouterTaskContext,
|
|
} from "./model-router.js";
|
|
import { routeModel } from "./model-router.js";
|
|
|
|
export interface ResolvedModelSelection {
|
|
provider?: string;
|
|
modelId?: string;
|
|
}
|
|
|
|
export const TEST_MODE_RESOLVED: ResolvedModelSelection = { provider: "mock", modelId: "scripted" };
|
|
|
|
export function isTestModeActive(settings?: Partial<Settings>): boolean {
|
|
return settings?.testMode === true || settings?.defaultProvider?.trim().toLowerCase() === "mock";
|
|
}
|
|
|
|
export function applyTestModeOverrides(
|
|
resolved: ResolvedModelSelection,
|
|
settings?: Partial<Settings>,
|
|
): ResolvedModelSelection {
|
|
return isTestModeActive(settings) ? TEST_MODE_RESOLVED : resolved;
|
|
}
|
|
|
|
type ModelPair =
|
|
| ResolvedModelSelection
|
|
| {
|
|
provider?: string | null;
|
|
modelId?: string | null;
|
|
}
|
|
| undefined;
|
|
|
|
type TaskModelLike = {
|
|
modelProvider?: string | null;
|
|
modelId?: string | null;
|
|
validatorModelProvider?: string | null;
|
|
validatorModelId?: string | null;
|
|
planningModelProvider?: string | null;
|
|
planningModelId?: string | null;
|
|
};
|
|
|
|
function hasCompleteModelPair(pair: ModelPair): pair is { provider: string; modelId: string } {
|
|
return Boolean(pair?.provider && pair?.modelId);
|
|
}
|
|
|
|
function pickFirstModelPair(...pairs: ModelPair[]): ResolvedModelSelection {
|
|
for (const pair of pairs) {
|
|
if (hasCompleteModelPair(pair)) {
|
|
return { provider: pair.provider, modelId: pair.modelId };
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
export function resolveProjectDefaultModel(settings?: Partial<Settings>): ResolvedModelSelection {
|
|
return applyTestModeOverrides(
|
|
pickFirstModelPair(
|
|
{
|
|
provider: settings?.defaultProviderOverride,
|
|
modelId: settings?.defaultModelIdOverride,
|
|
},
|
|
{
|
|
provider: settings?.defaultProvider,
|
|
modelId: settings?.defaultModelId,
|
|
},
|
|
),
|
|
settings,
|
|
);
|
|
}
|
|
|
|
export function resolveExecutionSettingsModel(settings?: Partial<Settings>): ResolvedModelSelection {
|
|
return applyTestModeOverrides(
|
|
pickFirstModelPair(
|
|
{
|
|
provider: settings?.executionProvider,
|
|
modelId: settings?.executionModelId,
|
|
},
|
|
{
|
|
provider: settings?.executionGlobalProvider,
|
|
modelId: settings?.executionGlobalModelId,
|
|
},
|
|
resolveProjectDefaultModel(settings),
|
|
),
|
|
settings,
|
|
);
|
|
}
|
|
|
|
export function resolvePlanningSettingsModel(settings?: Partial<Settings>): ResolvedModelSelection {
|
|
return applyTestModeOverrides(
|
|
pickFirstModelPair(
|
|
{
|
|
provider: settings?.planningProvider,
|
|
modelId: settings?.planningModelId,
|
|
},
|
|
{
|
|
provider: settings?.planningGlobalProvider,
|
|
modelId: settings?.planningGlobalModelId,
|
|
},
|
|
resolveProjectDefaultModel(settings),
|
|
),
|
|
settings,
|
|
);
|
|
}
|
|
|
|
export function resolveValidatorSettingsModel(settings?: Partial<Settings>): ResolvedModelSelection {
|
|
return applyTestModeOverrides(
|
|
pickFirstModelPair(
|
|
{
|
|
provider: settings?.validatorProvider,
|
|
modelId: settings?.validatorModelId,
|
|
},
|
|
{
|
|
provider: settings?.validatorGlobalProvider,
|
|
modelId: settings?.validatorGlobalModelId,
|
|
},
|
|
resolveProjectDefaultModel(settings),
|
|
),
|
|
settings,
|
|
);
|
|
}
|
|
|
|
export function resolveTitleSummarizerSettingsModel(settings?: Partial<Settings>): ResolvedModelSelection {
|
|
return applyTestModeOverrides(
|
|
pickFirstModelPair(
|
|
{
|
|
provider: settings?.titleSummarizerProvider,
|
|
modelId: settings?.titleSummarizerModelId,
|
|
},
|
|
{
|
|
provider: settings?.titleSummarizerGlobalProvider,
|
|
modelId: settings?.titleSummarizerGlobalModelId,
|
|
},
|
|
{
|
|
provider: settings?.planningProvider,
|
|
modelId: settings?.planningModelId,
|
|
},
|
|
resolveProjectDefaultModel(settings),
|
|
),
|
|
settings,
|
|
);
|
|
}
|
|
|
|
export function resolveTaskExecutionModel(
|
|
task: TaskModelLike,
|
|
settings?: Partial<Settings>,
|
|
): ResolvedModelSelection {
|
|
return applyTestModeOverrides(
|
|
pickFirstModelPair(
|
|
{
|
|
provider: task.modelProvider,
|
|
modelId: task.modelId,
|
|
},
|
|
resolveExecutionSettingsModel(settings),
|
|
),
|
|
settings,
|
|
);
|
|
}
|
|
|
|
export function resolveTaskValidatorModel(
|
|
task: TaskModelLike,
|
|
settings?: Partial<Settings>,
|
|
): ResolvedModelSelection {
|
|
return applyTestModeOverrides(
|
|
pickFirstModelPair(
|
|
{
|
|
provider: task.validatorModelProvider,
|
|
modelId: task.validatorModelId,
|
|
},
|
|
resolveValidatorSettingsModel(settings),
|
|
),
|
|
settings,
|
|
);
|
|
}
|
|
|
|
export function resolveTaskPlanningModel(
|
|
task: TaskModelLike,
|
|
settings?: Partial<Settings>,
|
|
): ResolvedModelSelection {
|
|
return applyTestModeOverrides(
|
|
pickFirstModelPair(
|
|
{
|
|
provider: task.planningModelProvider,
|
|
modelId: task.planningModelId,
|
|
},
|
|
resolvePlanningSettingsModel(settings),
|
|
),
|
|
settings,
|
|
);
|
|
}
|
|
|
|
// ── Fusion Model Router lane wrappers (U17 / KTD9) ─────────────────────────
|
|
//
|
|
// These are the **governed** session-start lanes: execution, planning, and
|
|
// validation. Each first resolves the lane's default pair exactly as today (the
|
|
// router's counterfactual), then hands it to the selection layer. The router is
|
|
// OFF by default — when disabled it returns the default pair byte-identically,
|
|
// so these wrappers are safe drop-ins. The non-routed resolvers above remain
|
|
// untouched; the settings-only resolvers, `resolveProjectDefaultModel`, and
|
|
// `resolveTitleSummarizerSettingsModel` are **ungoverned** (no task signal /
|
|
// non-session purpose) and the router never touches them.
|
|
|
|
/** Options shared by the router-aware lane resolvers. */
|
|
export interface RouterLaneOptions {
|
|
/** Per-task per-lane override pair (e.g. a column-agent binding). When complete,
|
|
* the router defers to it. */
|
|
overridePair?: ResolvedModelSelection | null;
|
|
/** Classification signal for the conservative v0 allowlist. */
|
|
context?: RouterTaskContext;
|
|
/** Governance gate — the router never returns a pair this rejects. */
|
|
isPermitted?: ModelGovernancePredicate;
|
|
}
|
|
|
|
function routeLane(
|
|
lane: RouterLane,
|
|
defaultPair: ResolvedModelSelection,
|
|
settings: Partial<Settings> | undefined,
|
|
options: RouterLaneOptions | undefined,
|
|
): RouterDecision {
|
|
return routeModel({
|
|
lane,
|
|
defaultPair,
|
|
overridePair: options?.overridePair ?? null,
|
|
context: options?.context,
|
|
settings,
|
|
isPermitted: options?.isPermitted,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Router-aware execution-lane resolution. Returns the full {@link RouterDecision}
|
|
* (selection + counterfactual + reason) so the caller can emit telemetry and wire
|
|
* the escalation seam. With the router disabled, `decision.selection` equals
|
|
* {@link resolveTaskExecutionModel}.
|
|
*/
|
|
export function routeTaskExecutionModel(
|
|
task: TaskModelLike,
|
|
settings?: Partial<Settings>,
|
|
options?: RouterLaneOptions,
|
|
): RouterDecision {
|
|
return routeLane("execution", resolveTaskExecutionModel(task, settings), settings, options);
|
|
}
|
|
|
|
/** Router-aware planning-lane resolution. See {@link routeTaskExecutionModel}. */
|
|
export function routeTaskPlanningModel(
|
|
task: TaskModelLike,
|
|
settings?: Partial<Settings>,
|
|
options?: RouterLaneOptions,
|
|
): RouterDecision {
|
|
return routeLane("planning", resolveTaskPlanningModel(task, settings), settings, options);
|
|
}
|
|
|
|
/** Router-aware validation-lane resolution. See {@link routeTaskExecutionModel}. */
|
|
export function routeTaskValidatorModel(
|
|
task: TaskModelLike,
|
|
settings?: Partial<Settings>,
|
|
options?: RouterLaneOptions,
|
|
): RouterDecision {
|
|
return routeLane("validation", resolveTaskValidatorModel(task, settings), settings, options);
|
|
}
|