berget 1.2.0 → 1.3.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.
- package/-27b-it +0 -0
- package/dist/package.json +5 -1
- package/dist/src/client.js +33 -43
- package/dist/src/commands/chat.js +50 -22
- package/dist/src/commands/models.js +2 -2
- package/dist/src/services/chat-service.js +230 -125
- package/dist/src/utils/default-api-key.js +20 -37
- package/dist/src/utils/logger.js +160 -0
- package/dist/src/utils/markdown-renderer.js +73 -0
- package/dist/src/utils/token-manager.js +6 -9
- package/package.json +5 -1
- package/src/client.ts +75 -90
- package/src/commands/chat.ts +64 -29
- package/src/commands/models.ts +4 -4
- package/src/services/chat-service.ts +386 -184
- package/src/utils/default-api-key.ts +20 -37
- package/src/utils/logger.ts +159 -0
- package/src/utils/markdown-renderer.ts +68 -0
- package/src/utils/token-manager.ts +6 -5
|
@@ -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,202 +49,403 @@ export class ChatService {
|
|
|
49
49
|
*/
|
|
50
50
|
public async createCompletion(options: ChatCompletionOptions): Promise<any> {
|
|
51
51
|
try {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
console.log(chalk.yellow('DEBUG: Raw options:'), typeof options, options ? 'defined' : 'undefined')
|
|
62
|
-
|
|
63
|
-
const headers: Record<string, string> = {}
|
|
64
|
-
|
|
65
|
-
// Check if debug is enabled
|
|
66
|
-
const isDebug = process.argv.includes('--debug')
|
|
67
|
-
|
|
68
|
-
if (isDebug) {
|
|
69
|
-
console.log(chalk.yellow('DEBUG: Starting createCompletion with options:'))
|
|
70
|
-
try {
|
|
71
|
-
console.log(chalk.yellow(JSON.stringify({
|
|
72
|
-
...options,
|
|
73
|
-
apiKey: options.apiKey ? '***' : undefined,
|
|
74
|
-
messages: options.messages ? `${options.messages.length} messages` : undefined
|
|
75
|
-
}, null, 2)))
|
|
76
|
-
} catch (error) {
|
|
77
|
-
console.log(chalk.red('ERROR: Failed to stringify options:'), error)
|
|
78
|
-
}
|
|
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 = []
|
|
79
61
|
}
|
|
80
|
-
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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)
|
|
87
81
|
}
|
|
88
|
-
|
|
82
|
+
|
|
83
|
+
const headers: Record<string, string> = {}
|
|
84
|
+
|
|
89
85
|
// Check for environment variables first - prioritize this over everything else
|
|
90
|
-
const envApiKey = process.env.BERGET_API_KEY
|
|
86
|
+
const envApiKey = process.env.BERGET_API_KEY
|
|
91
87
|
if (envApiKey) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
optionsCopy
|
|
96
|
-
}
|
|
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
|
+
}
|
|
97
99
|
// Only try to get the default API key if no API key is provided and no env var is set
|
|
98
|
-
else
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
|
|
100
|
+
else {
|
|
101
|
+
logger.debug('No API key provided, trying to get default')
|
|
102
|
+
|
|
103
103
|
try {
|
|
104
104
|
// Import the DefaultApiKeyManager directly
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const defaultApiKeyManager = DefaultApiKeyManager.getInstance()
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
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
|
+
|
|
116
114
|
// Try to get the default API key
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
+
|
|
129
127
|
if (apiKey) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
optionsCopy.apiKey = apiKey;
|
|
128
|
+
logger.debug('Using API key from default API key manager')
|
|
129
|
+
optionsCopy.apiKey = apiKey
|
|
134
130
|
} else {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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')
|
|
145
147
|
}
|
|
146
|
-
|
|
148
|
+
|
|
147
149
|
// Set the API key in the options
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
150
|
+
logger.debug('Setting API key in options')
|
|
151
|
+
|
|
152
152
|
// Only set the API key if it's not null
|
|
153
153
|
if (apiKey) {
|
|
154
|
-
optionsCopy.apiKey = apiKey
|
|
154
|
+
optionsCopy.apiKey = apiKey
|
|
155
155
|
}
|
|
156
156
|
} catch (error) {
|
|
157
|
-
|
|
157
|
+
logger.error('Error getting API key:')
|
|
158
158
|
if (error instanceof Error) {
|
|
159
|
-
|
|
159
|
+
logger.error(error.message)
|
|
160
160
|
}
|
|
161
|
-
|
|
161
|
+
logger.warn(
|
|
162
|
+
'Please create an API key with: berget api-keys create --name "My Key"'
|
|
163
|
+
)
|
|
162
164
|
throw new Error('Failed to get API key')
|
|
163
165
|
}
|
|
164
166
|
}
|
|
165
|
-
|
|
166
|
-
if
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
apiKey: optionsCopy.apiKey ? '***' : undefined // Hide the actual API key in debug output
|
|
171
|
-
}, null, 2)))
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// If an API key is provided, use it for this request
|
|
175
|
-
if (optionsCopy.apiKey) {
|
|
176
|
-
headers['Authorization'] = `Bearer ${optionsCopy.apiKey}`
|
|
177
|
-
// Remove apiKey from options before sending to API
|
|
178
|
-
const { apiKey, ...requestOptions } = optionsCopy
|
|
179
|
-
|
|
180
|
-
if (isDebug) {
|
|
181
|
-
console.log(chalk.yellow('DEBUG: Using provided API key'))
|
|
182
|
-
console.log(chalk.yellow('DEBUG: Request options:'))
|
|
183
|
-
console.log(chalk.yellow(JSON.stringify(requestOptions, null, 2)))
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
try {
|
|
187
|
-
const response = await this.client.POST('/v1/chat/completions', {
|
|
188
|
-
body: requestOptions,
|
|
189
|
-
headers
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
// Check if response has an error property
|
|
193
|
-
const responseAny = response as any;
|
|
194
|
-
if (responseAny && responseAny.error)
|
|
195
|
-
throw new Error(JSON.stringify(responseAny.error))
|
|
196
|
-
|
|
197
|
-
if (isDebug) {
|
|
198
|
-
console.log(chalk.yellow('DEBUG: API response:'))
|
|
199
|
-
console.log(chalk.yellow(JSON.stringify(response, null, 2)))
|
|
200
|
-
|
|
201
|
-
// Output the complete response data for debugging
|
|
202
|
-
console.log(chalk.yellow('DEBUG: Complete response data:'))
|
|
203
|
-
console.log(chalk.yellow(JSON.stringify(response.data, null, 2)))
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return response.data
|
|
207
|
-
} catch (requestError) {
|
|
208
|
-
if (process.argv.includes('--debug')) {
|
|
209
|
-
console.log(chalk.red(`DEBUG: Request error: ${requestError instanceof Error ? requestError.message : String(requestError)}`))
|
|
210
|
-
}
|
|
211
|
-
throw requestError
|
|
212
|
-
}
|
|
213
|
-
} else {
|
|
214
|
-
// We've exhausted all options for getting an API key
|
|
215
|
-
console.log(chalk.yellow('No API key available. You need to either:'));
|
|
216
|
-
console.log(chalk.yellow('1. Create an API key with: berget api-keys create --name "My Key"'));
|
|
217
|
-
console.log(chalk.yellow('2. Set a default API key with: berget api-keys set-default <id>'));
|
|
218
|
-
console.log(chalk.yellow('3. Provide an API key with the --api-key option'));
|
|
219
|
-
console.log(chalk.yellow('4. Set the BERGET_API_KEY environment variable'));
|
|
220
|
-
console.log(chalk.yellow('\nExample:'));
|
|
221
|
-
console.log(chalk.yellow(' export BERGET_API_KEY=your_api_key_here'));
|
|
222
|
-
console.log(chalk.yellow(' # or for a single command:'));
|
|
223
|
-
console.log(chalk.yellow(' BERGET_API_KEY=your_api_key_here berget chat run google/gemma-3-27b-it'));
|
|
224
|
-
throw new Error('No API key available. Please provide an API key or set a default API key.');
|
|
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'
|
|
225
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)
|
|
226
187
|
} catch (error) {
|
|
227
188
|
// Improved error handling
|
|
228
|
-
let errorMessage = 'Failed to create chat completion'
|
|
229
|
-
|
|
189
|
+
let errorMessage = 'Failed to create chat completion'
|
|
190
|
+
|
|
230
191
|
if (error instanceof Error) {
|
|
231
192
|
try {
|
|
232
193
|
// Try to parse the error message as JSON
|
|
233
|
-
const parsedError = JSON.parse(error.message)
|
|
194
|
+
const parsedError = JSON.parse(error.message)
|
|
234
195
|
if (parsedError.error && parsedError.error.message) {
|
|
235
|
-
errorMessage = `Chat error: ${parsedError.error.message}
|
|
196
|
+
errorMessage = `Chat error: ${parsedError.error.message}`
|
|
236
197
|
}
|
|
237
198
|
} catch (e) {
|
|
238
199
|
// If parsing fails, use the original error message
|
|
239
|
-
errorMessage = `Chat error: ${error.message}
|
|
200
|
+
errorMessage = `Chat error: ${error.message}`
|
|
240
201
|
}
|
|
241
202
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
throw new Error(errorMessage)
|
|
203
|
+
|
|
204
|
+
logger.error(errorMessage)
|
|
205
|
+
throw new Error(errorMessage)
|
|
245
206
|
}
|
|
246
207
|
}
|
|
247
|
-
|
|
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
|
+
|
|
248
449
|
/**
|
|
249
450
|
* List available models
|
|
250
451
|
* Command: berget chat list
|
|
@@ -252,14 +453,14 @@ export class ChatService {
|
|
|
252
453
|
public async listModels(apiKey?: string): Promise<any> {
|
|
253
454
|
try {
|
|
254
455
|
// Check for environment variable first, then fallback to provided API key
|
|
255
|
-
const envApiKey = process.env.BERGET_API_KEY
|
|
256
|
-
const effectiveApiKey = envApiKey || apiKey
|
|
257
|
-
|
|
456
|
+
const envApiKey = process.env.BERGET_API_KEY
|
|
457
|
+
const effectiveApiKey = envApiKey || apiKey
|
|
458
|
+
|
|
258
459
|
if (effectiveApiKey) {
|
|
259
460
|
const headers = {
|
|
260
|
-
|
|
461
|
+
Authorization: effectiveApiKey,
|
|
261
462
|
}
|
|
262
|
-
|
|
463
|
+
|
|
263
464
|
const { data, error } = await this.client.GET('/v1/models', { headers })
|
|
264
465
|
if (error) throw new Error(JSON.stringify(error))
|
|
265
466
|
return data
|
|
@@ -270,26 +471,27 @@ export class ChatService {
|
|
|
270
471
|
}
|
|
271
472
|
} catch (error) {
|
|
272
473
|
// Improved error handling
|
|
273
|
-
let errorMessage = 'Failed to list models'
|
|
274
|
-
|
|
474
|
+
let errorMessage = 'Failed to list models'
|
|
475
|
+
|
|
275
476
|
if (error instanceof Error) {
|
|
276
477
|
try {
|
|
277
478
|
// Try to parse the error message as JSON
|
|
278
|
-
const parsedError = JSON.parse(error.message)
|
|
479
|
+
const parsedError = JSON.parse(error.message)
|
|
279
480
|
if (parsedError.error) {
|
|
280
|
-
errorMessage = `Models error: ${
|
|
281
|
-
parsedError.error
|
|
282
|
-
|
|
481
|
+
errorMessage = `Models error: ${
|
|
482
|
+
typeof parsedError.error === 'string'
|
|
483
|
+
? parsedError.error
|
|
484
|
+
: parsedError.error.message || JSON.stringify(parsedError.error)
|
|
485
|
+
}`
|
|
283
486
|
}
|
|
284
487
|
} catch (e) {
|
|
285
488
|
// If parsing fails, use the original error message
|
|
286
|
-
errorMessage = `Models error: ${error.message}
|
|
489
|
+
errorMessage = `Models error: ${error.message}`
|
|
287
490
|
}
|
|
288
491
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
throw new Error(errorMessage)
|
|
492
|
+
|
|
493
|
+
logger.error(errorMessage)
|
|
494
|
+
throw new Error(errorMessage)
|
|
292
495
|
}
|
|
293
496
|
}
|
|
294
|
-
|
|
295
497
|
}
|