berget 1.3.1 → 2.0.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/.env.example +5 -0
- package/.github/workflows/publish.yml +56 -0
- package/.github/workflows/test.yml +38 -0
- package/AGENTS.md +184 -0
- package/README.md +177 -38
- package/TODO.md +2 -0
- package/blog-post.md +176 -0
- package/dist/index.js +11 -8
- package/dist/package.json +14 -3
- package/dist/src/commands/api-keys.js +4 -2
- package/dist/src/commands/chat.js +182 -23
- package/dist/src/commands/code.js +1424 -0
- package/dist/src/commands/index.js +2 -0
- package/dist/src/constants/command-structure.js +12 -0
- package/dist/src/schemas/opencode-schema.json +1121 -0
- package/dist/src/services/chat-service.js +10 -10
- package/dist/src/services/cluster-service.js +1 -1
- package/dist/src/utils/default-api-key.js +2 -2
- package/dist/src/utils/env-manager.js +86 -0
- package/dist/src/utils/error-handler.js +10 -3
- package/dist/src/utils/markdown-renderer.js +4 -4
- package/dist/src/utils/opencode-validator.js +122 -0
- package/dist/src/utils/token-manager.js +2 -2
- package/dist/tests/commands/chat.test.js +109 -0
- package/dist/tests/commands/code.test.js +414 -0
- package/dist/tests/utils/env-manager.test.js +148 -0
- package/dist/tests/utils/opencode-validator.test.js +103 -0
- package/dist/vitest.config.js +9 -0
- package/index.ts +67 -32
- package/opencode.json +182 -0
- package/package.json +14 -3
- package/src/client.ts +20 -20
- package/src/commands/api-keys.ts +93 -60
- package/src/commands/auth.ts +4 -2
- package/src/commands/billing.ts +6 -3
- package/src/commands/chat.ts +291 -97
- package/src/commands/clusters.ts +2 -2
- package/src/commands/code.ts +1696 -0
- package/src/commands/index.ts +2 -0
- package/src/commands/models.ts +3 -3
- package/src/commands/users.ts +2 -2
- package/src/constants/command-structure.ts +112 -58
- package/src/schemas/opencode-schema.json +991 -0
- package/src/services/api-key-service.ts +1 -1
- package/src/services/auth-service.ts +27 -25
- package/src/services/chat-service.ts +37 -44
- package/src/services/cluster-service.ts +5 -5
- package/src/services/collaborator-service.ts +3 -3
- package/src/services/flux-service.ts +2 -2
- package/src/services/helm-service.ts +2 -2
- package/src/services/kubectl-service.ts +3 -6
- package/src/types/api.d.ts +1032 -1010
- package/src/types/json.d.ts +3 -3
- package/src/utils/default-api-key.ts +54 -42
- package/src/utils/env-manager.ts +98 -0
- package/src/utils/error-handler.ts +24 -15
- package/src/utils/logger.ts +12 -12
- package/src/utils/markdown-renderer.ts +18 -18
- package/src/utils/opencode-validator.ts +134 -0
- package/src/utils/token-manager.ts +35 -23
- package/tests/commands/chat.test.ts +129 -0
- package/tests/commands/code.test.ts +505 -0
- package/tests/utils/env-manager.test.ts +199 -0
- package/tests/utils/opencode-validator.test.ts +118 -0
- package/tsconfig.json +8 -8
- package/vitest.config.ts +8 -0
- package/-27b-it +0 -0
package/src/commands/chat.ts
CHANGED
|
@@ -2,7 +2,11 @@ import { Command } from 'commander'
|
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import readline from 'readline'
|
|
4
4
|
import { COMMAND_GROUPS, SUBCOMMANDS } from '../constants/command-structure'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
ChatService,
|
|
7
|
+
ChatMessage,
|
|
8
|
+
ChatCompletionOptions,
|
|
9
|
+
} from '../services/chat-service'
|
|
6
10
|
import { ApiKeyService } from '../services/api-key-service'
|
|
7
11
|
import { AuthService } from '../services/auth-service'
|
|
8
12
|
import { handleError } from '../utils/error-handler'
|
|
@@ -37,66 +41,75 @@ export function registerChatCommands(program: Command): void {
|
|
|
37
41
|
chat
|
|
38
42
|
.command(SUBCOMMANDS.CHAT.RUN)
|
|
39
43
|
.description('Run a chat session with a specified model')
|
|
40
|
-
.argument('[model]', 'Model to use (default:
|
|
44
|
+
.argument('[model]', 'Model to use (default: openai/gpt-oss)')
|
|
45
|
+
.argument('[message]', 'Message to send directly (skips interactive mode)')
|
|
41
46
|
.option('-s, --system <message>', 'System message')
|
|
42
47
|
.option('-t, --temperature <temp>', 'Temperature (0-1)', parseFloat)
|
|
43
48
|
.option('-m, --max-tokens <tokens>', 'Maximum tokens to generate', parseInt)
|
|
44
49
|
.option('-k, --api-key <key>', 'API key to use for this chat session')
|
|
45
50
|
.option(
|
|
46
51
|
'--api-key-id <id>',
|
|
47
|
-
'ID of the API key to use from your saved keys'
|
|
52
|
+
'ID of the API key to use from your saved keys',
|
|
48
53
|
)
|
|
49
|
-
.option(
|
|
50
|
-
|
|
54
|
+
.option(
|
|
55
|
+
'--no-stream',
|
|
56
|
+
'Disable streaming (streaming is enabled by default)',
|
|
57
|
+
)
|
|
58
|
+
.action(async (model, message, options) => {
|
|
51
59
|
try {
|
|
52
60
|
const chatService = ChatService.getInstance()
|
|
53
61
|
|
|
54
62
|
// Check if we have an API key or need to get one
|
|
55
63
|
let apiKey = options.apiKey
|
|
56
64
|
let apiKeyId = options.apiKeyId
|
|
57
|
-
|
|
65
|
+
|
|
58
66
|
// Check for environment variable first
|
|
59
|
-
const envApiKey = process.env.BERGET_API_KEY
|
|
67
|
+
const envApiKey = process.env.BERGET_API_KEY
|
|
60
68
|
if (envApiKey) {
|
|
61
69
|
console.log(
|
|
62
|
-
chalk.dim(`Using API key from BERGET_API_KEY environment variable`)
|
|
70
|
+
chalk.dim(`Using API key from BERGET_API_KEY environment variable`),
|
|
63
71
|
)
|
|
64
|
-
apiKey = envApiKey
|
|
65
|
-
|
|
72
|
+
apiKey = envApiKey
|
|
73
|
+
|
|
66
74
|
// Debug the API key (first few characters only)
|
|
67
75
|
if (process.argv.includes('--debug')) {
|
|
68
76
|
console.log(
|
|
69
|
-
chalk.yellow(
|
|
77
|
+
chalk.yellow(
|
|
78
|
+
`DEBUG: API key from env starts with: ${envApiKey.substring(0, 4)}...`,
|
|
79
|
+
),
|
|
70
80
|
)
|
|
71
81
|
}
|
|
72
82
|
}
|
|
73
83
|
// If API key is already provided via command line, use it
|
|
74
84
|
else if (options.apiKey) {
|
|
75
|
-
console.log(
|
|
76
|
-
|
|
77
|
-
)
|
|
78
|
-
apiKey = options.apiKey;
|
|
85
|
+
console.log(chalk.dim(`Using API key from command line argument`))
|
|
86
|
+
apiKey = options.apiKey
|
|
79
87
|
}
|
|
80
88
|
// If no API key or API key ID provided and no env var, check for default API key
|
|
81
89
|
else if (!apiKey && !apiKeyId) {
|
|
82
90
|
try {
|
|
83
91
|
const defaultApiKeyManager = DefaultApiKeyManager.getInstance()
|
|
84
|
-
const defaultApiKeyData =
|
|
92
|
+
const defaultApiKeyData =
|
|
93
|
+
defaultApiKeyManager.getDefaultApiKeyData()
|
|
85
94
|
|
|
86
95
|
if (defaultApiKeyData) {
|
|
87
96
|
apiKeyId = defaultApiKeyData.id
|
|
88
97
|
apiKey = defaultApiKeyData.key
|
|
89
|
-
|
|
98
|
+
|
|
90
99
|
if (apiKey) {
|
|
91
100
|
console.log(
|
|
92
|
-
chalk.dim(`Using default API key: ${defaultApiKeyData.name}`)
|
|
101
|
+
chalk.dim(`Using default API key: ${defaultApiKeyData.name}`),
|
|
93
102
|
)
|
|
94
103
|
} else {
|
|
95
104
|
console.log(
|
|
96
|
-
chalk.yellow(
|
|
105
|
+
chalk.yellow(
|
|
106
|
+
`Default API key "${defaultApiKeyData.name}" exists but the key value is missing.`,
|
|
107
|
+
),
|
|
97
108
|
)
|
|
98
109
|
console.log(
|
|
99
|
-
chalk.yellow(
|
|
110
|
+
chalk.yellow(
|
|
111
|
+
`Try rotating the key with: berget api-keys rotate ${defaultApiKeyData.id}`,
|
|
112
|
+
),
|
|
100
113
|
)
|
|
101
114
|
}
|
|
102
115
|
} else {
|
|
@@ -109,24 +122,24 @@ export function registerChatCommands(program: Command): void {
|
|
|
109
122
|
if (!apiKey) {
|
|
110
123
|
console.log(
|
|
111
124
|
chalk.red(
|
|
112
|
-
'Error: An API key is required to use the chat command.'
|
|
113
|
-
)
|
|
125
|
+
'Error: An API key is required to use the chat command.',
|
|
126
|
+
),
|
|
114
127
|
)
|
|
115
128
|
console.log(chalk.yellow('You can:'))
|
|
116
129
|
console.log(
|
|
117
130
|
chalk.yellow(
|
|
118
|
-
'1. Create an API key with: berget api-keys create --name "My Key"'
|
|
119
|
-
)
|
|
131
|
+
'1. Create an API key with: berget api-keys create --name "My Key"',
|
|
132
|
+
),
|
|
120
133
|
)
|
|
121
134
|
console.log(
|
|
122
135
|
chalk.yellow(
|
|
123
|
-
'2. Set a default API key with: berget api-keys set-default <id>'
|
|
124
|
-
)
|
|
136
|
+
'2. Set a default API key with: berget api-keys set-default <id>',
|
|
137
|
+
),
|
|
125
138
|
)
|
|
126
139
|
console.log(
|
|
127
140
|
chalk.yellow(
|
|
128
|
-
'3. Provide an API key with the --api-key option'
|
|
129
|
-
)
|
|
141
|
+
'3. Provide an API key with the --api-key option',
|
|
142
|
+
),
|
|
130
143
|
)
|
|
131
144
|
return
|
|
132
145
|
}
|
|
@@ -134,7 +147,7 @@ export function registerChatCommands(program: Command): void {
|
|
|
134
147
|
} catch (error) {
|
|
135
148
|
if (process.argv.includes('--debug')) {
|
|
136
149
|
console.log(
|
|
137
|
-
chalk.yellow('DEBUG: Error checking default API key:')
|
|
150
|
+
chalk.yellow('DEBUG: Error checking default API key:'),
|
|
138
151
|
)
|
|
139
152
|
console.log(chalk.yellow(String(error)))
|
|
140
153
|
}
|
|
@@ -147,14 +160,14 @@ export function registerChatCommands(program: Command): void {
|
|
|
147
160
|
const apiKeyService = ApiKeyService.getInstance()
|
|
148
161
|
const keys = await apiKeyService.list()
|
|
149
162
|
const selectedKey = keys.find(
|
|
150
|
-
(key) => key.id.toString() === options.apiKeyId
|
|
163
|
+
(key) => key.id.toString() === options.apiKeyId,
|
|
151
164
|
)
|
|
152
165
|
|
|
153
166
|
if (!selectedKey) {
|
|
154
167
|
console.log(
|
|
155
168
|
chalk.yellow(
|
|
156
|
-
`API key with ID ${options.apiKeyId} not found. Using default authentication
|
|
157
|
-
)
|
|
169
|
+
`API key with ID ${options.apiKeyId} not found. Using default authentication.`,
|
|
170
|
+
),
|
|
158
171
|
)
|
|
159
172
|
} else {
|
|
160
173
|
console.log(chalk.dim(`Using API key: ${selectedKey.name}`))
|
|
@@ -163,37 +176,43 @@ export function registerChatCommands(program: Command): void {
|
|
|
163
176
|
if (
|
|
164
177
|
await confirm(
|
|
165
178
|
chalk.yellow(
|
|
166
|
-
`To use API key "${selectedKey.name}", it needs to be rotated. This will invalidate the current key. Continue? (y/n)
|
|
167
|
-
)
|
|
179
|
+
`To use API key "${selectedKey.name}", it needs to be rotated. This will invalidate the current key. Continue? (y/n)`,
|
|
180
|
+
),
|
|
168
181
|
)
|
|
169
182
|
) {
|
|
170
183
|
const rotatedKey = await apiKeyService.rotate(options.apiKeyId)
|
|
171
184
|
apiKey = rotatedKey.key
|
|
172
185
|
console.log(
|
|
173
186
|
chalk.green(
|
|
174
|
-
`API key "${selectedKey.name}" rotated successfully
|
|
175
|
-
)
|
|
187
|
+
`API key "${selectedKey.name}" rotated successfully.`,
|
|
188
|
+
),
|
|
176
189
|
)
|
|
177
190
|
} else {
|
|
178
191
|
console.log(
|
|
179
|
-
chalk.yellow('Using default authentication instead.')
|
|
192
|
+
chalk.yellow('Using default authentication instead.'),
|
|
180
193
|
)
|
|
181
194
|
}
|
|
182
195
|
}
|
|
183
196
|
} catch (error) {
|
|
184
197
|
// Check if this is an authentication error
|
|
185
|
-
const errorMessage =
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
198
|
+
const errorMessage =
|
|
199
|
+
error instanceof Error ? error.message : String(error)
|
|
200
|
+
const isAuthError =
|
|
201
|
+
errorMessage.includes('Unauthorized') ||
|
|
202
|
+
errorMessage.includes('Authentication failed') ||
|
|
203
|
+
errorMessage.includes('AUTH_FAILED')
|
|
204
|
+
|
|
190
205
|
if (isAuthError) {
|
|
191
|
-
console.log(
|
|
206
|
+
console.log(
|
|
207
|
+
chalk.yellow(
|
|
208
|
+
'Authentication required. Please run `berget auth login` first.',
|
|
209
|
+
),
|
|
210
|
+
)
|
|
192
211
|
} else {
|
|
193
|
-
console.error(chalk.red('Error fetching API key:'))
|
|
194
|
-
console.error(error)
|
|
212
|
+
console.error(chalk.red('Error fetching API key:'))
|
|
213
|
+
console.error(error)
|
|
195
214
|
}
|
|
196
|
-
console.log(chalk.yellow('Using default authentication instead.'))
|
|
215
|
+
console.log(chalk.yellow('Using default authentication instead.'))
|
|
197
216
|
}
|
|
198
217
|
}
|
|
199
218
|
|
|
@@ -207,23 +226,17 @@ export function registerChatCommands(program: Command): void {
|
|
|
207
226
|
console.log(chalk.yellow('1. Log in with `berget auth login`'))
|
|
208
227
|
console.log(chalk.yellow('2. Provide an API key with `--api-key`'))
|
|
209
228
|
console.log(
|
|
210
|
-
chalk.yellow('3. Provide an API key ID with `--api-key-id`')
|
|
229
|
+
chalk.yellow('3. Provide an API key ID with `--api-key-id`'),
|
|
211
230
|
)
|
|
212
231
|
console.log(
|
|
213
232
|
chalk.yellow(
|
|
214
|
-
'4. Set a default API key with `berget api-keys set-default <id>`'
|
|
215
|
-
)
|
|
233
|
+
'4. Set a default API key with `berget api-keys set-default <id>`',
|
|
234
|
+
),
|
|
216
235
|
)
|
|
217
236
|
return
|
|
218
237
|
}
|
|
219
238
|
}
|
|
220
239
|
|
|
221
|
-
// Set up readline interface for user input
|
|
222
|
-
const rl = readline.createInterface({
|
|
223
|
-
input: process.stdin,
|
|
224
|
-
output: process.stdout,
|
|
225
|
-
})
|
|
226
|
-
|
|
227
240
|
// Prepare messages array
|
|
228
241
|
const messages: ChatMessage[] = []
|
|
229
242
|
|
|
@@ -235,6 +248,151 @@ export function registerChatCommands(program: Command): void {
|
|
|
235
248
|
})
|
|
236
249
|
}
|
|
237
250
|
|
|
251
|
+
// Check if input is being piped in
|
|
252
|
+
let inputMessage = message
|
|
253
|
+
let stdinContent = ''
|
|
254
|
+
|
|
255
|
+
if (!process.stdin.isTTY) {
|
|
256
|
+
// Read from stdin (piped input)
|
|
257
|
+
const chunks = []
|
|
258
|
+
for await (const chunk of process.stdin) {
|
|
259
|
+
chunks.push(chunk)
|
|
260
|
+
}
|
|
261
|
+
stdinContent = Buffer.concat(chunks).toString('utf8').trim()
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Combine stdin content with message if both exist
|
|
265
|
+
if (stdinContent && message) {
|
|
266
|
+
inputMessage = `${stdinContent}\n\n${message}`
|
|
267
|
+
} else if (stdinContent && !message) {
|
|
268
|
+
inputMessage = stdinContent
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// If a message is provided (either as argument, from stdin, or both), send it directly and exit
|
|
272
|
+
if (inputMessage) {
|
|
273
|
+
// Add user message
|
|
274
|
+
messages.push({
|
|
275
|
+
role: 'user',
|
|
276
|
+
content: inputMessage,
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
// Call the API
|
|
281
|
+
const completionOptions: ChatCompletionOptions = {
|
|
282
|
+
model: model || 'openai/gpt-oss',
|
|
283
|
+
messages: messages,
|
|
284
|
+
temperature:
|
|
285
|
+
options.temperature !== undefined ? options.temperature : 0.7,
|
|
286
|
+
max_tokens: options.maxTokens || 4096,
|
|
287
|
+
stream: options.stream !== false,
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Only add apiKey if it actually exists
|
|
291
|
+
if (apiKey) {
|
|
292
|
+
completionOptions.apiKey = apiKey
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Add streaming support (now default)
|
|
296
|
+
if (completionOptions.stream) {
|
|
297
|
+
let assistantResponse = ''
|
|
298
|
+
|
|
299
|
+
// Stream the response in real-time
|
|
300
|
+
completionOptions.onChunk = (chunk: any) => {
|
|
301
|
+
if (
|
|
302
|
+
chunk.choices &&
|
|
303
|
+
chunk.choices[0] &&
|
|
304
|
+
chunk.choices[0].delta &&
|
|
305
|
+
chunk.choices[0].delta.content
|
|
306
|
+
) {
|
|
307
|
+
const content = chunk.choices[0].delta.content
|
|
308
|
+
try {
|
|
309
|
+
process.stdout.write(content)
|
|
310
|
+
} catch (error: any) {
|
|
311
|
+
// Handle EPIPE errors gracefully (when pipe is closed)
|
|
312
|
+
if (error.code === 'EPIPE') {
|
|
313
|
+
// Stop streaming if the pipe is closed
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
throw error
|
|
317
|
+
}
|
|
318
|
+
assistantResponse += content
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
await chatService.createCompletion(completionOptions)
|
|
324
|
+
} catch (streamError) {
|
|
325
|
+
console.error(chalk.red('\nStreaming error:'), streamError)
|
|
326
|
+
|
|
327
|
+
// Fallback to non-streaming if streaming fails
|
|
328
|
+
console.log(
|
|
329
|
+
chalk.yellow('Falling back to non-streaming mode...'),
|
|
330
|
+
)
|
|
331
|
+
completionOptions.stream = false
|
|
332
|
+
delete completionOptions.onChunk
|
|
333
|
+
|
|
334
|
+
const response =
|
|
335
|
+
await chatService.createCompletion(completionOptions)
|
|
336
|
+
|
|
337
|
+
if (
|
|
338
|
+
response &&
|
|
339
|
+
response.choices &&
|
|
340
|
+
response.choices[0] &&
|
|
341
|
+
response.choices[0].message
|
|
342
|
+
) {
|
|
343
|
+
assistantResponse = response.choices[0].message.content
|
|
344
|
+
console.log(assistantResponse)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
console.log() // Add newline at the end
|
|
348
|
+
return
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const response =
|
|
352
|
+
await chatService.createCompletion(completionOptions)
|
|
353
|
+
|
|
354
|
+
// Check if response has the expected structure
|
|
355
|
+
if (
|
|
356
|
+
!response ||
|
|
357
|
+
!response.choices ||
|
|
358
|
+
!response.choices[0] ||
|
|
359
|
+
!response.choices[0].message
|
|
360
|
+
) {
|
|
361
|
+
console.error(
|
|
362
|
+
chalk.red('Error: Unexpected response format from API'),
|
|
363
|
+
)
|
|
364
|
+
console.error(
|
|
365
|
+
chalk.red('Response:', JSON.stringify(response, null, 2)),
|
|
366
|
+
)
|
|
367
|
+
throw new Error('Unexpected response format from API')
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Get assistant's response
|
|
371
|
+
const assistantMessage = response.choices[0].message.content
|
|
372
|
+
|
|
373
|
+
// Display the response
|
|
374
|
+
if (containsMarkdown(assistantMessage)) {
|
|
375
|
+
console.log(renderMarkdown(assistantMessage))
|
|
376
|
+
} else {
|
|
377
|
+
console.log(assistantMessage)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return
|
|
381
|
+
} catch (error) {
|
|
382
|
+
console.error(chalk.red('Error: Failed to get response'))
|
|
383
|
+
if (error instanceof Error) {
|
|
384
|
+
console.error(chalk.red(error.message))
|
|
385
|
+
}
|
|
386
|
+
process.exit(1)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Set up readline interface for user input (only for interactive mode)
|
|
391
|
+
const rl = readline.createInterface({
|
|
392
|
+
input: process.stdin,
|
|
393
|
+
output: process.stdout,
|
|
394
|
+
})
|
|
395
|
+
|
|
238
396
|
console.log(chalk.cyan('Chat with Berget AI (type "exit" to quit)'))
|
|
239
397
|
console.log(chalk.cyan('----------------------------------------'))
|
|
240
398
|
|
|
@@ -257,51 +415,87 @@ export function registerChatCommands(program: Command): void {
|
|
|
257
415
|
try {
|
|
258
416
|
// Call the API
|
|
259
417
|
const completionOptions: ChatCompletionOptions = {
|
|
260
|
-
model:
|
|
418
|
+
model: model || 'openai/gpt-oss',
|
|
261
419
|
messages: messages,
|
|
262
420
|
temperature:
|
|
263
421
|
options.temperature !== undefined ? options.temperature : 0.7,
|
|
264
422
|
max_tokens: options.maxTokens || 4096,
|
|
265
|
-
stream: options.stream
|
|
423
|
+
stream: options.stream !== false,
|
|
266
424
|
}
|
|
267
425
|
|
|
268
426
|
// Only add apiKey if it actually exists
|
|
269
427
|
if (apiKey) {
|
|
270
428
|
completionOptions.apiKey = apiKey
|
|
271
429
|
}
|
|
272
|
-
|
|
273
|
-
// Add streaming support
|
|
274
|
-
if (
|
|
430
|
+
|
|
431
|
+
// Add streaming support (now default)
|
|
432
|
+
if (completionOptions.stream) {
|
|
275
433
|
let assistantResponse = ''
|
|
276
434
|
console.log(chalk.blue('Assistant: '))
|
|
277
|
-
|
|
278
|
-
//
|
|
279
|
-
// since markdown needs the complete text to render properly
|
|
435
|
+
|
|
436
|
+
// Stream the response in real-time
|
|
280
437
|
completionOptions.onChunk = (chunk: any) => {
|
|
281
|
-
if (
|
|
438
|
+
if (
|
|
439
|
+
chunk.choices &&
|
|
440
|
+
chunk.choices[0] &&
|
|
441
|
+
chunk.choices[0].delta &&
|
|
442
|
+
chunk.choices[0].delta.content
|
|
443
|
+
) {
|
|
282
444
|
const content = chunk.choices[0].delta.content
|
|
283
|
-
|
|
445
|
+
try {
|
|
446
|
+
process.stdout.write(content)
|
|
447
|
+
} catch (error: any) {
|
|
448
|
+
// Handle EPIPE errors gracefully (when pipe is closed)
|
|
449
|
+
if (error.code === 'EPIPE') {
|
|
450
|
+
// Stop streaming if the pipe is closed
|
|
451
|
+
return
|
|
452
|
+
}
|
|
453
|
+
throw error
|
|
454
|
+
}
|
|
284
455
|
assistantResponse += content
|
|
285
456
|
}
|
|
286
457
|
}
|
|
287
|
-
|
|
288
|
-
|
|
458
|
+
|
|
459
|
+
try {
|
|
460
|
+
await chatService.createCompletion(completionOptions)
|
|
461
|
+
} catch (streamError) {
|
|
462
|
+
console.error(chalk.red('\nStreaming error:'), streamError)
|
|
463
|
+
|
|
464
|
+
// Fallback to non-streaming if streaming fails
|
|
465
|
+
console.log(
|
|
466
|
+
chalk.yellow('Falling back to non-streaming mode...'),
|
|
467
|
+
)
|
|
468
|
+
completionOptions.stream = false
|
|
469
|
+
delete completionOptions.onChunk
|
|
470
|
+
|
|
471
|
+
const response =
|
|
472
|
+
await chatService.createCompletion(completionOptions)
|
|
473
|
+
|
|
474
|
+
if (
|
|
475
|
+
response &&
|
|
476
|
+
response.choices &&
|
|
477
|
+
response.choices[0] &&
|
|
478
|
+
response.choices[0].message
|
|
479
|
+
) {
|
|
480
|
+
assistantResponse = response.choices[0].message.content
|
|
481
|
+
console.log(assistantResponse)
|
|
482
|
+
}
|
|
483
|
+
}
|
|
289
484
|
console.log('\n')
|
|
290
|
-
|
|
485
|
+
|
|
291
486
|
// Add assistant response to messages
|
|
292
487
|
messages.push({
|
|
293
488
|
role: 'assistant',
|
|
294
|
-
content: assistantResponse
|
|
489
|
+
content: assistantResponse,
|
|
295
490
|
})
|
|
296
|
-
|
|
491
|
+
|
|
297
492
|
// Continue the conversation
|
|
298
493
|
askQuestion()
|
|
299
494
|
return
|
|
300
495
|
}
|
|
301
|
-
|
|
302
|
-
const response =
|
|
303
|
-
completionOptions
|
|
304
|
-
)
|
|
496
|
+
|
|
497
|
+
const response =
|
|
498
|
+
await chatService.createCompletion(completionOptions)
|
|
305
499
|
|
|
306
500
|
// Debug output
|
|
307
501
|
if (program.opts().debug) {
|
|
@@ -317,10 +511,10 @@ export function registerChatCommands(program: Command): void {
|
|
|
317
511
|
!response.choices[0].message
|
|
318
512
|
) {
|
|
319
513
|
console.error(
|
|
320
|
-
chalk.red('Error: Unexpected response format from API')
|
|
514
|
+
chalk.red('Error: Unexpected response format from API'),
|
|
321
515
|
)
|
|
322
516
|
console.error(
|
|
323
|
-
chalk.red('Response:', JSON.stringify(response, null, 2))
|
|
517
|
+
chalk.red('Response:', JSON.stringify(response, null, 2)),
|
|
324
518
|
)
|
|
325
519
|
throw new Error('Unexpected response format from API')
|
|
326
520
|
}
|
|
@@ -336,14 +530,14 @@ export function registerChatCommands(program: Command): void {
|
|
|
336
530
|
|
|
337
531
|
// Display the response
|
|
338
532
|
console.log(chalk.blue('Assistant: '))
|
|
339
|
-
|
|
533
|
+
|
|
340
534
|
// Check if the response contains markdown and render it if it does
|
|
341
535
|
if (containsMarkdown(assistantMessage)) {
|
|
342
536
|
console.log(renderMarkdown(assistantMessage))
|
|
343
537
|
} else {
|
|
344
538
|
console.log(assistantMessage)
|
|
345
539
|
}
|
|
346
|
-
|
|
540
|
+
|
|
347
541
|
console.log() // Empty line for better readability
|
|
348
542
|
|
|
349
543
|
// Continue the conversation
|
|
@@ -372,7 +566,7 @@ export function registerChatCommands(program: Command): void {
|
|
|
372
566
|
.option('-k, --api-key <key>', 'API key to use for this request')
|
|
373
567
|
.option(
|
|
374
568
|
'--api-key-id <id>',
|
|
375
|
-
'ID of the API key to use from your saved keys'
|
|
569
|
+
'ID of the API key to use from your saved keys',
|
|
376
570
|
)
|
|
377
571
|
.action(async (options) => {
|
|
378
572
|
try {
|
|
@@ -388,7 +582,7 @@ export function registerChatCommands(program: Command): void {
|
|
|
388
582
|
if (defaultApiKeyData) {
|
|
389
583
|
apiKeyId = defaultApiKeyData.id
|
|
390
584
|
console.log(
|
|
391
|
-
chalk.dim(`Using default API key: ${defaultApiKeyData.name}`)
|
|
585
|
+
chalk.dim(`Using default API key: ${defaultApiKeyData.name}`),
|
|
392
586
|
)
|
|
393
587
|
}
|
|
394
588
|
}
|
|
@@ -398,14 +592,14 @@ export function registerChatCommands(program: Command): void {
|
|
|
398
592
|
const apiKeyService = ApiKeyService.getInstance()
|
|
399
593
|
const keys = await apiKeyService.list()
|
|
400
594
|
const selectedKey = keys.find(
|
|
401
|
-
(key) => key.id.toString() === options.apiKeyId
|
|
595
|
+
(key) => key.id.toString() === options.apiKeyId,
|
|
402
596
|
)
|
|
403
597
|
|
|
404
598
|
if (!selectedKey) {
|
|
405
599
|
console.log(
|
|
406
600
|
chalk.yellow(
|
|
407
|
-
`API key with ID ${options.apiKeyId} not found. Using default authentication
|
|
408
|
-
)
|
|
601
|
+
`API key with ID ${options.apiKeyId} not found. Using default authentication.`,
|
|
602
|
+
),
|
|
409
603
|
)
|
|
410
604
|
} else {
|
|
411
605
|
console.log(chalk.dim(`Using API key: ${selectedKey.name}`))
|
|
@@ -414,20 +608,20 @@ export function registerChatCommands(program: Command): void {
|
|
|
414
608
|
if (
|
|
415
609
|
await confirm(
|
|
416
610
|
chalk.yellow(
|
|
417
|
-
`To use API key "${selectedKey.name}", it needs to be rotated. This will invalidate the current key. Continue? (y/n)
|
|
418
|
-
)
|
|
611
|
+
`To use API key "${selectedKey.name}", it needs to be rotated. This will invalidate the current key. Continue? (y/n)`,
|
|
612
|
+
),
|
|
419
613
|
)
|
|
420
614
|
) {
|
|
421
615
|
const rotatedKey = await apiKeyService.rotate(options.apiKeyId)
|
|
422
616
|
apiKey = rotatedKey.key
|
|
423
617
|
console.log(
|
|
424
618
|
chalk.green(
|
|
425
|
-
`API key "${selectedKey.name}" rotated successfully
|
|
426
|
-
)
|
|
619
|
+
`API key "${selectedKey.name}" rotated successfully.`,
|
|
620
|
+
),
|
|
427
621
|
)
|
|
428
622
|
} else {
|
|
429
623
|
console.log(
|
|
430
|
-
chalk.yellow('Using default authentication instead.')
|
|
624
|
+
chalk.yellow('Using default authentication instead.'),
|
|
431
625
|
)
|
|
432
626
|
}
|
|
433
627
|
}
|
|
@@ -450,14 +644,15 @@ export function registerChatCommands(program: Command): void {
|
|
|
450
644
|
console.log(chalk.bold('Available Chat Models:'))
|
|
451
645
|
console.log(chalk.dim('─'.repeat(70)))
|
|
452
646
|
console.log(
|
|
453
|
-
chalk.dim('MODEL ID'.padEnd(40)) +
|
|
454
|
-
chalk.dim('CAPABILITIES')
|
|
647
|
+
chalk.dim('MODEL ID'.padEnd(40)) + chalk.dim('CAPABILITIES'),
|
|
455
648
|
)
|
|
456
649
|
console.log(chalk.dim('─'.repeat(70)))
|
|
457
650
|
|
|
458
651
|
// Filter to only show active models
|
|
459
|
-
const activeModels = models.data.filter(
|
|
460
|
-
|
|
652
|
+
const activeModels = models.data.filter(
|
|
653
|
+
(model: any) => model.active === true,
|
|
654
|
+
)
|
|
655
|
+
|
|
461
656
|
activeModels.forEach((model: any) => {
|
|
462
657
|
const capabilities = []
|
|
463
658
|
if (model.capabilities.vision) capabilities.push('vision')
|
|
@@ -466,12 +661,11 @@ export function registerChatCommands(program: Command): void {
|
|
|
466
661
|
if (model.capabilities.json_mode) capabilities.push('json_mode')
|
|
467
662
|
|
|
468
663
|
// Format model ID in Huggingface compatible format (owner/model)
|
|
469
|
-
const modelId = `${model.owned_by.toLowerCase()}/${model.id}`.padEnd(
|
|
470
|
-
|
|
471
|
-
console.log(
|
|
472
|
-
modelId +
|
|
473
|
-
capabilities.join(', ')
|
|
664
|
+
const modelId = `${model.owned_by.toLowerCase()}/${model.id}`.padEnd(
|
|
665
|
+
40,
|
|
474
666
|
)
|
|
667
|
+
|
|
668
|
+
console.log(modelId + capabilities.join(', '))
|
|
475
669
|
})
|
|
476
670
|
} catch (error) {
|
|
477
671
|
handleError('Failed to list chat models', error)
|
package/src/commands/clusters.ts
CHANGED
|
@@ -22,8 +22,8 @@ export function registerClusterCommands(program: Command): void {
|
|
|
22
22
|
clusters.forEach((cluster: Cluster) => {
|
|
23
23
|
console.log(
|
|
24
24
|
`${cluster.name.padEnd(22)} ${cluster.status.padEnd(9)} ${String(
|
|
25
|
-
cluster.nodes
|
|
26
|
-
).padEnd(8)} ${cluster.created}
|
|
25
|
+
cluster.nodes,
|
|
26
|
+
).padEnd(8)} ${cluster.created}`,
|
|
27
27
|
)
|
|
28
28
|
})
|
|
29
29
|
} catch (error) {
|