(model: string)
| 98 | * 3. Full model IDs ("claude-opus-4-5-20251101") — exact match only |
| 99 | */ |
| 100 | export function isModelAllowed(model: string): boolean { |
| 101 | const settings = getSettings_DEPRECATED() || {} |
| 102 | const { availableModels } = settings |
| 103 | if (!availableModels) { |
| 104 | return true // No restrictions |
| 105 | } |
| 106 | if (availableModels.length === 0) { |
| 107 | return false // Empty allowlist blocks all user-specified models |
| 108 | } |
| 109 | |
| 110 | const resolvedModel = resolveOverriddenModel(model) |
| 111 | const normalizedModel = resolvedModel.trim().toLowerCase() |
| 112 | const normalizedAllowlist = availableModels.map(m => m.trim().toLowerCase()) |
| 113 | |
| 114 | // Direct match (alias-to-alias or full-name-to-full-name) |
| 115 | // Skip family aliases that have been narrowed by specific entries — |
| 116 | // e.g., "opus" in ["opus", "opus-4-5"] should NOT directly match, |
| 117 | // because the admin intends to restrict to opus 4.5 only. |
| 118 | if (normalizedAllowlist.includes(normalizedModel)) { |
| 119 | if ( |
| 120 | !isModelFamilyAlias(normalizedModel) || |
| 121 | !familyHasSpecificEntries(normalizedModel, normalizedAllowlist) |
| 122 | ) { |
| 123 | return true |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | // Family-level aliases in the allowlist match any model in that family, |
| 128 | // but only if no more specific entries exist for that family. |
| 129 | // e.g., ["opus"] allows all opus, but ["opus", "opus-4-5"] only allows opus 4.5. |
| 130 | for (const entry of normalizedAllowlist) { |
| 131 | if ( |
| 132 | isModelFamilyAlias(entry) && |
| 133 | !familyHasSpecificEntries(entry, normalizedAllowlist) && |
| 134 | modelBelongsToFamily(normalizedModel, entry) |
| 135 | ) { |
| 136 | return true |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | // For non-family entries, do bidirectional alias resolution |
| 141 | // If model is an alias, resolve it and check if the resolved name is in the list |
| 142 | if (isModelAlias(normalizedModel)) { |
| 143 | const resolved = parseUserSpecifiedModel(normalizedModel).toLowerCase() |
| 144 | if (normalizedAllowlist.includes(resolved)) { |
| 145 | return true |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | // If any non-family alias in the allowlist resolves to the input model |
| 150 | for (const entry of normalizedAllowlist) { |
| 151 | if (!isModelFamilyAlias(entry) && isModelAlias(entry)) { |
| 152 | const resolved = parseUserSpecifiedModel(entry).toLowerCase() |
| 153 | if (resolved === normalizedModel) { |
| 154 | return true |
| 155 | } |
| 156 | } |
| 157 | } |
no test coverage detected