';
printf(
/* translators: %s: link to the core Connectors admin screen */
esc_html__(
'API keys are managed centrally on the %s screen. Use the field below to pick a default model — any plugin that calls wp_ai_client_prompt() will be able to route requests through Vercel AI Gateway.',
'ai-provider-for-vercel-ai-gateway'
),
'' .
esc_html__('Settings → Connectors', 'ai-provider-for-vercel-ai-gateway') .
''
);
echo '
';
return;
}
echo '
' . esc_html__(
'Enter your Vercel AI Gateway API key and pick a default model. Any plugin that calls wp_ai_client_prompt() will be able to route requests through Vercel AI Gateway.',
'ai-provider-for-vercel-ai-gateway'
) . '
';
},
self::PAGE_SLUG
);
add_settings_field(
'api_key',
__('API key', 'ai-provider-for-vercel-ai-gateway'),
[self::class, 'render_api_key_field'],
self::PAGE_SLUG,
'vercel_ai_gateway_provider_main'
);
add_settings_field(
'default_model',
__('Default text model', 'ai-provider-for-vercel-ai-gateway'),
[self::class, 'render_default_model_field'],
self::PAGE_SLUG,
'vercel_ai_gateway_provider_main'
);
add_settings_field(
'default_image_model',
__('Default image model', 'ai-provider-for-vercel-ai-gateway'),
[self::class, 'render_default_image_model_field'],
self::PAGE_SLUG,
'vercel_ai_gateway_provider_main'
);
}
/**
* Sanitizes the saved settings array.
*
* Also clears the cached model list when the API key changes so the next
* request re-fetches /models with the new credential.
*
* @since 1.0.0
*
* @param mixed $input Raw posted value.
* @return array{api_key: string, default_model: string}
*/
public static function sanitize_settings($input): array
{
$current = get_plugin_settings();
if (!is_array($input)) {
$input = [];
}
// When the core Connectors screen owns the API key, the field is not
// rendered on this page — preserve whatever is already stored so a
// "Save changes" click on this page does not blow it away.
if (array_key_exists('api_key', $input)) {
$apiKey = sanitize_text_field(trim((string) $input['api_key']));
} else {
$apiKey = (string) ($current['api_key'] ?? '');
}
$defaultModel = isset($input['default_model']) ? trim((string) $input['default_model']) : '';
$defaultModel = sanitize_text_field($defaultModel);
$defaultImageModel = isset($input['default_image_model']) ? trim((string) $input['default_image_model']) : '';
$defaultImageModel = sanitize_text_field($defaultImageModel);
// Bust the cached /v1/models list when:
// - the API key changes (different keys can have different rosters), or
// - either default-model selection changes — the directory augments
// its output with user-configured defaults, so the cache must be
// rebuilt to surface the new selection to the SDK.
$apiKeyChanged = $apiKey !== ($current['api_key'] ?? '');
$textModelChanged = $defaultModel !== ($current['default_model'] ?? '');
$imageModelChanged = $defaultImageModel !== ($current['default_image_model'] ?? '');
if ($apiKeyChanged || $textModelChanged || $imageModelChanged) {
clear_models_cache();
}
return [
'api_key' => $apiKey,
'default_model' => $defaultModel,
'default_image_model' => $defaultImageModel,
];
}
/**
* Renders the API key form field. Stored masked when set.
*
* When the WordPress core "Connectors" screen is available, this field
* is replaced by a read-only summary that defers to that screen, since
* core auto-discovers this provider and storing the key in two places
* would only confuse the user.
*
* @since 1.0.0
*
* @return void
*/
public static function render_api_key_field(): void
{
if (core_connector_is_available()) {
self::render_api_key_field_managed_by_core();
return;
}
$settings = get_plugin_settings();
$value = (string) ($settings['api_key'] ?? '');
$option = VERCEL_AI_GATEWAY_PROVIDER_OPTION;
printf(
'',
esc_attr($option),
esc_attr($value)
);
echo '
';
if (defined('AI_GATEWAY_API_KEY') && AI_GATEWAY_API_KEY) {
echo '
' . esc_html__(
'A value is also defined in wp-config.php as AI_GATEWAY_API_KEY and will be used as a fallback when this field is empty.',
'ai-provider-for-vercel-ai-gateway'
) . '
';
}
}
/**
* Renders the read-only API key summary used when WP core's Connectors
* screen is in charge of the key.
*
* Shows where the key currently lives (database, env var, constant, or
* not configured) without ever revealing the value, and links straight
* to Settings → Connectors for editing.
*
* @since 1.0.0
*
* @return void
*/
private static function render_api_key_field_managed_by_core(): void
{
$connectorsUrl = admin_url('options-connectors.php');
$status = self::resolve_api_key_status();
echo '
';
}
/**
* Resolves where the active API key is coming from, for display in the
* read-only summary on the settings page. Never returns the key itself.
*
* @since 1.0.0
*
* @return array{label: string, detail: string}
*/
private static function resolve_api_key_status(): array
{
$envValue = getenv('AI_GATEWAY_API_KEY');
if (is_string($envValue) && $envValue !== '') {
return [
'label' => __('Configured via environment variable', 'ai-provider-for-vercel-ai-gateway'),
'detail' => __('AI_GATEWAY_API_KEY (environment) overrides any saved value.', 'ai-provider-for-vercel-ai-gateway'),
];
}
if (defined('AI_GATEWAY_API_KEY') && is_string(AI_GATEWAY_API_KEY) && AI_GATEWAY_API_KEY !== '') {
return [
'label' => __('Configured via wp-config.php constant', 'ai-provider-for-vercel-ai-gateway'),
'detail' => __('AI_GATEWAY_API_KEY (constant) overrides any saved value.', 'ai-provider-for-vercel-ai-gateway'),
];
}
$coreKey = (string) get_option(VERCEL_AI_GATEWAY_PROVIDER_CORE_CONNECTOR_OPTION, '');
if ($coreKey !== '') {
return [
'label' => __('Configured', 'ai-provider-for-vercel-ai-gateway'),
'detail' => __('Saved on Settings → Connectors.', 'ai-provider-for-vercel-ai-gateway'),
];
}
$settings = get_plugin_settings();
if (!empty($settings['api_key'])) {
return [
'label' => __('Configured (legacy)', 'ai-provider-for-vercel-ai-gateway'),
'detail' => __('A value saved by an older version of this plugin is still being used as a fallback. Re-enter it on Settings → Connectors to migrate.', 'ai-provider-for-vercel-ai-gateway'),
];
}
return [
'label' => __('Not configured', 'ai-provider-for-vercel-ai-gateway'),
'detail' => '',
];
}
/**
* Renders the default text-generation model form field.
*
* Renders a