PluginProbe
AI / trunk
AI vtrunk
1.3.0 1.2.0 1.1.0 1.0.2 1.0.1 1.0.0 0.9.0 trunk 0.1.1 0.2.0 0.2.1 0.3.0 0.3.1 0.4.0 0.4.1 0.5.0 0.6.0 0.7.0 0.8.0
ai / includes / REST / Models_Controller.php

Models_Controller.php in AI trunk, at includes/REST/Models_Controller.php

242 lines 5.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * REST controller for provider/model discovery.
4 *
5 * @package WordPress\AI\REST
6 */
7
8 declare( strict_types=1 );
9
10 namespace WordPress\AI\REST;
11
12 use WordPress\AiClient\AiClient;
13 use WordPress\AiClient\Messages\Enums\ModalityEnum;
14 use WordPress\AiClient\Providers\Models\DTO\ModelRequirements;
15 use WordPress\AiClient\Providers\Models\DTO\RequiredOption;
16 use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum;
17 use WordPress\AiClient\Providers\Models\Enums\OptionEnum;
18
19 use function WordPress\AI\get_ai_connectors;
20
21 // Exit if accessed directly.
22 defined( 'ABSPATH' ) || exit;
23
24 /**
25 * Handles the GET /ai/v1/providers REST endpoint.
26 *
27 * Returns providers and models filtered by capability.
28 *
29 * @since 0.9.0
30 */
31 final class Models_Controller {
32
33 /**
34 * The REST API namespace.
35 *
36 * @since 0.9.0
37 *
38 * @var string
39 */
40 private const API_NAMESPACE = 'ai/v1';
41
42 /**
43 * The REST API route.
44 *
45 * @since 0.9.0
46 *
47 * @var string
48 */
49 private const ROUTE = '/providers';
50
51 /**
52 * Supported capability values.
53 *
54 * @since 0.9.0
55 *
56 * @var list<string>
57 */
58 private const VALID_CAPABILITIES = array( 'text_generation', 'image_generation', 'vision' ); // phpcs:ignore SlevomatCodingStandard.Classes.DisallowMultiConstantDefinition -- This is a single array constant.
59
60 /**
61 * Initializes the REST routes.
62 *
63 * @since 0.9.0
64 */
65 public function init(): void {
66 add_action( 'rest_api_init', array( $this, 'register_routes' ) );
67 }
68
69 /**
70 * Registers the REST routes.
71 *
72 * @since 0.9.0
73 */
74 public function register_routes(): void {
75 register_rest_route(
76 self::API_NAMESPACE,
77 self::ROUTE,
78 array(
79 'methods' => 'GET',
80 'callback' => array( $this, 'get_providers' ),
81 'permission_callback' => array( $this, 'check_permission' ),
82 'args' => array(
83 'capability' => array(
84 'type' => 'string',
85 'required' => true,
86 'enum' => self::VALID_CAPABILITIES,
87 'sanitize_callback' => 'sanitize_key',
88 ),
89 ),
90 )
91 );
92 }
93
94 /**
95 * Checks whether the current user can access this endpoint.
96 *
97 * @since 0.9.0
98 *
99 * @return bool True if the user has permission.
100 */
101 public function check_permission(): bool {
102 return current_user_can( 'manage_options' );
103 }
104
105 /**
106 * Returns providers and their models for the requested capability.
107 *
108 * @since 0.9.0
109 *
110 * @param \WP_REST_Request $request The REST request.
111 * @return \WP_REST_Response|\WP_Error The response.
112 */
113 public function get_providers( \WP_REST_Request $request ) {
114 if ( ! class_exists( AiClient::class ) ) {
115 return new \WP_Error(
116 'ai_client_unavailable',
117 __( 'AI client is not available.', 'ai' ),
118 array( 'status' => 500 )
119 );
120 }
121
122 $capability = (string) $request->get_param( 'capability' );
123
124 try {
125 $requirements = $this->build_requirements( $capability );
126 } catch ( \InvalidArgumentException $e ) {
127 return new \WP_Error(
128 'invalid_capability',
129 $e->getMessage(),
130 array( 'status' => 400 )
131 );
132 }
133
134 try {
135 $providers = $this->fetch_providers( $requirements );
136 } catch ( \Throwable $e ) {
137 return new \WP_Error(
138 'model_fetch_failed',
139 $e->getMessage(),
140 array( 'status' => 500 )
141 );
142 }
143
144 return new \WP_REST_Response( $providers, 200 );
145 }
146
147 /**
148 * Builds model requirements for a capability string.
149 *
150 * @since 0.9.0
151 *
152 * @param string $capability The capability slug.
153 * @return \WordPress\AiClient\Providers\Models\DTO\ModelRequirements The requirements.
154 * @throws \InvalidArgumentException If the capability is unrecognized.
155 */
156 private function build_requirements( string $capability ): ModelRequirements {
157 switch ( $capability ) {
158 case 'text_generation':
159 return new ModelRequirements(
160 array( CapabilityEnum::textGeneration() ),
161 array()
162 );
163
164 case 'image_generation':
165 return new ModelRequirements(
166 array( CapabilityEnum::imageGeneration() ),
167 array()
168 );
169
170 case 'vision':
171 return new ModelRequirements(
172 array( CapabilityEnum::textGeneration() ),
173 array(
174 new RequiredOption(
175 OptionEnum::inputModalities(),
176 array( ModalityEnum::text(), ModalityEnum::image() )
177 ),
178 )
179 );
180
181 default:
182 throw new \InvalidArgumentException(
183 sprintf(
184 /* translators: %s: Capability slug. */
185 esc_html__( 'Unsupported capability: %s', 'ai' ),
186 esc_html( $capability )
187 )
188 );
189 }
190 }
191
192 /**
193 * Fetches providers and their models matching the given requirements.
194 *
195 * Only considers active connectors registered in the plugin.
196 *
197 * @since 0.9.0
198 *
199 * @param \WordPress\AiClient\Providers\Models\DTO\ModelRequirements $requirements The model requirements.
200 * @return list<array{id: string, name: string, models: list<array{id: string, name: string}>}> Provider data.
201 */
202 private function fetch_providers( ModelRequirements $requirements ): array {
203 $registry = AiClient::defaultRegistry();
204 $active_connectors = get_ai_connectors();
205 $providers = array();
206
207 foreach ( array_keys( $active_connectors ) as $connector_id ) {
208 try {
209 $models = $registry->findProviderModelsMetadataForSupport( $connector_id, $requirements );
210
211 if ( empty( $models ) ) {
212 continue;
213 }
214
215 $provider_class = $registry->getProviderClassName( $connector_id );
216
217 /** @var \WordPress\AiClient\Providers\Contracts\ProviderInterface $provider_class */
218 $provider_name = $provider_class::metadata()->getName();
219
220 $model_items = array();
221 foreach ( $models as $model ) {
222 $model_items[] = array(
223 'id' => $model->getId(),
224 'name' => $model->getName(),
225 );
226 }
227
228 $providers[] = array(
229 'id' => $connector_id,
230 'name' => $provider_name,
231 'models' => $model_items,
232 );
233 } catch ( \Throwable $e ) {
234 // Skip providers that throw during model discovery.
235 continue;
236 }
237 }
238
239 return $providers;
240 }
241 }
242