resolveProviderOption(); if ($provider === false) { return self::FAILURE; } $limit = max(1, (int) $this->option('limit')); $providers = $provider !== null ? [$provider] : ['together', 'vision_gateway', 'gemini', 'home']; $inspections = array_map(fn (string $name): array => $this->inspectProvider($name, $limit), $providers); $this->table( ['Provider', 'Status', 'Configured model', 'Models endpoint'], array_map(fn (array $inspection): array => [ $inspection['provider'], $inspection['status'], $inspection['configured_model'] ?: 'n/a', $inspection['models_endpoint'] ?: 'n/a', ], $inspections) ); foreach ($inspections as $inspection) { $this->line(''); $this->comment(strtoupper($inspection['provider'])); $this->line(' Base URL : ' . ($inspection['base_url'] ?: 'n/a')); $this->line(' Configured : ' . ($inspection['configured'] ? 'yes' : 'no')); $this->line(' Status : ' . $inspection['status']); if ($inspection['error'] !== null) { $this->line(' Error : ' . $inspection['error']); } if ($inspection['models'] === []) { $this->line(' Models : none reported'); continue; } $this->line(' Models'); foreach ($inspection['models'] as $model) { $this->line(' - ' . $model); } } return self::SUCCESS; } private function inspectProvider(string $provider, int $limit): array { $baseUrl = $this->providerBaseUrl($provider); $configuredModel = $this->configuredModel($provider); $modelsEndpoint = $this->modelsEndpoint($provider, $baseUrl); if (! $this->isConfigured($provider, $baseUrl, $configuredModel)) { return [ 'provider' => $provider, 'status' => 'not_configured', 'configured' => false, 'configured_model' => $configuredModel, 'base_url' => $baseUrl, 'models_endpoint' => $modelsEndpoint, 'models' => [], 'error' => 'Required configuration is missing.', ]; } try { $response = $this->buildRequest($provider)->get($modelsEndpoint); } catch (\Illuminate\Http\Client\ConnectionException $e) { return [ 'provider' => $provider, 'status' => 'offline', 'configured' => true, 'configured_model' => $configuredModel, 'base_url' => $baseUrl, 'models_endpoint' => $modelsEndpoint, 'models' => [], 'error' => $e->getMessage(), ]; } if (! $response->successful()) { return [ 'provider' => $provider, 'status' => 'http_' . $response->status(), 'configured' => true, 'configured_model' => $configuredModel, 'base_url' => $baseUrl, 'models_endpoint' => $modelsEndpoint, 'models' => [], 'error' => mb_substr(trim($response->body()), 0, 300), ]; } return [ 'provider' => $provider, 'status' => 'online', 'configured' => true, 'configured_model' => $configuredModel, 'base_url' => $baseUrl, 'models_endpoint' => $modelsEndpoint, 'models' => array_slice($this->extractModels($provider, $response), 0, $limit), 'error' => null, ]; } private function resolveProviderOption(): string|false|null { $rawProvider = $this->option('provider'); if ($rawProvider === null || trim((string) $rawProvider) === '') { return null; } return match (strtolower(trim((string) $rawProvider))) { 'vision_gateway', 'vision', 'local' => 'vision_gateway', 'gemini' => 'gemini', 'home', 'lmstudio', 'lm_studio' => 'home', 'together', 'together_ai' => 'together', default => $this->invalidProvider((string) $rawProvider), }; } private function invalidProvider(string $provider): false { $this->error("Invalid provider [{$provider}]. Supported values: together, vision_gateway, vision, gemini, home."); return false; } private function isConfigured(string $provider, string $baseUrl, string $configuredModel): bool { return match ($provider) { 'vision_gateway' => $baseUrl !== '' && trim((string) config('vision.gateway.api_key', '')) !== '', 'gemini' => $baseUrl !== '' && trim((string) config('ai_biography.gemini.api_key', '')) !== '' && $configuredModel !== '', 'home' => $baseUrl !== '' && $configuredModel !== '', 'together' => trim((string) config('ai_biography.together.api_key', '')) !== '' && $configuredModel !== '', default => false, }; } private function configuredModel(string $provider): string { return match ($provider) { 'vision_gateway' => trim((string) config('ai_biography.llm_model', 'vision-gateway')), 'gemini' => trim((string) config('ai_biography.gemini.model', 'gemini-flash-latest')), 'home' => trim((string) config('ai_biography.home.model', 'qwen/qwen3.5-9b')), 'together' => trim((string) config('ai_biography.together.model', 'google/gemma-3n-E4B-it')), default => '', }; } private function providerBaseUrl(string $provider): string { return match ($provider) { 'vision_gateway' => rtrim((string) config('vision.gateway.base_url', ''), '/'), 'gemini' => rtrim((string) config('ai_biography.gemini.base_url', 'https://generativelanguage.googleapis.com'), '/'), 'home' => rtrim((string) config('ai_biography.home.base_url', 'http://home.klevze.si:8200'), '/'), 'together' => rtrim((string) config('ai_biography.together.base_url', 'https://api.together.xyz'), '/'), default => '', }; } private function modelsEndpoint(string $provider, string $baseUrl): string { if ($baseUrl === '') { return ''; } return match ($provider) { 'gemini' => $baseUrl . '/v1beta/models', default => $baseUrl . '/v1/models', }; } private function buildRequest(string $provider): PendingRequest { $request = Http::acceptJson()->contentType('application/json'); return match ($provider) { 'vision_gateway' => $request ->withHeaders(['X-API-Key' => (string) config('vision.gateway.api_key', '')]) ->connectTimeout(max(1, (int) config('vision.gateway.connect_timeout_seconds', 3))) ->timeout(max(5, (int) config('ai_biography.llm_timeout_seconds', 30))), 'gemini' => $request ->withHeaders(['X-goog-api-key' => (string) config('ai_biography.gemini.api_key', '')]) ->connectTimeout(max(1, (int) config('vision.gateway.connect_timeout_seconds', 3))) ->timeout(max(5, (int) config('ai_biography.llm_timeout_seconds', 30))), 'together' => $request ->withToken((string) config('ai_biography.together.api_key', '')) ->connectTimeout(max(1, (int) config('ai_biography.together.connect_timeout_seconds', 5))) ->timeout(max(5, (int) config('ai_biography.together.timeout_seconds', config('ai_biography.llm_timeout_seconds', 90)))), 'home' => $this->buildHomeRequest($request), default => $request, }; } private function buildHomeRequest(PendingRequest $request): PendingRequest { $request = $request ->connectTimeout(max(1, (int) config('ai_biography.home.connect_timeout_seconds', 3))) ->timeout(max(5, (int) config('ai_biography.home.timeout_seconds', config('ai_biography.llm_timeout_seconds', 30)))); if (! (bool) config('ai_biography.home.verify_ssl', true)) { $request = $request->withoutVerifying(); } $apiKey = trim((string) config('ai_biography.home.api_key', '')); return $apiKey !== '' ? $request->withToken($apiKey) : $request; } /** * @return list */ private function extractModels(string $provider, Response $response): array { $json = $response->json(); if ($provider === 'gemini') { return collect((array) ($json['models'] ?? [])) ->map(fn ($model) => is_array($model) ? (string) ($model['name'] ?? $model['displayName'] ?? '') : '') ->filter(fn (string $name): bool => $name !== '') ->values() ->all(); } return collect((array) ($json['data'] ?? [])) ->map(fn ($model) => is_array($model) ? (string) ($model['id'] ?? '') : '') ->filter(fn (string $name): bool => $name !== '') ->values() ->all(); } }