PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.23
MxChat – AI Chatbot & Content Generation for WordPress v3.2.23
3.2.25 3.2.24 3.2.23 3.2.22 3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 All 156 releases
mxchat-basic / includes / class-mxchat-editor-assistant-rest.php

class-mxchat-editor-assistant-rest.php in MxChat – AI Chatbot & Content Generation for WordPress 3.2.23, at includes/class-mxchat-editor-assistant-rest.php

584 lines 26.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * REST endpoint that transforms a single block of text using whichever chat
4 * provider the site has configured in mxchat-basic.
5 *
6 * POST /wp-json/mxchat-editor/v1/transform
7 * { action: <slug>, content: <string>, locale?: <iso code> }
8 * → { result: <string>, action: <slug>, model: <string> }
9 *
10 * Auth: standard WP REST nonce (X-WP-Nonce header set by the Gutenberg sidebar
11 * via wp.apiFetch). Caller must have edit_posts capability.
12 *
13 * Provider dispatch: reads mxchat_options['model'] + the matching API key
14 * (openai api_key, gemini_api_key, claude_api_key for Anthropic) and makes a
15 * one-shot chat-completion call. Grok / custom providers are out of scope for v0.1.
16 *
17 * @package MxChatEditorAssistant
18 */
19
20 if (!defined('ABSPATH')) {
21 exit;
22 }
23
24 class MxChat_Editor_Assistant_REST {
25
26 const NAMESPACE_V1 = 'mxchat-editor/v1';
27 const ROUTE_TRANSFORM = '/transform';
28
29 public static function register() {
30 add_action('rest_api_init', array(__CLASS__, 'register_routes'));
31 }
32
33 public static function register_routes() {
34 register_rest_route(
35 self::NAMESPACE_V1,
36 self::ROUTE_TRANSFORM,
37 array(
38 'methods' => 'POST',
39 'callback' => array(__CLASS__, 'handle_transform'),
40 'permission_callback' => array(__CLASS__, 'permission_check'),
41 'args' => array(
42 'action' => array(
43 'required' => true,
44 'type' => 'string',
45 ),
46 'content' => array(
47 'required' => true,
48 'type' => 'string',
49 ),
50 'locale' => array(
51 'required' => false,
52 'type' => 'string',
53 ),
54 ),
55 )
56 );
57 }
58
59 public static function permission_check() {
60 return current_user_can('edit_posts');
61 }
62
63 /**
64 * Action registry. Each action defines a system prompt template; the user
65 * content is appended verbatim. Keep prompts short — block-level surgery,
66 * not essay generation.
67 */
68 public static function get_actions() {
69 return array(
70 'rewrite_concise' => array(
71 'label' => __('Rewrite — concise', 'mxchat'),
72 'description' => __('Shorter, tighter version of the selected block.', 'mxchat'),
73 'system' => 'Rewrite the user\'s text to be more concise. Preserve the meaning, tone, and any inline HTML formatting (links, em, strong). Return ONLY the rewritten text — no preface, no explanation, no quotation marks around the output.',
74 ),
75 'rewrite_expand' => array(
76 'label' => __('Rewrite — expand', 'mxchat'),
77 'description' => __('Longer, more detailed version of the selected block.', 'mxchat'),
78 'system' => 'Rewrite the user\'s text in an expanded form, adding helpful detail and clarification while keeping the same meaning. Preserve inline HTML formatting. Return ONLY the rewritten text.',
79 ),
80 'rewrite_formal' => array(
81 'label' => __('Rewrite — formal', 'mxchat'),
82 'description' => __('More formal, professional register.', 'mxchat'),
83 'system' => 'Rewrite the user\'s text in a more formal, professional tone. Preserve meaning and inline HTML formatting. Return ONLY the rewritten text.',
84 ),
85 'rewrite_casual' => array(
86 'label' => __('Rewrite — casual', 'mxchat'),
87 'description' => __('More casual, conversational tone.', 'mxchat'),
88 'system' => 'Rewrite the user\'s text in a more casual, conversational tone. Preserve meaning and inline HTML formatting. Return ONLY the rewritten text.',
89 ),
90 'summarize' => array(
91 'label' => __('Summarize', 'mxchat'),
92 'description' => __('One-paragraph summary of the selected block.', 'mxchat'),
93 'system' => 'Summarize the user\'s text in a single paragraph. Capture the key point only. Return ONLY the summary text, no preface.',
94 ),
95 'translate' => array(
96 'label' => __('Translate', 'mxchat'),
97 'description' => __('Translate to a target language.', 'mxchat'),
98 'system' => 'Translate the user\'s text into {LOCALE_LABEL}. Preserve meaning and inline HTML formatting. Return ONLY the translated text, no preface, no explanation.',
99 'needs_locale' => true,
100 ),
101 'continue' => array(
102 'label' => __('Continue writing', 'mxchat'),
103 'description' => __('Generate the next sentence(s) that would naturally follow.', 'mxchat'),
104 'system' => 'Continue the user\'s text. Generate 1-3 sentences that naturally follow what they have written, matching tone and style. Return ONLY the continuation — do NOT repeat the user\'s text. Do NOT add a preface like "Here\'s a continuation:".',
105 ),
106 'fix_grammar' => array(
107 'label' => __('Fix grammar & spelling', 'mxchat'),
108 'description' => __('Correct grammar and spelling without changing meaning.', 'mxchat'),
109 'system' => 'Correct grammar and spelling in the user\'s text. Do NOT change meaning, tone, or style. If there is nothing to fix, return the original text unchanged. Return ONLY the corrected text.',
110 ),
111 );
112 }
113
114 /**
115 * Lighter version of the action registry for JS — strips the system prompt
116 * so it doesn't ship to the browser. Order is preserved.
117 */
118 public static function get_actions_for_client() {
119 $out = array();
120 foreach (self::get_actions() as $slug => $cfg) {
121 $out[] = array(
122 'slug' => $slug,
123 'label' => $cfg['label'],
124 'description' => $cfg['description'],
125 'needs_locale' => !empty($cfg['needs_locale']),
126 );
127 }
128 return $out;
129 }
130
131 public static function handle_transform(WP_REST_Request $request) {
132 // Folded into core (plan-8cb0cb) as a FREE feature — no PRO license gate.
133 // The block-editor availability is controlled by the mxchat_editor_assistant_enabled
134 // toggle (off by default); when off, this route is never registered.
135 $slug = sanitize_key($request->get_param('action'));
136 $content = (string) $request->get_param('content');
137 $locale = sanitize_text_field((string) $request->get_param('locale'));
138
139 $actions = self::get_actions();
140 if (!isset($actions[$slug])) {
141 return new WP_Error(
142 'mxchat_editor_assistant_unknown_action',
143 __('Unknown action.', 'mxchat'),
144 array('status' => 400)
145 );
146 }
147
148 $content = trim($content);
149 if ($content === '') {
150 return new WP_Error(
151 'mxchat_editor_assistant_empty',
152 __('Block has no text content to transform.', 'mxchat'),
153 array('status' => 400)
154 );
155 }
156 if (strlen($content) > 16000) {
157 return new WP_Error(
158 'mxchat_editor_assistant_too_long',
159 __('Block is too long for v0.1 (16k char cap). Split it first.', 'mxchat'),
160 array('status' => 413)
161 );
162 }
163
164 $action = $actions[$slug];
165 $system_prompt = $action['system'];
166 if (!empty($action['needs_locale'])) {
167 $locale_label = self::resolve_locale_label($locale);
168 if ($locale_label === '') {
169 return new WP_Error(
170 'mxchat_editor_assistant_locale',
171 __('Target language is required for Translate.', 'mxchat'),
172 array('status' => 400)
173 );
174 }
175 $system_prompt = str_replace('{LOCALE_LABEL}', $locale_label, $system_prompt);
176 }
177
178 $provider_result = self::dispatch_to_provider($system_prompt, $content);
179 if (is_wp_error($provider_result)) {
180 return $provider_result;
181 }
182
183 return rest_ensure_response(array(
184 'result' => $provider_result['text'],
185 'action' => $slug,
186 'model' => $provider_result['model'],
187 ));
188 }
189
190 public static function resolve_locale_label($iso) {
191 $map = array(
192 'en' => 'English',
193 'es' => 'Spanish',
194 'fr' => 'French',
195 'de' => 'German',
196 'ja' => 'Japanese',
197 'pt' => 'Portuguese',
198 );
199 return isset($map[$iso]) ? $map[$iso] : '';
200 }
201
202 /**
203 * Provider dispatch — one-shot chat completion against whichever model is
204 * configured in mxchat_options. Mirrors the providers mxchat-basic ships:
205 * OpenAI (gpt-*), Anthropic (claude-*), Google (gemini-*).
206 *
207 * Grok and any custom provider degrade with a clear error in v0.1.
208 */
209 private static function dispatch_to_provider($system_prompt, $user_content) {
210 $opts = get_option('mxchat_options', array());
211 $model = isset($opts['model']) ? (string) $opts['model'] : '';
212 if ($model === '') {
213 return new WP_Error(
214 'mxchat_editor_assistant_no_model',
215 __('No AI model is configured in MxChat Settings. Set a model first.', 'mxchat'),
216 array('status' => 503)
217 );
218 }
219
220 $provider = self::detect_provider($model);
221 switch ($provider) {
222 case 'openai':
223 return self::call_openai($opts, $model, $system_prompt, $user_content);
224 case 'anthropic':
225 return self::call_anthropic($opts, $model, $system_prompt, $user_content);
226 case 'google':
227 return self::call_gemini($opts, $model, $system_prompt, $user_content);
228 case 'grok':
229 return self::call_grok($opts, $model, $system_prompt, $user_content);
230 case 'custom':
231 return self::call_custom($opts, $system_prompt, $user_content);
232 default:
233 return new WP_Error(
234 'mxchat_editor_assistant_provider_unsupported',
235 sprintf(
236 /* translators: %s: model name */
237 __('The Editor Assistant supports OpenAI, Anthropic, Google, Grok (xAI), and custom OpenAI-compatible providers. Configured model (%s) is not yet supported — set the site to one of those in MxChat → Settings.', 'mxchat'),
238 esc_html($model)
239 ),
240 array('status' => 501)
241 );
242 }
243 }
244
245 /**
246 * Map the configured model id to a provider family. Mirrors mxchat-basic's
247 * own routing: prefix-based, with 'custom-provider' as the sentinel model
248 * for the OpenAI-compatible custom/Azure path.
249 */
250 public static function detect_provider($model) {
251 $m = strtolower($model);
252 if ($m === 'custom-provider') {
253 return 'custom';
254 }
255 if (strpos($m, 'gpt-') === 0 || strpos($m, 'o1') === 0 || strpos($m, 'o3') === 0 || strpos($m, 'o4') === 0) {
256 return 'openai';
257 }
258 if (strpos($m, 'claude') !== false) {
259 return 'anthropic';
260 }
261 if (strpos($m, 'gemini') !== false) {
262 return 'google';
263 }
264 if (strpos($m, 'grok') === 0) {
265 return 'grok';
266 }
267 return 'unsupported';
268 }
269
270 private static function call_openai($opts, $model, $system_prompt, $user_content) {
271 $api_key = isset($opts['api_key']) ? trim((string) $opts['api_key']) : '';
272 if ($api_key === '') {
273 return new WP_Error(
274 'mxchat_editor_assistant_no_key',
275 __('OpenAI API key is missing in MxChat settings.', 'mxchat'),
276 array('status' => 503)
277 );
278 }
279 $body = array(
280 'model' => $model,
281 'messages' => array(
282 array('role' => 'system', 'content' => $system_prompt),
283 array('role' => 'user', 'content' => $user_content),
284 ),
285 'temperature' => 0.4,
286 );
287
288 // GPT-5 reasoning models reject non-default temperature (API 400: only the
289 // default 1 is supported) and route reasoning via reasoning_effort. Mirror the
290 // streaming sibling (class-mxchat-editor-assistant-stream.php stream_openai_compatible)
291 // and mxchat-basic's integrator EXACTLY so the non-streaming path (multi-block +
292 // any non-stream fallback) is GPT-5-safe. Without this the multi-block path 400s on
293 // every GPT-5 model — which is most installs (plan-8cb0cb fold consistency fix).
294 // Only triggers for gpt-5* ids; Grok / custom OpenAI-compatible keep the 0.4 above.
295 if (strpos($model, 'gpt-5') === 0 || strpos($model, 'gpt-6') === 0) { // gpt-6 too (plan 45c22d)
296 $body['temperature'] = 1;
297 $no_reasoning_models = array('gpt-5.2', 'gpt-5.1-chat-latest', 'gpt-5.3-chat-latest', 'gpt-5.4-mini', 'gpt-5.4-nano');
298 if (!in_array($model, $no_reasoning_models, true)) {
299 if ($model === 'gpt-5.1-2025-11-13') {
300 $body['reasoning_effort'] = 'low';
301 } elseif ($model === 'gpt-5.5' || $model === 'gpt-5.4') {
302 $body['reasoning_effort'] = 'none';
303 } elseif (in_array($model, array('gpt-6-astra', 'gpt-6-sol', 'gpt-6-luna'), true)) {
304 // plan 45c22d (probed 2026-09-27): gpt-6 rejects 'minimal'; core uses 'low'.
305 $body['reasoning_effort'] = 'low';
306 } elseif (in_array($model, array('gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna'), true)) {
307 // plan-6fb107: gpt-5.6 rejects 'minimal' (live 400); core uses 'low'.
308 $body['reasoning_effort'] = 'low';
309 } else {
310 $body['reasoning_effort'] = 'minimal';
311 }
312 }
313 }
314
315 $response = wp_remote_post('https://api.openai.com/v1/chat/completions', array(
316 'timeout' => 45,
317 'headers' => array(
318 'Authorization' => 'Bearer ' . $api_key,
319 'Content-Type' => 'application/json',
320 ),
321 'body' => wp_json_encode($body),
322 ));
323 if (is_wp_error($response)) {
324 return $response;
325 }
326 $code = wp_remote_retrieve_response_code($response);
327 $data = json_decode(wp_remote_retrieve_body($response), true);
328 if ($code !== 200 || !is_array($data) || empty($data['choices'][0]['message']['content'])) {
329 $msg = isset($data['error']['message']) ? $data['error']['message'] : 'OpenAI request failed';
330 return new WP_Error('mxchat_editor_assistant_openai', $msg, array('status' => 502));
331 }
332 return array(
333 'text' => trim((string) $data['choices'][0]['message']['content']),
334 'model' => $model,
335 );
336 }
337
338 private static function call_anthropic($opts, $model, $system_prompt, $user_content) {
339 // mxchat-basic stores the Claude/Anthropic key under 'claude_api_key' (the
340 // canonical core option). Read that first; fall back to 'anthropic_api_key'
341 // for forward-compat if a future field ever uses that name.
342 $api_key = trim((string) ($opts['claude_api_key'] ?? $opts['anthropic_api_key'] ?? ''));
343 if ($api_key === '') {
344 return new WP_Error(
345 'mxchat_editor_assistant_no_key',
346 __('Anthropic API key is missing in MxChat settings.', 'mxchat'),
347 array('status' => 503)
348 );
349 }
350 $body = array(
351 'model' => $model,
352 'max_tokens' => 1024,
353 // Prompt-cache breakpoint (plan 1ff43b): the editor-assistant
354 // instruction is a fixed template reused across requests.
355 'system' => trim((string) $system_prompt) === '' ? $system_prompt : array(
356 array('type' => 'text', 'text' => $system_prompt, 'cache_control' => array('type' => 'ephemeral')),
357 ),
358 'messages' => array(
359 array('role' => 'user', 'content' => $user_content),
360 ),
361 );
362 $response = wp_remote_post('https://api.anthropic.com/v1/messages', array(
363 'timeout' => 45,
364 'headers' => array(
365 'x-api-key' => $api_key,
366 'anthropic-version' => '2023-06-01',
367 'Content-Type' => 'application/json',
368 ),
369 'body' => wp_json_encode($body),
370 ));
371 if (is_wp_error($response)) {
372 return $response;
373 }
374 $code = wp_remote_retrieve_response_code($response);
375 $data = json_decode(wp_remote_retrieve_body($response), true);
376 if ($code !== 200 || !is_array($data)) {
377 $msg = isset($data['error']['message']) ? $data['error']['message'] : 'Anthropic request failed';
378 return new WP_Error('mxchat_editor_assistant_anthropic', $msg, array('status' => 502));
379 }
380 // claude-fable-5 prepends a `thinking` block, so content[0] is not the
381 // answer — take the first TEXT block.
382 $text = '';
383 foreach ((array) ($data['content'] ?? array()) as $block) {
384 if (isset($block['type'], $block['text']) && $block['type'] === 'text' && trim($block['text']) !== '') {
385 $text = trim($block['text']);
386 break;
387 }
388 }
389 if ($text === '') {
390 $msg = isset($data['error']['message']) ? $data['error']['message'] : 'Anthropic request failed';
391 return new WP_Error('mxchat_editor_assistant_anthropic', $msg, array('status' => 502));
392 }
393 return array(
394 'text' => $text,
395 'model' => $model,
396 );
397 }
398
399 private static function call_gemini($opts, $model, $system_prompt, $user_content) {
400 $api_key = isset($opts['gemini_api_key']) ? trim((string) $opts['gemini_api_key']) : '';
401 if ($api_key === '') {
402 return new WP_Error(
403 'mxchat_editor_assistant_no_key',
404 __('Google Gemini API key is missing in MxChat settings.', 'mxchat'),
405 array('status' => 503)
406 );
407 }
408 // Gemini's REST shape — system prompt rides as a separate system_instruction.
409 $url = sprintf(
410 'https://generativelanguage.googleapis.com/v1beta/models/%s:generateContent?key=%s',
411 rawurlencode($model),
412 rawurlencode($api_key)
413 );
414 $body = array(
415 'system_instruction' => array(
416 'parts' => array(array('text' => $system_prompt)),
417 ),
418 'contents' => array(
419 array(
420 'role' => 'user',
421 'parts' => array(array('text' => $user_content)),
422 ),
423 ),
424 'generationConfig' => array(
425 'temperature' => 0.4,
426 ),
427 );
428 $response = wp_remote_post($url, array(
429 'timeout' => 45,
430 'headers' => array('Content-Type' => 'application/json'),
431 'body' => wp_json_encode($body),
432 ));
433 if (is_wp_error($response)) {
434 return $response;
435 }
436 $code = wp_remote_retrieve_response_code($response);
437 $data = json_decode(wp_remote_retrieve_body($response), true);
438 if ($code !== 200 || !is_array($data)) {
439 $msg = isset($data['error']['message']) ? $data['error']['message'] : 'Gemini request failed';
440 return new WP_Error('mxchat_editor_assistant_gemini', $msg, array('status' => 502));
441 }
442 $parts = isset($data['candidates'][0]['content']['parts']) ? $data['candidates'][0]['content']['parts'] : array();
443 $text = '';
444 foreach ($parts as $part) {
445 if (isset($part['text'])) {
446 $text .= $part['text'];
447 }
448 }
449 if ($text === '') {
450 return new WP_Error('mxchat_editor_assistant_gemini', __('Gemini returned no text content.', 'mxchat'), array('status' => 502));
451 }
452 return array(
453 'text' => trim($text),
454 'model' => $model,
455 );
456 }
457
458 /**
459 * Grok (xAI) — OpenAI-compatible Chat Completions at api.x.ai. Reads the same
460 * 'xai_api_key' option mxchat-basic uses for its Grok provider.
461 */
462 private static function call_grok($opts, $model, $system_prompt, $user_content) {
463 $api_key = isset($opts['xai_api_key']) ? trim((string) $opts['xai_api_key']) : '';
464 if ($api_key === '') {
465 return new WP_Error(
466 'mxchat_editor_assistant_no_key',
467 __('Grok (xAI) API key is missing in MxChat settings.', 'mxchat'),
468 array('status' => 503)
469 );
470 }
471 $body = array(
472 'model' => $model,
473 'messages' => array(
474 array('role' => 'system', 'content' => $system_prompt),
475 array('role' => 'user', 'content' => $user_content),
476 ),
477 'temperature' => 0.4,
478 );
479 $response = wp_remote_post('https://api.x.ai/v1/chat/completions', array(
480 'timeout' => 45,
481 'headers' => array(
482 'Authorization' => 'Bearer ' . $api_key,
483 'Content-Type' => 'application/json',
484 ),
485 'body' => wp_json_encode($body),
486 ));
487 if (is_wp_error($response)) {
488 return $response;
489 }
490 $code = wp_remote_retrieve_response_code($response);
491 $data = json_decode(wp_remote_retrieve_body($response), true);
492 if ($code !== 200 || !is_array($data) || empty($data['choices'][0]['message']['content'])) {
493 $msg = isset($data['error']['message']) ? $data['error']['message'] : 'Grok request failed';
494 return new WP_Error('mxchat_editor_assistant_grok', $msg, array('status' => 502));
495 }
496 return array(
497 'text' => trim((string) $data['choices'][0]['message']['content']),
498 'model' => $model,
499 );
500 }
501
502 /**
503 * Custom OpenAI-compatible provider (Ollama, LM Studio, vLLM, llama.cpp,
504 * Azure OpenAI, …). Resolves the same custom_provider_* config keys + auth
505 * scheme + Azure api-version query string that mxchat-basic's chat dispatcher
506 * uses, so a site configured for Azure chat works here with zero extra setup.
507 */
508 private static function call_custom($opts, $system_prompt, $user_content) {
509 $cfg = self::resolve_custom_provider($opts);
510 if ($cfg['base_url'] === '') {
511 return new WP_Error(
512 'mxchat_editor_assistant_no_custom_base',
513 __('Custom provider Base URL is not configured in MxChat settings.', 'mxchat'),
514 array('status' => 503)
515 );
516 }
517 $body = array(
518 'model' => $cfg['model'],
519 'messages' => array(
520 array('role' => 'system', 'content' => $system_prompt),
521 array('role' => 'user', 'content' => $user_content),
522 ),
523 'temperature' => 0.4,
524 );
525 $response = wp_remote_post($cfg['chat_url'], array(
526 'timeout' => 45,
527 'headers' => $cfg['headers'],
528 'body' => wp_json_encode($body),
529 ));
530 if (is_wp_error($response)) {
531 return $response;
532 }
533 $code = wp_remote_retrieve_response_code($response);
534 $data = json_decode(wp_remote_retrieve_body($response), true);
535 if ($code !== 200 || !is_array($data) || empty($data['choices'][0]['message']['content'])) {
536 $msg = isset($data['error']['message']) ? $data['error']['message'] : 'Custom provider request failed';
537 return new WP_Error('mxchat_editor_assistant_custom', $msg, array('status' => 502));
538 }
539 return array(
540 'text' => trim((string) $data['choices'][0]['message']['content']),
541 'model' => $cfg['model'],
542 );
543 }
544
545 /**
546 * Resolve the custom-provider config from mxchat_options. Returns the same
547 * shape mxchat-basic's mxchat_resolve_custom_provider() returns, but with
548 * an associative-array header map (wp_remote_post / curl friendly).
549 *
550 * @return array{base_url:string,api_key:string,model:string,auth_scheme:string,api_version:string,chat_url:string,headers:array}
551 */
552 public static function resolve_custom_provider($opts) {
553 $base_url = isset($opts['custom_provider_base_url']) ? rtrim(trim((string) $opts['custom_provider_base_url']), '/') : '';
554 $api_key = isset($opts['custom_provider_api_key']) ? trim((string) $opts['custom_provider_api_key']) : '';
555 $model = isset($opts['custom_provider_model']) ? trim((string) $opts['custom_provider_model']) : '';
556 $auth_scheme = isset($opts['custom_provider_auth_scheme']) ? (string) $opts['custom_provider_auth_scheme'] : 'bearer';
557 $api_version = isset($opts['custom_provider_api_version']) ? trim((string) $opts['custom_provider_api_version']) : '';
558
559 $chat_url = $base_url === '' ? '' : $base_url . '/chat/completions';
560 if ($chat_url !== '' && $api_version !== '') {
561 $chat_url .= (strpos($chat_url, '?') === false ? '?' : '&') . 'api-version=' . rawurlencode($api_version);
562 }
563
564 $headers = array('Content-Type' => 'application/json');
565 if ($api_key !== '') {
566 if ($auth_scheme === 'api-key') {
567 $headers['api-key'] = $api_key;
568 } else {
569 $headers['Authorization'] = 'Bearer ' . $api_key;
570 }
571 }
572
573 return array(
574 'base_url' => $base_url,
575 'api_key' => $api_key,
576 'model' => $model !== '' ? $model : 'default',
577 'auth_scheme' => $auth_scheme,
578 'api_version' => $api_version,
579 'chat_url' => $chat_url,
580 'headers' => $headers,
581 );
582 }
583 }
584