berget 1.0.0 → 1.2.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/README.md +92 -0
- package/dist/index.js +49 -467
- package/dist/package.json +35 -0
- package/dist/src/client.js +210 -102
- package/dist/src/commands/api-keys.js +277 -0
- package/dist/src/commands/auth.js +65 -0
- package/dist/src/commands/autocomplete.js +24 -0
- package/dist/src/commands/billing.js +53 -0
- package/dist/src/commands/chat.js +342 -0
- package/dist/src/commands/clusters.js +69 -0
- package/dist/src/commands/index.js +25 -0
- package/dist/src/commands/models.js +69 -0
- package/dist/src/commands/users.js +43 -0
- package/dist/src/constants/command-structure.js +14 -0
- package/dist/src/services/api-key-service.js +6 -16
- package/dist/src/services/auth-service.js +49 -47
- package/dist/src/services/chat-service.js +300 -0
- package/dist/src/utils/config-checker.js +50 -0
- package/dist/src/utils/default-api-key.js +237 -0
- package/dist/src/utils/error-handler.js +4 -4
- package/dist/src/utils/token-manager.js +165 -0
- package/index.ts +56 -566
- package/package.json +8 -2
- package/src/client.ts +279 -80
- package/src/commands/api-keys.ts +374 -0
- package/src/commands/auth.ts +58 -0
- package/src/commands/autocomplete.ts +19 -0
- package/src/commands/billing.ts +41 -0
- package/src/commands/chat.ts +445 -0
- package/src/commands/clusters.ts +65 -0
- package/src/commands/index.ts +23 -0
- package/src/commands/models.ts +63 -0
- package/src/commands/users.ts +37 -0
- package/src/constants/command-structure.ts +16 -0
- package/src/services/api-key-service.ts +12 -20
- package/src/services/auth-service.ts +90 -50
- package/src/services/chat-service.ts +295 -0
- package/src/types/api.d.ts +238 -178
- package/src/types/json.d.ts +4 -0
- package/src/utils/config-checker.ts +23 -0
- package/src/utils/default-api-key.ts +229 -0
- package/src/utils/error-handler.ts +4 -4
- package/src/utils/token-manager.ts +150 -0
- package/tsconfig.json +1 -1
|
@@ -33,10 +33,10 @@ export interface ApiKeyResponse {
|
|
|
33
33
|
export class ApiKeyService {
|
|
34
34
|
private static instance: ApiKeyService
|
|
35
35
|
private client = createAuthenticatedClient()
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
// Command group name for this service
|
|
38
38
|
public static readonly COMMAND_GROUP = COMMAND_GROUPS.API_KEYS
|
|
39
|
-
|
|
39
|
+
|
|
40
40
|
// Subcommands for this service
|
|
41
41
|
public static readonly COMMANDS = SUBCOMMANDS.API_KEYS
|
|
42
42
|
|
|
@@ -56,18 +56,7 @@ export class ApiKeyService {
|
|
|
56
56
|
public async list(): Promise<ApiKey[]> {
|
|
57
57
|
try {
|
|
58
58
|
const { data, error } = await this.client.GET('/v1/api-keys')
|
|
59
|
-
if (error)
|
|
60
|
-
// Check if this is an authentication error
|
|
61
|
-
const errorObj = typeof error === 'string' ? JSON.parse(error) : error;
|
|
62
|
-
if (errorObj.status === 401) {
|
|
63
|
-
throw new Error(JSON.stringify({
|
|
64
|
-
error: "Authentication failed. Your session may have expired.",
|
|
65
|
-
code: "AUTH_FAILED",
|
|
66
|
-
details: "Please run 'berget login' to authenticate again."
|
|
67
|
-
}))
|
|
68
|
-
}
|
|
69
|
-
throw new Error(JSON.stringify(error))
|
|
70
|
-
}
|
|
59
|
+
if (error) throw error
|
|
71
60
|
return data || []
|
|
72
61
|
} catch (error) {
|
|
73
62
|
handleError('Failed to list API keys', error)
|
|
@@ -82,7 +71,7 @@ export class ApiKeyService {
|
|
|
82
71
|
public async create(options: CreateApiKeyOptions): Promise<ApiKeyResponse> {
|
|
83
72
|
try {
|
|
84
73
|
const { data, error } = await this.client.POST('/v1/api-keys', {
|
|
85
|
-
body: options
|
|
74
|
+
body: options,
|
|
86
75
|
})
|
|
87
76
|
if (error) throw new Error(JSON.stringify(error))
|
|
88
77
|
return data!
|
|
@@ -99,7 +88,7 @@ export class ApiKeyService {
|
|
|
99
88
|
public async delete(id: string): Promise<boolean> {
|
|
100
89
|
try {
|
|
101
90
|
const { error } = await this.client.DELETE('/v1/api-keys/{id}', {
|
|
102
|
-
params: { path: { id } }
|
|
91
|
+
params: { path: { id } },
|
|
103
92
|
})
|
|
104
93
|
if (error) throw new Error(JSON.stringify(error))
|
|
105
94
|
return true
|
|
@@ -115,9 +104,12 @@ export class ApiKeyService {
|
|
|
115
104
|
*/
|
|
116
105
|
public async rotate(id: string): Promise<ApiKeyResponse> {
|
|
117
106
|
try {
|
|
118
|
-
const { data, error } = await this.client.PUT(
|
|
119
|
-
|
|
120
|
-
|
|
107
|
+
const { data, error } = await this.client.PUT(
|
|
108
|
+
'/v1/api-keys/{id}/rotate',
|
|
109
|
+
{
|
|
110
|
+
params: { path: { id } },
|
|
111
|
+
}
|
|
112
|
+
)
|
|
121
113
|
if (error) throw new Error(JSON.stringify(error))
|
|
122
114
|
return data!
|
|
123
115
|
} catch (error) {
|
|
@@ -133,7 +125,7 @@ export class ApiKeyService {
|
|
|
133
125
|
public async describe(id: string): Promise<any> {
|
|
134
126
|
try {
|
|
135
127
|
const { data, error } = await this.client.GET('/v1/api-keys/{id}/usage', {
|
|
136
|
-
params: { path: { id } }
|
|
128
|
+
params: { path: { id } },
|
|
137
129
|
})
|
|
138
130
|
if (error) throw new Error(JSON.stringify(error))
|
|
139
131
|
return data
|
|
@@ -16,10 +16,10 @@ import { COMMAND_GROUPS, SUBCOMMANDS } from '../constants/command-structure'
|
|
|
16
16
|
export class AuthService {
|
|
17
17
|
private static instance: AuthService
|
|
18
18
|
private client = createAuthenticatedClient()
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
// Command group name for this service
|
|
21
21
|
public static readonly COMMAND_GROUP = COMMAND_GROUPS.AUTH
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
// Subcommands for this service
|
|
24
24
|
public static readonly COMMANDS = SUBCOMMANDS.AUTH
|
|
25
25
|
|
|
@@ -32,6 +32,21 @@ export class AuthService {
|
|
|
32
32
|
return AuthService.instance
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
public async whoami(): Promise<any> {
|
|
36
|
+
try {
|
|
37
|
+
const { data: profile, error } = await this.client.GET('/v1/users/me')
|
|
38
|
+
if (error) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
error ? JSON.stringify(error) : 'Failed to get user profile'
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
return profile
|
|
44
|
+
} catch (error) {
|
|
45
|
+
handleError('Failed to get user profile', error)
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
35
50
|
public async login(): Promise<boolean> {
|
|
36
51
|
try {
|
|
37
52
|
// Clear any existing token to ensure a fresh login
|
|
@@ -53,27 +68,40 @@ export class AuthService {
|
|
|
53
68
|
)
|
|
54
69
|
}
|
|
55
70
|
|
|
71
|
+
// Type assertion for deviceData
|
|
72
|
+
const typedDeviceData = deviceData as {
|
|
73
|
+
verification_url?: string
|
|
74
|
+
user_code?: string
|
|
75
|
+
device_code?: string
|
|
76
|
+
expires_in?: number
|
|
77
|
+
interval?: number
|
|
78
|
+
}
|
|
79
|
+
|
|
56
80
|
// Display information to user
|
|
57
81
|
console.log(chalk.cyan('\nTo complete login:'))
|
|
58
82
|
console.log(
|
|
59
83
|
chalk.cyan(
|
|
60
84
|
`1. Open this URL: ${chalk.bold(
|
|
61
|
-
|
|
85
|
+
typedDeviceData.verification_url ||
|
|
86
|
+
'https://keycloak.berget.ai/device'
|
|
62
87
|
)}`
|
|
63
88
|
)
|
|
64
89
|
)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
90
|
+
if (!typedDeviceData.verification_url)
|
|
91
|
+
console.log(
|
|
92
|
+
chalk.cyan(
|
|
93
|
+
`2. Enter this code: ${chalk.bold(
|
|
94
|
+
typedDeviceData.user_code || ''
|
|
95
|
+
)}\n`
|
|
96
|
+
)
|
|
68
97
|
)
|
|
69
|
-
)
|
|
70
98
|
|
|
71
99
|
// Try to open browser automatically
|
|
72
100
|
try {
|
|
73
|
-
if (
|
|
101
|
+
if (typedDeviceData.verification_url) {
|
|
74
102
|
// Use dynamic import for the 'open' package
|
|
75
|
-
const open = await import('open').then(m => m.default)
|
|
76
|
-
await open(
|
|
103
|
+
const open = await import('open').then((m) => m.default)
|
|
104
|
+
await open(typedDeviceData.verification_url)
|
|
77
105
|
console.log(
|
|
78
106
|
chalk.dim(
|
|
79
107
|
"Browser opened automatically. If it didn't open, please use the URL above."
|
|
@@ -93,9 +121,11 @@ export class AuthService {
|
|
|
93
121
|
// Step 2: Poll for completion
|
|
94
122
|
const startTime = Date.now()
|
|
95
123
|
const expiresIn =
|
|
96
|
-
|
|
124
|
+
typedDeviceData.expires_in !== undefined
|
|
125
|
+
? typedDeviceData.expires_in
|
|
126
|
+
: 900
|
|
97
127
|
const expiresAt = startTime + expiresIn * 1000
|
|
98
|
-
let pollInterval = (
|
|
128
|
+
let pollInterval = (typedDeviceData.interval || 5) * 1000
|
|
99
129
|
|
|
100
130
|
const spinner = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
101
131
|
let spinnerIdx = 0
|
|
@@ -111,7 +141,7 @@ export class AuthService {
|
|
|
111
141
|
spinnerIdx = (spinnerIdx + 1) % spinner.length
|
|
112
142
|
|
|
113
143
|
// Check if authentication is complete
|
|
114
|
-
const deviceCode =
|
|
144
|
+
const deviceCode = typedDeviceData.device_code || ''
|
|
115
145
|
const { data: tokenData, error: tokenError } = await apiClient.POST(
|
|
116
146
|
'/v1/auth/device/token',
|
|
117
147
|
{
|
|
@@ -170,21 +200,56 @@ export class AuthService {
|
|
|
170
200
|
}
|
|
171
201
|
continue
|
|
172
202
|
}
|
|
173
|
-
} else if (tokenData
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
203
|
+
} else if (tokenData) {
|
|
204
|
+
// Type assertion for tokenData
|
|
205
|
+
const typedTokenData = tokenData as {
|
|
206
|
+
token?: string
|
|
207
|
+
refresh_token?: string
|
|
208
|
+
expires_in?: number
|
|
209
|
+
refresh_expires_in?: number
|
|
210
|
+
user?: {
|
|
211
|
+
id?: string
|
|
212
|
+
email?: string
|
|
213
|
+
name?: string
|
|
214
|
+
}
|
|
215
|
+
}
|
|
179
216
|
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
217
|
+
if (typedTokenData.token) {
|
|
218
|
+
// Success!
|
|
219
|
+
saveAuthToken(
|
|
220
|
+
typedTokenData.token,
|
|
221
|
+
typedTokenData.refresh_token || '',
|
|
222
|
+
typedTokenData.expires_in || 3600
|
|
184
223
|
)
|
|
185
|
-
}
|
|
186
224
|
|
|
187
|
-
|
|
225
|
+
if (process.argv.includes('--debug')) {
|
|
226
|
+
console.log(chalk.yellow('DEBUG: Token data received:'))
|
|
227
|
+
console.log(
|
|
228
|
+
chalk.yellow(
|
|
229
|
+
JSON.stringify(
|
|
230
|
+
{
|
|
231
|
+
expires_in: typedTokenData.expires_in,
|
|
232
|
+
refresh_expires_in: typedTokenData.refresh_expires_in,
|
|
233
|
+
},
|
|
234
|
+
null,
|
|
235
|
+
2
|
|
236
|
+
)
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
process.stdout.write('\r' + ' '.repeat(50) + '\r') // Clear the spinner line
|
|
242
|
+
console.log(chalk.green('✓ Successfully logged in to Berget'))
|
|
243
|
+
|
|
244
|
+
if (typedTokenData.user) {
|
|
245
|
+
const user = typedTokenData.user
|
|
246
|
+
console.log(
|
|
247
|
+
chalk.green(`Logged in as ${user.name || user.email || 'User'}`)
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return true
|
|
252
|
+
}
|
|
188
253
|
}
|
|
189
254
|
}
|
|
190
255
|
|
|
@@ -195,29 +260,4 @@ export class AuthService {
|
|
|
195
260
|
return false
|
|
196
261
|
}
|
|
197
262
|
}
|
|
198
|
-
|
|
199
|
-
public async isAuthenticated(): Promise<boolean> {
|
|
200
|
-
try {
|
|
201
|
-
// Call an API endpoint that requires authentication
|
|
202
|
-
const { data, error } = await this.client.GET('/v1/users/me')
|
|
203
|
-
return !!data && !error
|
|
204
|
-
} catch {
|
|
205
|
-
return false
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Get current user profile
|
|
211
|
-
* Command: berget auth whoami
|
|
212
|
-
*/
|
|
213
|
-
public async whoami() {
|
|
214
|
-
try {
|
|
215
|
-
const { data, error } = await this.client.GET('/v1/users/me')
|
|
216
|
-
if (error) throw new Error(JSON.stringify(error))
|
|
217
|
-
return data
|
|
218
|
-
} catch (error) {
|
|
219
|
-
handleError('Failed to get user profile', error)
|
|
220
|
-
throw error
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
263
|
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { createAuthenticatedClient } from '../client'
|
|
2
|
+
import { COMMAND_GROUPS, SUBCOMMANDS } from '../constants/command-structure'
|
|
3
|
+
import chalk from 'chalk'
|
|
4
|
+
|
|
5
|
+
export interface ChatMessage {
|
|
6
|
+
role: 'system' | 'user' | 'assistant'
|
|
7
|
+
content: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ChatCompletionOptions {
|
|
11
|
+
model: string
|
|
12
|
+
messages: ChatMessage[]
|
|
13
|
+
temperature?: number
|
|
14
|
+
max_tokens?: number
|
|
15
|
+
stream?: boolean
|
|
16
|
+
top_p?: number
|
|
17
|
+
apiKey?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Service for interacting with the chat API
|
|
22
|
+
* Command group: chat
|
|
23
|
+
*/
|
|
24
|
+
export class ChatService {
|
|
25
|
+
private static instance: ChatService
|
|
26
|
+
private client = createAuthenticatedClient()
|
|
27
|
+
|
|
28
|
+
// Command group name for this service
|
|
29
|
+
public static readonly COMMAND_GROUP = 'chat'
|
|
30
|
+
|
|
31
|
+
// Subcommands for this service
|
|
32
|
+
public static readonly COMMANDS = {
|
|
33
|
+
RUN: 'run',
|
|
34
|
+
LIST: 'list'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private constructor() {}
|
|
38
|
+
|
|
39
|
+
public static getInstance(): ChatService {
|
|
40
|
+
if (!ChatService.instance) {
|
|
41
|
+
ChatService.instance = new ChatService()
|
|
42
|
+
}
|
|
43
|
+
return ChatService.instance
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Create a chat completion
|
|
48
|
+
* Command: berget chat completion
|
|
49
|
+
*/
|
|
50
|
+
public async createCompletion(options: ChatCompletionOptions): Promise<any> {
|
|
51
|
+
try {
|
|
52
|
+
console.log(chalk.yellow('DEBUG: Starting createCompletion method'))
|
|
53
|
+
|
|
54
|
+
// Check if options is defined
|
|
55
|
+
if (!options) {
|
|
56
|
+
console.log(chalk.red('ERROR: options is undefined'))
|
|
57
|
+
throw new Error('Chat completion options are undefined')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Log the raw options object
|
|
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
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Create a copy of options to avoid modifying the original
|
|
82
|
+
const optionsCopy = { ...options }
|
|
83
|
+
|
|
84
|
+
if (isDebug) {
|
|
85
|
+
console.log(chalk.yellow('DEBUG: Checking for API key'))
|
|
86
|
+
console.log(chalk.yellow(`DEBUG: optionsCopy.apiKey exists: ${!!optionsCopy.apiKey}`))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check for environment variables first - prioritize this over everything else
|
|
90
|
+
const envApiKey = process.env.BERGET_API_KEY;
|
|
91
|
+
if (envApiKey) {
|
|
92
|
+
if (isDebug) {
|
|
93
|
+
console.log(chalk.yellow('DEBUG: Using API key from BERGET_API_KEY environment variable'));
|
|
94
|
+
}
|
|
95
|
+
optionsCopy.apiKey = envApiKey;
|
|
96
|
+
}
|
|
97
|
+
// Only try to get the default API key if no API key is provided and no env var is set
|
|
98
|
+
else if (!optionsCopy.apiKey) {
|
|
99
|
+
if (isDebug) {
|
|
100
|
+
console.log(chalk.yellow('DEBUG: No API key provided, trying to get default'))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
// Import the DefaultApiKeyManager directly
|
|
105
|
+
if (isDebug) {
|
|
106
|
+
console.log(chalk.yellow('DEBUG: Importing DefaultApiKeyManager'))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const DefaultApiKeyManager = (await import('../utils/default-api-key')).DefaultApiKeyManager;
|
|
110
|
+
const defaultApiKeyManager = DefaultApiKeyManager.getInstance();
|
|
111
|
+
|
|
112
|
+
if (isDebug) {
|
|
113
|
+
console.log(chalk.yellow('DEBUG: Got DefaultApiKeyManager instance'))
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Try to get the default API key
|
|
117
|
+
if (isDebug) {
|
|
118
|
+
console.log(chalk.yellow('DEBUG: Calling promptForDefaultApiKey'))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const defaultApiKeyData = defaultApiKeyManager.getDefaultApiKeyData();
|
|
122
|
+
const apiKey = defaultApiKeyData?.key || await defaultApiKeyManager.promptForDefaultApiKey();
|
|
123
|
+
|
|
124
|
+
if (isDebug) {
|
|
125
|
+
console.log(chalk.yellow(`DEBUG: Default API key data exists: ${!!defaultApiKeyData}`))
|
|
126
|
+
console.log(chalk.yellow(`DEBUG: promptForDefaultApiKey returned: ${apiKey ? 'a key' : 'null'}`))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (apiKey) {
|
|
130
|
+
if (isDebug) {
|
|
131
|
+
console.log(chalk.yellow('DEBUG: Using API key from default API key manager'));
|
|
132
|
+
}
|
|
133
|
+
optionsCopy.apiKey = apiKey;
|
|
134
|
+
} else {
|
|
135
|
+
console.log(chalk.yellow('No API key available. You need to either:'));
|
|
136
|
+
console.log(chalk.yellow('1. Create an API key with: berget api-keys create --name "My Key"'));
|
|
137
|
+
console.log(chalk.yellow('2. Set a default API key with: berget api-keys set-default <id>'));
|
|
138
|
+
console.log(chalk.yellow('3. Provide an API key with the --api-key option'));
|
|
139
|
+
console.log(chalk.yellow('4. Set the BERGET_API_KEY environment variable'));
|
|
140
|
+
console.log(chalk.yellow('\nExample:'));
|
|
141
|
+
console.log(chalk.yellow(' export BERGET_API_KEY=your_api_key_here'));
|
|
142
|
+
console.log(chalk.yellow(' # or for a single command:'));
|
|
143
|
+
console.log(chalk.yellow(' BERGET_API_KEY=your_api_key_here berget chat run google/gemma-3-27b-it'));
|
|
144
|
+
throw new Error('No API key provided and no default API key set');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Set the API key in the options
|
|
148
|
+
if (isDebug) {
|
|
149
|
+
console.log(chalk.yellow('DEBUG: Setting API key in options'))
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Only set the API key if it's not null
|
|
153
|
+
if (apiKey) {
|
|
154
|
+
optionsCopy.apiKey = apiKey;
|
|
155
|
+
}
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.log(chalk.red('Error getting API key:'))
|
|
158
|
+
if (error instanceof Error) {
|
|
159
|
+
console.log(chalk.red(error.message))
|
|
160
|
+
}
|
|
161
|
+
console.log(chalk.yellow('Please create an API key with: berget api-keys create --name "My Key"'))
|
|
162
|
+
throw new Error('Failed to get API key')
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (isDebug) {
|
|
167
|
+
console.log(chalk.yellow('DEBUG: Chat completion options:'))
|
|
168
|
+
console.log(chalk.yellow(JSON.stringify({
|
|
169
|
+
...optionsCopy,
|
|
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.');
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
// Improved error handling
|
|
228
|
+
let errorMessage = 'Failed to create chat completion';
|
|
229
|
+
|
|
230
|
+
if (error instanceof Error) {
|
|
231
|
+
try {
|
|
232
|
+
// Try to parse the error message as JSON
|
|
233
|
+
const parsedError = JSON.parse(error.message);
|
|
234
|
+
if (parsedError.error && parsedError.error.message) {
|
|
235
|
+
errorMessage = `Chat error: ${parsedError.error.message}`;
|
|
236
|
+
}
|
|
237
|
+
} catch (e) {
|
|
238
|
+
// If parsing fails, use the original error message
|
|
239
|
+
errorMessage = `Chat error: ${error.message}`;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
console.error(chalk.red(errorMessage));
|
|
244
|
+
throw new Error(errorMessage);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* List available models
|
|
250
|
+
* Command: berget chat list
|
|
251
|
+
*/
|
|
252
|
+
public async listModels(apiKey?: string): Promise<any> {
|
|
253
|
+
try {
|
|
254
|
+
// 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
|
+
|
|
258
|
+
if (effectiveApiKey) {
|
|
259
|
+
const headers = {
|
|
260
|
+
'Authorization': `Bearer ${effectiveApiKey}`
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const { data, error } = await this.client.GET('/v1/models', { headers })
|
|
264
|
+
if (error) throw new Error(JSON.stringify(error))
|
|
265
|
+
return data
|
|
266
|
+
} else {
|
|
267
|
+
const { data, error } = await this.client.GET('/v1/models')
|
|
268
|
+
if (error) throw new Error(JSON.stringify(error))
|
|
269
|
+
return data
|
|
270
|
+
}
|
|
271
|
+
} catch (error) {
|
|
272
|
+
// Improved error handling
|
|
273
|
+
let errorMessage = 'Failed to list models';
|
|
274
|
+
|
|
275
|
+
if (error instanceof Error) {
|
|
276
|
+
try {
|
|
277
|
+
// Try to parse the error message as JSON
|
|
278
|
+
const parsedError = JSON.parse(error.message);
|
|
279
|
+
if (parsedError.error) {
|
|
280
|
+
errorMessage = `Models error: ${typeof parsedError.error === 'string' ?
|
|
281
|
+
parsedError.error :
|
|
282
|
+
(parsedError.error.message || JSON.stringify(parsedError.error))}`;
|
|
283
|
+
}
|
|
284
|
+
} catch (e) {
|
|
285
|
+
// If parsing fails, use the original error message
|
|
286
|
+
errorMessage = `Models error: ${error.message}`;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
console.error(chalk.red(errorMessage));
|
|
291
|
+
throw new Error(errorMessage);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
}
|