berget 1.1.0 → 1.3.0
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.
- package/-27b-it +0 -0
- package/dist/index.js +47 -1
- package/dist/package.json +35 -0
- package/dist/src/client.js +55 -48
- package/dist/src/commands/api-keys.js +13 -7
- package/dist/src/commands/chat.js +100 -18
- package/dist/src/services/api-key-service.js +6 -16
- package/dist/src/services/chat-service.js +284 -56
- package/dist/src/utils/default-api-key.js +115 -6
- package/dist/src/utils/error-handler.js +4 -4
- package/dist/src/utils/logger.js +160 -0
- package/dist/src/utils/token-manager.js +6 -9
- package/index.ts +52 -1
- package/package.json +3 -2
- package/src/client.ts +83 -81
- package/src/commands/api-keys.ts +17 -7
- package/src/commands/chat.ts +142 -22
- package/src/services/api-key-service.ts +12 -20
- package/src/services/chat-service.ts +407 -87
- package/src/types/api.d.ts +203 -9
- package/src/types/json.d.ts +4 -0
- package/src/utils/default-api-key.ts +124 -6
- package/src/utils/error-handler.ts +4 -4
- package/src/utils/logger.ts +159 -0
- package/src/utils/token-manager.ts +6 -5
- package/tsconfig.json +1 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { createAuthenticatedClient } from '../client'
|
|
2
|
-
import {
|
|
3
|
-
import chalk from 'chalk'
|
|
1
|
+
import { createAuthenticatedClient, API_BASE_URL } from '../client'
|
|
2
|
+
import { logger } from '../utils/logger'
|
|
4
3
|
|
|
5
4
|
export interface ChatMessage {
|
|
6
5
|
role: 'system' | 'user' | 'assistant'
|
|
@@ -8,13 +7,14 @@ export interface ChatMessage {
|
|
|
8
7
|
}
|
|
9
8
|
|
|
10
9
|
export interface ChatCompletionOptions {
|
|
11
|
-
model
|
|
10
|
+
model?: string
|
|
12
11
|
messages: ChatMessage[]
|
|
13
12
|
temperature?: number
|
|
14
13
|
max_tokens?: number
|
|
15
14
|
stream?: boolean
|
|
16
15
|
top_p?: number
|
|
17
16
|
apiKey?: string
|
|
17
|
+
onChunk?: (chunk: any) => void
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -24,14 +24,14 @@ export interface ChatCompletionOptions {
|
|
|
24
24
|
export class ChatService {
|
|
25
25
|
private static instance: ChatService
|
|
26
26
|
private client = createAuthenticatedClient()
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
// Command group name for this service
|
|
29
29
|
public static readonly COMMAND_GROUP = 'chat'
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
// Subcommands for this service
|
|
32
32
|
public static readonly COMMANDS = {
|
|
33
33
|
RUN: 'run',
|
|
34
|
-
LIST: 'list'
|
|
34
|
+
LIST: 'list',
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
private constructor() {}
|
|
@@ -49,99 +49,418 @@ export class ChatService {
|
|
|
49
49
|
*/
|
|
50
50
|
public async createCompletion(options: ChatCompletionOptions): Promise<any> {
|
|
51
51
|
try {
|
|
52
|
+
logger.debug('Starting createCompletion method')
|
|
53
|
+
|
|
54
|
+
// Initialize options if undefined
|
|
55
|
+
const optionsCopy = options ? { ...options } : { messages: [] }
|
|
56
|
+
|
|
57
|
+
// Check if messages are defined
|
|
58
|
+
if (!optionsCopy.messages || !Array.isArray(optionsCopy.messages)) {
|
|
59
|
+
logger.error('messages is undefined or not an array')
|
|
60
|
+
optionsCopy.messages = []
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Log the options object
|
|
64
|
+
logger.debug('Starting createCompletion with options:')
|
|
65
|
+
try {
|
|
66
|
+
logger.debug(
|
|
67
|
+
JSON.stringify(
|
|
68
|
+
{
|
|
69
|
+
...optionsCopy,
|
|
70
|
+
apiKey: optionsCopy.apiKey ? '***' : undefined,
|
|
71
|
+
messages: optionsCopy.messages
|
|
72
|
+
? `${optionsCopy.messages.length} messages`
|
|
73
|
+
: '0 messages',
|
|
74
|
+
},
|
|
75
|
+
null,
|
|
76
|
+
2
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
} catch (error) {
|
|
80
|
+
logger.error('Failed to stringify options:', error)
|
|
81
|
+
}
|
|
82
|
+
|
|
52
83
|
const headers: Record<string, string> = {}
|
|
53
|
-
|
|
54
|
-
// Check
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// If
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
84
|
+
|
|
85
|
+
// Check for environment variables first - prioritize this over everything else
|
|
86
|
+
const envApiKey = process.env.BERGET_API_KEY
|
|
87
|
+
if (envApiKey) {
|
|
88
|
+
logger.debug('Using API key from BERGET_API_KEY environment variable')
|
|
89
|
+
optionsCopy.apiKey = envApiKey
|
|
90
|
+
// Skip the default API key logic if we already have a key
|
|
91
|
+
return this.executeCompletion(optionsCopy, headers)
|
|
92
|
+
}
|
|
93
|
+
// If API key is already provided, use it directly
|
|
94
|
+
else if (optionsCopy.apiKey) {
|
|
95
|
+
logger.debug('Using API key provided in options')
|
|
96
|
+
// Skip the default API key logic if we already have a key
|
|
97
|
+
return this.executeCompletion(optionsCopy, headers)
|
|
98
|
+
}
|
|
99
|
+
// Only try to get the default API key if no API key is provided and no env var is set
|
|
100
|
+
else {
|
|
101
|
+
logger.debug('No API key provided, trying to get default')
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
// Import the DefaultApiKeyManager directly
|
|
105
|
+
logger.debug('Importing DefaultApiKeyManager')
|
|
106
|
+
|
|
107
|
+
const DefaultApiKeyManager = (
|
|
108
|
+
await import('../utils/default-api-key')
|
|
109
|
+
).DefaultApiKeyManager
|
|
110
|
+
const defaultApiKeyManager = DefaultApiKeyManager.getInstance()
|
|
111
|
+
|
|
112
|
+
logger.debug('Got DefaultApiKeyManager instance')
|
|
113
|
+
|
|
114
|
+
// Try to get the default API key
|
|
115
|
+
logger.debug('Calling promptForDefaultApiKey')
|
|
116
|
+
|
|
117
|
+
const defaultApiKeyData = defaultApiKeyManager.getDefaultApiKeyData()
|
|
118
|
+
const apiKey =
|
|
119
|
+
defaultApiKeyData?.key ||
|
|
120
|
+
(await defaultApiKeyManager.promptForDefaultApiKey())
|
|
121
|
+
|
|
122
|
+
logger.debug(`Default API key data exists: ${!!defaultApiKeyData}`)
|
|
123
|
+
logger.debug(
|
|
124
|
+
`promptForDefaultApiKey returned: ${apiKey ? 'a key' : 'null'}`
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if (apiKey) {
|
|
128
|
+
logger.debug('Using API key from default API key manager')
|
|
129
|
+
optionsCopy.apiKey = apiKey
|
|
130
|
+
} else {
|
|
131
|
+
logger.warn('No API key available. You need to either:')
|
|
132
|
+
logger.warn(
|
|
133
|
+
'1. Create an API key with: berget api-keys create --name "My Key"'
|
|
134
|
+
)
|
|
135
|
+
logger.warn(
|
|
136
|
+
'2. Set a default API key with: berget api-keys set-default <id>'
|
|
137
|
+
)
|
|
138
|
+
logger.warn('3. Provide an API key with the --api-key option')
|
|
139
|
+
logger.warn('4. Set the BERGET_API_KEY environment variable')
|
|
140
|
+
logger.warn('\nExample:')
|
|
141
|
+
logger.warn(' export BERGET_API_KEY=your_api_key_here')
|
|
142
|
+
logger.warn(' # or for a single command:')
|
|
143
|
+
logger.warn(
|
|
144
|
+
' BERGET_API_KEY=your_api_key_here berget chat run google/gemma-3-27b-it'
|
|
145
|
+
)
|
|
146
|
+
throw new Error('No API key provided and no default API key set')
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Set the API key in the options
|
|
150
|
+
logger.debug('Setting API key in options')
|
|
151
|
+
|
|
152
|
+
// Only set the API key if it's not null
|
|
153
|
+
if (apiKey) {
|
|
154
|
+
optionsCopy.apiKey = apiKey
|
|
155
|
+
}
|
|
156
|
+
} catch (error) {
|
|
157
|
+
logger.error('Error getting API key:')
|
|
158
|
+
if (error instanceof Error) {
|
|
159
|
+
logger.error(error.message)
|
|
160
|
+
}
|
|
161
|
+
logger.warn(
|
|
162
|
+
'Please create an API key with: berget api-keys create --name "My Key"'
|
|
163
|
+
)
|
|
164
|
+
throw new Error('Failed to get API key')
|
|
107
165
|
}
|
|
108
|
-
|
|
109
|
-
if (error) throw new Error(JSON.stringify(error))
|
|
110
|
-
return data
|
|
111
166
|
}
|
|
167
|
+
|
|
168
|
+
// Set default model if not provided
|
|
169
|
+
if (!optionsCopy.model) {
|
|
170
|
+
logger.debug('No model specified, using default: google/gemma-3-27b-it')
|
|
171
|
+
optionsCopy.model = 'google/gemma-3-27b-it'
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
logger.debug('Chat completion options:')
|
|
175
|
+
logger.debug(
|
|
176
|
+
JSON.stringify(
|
|
177
|
+
{
|
|
178
|
+
...optionsCopy,
|
|
179
|
+
apiKey: optionsCopy.apiKey ? '***' : undefined, // Hide the actual API key in debug output
|
|
180
|
+
},
|
|
181
|
+
null,
|
|
182
|
+
2
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return this.executeCompletion(optionsCopy, headers)
|
|
112
187
|
} catch (error) {
|
|
113
188
|
// Improved error handling
|
|
114
|
-
let errorMessage = 'Failed to create chat completion'
|
|
115
|
-
|
|
189
|
+
let errorMessage = 'Failed to create chat completion'
|
|
190
|
+
|
|
116
191
|
if (error instanceof Error) {
|
|
117
192
|
try {
|
|
118
193
|
// Try to parse the error message as JSON
|
|
119
|
-
const parsedError = JSON.parse(error.message)
|
|
194
|
+
const parsedError = JSON.parse(error.message)
|
|
120
195
|
if (parsedError.error && parsedError.error.message) {
|
|
121
|
-
errorMessage = `Chat error: ${parsedError.error.message}
|
|
196
|
+
errorMessage = `Chat error: ${parsedError.error.message}`
|
|
122
197
|
}
|
|
123
198
|
} catch (e) {
|
|
124
199
|
// If parsing fails, use the original error message
|
|
125
|
-
errorMessage = `Chat error: ${error.message}
|
|
200
|
+
errorMessage = `Chat error: ${error.message}`
|
|
126
201
|
}
|
|
127
202
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
throw new Error(errorMessage)
|
|
203
|
+
|
|
204
|
+
logger.error(errorMessage)
|
|
205
|
+
throw new Error(errorMessage)
|
|
131
206
|
}
|
|
132
207
|
}
|
|
133
|
-
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Execute the completion request with the provided options
|
|
211
|
+
* @param options The completion options
|
|
212
|
+
* @param headers Additional headers to include
|
|
213
|
+
* @returns The completion response
|
|
214
|
+
*/
|
|
215
|
+
private async executeCompletion(
|
|
216
|
+
options: ChatCompletionOptions,
|
|
217
|
+
headers: Record<string, string> = {}
|
|
218
|
+
): Promise<any> {
|
|
219
|
+
try {
|
|
220
|
+
// If an API key is provided, use it for this request
|
|
221
|
+
if (options.apiKey) {
|
|
222
|
+
// API keys should be sent directly, not with Bearer prefix
|
|
223
|
+
headers['Authorization'] = options.apiKey
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Remove apiKey and onChunk from options before sending to API
|
|
227
|
+
const { apiKey, onChunk, ...requestOptions } = options
|
|
228
|
+
|
|
229
|
+
logger.debug('Request options:')
|
|
230
|
+
logger.debug(
|
|
231
|
+
JSON.stringify(
|
|
232
|
+
{
|
|
233
|
+
...requestOptions,
|
|
234
|
+
messages: requestOptions.messages
|
|
235
|
+
? `${requestOptions.messages.length} messages`
|
|
236
|
+
: '0 messages',
|
|
237
|
+
},
|
|
238
|
+
null,
|
|
239
|
+
2
|
|
240
|
+
)
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
// Handle streaming responses differently
|
|
244
|
+
if (requestOptions.stream && onChunk) {
|
|
245
|
+
return await this.handleStreamingResponse(
|
|
246
|
+
{ ...requestOptions, onChunk },
|
|
247
|
+
headers
|
|
248
|
+
)
|
|
249
|
+
} else {
|
|
250
|
+
// Ensure model is always defined for the API call
|
|
251
|
+
const requestBody = {
|
|
252
|
+
...requestOptions,
|
|
253
|
+
model: requestOptions.model || 'google/gemma-3-27b-it',
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Debug the headers being sent
|
|
257
|
+
logger.debug('Headers being sent:')
|
|
258
|
+
logger.debug(JSON.stringify(headers, null, 2))
|
|
259
|
+
|
|
260
|
+
const response = await this.client.POST('/v1/chat/completions', {
|
|
261
|
+
body: requestBody,
|
|
262
|
+
headers,
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
// Check if response has an error property
|
|
266
|
+
const responseAny = response as any
|
|
267
|
+
if (responseAny && responseAny.error)
|
|
268
|
+
throw new Error(JSON.stringify(responseAny.error))
|
|
269
|
+
|
|
270
|
+
logger.debug('API response:')
|
|
271
|
+
logger.debug(JSON.stringify(response, null, 2))
|
|
272
|
+
|
|
273
|
+
// Output the complete response data for debugging
|
|
274
|
+
logger.debug('Complete response data:')
|
|
275
|
+
logger.debug(JSON.stringify(response.data, null, 2))
|
|
276
|
+
|
|
277
|
+
return response.data
|
|
278
|
+
}
|
|
279
|
+
} catch (requestError) {
|
|
280
|
+
logger.debug(
|
|
281
|
+
`Request error: ${
|
|
282
|
+
requestError instanceof Error
|
|
283
|
+
? requestError.message
|
|
284
|
+
: String(requestError)
|
|
285
|
+
}`
|
|
286
|
+
)
|
|
287
|
+
throw requestError
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Handle the case when no API key is available
|
|
293
|
+
*/
|
|
294
|
+
private handleNoApiKey(): never {
|
|
295
|
+
// We've exhausted all options for getting an API key
|
|
296
|
+
logger.warn('No API key available. You need to either:')
|
|
297
|
+
logger.warn(
|
|
298
|
+
'1. Create an API key with: berget api-keys create --name "My Key"'
|
|
299
|
+
)
|
|
300
|
+
logger.warn(
|
|
301
|
+
'2. Set a default API key with: berget api-keys set-default <id>'
|
|
302
|
+
)
|
|
303
|
+
logger.warn('3. Provide an API key with the --api-key option')
|
|
304
|
+
logger.warn('4. Set the BERGET_API_KEY environment variable')
|
|
305
|
+
logger.warn('\nExample:')
|
|
306
|
+
logger.warn(' export BERGET_API_KEY=your_api_key_here')
|
|
307
|
+
logger.warn(' # or for a single command:')
|
|
308
|
+
logger.warn(
|
|
309
|
+
' BERGET_API_KEY=your_api_key_here berget chat run google/gemma-3-27b-it'
|
|
310
|
+
)
|
|
311
|
+
throw new Error(
|
|
312
|
+
'No API key available. Please provide an API key or set a default API key.'
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Handle streaming response from the API
|
|
318
|
+
* @param options Request options
|
|
319
|
+
* @param headers Request headers
|
|
320
|
+
* @returns A promise that resolves when the stream is complete
|
|
321
|
+
*/
|
|
322
|
+
private async handleStreamingResponse(
|
|
323
|
+
options: any,
|
|
324
|
+
headers: Record<string, string>
|
|
325
|
+
): Promise<any> {
|
|
326
|
+
logger.debug('Handling streaming response')
|
|
327
|
+
|
|
328
|
+
// Create URL with query parameters
|
|
329
|
+
const url = new URL(`${API_BASE_URL}/v1/chat/completions`)
|
|
330
|
+
|
|
331
|
+
// Debug the headers and options
|
|
332
|
+
logger.debug('Streaming headers:')
|
|
333
|
+
logger.debug(JSON.stringify(headers, null, 2))
|
|
334
|
+
|
|
335
|
+
logger.debug('Streaming options:')
|
|
336
|
+
logger.debug(
|
|
337
|
+
JSON.stringify(
|
|
338
|
+
{
|
|
339
|
+
...options,
|
|
340
|
+
onChunk: options.onChunk ? 'function present' : 'no function',
|
|
341
|
+
},
|
|
342
|
+
null,
|
|
343
|
+
2
|
|
344
|
+
)
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
// Make fetch request directly to handle streaming
|
|
349
|
+
const response = await fetch(url.toString(), {
|
|
350
|
+
method: 'POST',
|
|
351
|
+
headers: {
|
|
352
|
+
'Content-Type': 'application/json',
|
|
353
|
+
Accept: 'text/event-stream',
|
|
354
|
+
...headers,
|
|
355
|
+
},
|
|
356
|
+
body: JSON.stringify(options),
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
if (!response.ok) {
|
|
360
|
+
const errorText = await response.text()
|
|
361
|
+
logger.error(
|
|
362
|
+
`Stream request failed: ${response.status} ${response.statusText}`
|
|
363
|
+
)
|
|
364
|
+
logger.debug(`Error response: ${errorText}`)
|
|
365
|
+
throw new Error(
|
|
366
|
+
`Stream request failed: ${response.status} ${response.statusText}`
|
|
367
|
+
)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (!response.body) {
|
|
371
|
+
throw new Error('No response body received')
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Process the stream
|
|
375
|
+
const reader = response.body.getReader()
|
|
376
|
+
const decoder = new TextDecoder()
|
|
377
|
+
let fullContent = ''
|
|
378
|
+
let fullResponse: any = null
|
|
379
|
+
|
|
380
|
+
while (true) {
|
|
381
|
+
const { done, value } = await reader.read()
|
|
382
|
+
if (done) break
|
|
383
|
+
|
|
384
|
+
const chunk = decoder.decode(value, { stream: true })
|
|
385
|
+
logger.debug(`Received chunk: ${chunk.length} bytes`)
|
|
386
|
+
|
|
387
|
+
// Process the chunk - it may contain multiple SSE events
|
|
388
|
+
const lines = chunk.split('\n')
|
|
389
|
+
for (const line of lines) {
|
|
390
|
+
if (line.startsWith('data:')) {
|
|
391
|
+
const jsonData = line.slice(5).trim()
|
|
392
|
+
|
|
393
|
+
// Skip empty data or [DONE] marker
|
|
394
|
+
if (jsonData === '' || jsonData === '[DONE]') continue
|
|
395
|
+
|
|
396
|
+
try {
|
|
397
|
+
const parsedData = JSON.parse(jsonData)
|
|
398
|
+
|
|
399
|
+
// Call the onChunk callback with the parsed data
|
|
400
|
+
if (options.onChunk) {
|
|
401
|
+
options.onChunk(parsedData)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Keep track of the full response
|
|
405
|
+
if (!fullResponse) {
|
|
406
|
+
fullResponse = parsedData
|
|
407
|
+
} else if (
|
|
408
|
+
parsedData.choices &&
|
|
409
|
+
parsedData.choices[0] &&
|
|
410
|
+
parsedData.choices[0].delta
|
|
411
|
+
) {
|
|
412
|
+
// Accumulate content for the full response
|
|
413
|
+
if (parsedData.choices[0].delta.content) {
|
|
414
|
+
fullContent += parsedData.choices[0].delta.content
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
} catch (e) {
|
|
418
|
+
logger.error(`Error parsing chunk: ${e}`)
|
|
419
|
+
logger.debug(`Problematic chunk: ${jsonData}`)
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Construct the final response object similar to non-streaming response
|
|
426
|
+
if (fullResponse) {
|
|
427
|
+
if (fullContent) {
|
|
428
|
+
fullResponse.choices[0].message = {
|
|
429
|
+
role: 'assistant',
|
|
430
|
+
content: fullContent,
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return fullResponse
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return {
|
|
437
|
+
choices: [{ message: { role: 'assistant', content: fullContent } }],
|
|
438
|
+
}
|
|
439
|
+
} catch (error) {
|
|
440
|
+
logger.error(
|
|
441
|
+
`Streaming error: ${
|
|
442
|
+
error instanceof Error ? error.message : String(error)
|
|
443
|
+
}`
|
|
444
|
+
)
|
|
445
|
+
throw error
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
134
449
|
/**
|
|
135
450
|
* List available models
|
|
136
451
|
* Command: berget chat list
|
|
137
452
|
*/
|
|
138
453
|
public async listModels(apiKey?: string): Promise<any> {
|
|
139
454
|
try {
|
|
140
|
-
|
|
455
|
+
// Check for environment variable first, then fallback to provided API key
|
|
456
|
+
const envApiKey = process.env.BERGET_API_KEY
|
|
457
|
+
const effectiveApiKey = envApiKey || apiKey
|
|
458
|
+
|
|
459
|
+
if (effectiveApiKey) {
|
|
141
460
|
const headers = {
|
|
142
|
-
|
|
461
|
+
Authorization: effectiveApiKey,
|
|
143
462
|
}
|
|
144
|
-
|
|
463
|
+
|
|
145
464
|
const { data, error } = await this.client.GET('/v1/models', { headers })
|
|
146
465
|
if (error) throw new Error(JSON.stringify(error))
|
|
147
466
|
return data
|
|
@@ -152,26 +471,27 @@ export class ChatService {
|
|
|
152
471
|
}
|
|
153
472
|
} catch (error) {
|
|
154
473
|
// Improved error handling
|
|
155
|
-
let errorMessage = 'Failed to list models'
|
|
156
|
-
|
|
474
|
+
let errorMessage = 'Failed to list models'
|
|
475
|
+
|
|
157
476
|
if (error instanceof Error) {
|
|
158
477
|
try {
|
|
159
478
|
// Try to parse the error message as JSON
|
|
160
|
-
const parsedError = JSON.parse(error.message)
|
|
479
|
+
const parsedError = JSON.parse(error.message)
|
|
161
480
|
if (parsedError.error) {
|
|
162
|
-
errorMessage = `Models error: ${
|
|
163
|
-
parsedError.error
|
|
164
|
-
|
|
481
|
+
errorMessage = `Models error: ${
|
|
482
|
+
typeof parsedError.error === 'string'
|
|
483
|
+
? parsedError.error
|
|
484
|
+
: parsedError.error.message || JSON.stringify(parsedError.error)
|
|
485
|
+
}`
|
|
165
486
|
}
|
|
166
487
|
} catch (e) {
|
|
167
488
|
// If parsing fails, use the original error message
|
|
168
|
-
errorMessage = `Models error: ${error.message}
|
|
489
|
+
errorMessage = `Models error: ${error.message}`
|
|
169
490
|
}
|
|
170
491
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
throw new Error(errorMessage)
|
|
492
|
+
|
|
493
|
+
logger.error(errorMessage)
|
|
494
|
+
throw new Error(errorMessage)
|
|
174
495
|
}
|
|
175
496
|
}
|
|
176
|
-
|
|
177
497
|
}
|