converse-mcp-server 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,348 @@
1
+ /**
2
+ * OpenAI Provider
3
+ *
4
+ * Provider implementation for OpenAI GPT models using the official OpenAI SDK v5.
5
+ * Implements the unified interface: async invoke(messages, options) => { content, stop_reason, rawResponse }
6
+ */
7
+
8
+ import OpenAI from 'openai';
9
+ import { debugLog, debugError } from '../utils/console.js';
10
+
11
+ // Define supported models with their capabilities
12
+ const SUPPORTED_MODELS = {
13
+ 'o3': {
14
+ modelName: 'o3',
15
+ friendlyName: 'OpenAI (O3)',
16
+ contextWindow: 200000,
17
+ maxOutputTokens: 100000,
18
+ supportsStreaming: true,
19
+ supportsImages: true,
20
+ supportsTemperature: false,
21
+ timeout: 300000, // 5 minutes
22
+ description: 'Strong reasoning (200K context) - Logical problems, code generation, systematic analysis'
23
+ },
24
+ 'o3-mini': {
25
+ modelName: 'o3-mini',
26
+ friendlyName: 'OpenAI (O3-mini)',
27
+ contextWindow: 200000,
28
+ maxOutputTokens: 100000,
29
+ supportsStreaming: true,
30
+ supportsImages: true,
31
+ supportsTemperature: false,
32
+ timeout: 300000,
33
+ description: 'Fast O3 variant (200K context) - Balanced performance/speed, moderate complexity',
34
+ aliases: ['o3mini']
35
+ },
36
+ 'o3-pro-2025-06-10': {
37
+ modelName: 'o3-pro-2025-06-10',
38
+ friendlyName: 'OpenAI (O3-Pro)',
39
+ contextWindow: 200000,
40
+ maxOutputTokens: 100000,
41
+ supportsStreaming: true,
42
+ supportsImages: true,
43
+ supportsTemperature: false,
44
+ timeout: 1800000, // 30 minutes
45
+ description: 'Professional-grade reasoning (200K context) - EXTREMELY EXPENSIVE: Only for the most complex problems',
46
+ aliases: ['o3-pro']
47
+ },
48
+ 'o4-mini': {
49
+ modelName: 'o4-mini',
50
+ friendlyName: 'OpenAI (O4-mini)',
51
+ contextWindow: 200000,
52
+ maxOutputTokens: 100000,
53
+ supportsStreaming: true,
54
+ supportsImages: true,
55
+ supportsTemperature: true,
56
+ timeout: 180000, // 3 minutes
57
+ description: 'Latest reasoning model (200K context) - Optimized for shorter contexts, rapid reasoning',
58
+ aliases: ['o4mini']
59
+ },
60
+ 'gpt-4.1-2025-04-14': {
61
+ modelName: 'gpt-4.1-2025-04-14',
62
+ friendlyName: 'OpenAI (GPT-4.1)',
63
+ contextWindow: 1000000,
64
+ maxOutputTokens: 32768,
65
+ supportsStreaming: true,
66
+ supportsImages: true,
67
+ supportsTemperature: true,
68
+ timeout: 300000,
69
+ description: 'GPT-4.1 (1M context) - Advanced reasoning model with large context window',
70
+ aliases: ['gpt4.1']
71
+ },
72
+ 'gpt-4o': {
73
+ modelName: 'gpt-4o',
74
+ friendlyName: 'OpenAI (GPT-4o)',
75
+ contextWindow: 128000,
76
+ maxOutputTokens: 16384,
77
+ supportsStreaming: true,
78
+ supportsImages: true,
79
+ supportsTemperature: true,
80
+ timeout: 180000,
81
+ description: 'GPT-4o (128K context) - Multimodal flagship model with vision capabilities'
82
+ },
83
+ 'gpt-4o-mini': {
84
+ modelName: 'gpt-4o-mini',
85
+ friendlyName: 'OpenAI (GPT-4o-mini)',
86
+ contextWindow: 128000,
87
+ maxOutputTokens: 16384,
88
+ supportsStreaming: true,
89
+ supportsImages: true,
90
+ supportsTemperature: true,
91
+ timeout: 120000,
92
+ description: 'GPT-4o-mini (128K context) - Fast and efficient multimodal model'
93
+ }
94
+ };
95
+
96
+ /**
97
+ * Custom error class for OpenAI provider errors
98
+ */
99
+ class OpenAIProviderError extends Error {
100
+ constructor(message, code, originalError = null) {
101
+ super(message);
102
+ this.name = 'OpenAIProviderError';
103
+ this.code = code;
104
+ this.originalError = originalError;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Resolve model name to canonical form, including aliases
110
+ */
111
+ function resolveModelName(modelName) {
112
+ const modelNameLower = modelName.toLowerCase();
113
+
114
+ // Check exact matches first
115
+ for (const [supportedModel] of Object.entries(SUPPORTED_MODELS)) {
116
+ if (supportedModel.toLowerCase() === modelNameLower) {
117
+ return supportedModel;
118
+ }
119
+ }
120
+
121
+ // Check aliases
122
+ for (const [supportedModel, config] of Object.entries(SUPPORTED_MODELS)) {
123
+ if (config.aliases) {
124
+ for (const alias of config.aliases) {
125
+ if (alias.toLowerCase() === modelNameLower) {
126
+ return supportedModel;
127
+ }
128
+ }
129
+ }
130
+ }
131
+
132
+ // Return as-is if not found (let OpenAI API handle unknown models)
133
+ return modelName;
134
+ }
135
+
136
+ /**
137
+ * Validate OpenAI API key format
138
+ */
139
+ function validateApiKey(apiKey) {
140
+ if (!apiKey || typeof apiKey !== 'string') {
141
+ return false;
142
+ }
143
+
144
+ // OpenAI API keys typically start with 'sk-' and are at least 20 characters
145
+ return apiKey.startsWith('sk-') && apiKey.length >= 20;
146
+ }
147
+
148
+ /**
149
+ * Convert messages to OpenAI format
150
+ */
151
+ function convertMessages(messages) {
152
+ if (!Array.isArray(messages)) {
153
+ throw new OpenAIProviderError('Messages must be an array', 'INVALID_MESSAGES');
154
+ }
155
+
156
+ return messages.map((msg, index) => {
157
+ if (!msg || typeof msg !== 'object') {
158
+ throw new OpenAIProviderError(`Message at index ${index} must be an object`, 'INVALID_MESSAGE');
159
+ }
160
+
161
+ const { role, content } = msg;
162
+
163
+ if (!role || !['system', 'user', 'assistant'].includes(role)) {
164
+ throw new OpenAIProviderError(`Invalid role "${role}" at message index ${index}`, 'INVALID_ROLE');
165
+ }
166
+
167
+ if (!content) {
168
+ throw new OpenAIProviderError(`Message content is required at index ${index}`, 'MISSING_CONTENT');
169
+ }
170
+
171
+ return { role, content };
172
+ });
173
+ }
174
+
175
+ /**
176
+ * Main OpenAI provider implementation
177
+ */
178
+ export const openaiProvider = {
179
+ /**
180
+ * Unified provider interface: invoke messages with options
181
+ * @param {Array} messages - Array of message objects with role and content
182
+ * @param {Object} options - Configuration options
183
+ * @returns {Object} - { content, stop_reason, rawResponse }
184
+ */
185
+ async invoke(messages, options = {}) {
186
+ const {
187
+ model = 'gpt-4o-mini',
188
+ temperature = 0.7,
189
+ maxTokens = null,
190
+ stream = false,
191
+ reasoningEffort = 'medium',
192
+ config,
193
+ ...otherOptions
194
+ } = options;
195
+
196
+ // Validate API key
197
+ if (!config?.apiKeys?.openai) {
198
+ throw new OpenAIProviderError('OpenAI API key not configured', 'MISSING_API_KEY');
199
+ }
200
+
201
+ if (!validateApiKey(config.apiKeys.openai)) {
202
+ throw new OpenAIProviderError('Invalid OpenAI API key format', 'INVALID_API_KEY');
203
+ }
204
+
205
+ // Initialize OpenAI client
206
+ const openai = new OpenAI({
207
+ apiKey: config.apiKeys.openai,
208
+ });
209
+
210
+ // Resolve model name
211
+ const resolvedModel = resolveModelName(model);
212
+ const modelConfig = SUPPORTED_MODELS[resolvedModel] || {};
213
+
214
+ // Convert and validate messages
215
+ const openaiMessages = convertMessages(messages);
216
+
217
+ // Build request payload (exclude reasoning_effort from otherOptions)
218
+ const { reasoning_effort: _unused, ...cleanOptions } = otherOptions;
219
+ const requestPayload = {
220
+ model: resolvedModel,
221
+ messages: openaiMessages,
222
+ stream,
223
+ ...cleanOptions
224
+ };
225
+
226
+ // Add temperature if model supports it
227
+ if (modelConfig.supportsTemperature !== false && temperature !== undefined) {
228
+ requestPayload.temperature = Math.max(0, Math.min(2, temperature));
229
+ }
230
+
231
+ // Add max tokens if specified
232
+ if (maxTokens) {
233
+ requestPayload.max_tokens = Math.min(maxTokens, modelConfig.maxOutputTokens || 100000);
234
+ }
235
+
236
+ // Add reasoning effort for thinking models (o3 series only)
237
+ if (resolvedModel.startsWith('o3') && reasoningEffort) {
238
+ requestPayload.reasoning_effort = reasoningEffort;
239
+ }
240
+ // Note: GPT-4o and other models don't support reasoning_effort parameter
241
+ // Only O3 series models support this parameter
242
+
243
+ try {
244
+ debugLog(`[OpenAI] Calling ${resolvedModel} with ${openaiMessages.length} messages`);
245
+
246
+ const startTime = Date.now();
247
+
248
+ // Make the API call
249
+ const response = await openai.chat.completions.create(requestPayload);
250
+
251
+ const responseTime = Date.now() - startTime;
252
+ debugLog(`[OpenAI] Response received in ${responseTime}ms`);
253
+
254
+ // Extract response data
255
+ const choice = response.choices[0];
256
+ if (!choice) {
257
+ throw new OpenAIProviderError('No response choice received from OpenAI', 'NO_RESPONSE_CHOICE');
258
+ }
259
+
260
+ const content = choice.message?.content;
261
+ if (!content) {
262
+ throw new OpenAIProviderError('No content in response from OpenAI', 'NO_RESPONSE_CONTENT');
263
+ }
264
+
265
+ // Extract usage information
266
+ const usage = response.usage || {};
267
+
268
+ // Return unified response format
269
+ return {
270
+ content,
271
+ stop_reason: choice.finish_reason || 'stop',
272
+ rawResponse: response,
273
+ metadata: {
274
+ model: response.model || resolvedModel,
275
+ usage: {
276
+ input_tokens: usage.prompt_tokens || 0,
277
+ output_tokens: usage.completion_tokens || 0,
278
+ total_tokens: usage.total_tokens || 0
279
+ },
280
+ response_time_ms: responseTime,
281
+ finish_reason: choice.finish_reason,
282
+ provider: 'openai'
283
+ }
284
+ };
285
+
286
+ } catch (error) {
287
+ debugError('[OpenAI] Error during API call:', error);
288
+
289
+ // Handle specific OpenAI errors
290
+ if (error.code === 'insufficient_quota') {
291
+ throw new OpenAIProviderError('OpenAI API quota exceeded', 'QUOTA_EXCEEDED', error);
292
+ } else if (error.code === 'invalid_api_key') {
293
+ throw new OpenAIProviderError('Invalid OpenAI API key', 'INVALID_API_KEY', error);
294
+ } else if (error.code === 'model_not_found') {
295
+ throw new OpenAIProviderError(`Model ${resolvedModel} not found`, 'MODEL_NOT_FOUND', error);
296
+ } else if (error.code === 'context_length_exceeded') {
297
+ throw new OpenAIProviderError('Context length exceeded for model', 'CONTEXT_LENGTH_EXCEEDED', error);
298
+ } else if (error.type === 'invalid_request_error') {
299
+ throw new OpenAIProviderError(`Invalid request: ${error.message}`, 'INVALID_REQUEST', error);
300
+ } else if (error.type === 'rate_limit_error') {
301
+ throw new OpenAIProviderError('OpenAI rate limit exceeded', 'RATE_LIMIT_EXCEEDED', error);
302
+ }
303
+
304
+ // Generic error handling
305
+ throw new OpenAIProviderError(
306
+ `OpenAI API error: ${error.message || 'Unknown error'}`,
307
+ 'API_ERROR',
308
+ error
309
+ );
310
+ }
311
+ },
312
+
313
+ /**
314
+ * Validate configuration for OpenAI provider
315
+ * @param {Object} config - Configuration object
316
+ * @returns {boolean} - True if configuration is valid
317
+ */
318
+ validateConfig(config) {
319
+ return !!(config?.apiKeys?.openai && validateApiKey(config.apiKeys.openai));
320
+ },
321
+
322
+ /**
323
+ * Check if provider is available with current configuration
324
+ * @param {Object} config - Configuration object
325
+ * @returns {boolean} - True if provider is available
326
+ */
327
+ isAvailable(config) {
328
+ return this.validateConfig(config);
329
+ },
330
+
331
+ /**
332
+ * Get supported models
333
+ * @returns {Object} - Map of supported models and their configurations
334
+ */
335
+ getSupportedModels() {
336
+ return SUPPORTED_MODELS;
337
+ },
338
+
339
+ /**
340
+ * Get model configuration
341
+ * @param {string} modelName - Model name
342
+ * @returns {Object|null} - Model configuration or null if not found
343
+ */
344
+ getModelConfig(modelName) {
345
+ const resolved = resolveModelName(modelName);
346
+ return SUPPORTED_MODELS[resolved] || null;
347
+ }
348
+ };
@@ -0,0 +1,305 @@
1
+ /**
2
+ * XAI (Grok) Provider
3
+ *
4
+ * Provider implementation for XAI Grok models using OpenAI-compatible API with custom baseURL.
5
+ * Implements the unified interface: async invoke(messages, options) => { content, stop_reason, rawResponse }
6
+ */
7
+
8
+ import OpenAI from 'openai';
9
+ import { debugLog, debugError } from '../utils/console.js';
10
+
11
+ // Define supported Grok models with their capabilities
12
+ const SUPPORTED_MODELS = {
13
+ 'grok-4-0709': {
14
+ modelName: 'grok-4-0709',
15
+ friendlyName: 'X.AI (Grok 4)',
16
+ contextWindow: 256000,
17
+ maxOutputTokens: 256000,
18
+ supportsStreaming: true,
19
+ supportsImages: true,
20
+ supportsTemperature: true,
21
+ timeout: 300000, // 5 minutes
22
+ description: 'GROK-4 (256K context) - Latest advanced model from X.AI with image support',
23
+ aliases: ['grok', 'grok4', 'grok-4', 'grok-4-latest']
24
+ },
25
+ 'grok-3': {
26
+ modelName: 'grok-3',
27
+ friendlyName: 'X.AI (Grok 3)',
28
+ contextWindow: 131072,
29
+ maxOutputTokens: 131072,
30
+ supportsStreaming: true,
31
+ supportsImages: false,
32
+ supportsTemperature: true,
33
+ timeout: 300000,
34
+ description: 'GROK-3 (131K context) - Previous generation reasoning model from X.AI',
35
+ aliases: ['grok3']
36
+ },
37
+ 'grok-3-fast': {
38
+ modelName: 'grok-3-fast',
39
+ friendlyName: 'X.AI (Grok 3 Fast)',
40
+ contextWindow: 131072,
41
+ maxOutputTokens: 131072,
42
+ supportsStreaming: true,
43
+ supportsImages: false,
44
+ supportsTemperature: true,
45
+ timeout: 300000,
46
+ description: 'GROK-3 Fast (131K context) - Higher performance variant, faster processing but more expensive',
47
+ aliases: ['grok3fast', 'grok3-fast']
48
+ }
49
+ };
50
+
51
+ /**
52
+ * Custom error class for XAI provider errors
53
+ */
54
+ class XAIProviderError extends Error {
55
+ constructor(message, code, originalError = null) {
56
+ super(message);
57
+ this.name = 'XAIProviderError';
58
+ this.code = code;
59
+ this.originalError = originalError;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Resolve model name to canonical form, including aliases
65
+ */
66
+ function resolveModelName(modelName) {
67
+ const modelNameLower = modelName.toLowerCase();
68
+
69
+ // Check exact matches first
70
+ for (const [supportedModel] of Object.entries(SUPPORTED_MODELS)) {
71
+ if (supportedModel.toLowerCase() === modelNameLower) {
72
+ return supportedModel;
73
+ }
74
+ }
75
+
76
+ // Check aliases
77
+ for (const [supportedModel, config] of Object.entries(SUPPORTED_MODELS)) {
78
+ if (config.aliases) {
79
+ for (const alias of config.aliases) {
80
+ if (alias.toLowerCase() === modelNameLower) {
81
+ return supportedModel;
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ // Return as-is if not found (let XAI API handle unknown models)
88
+ return modelName;
89
+ }
90
+
91
+ /**
92
+ * Validate XAI API key format
93
+ */
94
+ function validateApiKey(apiKey) {
95
+ if (!apiKey || typeof apiKey !== 'string') {
96
+ return false;
97
+ }
98
+
99
+ // XAI API keys typically start with 'xai-' and are at least 20 characters
100
+ return apiKey.startsWith('xai-') && apiKey.length >= 20;
101
+ }
102
+
103
+ /**
104
+ * Convert messages to XAI/OpenAI format
105
+ */
106
+ function convertMessages(messages) {
107
+ if (!Array.isArray(messages)) {
108
+ throw new XAIProviderError('Messages must be an array', 'INVALID_MESSAGES');
109
+ }
110
+
111
+ return messages.map((msg, index) => {
112
+ if (!msg || typeof msg !== 'object') {
113
+ throw new XAIProviderError(`Message at index ${index} must be an object`, 'INVALID_MESSAGE');
114
+ }
115
+
116
+ const { role, content } = msg;
117
+
118
+ if (!role || !['system', 'user', 'assistant'].includes(role)) {
119
+ throw new XAIProviderError(`Invalid role "${role}" at message index ${index}`, 'INVALID_ROLE');
120
+ }
121
+
122
+ if (!content) {
123
+ throw new XAIProviderError(`Message content is required at index ${index}`, 'MISSING_CONTENT');
124
+ }
125
+
126
+ return { role, content };
127
+ });
128
+ }
129
+
130
+ /**
131
+ * Main XAI provider implementation
132
+ */
133
+ export const xaiProvider = {
134
+ /**
135
+ * Unified provider interface: invoke messages with options
136
+ * @param {Array} messages - Array of message objects with role and content
137
+ * @param {Object} options - Configuration options
138
+ * @returns {Object} - { content, stop_reason, rawResponse }
139
+ */
140
+ async invoke(messages, options = {}) {
141
+ const {
142
+ model = 'grok-4-0709',
143
+ temperature = 0.7,
144
+ maxTokens = null,
145
+ stream = false,
146
+ reasoningEffort = 'medium',
147
+ config,
148
+ ...otherOptions
149
+ } = options;
150
+
151
+ // Validate API key
152
+ if (!config?.apiKeys?.xai) {
153
+ throw new XAIProviderError('XAI API key not configured', 'MISSING_API_KEY');
154
+ }
155
+
156
+ if (!validateApiKey(config.apiKeys.xai)) {
157
+ throw new XAIProviderError('Invalid XAI API key format', 'INVALID_API_KEY');
158
+ }
159
+
160
+ // Get base URL from config or use default
161
+ const baseURL = config.providers?.xaiBaseUrl || 'https://api.x.ai/v1';
162
+
163
+ // Initialize OpenAI client with XAI base URL
164
+ const openai = new OpenAI({
165
+ apiKey: config.apiKeys.xai,
166
+ baseURL,
167
+ });
168
+
169
+ // Resolve model name
170
+ const resolvedModel = resolveModelName(model);
171
+ const modelConfig = SUPPORTED_MODELS[resolvedModel] || {};
172
+
173
+ // Convert and validate messages
174
+ const xaiMessages = convertMessages(messages);
175
+
176
+ // Filter out unsupported parameters for XAI/Grok models
177
+ const { reasoning_effort, reasoningEffort: reasoningEffortAlias, ...supportedOptions } = otherOptions;
178
+
179
+ // Build request payload
180
+ const requestPayload = {
181
+ model: resolvedModel,
182
+ messages: xaiMessages,
183
+ stream,
184
+ ...supportedOptions
185
+ };
186
+
187
+ // Add temperature (all Grok models support temperature)
188
+ if (temperature !== undefined) {
189
+ requestPayload.temperature = Math.max(0, Math.min(2, temperature));
190
+ }
191
+
192
+ // Add max tokens if specified
193
+ if (maxTokens) {
194
+ requestPayload.max_tokens = Math.min(maxTokens, modelConfig.maxOutputTokens || 256000);
195
+ }
196
+
197
+ // Note: XAI/Grok models don't currently support reasoning_effort parameter
198
+ // We silently ignore it for API consistency (no need to log warnings in tests)
199
+
200
+ try {
201
+ debugLog(`[XAI] Calling ${resolvedModel} with ${xaiMessages.length} messages`);
202
+
203
+ const startTime = Date.now();
204
+
205
+ // Make the API call
206
+ const response = await openai.chat.completions.create(requestPayload);
207
+
208
+ const responseTime = Date.now() - startTime;
209
+ debugLog(`[XAI] Response received in ${responseTime}ms`);
210
+
211
+ // Extract response data
212
+ const choice = response.choices[0];
213
+ if (!choice) {
214
+ throw new XAIProviderError('No response choice received from XAI', 'NO_RESPONSE_CHOICE');
215
+ }
216
+
217
+ const content = choice.message?.content;
218
+ if (!content) {
219
+ throw new XAIProviderError('No content in response from XAI', 'NO_RESPONSE_CONTENT');
220
+ }
221
+
222
+ // Extract usage information
223
+ const usage = response.usage || {};
224
+
225
+ // Return unified response format
226
+ return {
227
+ content,
228
+ stop_reason: choice.finish_reason || 'stop',
229
+ rawResponse: response,
230
+ metadata: {
231
+ model: response.model || resolvedModel,
232
+ usage: {
233
+ input_tokens: usage.prompt_tokens || 0,
234
+ output_tokens: usage.completion_tokens || 0,
235
+ total_tokens: usage.total_tokens || 0
236
+ },
237
+ response_time_ms: responseTime,
238
+ finish_reason: choice.finish_reason,
239
+ provider: 'xai'
240
+ }
241
+ };
242
+
243
+ } catch (error) {
244
+ debugError('[XAI] Error during API call:', error);
245
+
246
+ // Handle specific XAI/OpenAI compatible errors
247
+ if (error.code === 'insufficient_quota') {
248
+ throw new XAIProviderError('XAI API quota exceeded', 'QUOTA_EXCEEDED', error);
249
+ } else if (error.code === 'invalid_api_key') {
250
+ throw new XAIProviderError('Invalid XAI API key', 'INVALID_API_KEY', error);
251
+ } else if (error.code === 'model_not_found') {
252
+ throw new XAIProviderError(`Model ${resolvedModel} not found`, 'MODEL_NOT_FOUND', error);
253
+ } else if (error.code === 'context_length_exceeded') {
254
+ throw new XAIProviderError('Context length exceeded for model', 'CONTEXT_LENGTH_EXCEEDED', error);
255
+ } else if (error.type === 'invalid_request_error') {
256
+ throw new XAIProviderError(`Invalid request: ${error.message}`, 'INVALID_REQUEST', error);
257
+ } else if (error.type === 'rate_limit_error') {
258
+ throw new XAIProviderError('XAI rate limit exceeded', 'RATE_LIMIT_EXCEEDED', error);
259
+ }
260
+
261
+ // Generic error handling
262
+ throw new XAIProviderError(
263
+ `XAI API error: ${error.message || 'Unknown error'}`,
264
+ 'API_ERROR',
265
+ error
266
+ );
267
+ }
268
+ },
269
+
270
+ /**
271
+ * Validate configuration for XAI provider
272
+ * @param {Object} config - Configuration object
273
+ * @returns {boolean} - True if configuration is valid
274
+ */
275
+ validateConfig(config) {
276
+ return !!(config?.apiKeys?.xai && validateApiKey(config.apiKeys.xai));
277
+ },
278
+
279
+ /**
280
+ * Check if provider is available with current configuration
281
+ * @param {Object} config - Configuration object
282
+ * @returns {boolean} - True if provider is available
283
+ */
284
+ isAvailable(config) {
285
+ return this.validateConfig(config);
286
+ },
287
+
288
+ /**
289
+ * Get supported models
290
+ * @returns {Object} - Map of supported models and their configurations
291
+ */
292
+ getSupportedModels() {
293
+ return SUPPORTED_MODELS;
294
+ },
295
+
296
+ /**
297
+ * Get model configuration
298
+ * @param {string} modelName - Model name
299
+ * @returns {Object|null} - Model configuration or null if not found
300
+ */
301
+ getModelConfig(modelName) {
302
+ const resolved = resolveModelName(modelName);
303
+ return SUPPORTED_MODELS[resolved] || null;
304
+ }
305
+ };