|
20 | 20 | use B13\Aim\Domain\Repository\ProviderConfigurationRepository; |
21 | 21 | use B13\Aim\Domain\Repository\RequestLogRepository; |
22 | 22 | use B13\Aim\Pagination\DemandedArrayPaginator; |
| 23 | +use B13\Aim\Provider\LiveModelDiscovery; |
23 | 24 | use B13\Aim\Registry\AiProviderRegistry; |
24 | 25 | use B13\Aim\Registry\DisabledModelRegistry; |
25 | 26 | use B13\Aim\Request\ConversationRequest; |
@@ -56,6 +57,7 @@ public function __construct( |
56 | 57 | private readonly DisabledModelRegistry $disabledModelRegistry, |
57 | 58 | private readonly RequestLogRepository $requestLogRepository, |
58 | 59 | private readonly Registry $registry, |
| 60 | + private readonly LiveModelDiscovery $liveModelDiscovery, |
59 | 61 | ) {} |
60 | 62 |
|
61 | 63 | public function overviewAction(ServerRequestInterface $request): ResponseInterface |
@@ -172,7 +174,7 @@ public function availableProvidersAction(ServerRequestInterface $request): Respo |
172 | 174 | 'id' => $modelId, |
173 | 175 | 'disabled' => $this->disabledModelRegistry->isDisabled($manifest->identifier, $modelId), |
174 | 176 | ], |
175 | | - array_keys($manifest->supportedModels), |
| 177 | + $this->collectModelIds($manifest), |
176 | 178 | ), |
177 | 179 | 'capabilities' => $this->resolveCapabilityLabels($manifest->capabilities), |
178 | 180 | ], |
@@ -207,6 +209,40 @@ public function toggleModelAction(ServerRequestInterface $request): ResponseInte |
207 | 209 | ]); |
208 | 210 | } |
209 | 211 |
|
| 212 | + /** |
| 213 | + * Collect the model IDs to surface for a provider in the Available |
| 214 | + * Providers modal. |
| 215 | + * |
| 216 | + * For static catalogs (OpenAI, Anthropic, …) this is just the manifest's |
| 217 | + * supportedModels. For dynamic catalogs (Ollama, LM Studio, …) the |
| 218 | + * manifest is empty at compile time; instead we walk every saved |
| 219 | + * configuration record for the provider, live-fetch the model list from |
| 220 | + * each unique endpoint, and merge the results so the admin can disable |
| 221 | + * specific models even on dynamic backends. |
| 222 | + * |
| 223 | + * @return list<string> |
| 224 | + */ |
| 225 | + private function collectModelIds(AiProviderManifest $manifest): array |
| 226 | + { |
| 227 | + if ($manifest->supportedModels !== []) { |
| 228 | + return array_keys($manifest->supportedModels); |
| 229 | + } |
| 230 | + |
| 231 | + $models = []; |
| 232 | + $seenEndpoints = []; |
| 233 | + foreach ($this->configurationRepository->findByProviderIdentifier($manifest->identifier) as $configuration) { |
| 234 | + $endpoint = $configuration->apiKey; |
| 235 | + if ($endpoint === '' || isset($seenEndpoints[$endpoint]) || !$this->liveModelDiscovery->isHttpEndpoint($endpoint)) { |
| 236 | + continue; |
| 237 | + } |
| 238 | + $seenEndpoints[$endpoint] = true; |
| 239 | + foreach ($this->liveModelDiscovery->fetchModelNames($endpoint) as $modelId) { |
| 240 | + $models[$modelId] = true; |
| 241 | + } |
| 242 | + } |
| 243 | + return array_keys($models); |
| 244 | + } |
| 245 | + |
210 | 246 | /** |
211 | 247 | * Map capability FQCNs to human-readable labels via locallang. |
212 | 248 | * |
|
0 commit comments