provider_resolver = $provider_resolver ?? static function ( $provider_id ) { return Gregius_Data_LLM_Registry::get_provider( $provider_id ); }; } /** * Fetch the live model ids for a provider. * * @since 1.0.0 * @param string $provider_id Provider id (e.g. 'gemini'). * @param string $api_key Provider API key. * @param bool $force Optional. Bypass the cache. Default false. * @return array|WP_Error Array of model ids, or WP_Error on failure. */ public function fetch_live_models( string $provider_id, string $api_key, bool $force = false ): array|WP_Error { $provider = call_user_func( $this->provider_resolver, $provider_id ); if ( is_wp_error( $provider ) || ! method_exists( $provider, 'list_remote_models' ) ) { return new WP_Error( 'gregius_data_discovery_unsupported', /* translators: %s: provider id */ sprintf( __( 'Provider "%s" does not support live model discovery.', 'gregius-data' ), $provider_id ) ); } $cache_key = self::CACHE_PREFIX . md5( $provider_id . '|' . $api_key ); if ( ! $force ) { $cached = get_transient( $cache_key ); if ( is_array( $cached ) ) { return $cached; } } $result = $provider->list_remote_models( $api_key ); if ( is_wp_error( $result ) ) { // Do not cache failures. return $result; } set_transient( $cache_key, $result, self::CACHE_TTL ); return $result; } /** * Resolve the lifecycle status for a single model. * * @since 1.0.0 * @param string $provider_id Provider id. * @param string $api_key Provider API key. * @param string $model_id Provider model id. * @return string 'active' | 'retired' | 'unknown'. */ public function resolve_lifecycle( string $provider_id, string $api_key, string $model_id ): string { $live = $this->fetch_live_models( $provider_id, $api_key ); if ( is_wp_error( $live ) ) { return 'unknown'; } if ( in_array( $model_id, $live, true ) ) { return 'active'; } // Absent from the list. Only conclude "retired" when the provider's // list actually covers this model's category — otherwise the list // simply omits that category (e.g. Cohere's /v2/models omits rerank). $category = $this->model_category( $provider_id, $model_id ); if ( null === $category || ! $this->list_covers_category( $provider_id, $category, $live ) ) { return 'unknown'; } return 'retired'; } /** * Look up a model's category from the catalog. * * @since 1.0.0 * @param string $provider_id Provider id. * @param string $model_id Provider model id. * @return string|null 'llm' | 'embedding' | 'rerank', or null if unknown. */ private function model_category( string $provider_id, string $model_id ): ?string { $models = Gregius_Data_Model_Catalog::models( $provider_id ); return isset( $models[ $model_id ]['kind'] ) ? $models[ $model_id ]['kind'] : null; } /** * Whether the live list contains a catalog model of the given category. * * @since 1.0.0 * @param string $provider_id Provider id. * @param string $category Catalog category. * @param array $live Live model ids. * @return bool */ private function list_covers_category( string $provider_id, string $category, array $live ): bool { foreach ( Gregius_Data_Model_Catalog::models( $provider_id ) as $id => $model ) { if ( isset( $model['kind'] ) && $model['kind'] === $category && in_array( $id, $live, true ) ) { return true; } } return false; } /** * Clear the cached live list for a provider + key. * * @since 1.0.0 * @param string $provider_id Provider id. * @param string $api_key Provider API key. * @return void */ public function flush( string $provider_id, string $api_key ): void { delete_transient( self::CACHE_PREFIX . md5( $provider_id . '|' . $api_key ) ); } } }