main
1export type ModelEntry = { provider: string; modelId: string };
2export type RejectionReason = "masked" | "not-in-catalog" | "missing-credentials";
3export type Rejection = { provider: string; modelId: string; reason: RejectionReason };
4export type Resolution = {
5 selected: { provider: string; modelId: string } | null;
6 rejected: Rejection[];
7};
8
9export type ResolveSubagentModelOptions = {
10 parentProvider?: string;
11 pinnedProvider?: string;
12 providerPreference: string[];
13 requestedModel: string;
14 modelFallbacks: Record<string, string[]>;
15 providerModelMasks: Record<string, string[]>;
16 catalog: ModelEntry[];
17 ready: ReadonlySet<string>;
18};
19
20const key = (provider: string, modelId: string) => `${provider}\u0000${modelId}`;
21
22function orderedUnique(values: Array<string | undefined>): string[] {
23 return [...new Set(values.filter((value): value is string => Boolean(value)))];
24}
25
26function catalogModelId(catalog: ModelEntry[], provider: string, requested: string): string | undefined {
27 return (
28 catalog.find((entry) => entry.provider === provider && entry.modelId === requested)?.modelId ??
29 catalog.find((entry) => entry.provider === provider && entry.modelId.startsWith(`${requested}@`))?.modelId
30 );
31}
32
33export function resolveSubagentModel(options: ResolveSubagentModelOptions): Resolution {
34 const providers = options.pinnedProvider
35 ? [options.pinnedProvider]
36 : orderedUnique([options.parentProvider, ...options.providerPreference]);
37 const models = orderedUnique([options.requestedModel, ...(options.modelFallbacks[options.requestedModel] ?? [])]);
38 const rejected: Rejection[] = [];
39
40 for (const provider of providers) {
41 const masked = new Set(options.providerModelMasks[provider] ?? []);
42 for (const requestedModel of models) {
43 if (masked.has(requestedModel)) {
44 rejected.push({ provider, modelId: requestedModel, reason: "masked" });
45 continue;
46 }
47 const modelId = catalogModelId(options.catalog, provider, requestedModel);
48 if (!modelId) {
49 rejected.push({ provider, modelId: requestedModel, reason: "not-in-catalog" });
50 continue;
51 }
52 if (!options.ready.has(key(provider, modelId))) {
53 rejected.push({ provider, modelId, reason: "missing-credentials" });
54 continue;
55 }
56 return { selected: { provider, modelId }, rejected };
57 }
58 }
59
60 return { selected: null, rejected };
61}