version() . '_' . hash( 'sha256', strval( $key ) ); } private function version(): string { $version = get_site_option( self::VERSION_OPTION, '' ); if ( ! is_string( $version ) || '' === $version ) { $version = wp_generate_uuid4(); add_site_option( self::VERSION_OPTION, $version ); $version = strval( get_site_option( self::VERSION_OPTION, $version ) ); } return hash( 'sha256', $version ); } private function ttl_seconds( $ttl ): int { if ( $ttl instanceof \DateInterval ) { $now = new \DateTimeImmutable(); $ttl = $now->add( $ttl )->getTimestamp() - $now->getTimestamp(); } if ( null === $ttl ) { return self::MAX_TTL; } return min( self::MAX_TTL, max( 0, intval( $ttl ) ) ); } public function get( $key, $default = null ) { $value = get_site_transient( $this->normalize_key( $key ) ); if ( ! is_array( $value ) || ! array_key_exists( 'value', $value ) ) { return $default; } return $value['value']; } public function set( $key, $value, $ttl = null ) { $seconds = $this->ttl_seconds( $ttl ); if ( 0 === $seconds ) { return $this->delete( $key ); } return set_site_transient( $this->normalize_key( $key ), array( 'value' => $value ), $seconds ); } public function delete( $key ) { return delete_site_transient( $this->normalize_key( $key ) ); } public function clear() { return update_site_option( self::VERSION_OPTION, wp_generate_uuid4() ); } public function getMultiple( $keys, $default = null ) { $values = array(); foreach ( $keys as $key ) { $values[ $key ] = $this->get( $key, $default ); } return $values; } public function setMultiple( $values, $ttl = null ) { $success = true; foreach ( $values as $key => $value ) { $success = $this->set( $key, $value, $ttl ) && $success; } return $success; } public function deleteMultiple( $keys ) { $success = true; foreach ( $keys as $key ) { $success = $this->delete( $key ) && $success; } return $success; } public function has( $key ) { $missing = new \stdClass(); return $missing !== $this->get( $key, $missing ); } } /** Wires the cache and model-list request safeguards into WordPress. */ class AI_Client_Cache_Manager { private ?AI_Client_Transient_Cache $owned_cache = null; public function __construct() { if ( class_exists( AiClient::class ) && null === AiClient::getCache() ) { $this->owned_cache = new AI_Client_Transient_Cache(); AiClient::setCache( $this->owned_cache ); } add_filter( 'http_request_args', array( $this, 'set_model_list_timeout' ), 10, 2 ); add_action( 'updated_option', array( $this, 'invalidate_for_credential_change' ), 10, 3 ); add_action( 'added_option', array( $this, 'invalidate_for_added_credential' ), 10, 2 ); add_action( 'deleted_option', array( $this, 'invalidate_for_deleted_credential' ), 10, 1 ); } public function set_model_list_timeout( array $args, string $url ): array { $method = strtoupper( strval( $args['method'] ?? 'GET' ) ); $path = strval( wp_parse_url( $url, PHP_URL_PATH ) ); if ( 'GET' === $method && preg_match( '#/models/?$#', $path ) ) { $args['timeout'] = 15; } return $args; } public function invalidate_for_credential_change( string $option, $old_value, $value ): void { if ( $old_value !== $value ) { $this->maybe_invalidate( $option ); } } public function invalidate_for_added_credential( string $option, $value ): void { $this->maybe_invalidate( $option ); } public function invalidate_for_deleted_credential( string $option ): void { $this->maybe_invalidate( $option ); } private function maybe_invalidate( string $option ): void { if ( $this->owned_cache && 0 === strpos( $option, 'connectors_ai_' ) && '_api_key' === substr( $option, -8 ) ) { $this->owned_cache->clear(); } } }