berget 2.2.7 → 2.2.8

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.
Files changed (130) hide show
  1. package/.github/workflows/publish.yml +6 -6
  2. package/.github/workflows/test.yml +1 -1
  3. package/.prettierrc +5 -3
  4. package/dist/index.js +24 -25
  5. package/dist/package.json +5 -3
  6. package/dist/src/agents/app.js +8 -8
  7. package/dist/src/agents/backend.js +3 -3
  8. package/dist/src/agents/devops.js +8 -8
  9. package/dist/src/agents/frontend.js +3 -3
  10. package/dist/src/agents/fullstack.js +3 -3
  11. package/dist/src/agents/index.js +18 -18
  12. package/dist/src/agents/quality.js +8 -8
  13. package/dist/src/agents/security.js +8 -8
  14. package/dist/src/client.js +115 -127
  15. package/dist/src/commands/api-keys.js +195 -202
  16. package/dist/src/commands/auth.js +16 -25
  17. package/dist/src/commands/autocomplete.js +8 -8
  18. package/dist/src/commands/billing.js +10 -19
  19. package/dist/src/commands/chat.js +139 -170
  20. package/dist/src/commands/clusters.js +21 -30
  21. package/dist/src/commands/code/__tests__/auth-sync.test.js +189 -186
  22. package/dist/src/commands/code/__tests__/fake-api-key-service.js +3 -13
  23. package/dist/src/commands/code/__tests__/fake-auth-service.js +21 -29
  24. package/dist/src/commands/code/__tests__/fake-command-runner.js +22 -33
  25. package/dist/src/commands/code/__tests__/fake-file-store.js +19 -41
  26. package/dist/src/commands/code/__tests__/fake-prompter.js +81 -97
  27. package/dist/src/commands/code/__tests__/setup-flow.test.js +295 -295
  28. package/dist/src/commands/code/adapters/clack-prompter.js +15 -32
  29. package/dist/src/commands/code/adapters/fs-file-store.js +25 -44
  30. package/dist/src/commands/code/adapters/spawn-command-runner.js +27 -41
  31. package/dist/src/commands/code/auth-sync.js +215 -228
  32. package/dist/src/commands/code/errors.js +15 -12
  33. package/dist/src/commands/code/setup.js +390 -425
  34. package/dist/src/commands/code.js +279 -294
  35. package/dist/src/commands/index.js +5 -5
  36. package/dist/src/commands/models.js +16 -25
  37. package/dist/src/commands/users.js +9 -18
  38. package/dist/src/constants/command-structure.js +138 -138
  39. package/dist/src/services/api-key-service.js +132 -152
  40. package/dist/src/services/auth-service.js +81 -95
  41. package/dist/src/services/browser-auth.js +121 -131
  42. package/dist/src/services/chat-service.js +369 -386
  43. package/dist/src/services/cluster-service.js +47 -62
  44. package/dist/src/services/collaborator-service.js +9 -21
  45. package/dist/src/services/flux-service.js +13 -25
  46. package/dist/src/services/helm-service.js +9 -21
  47. package/dist/src/services/kubectl-service.js +15 -29
  48. package/dist/src/utils/config-checker.js +7 -7
  49. package/dist/src/utils/config-loader.js +109 -109
  50. package/dist/src/utils/default-api-key.js +129 -139
  51. package/dist/src/utils/env-manager.js +55 -66
  52. package/dist/src/utils/error-handler.js +62 -62
  53. package/dist/src/utils/logger.js +74 -67
  54. package/dist/src/utils/markdown-renderer.js +28 -28
  55. package/dist/src/utils/opencode-validator.js +67 -69
  56. package/dist/src/utils/token-manager.js +67 -65
  57. package/dist/tests/commands/chat.test.js +30 -39
  58. package/dist/tests/commands/code.test.js +186 -195
  59. package/dist/tests/utils/config-loader.test.js +107 -107
  60. package/dist/tests/utils/env-manager.test.js +81 -90
  61. package/dist/tests/utils/opencode-validator.test.js +42 -41
  62. package/dist/vitest.config.js +1 -1
  63. package/eslint.config.mjs +50 -30
  64. package/index.ts +30 -31
  65. package/package.json +5 -3
  66. package/src/agents/app.ts +9 -9
  67. package/src/agents/backend.ts +4 -4
  68. package/src/agents/devops.ts +9 -9
  69. package/src/agents/frontend.ts +4 -4
  70. package/src/agents/fullstack.ts +4 -4
  71. package/src/agents/index.ts +27 -25
  72. package/src/agents/quality.ts +9 -9
  73. package/src/agents/security.ts +9 -9
  74. package/src/agents/types.ts +10 -10
  75. package/src/client.ts +85 -77
  76. package/src/commands/api-keys.ts +190 -185
  77. package/src/commands/auth.ts +15 -14
  78. package/src/commands/autocomplete.ts +10 -10
  79. package/src/commands/billing.ts +13 -12
  80. package/src/commands/chat.ts +145 -142
  81. package/src/commands/clusters.ts +20 -19
  82. package/src/commands/code/__tests__/auth-sync.test.ts +176 -175
  83. package/src/commands/code/__tests__/fake-api-key-service.ts +2 -2
  84. package/src/commands/code/__tests__/fake-auth-service.ts +18 -18
  85. package/src/commands/code/__tests__/fake-command-runner.ts +28 -22
  86. package/src/commands/code/__tests__/fake-file-store.ts +15 -15
  87. package/src/commands/code/__tests__/fake-prompter.ts +86 -85
  88. package/src/commands/code/__tests__/setup-flow.test.ts +253 -251
  89. package/src/commands/code/adapters/clack-prompter.ts +32 -30
  90. package/src/commands/code/adapters/fs-file-store.ts +18 -17
  91. package/src/commands/code/adapters/spawn-command-runner.ts +20 -15
  92. package/src/commands/code/auth-sync.ts +210 -210
  93. package/src/commands/code/errors.ts +11 -11
  94. package/src/commands/code/ports/auth-services.ts +7 -7
  95. package/src/commands/code/ports/command-runner.ts +2 -2
  96. package/src/commands/code/ports/file-store.ts +3 -3
  97. package/src/commands/code/ports/prompter.ts +13 -13
  98. package/src/commands/code/setup.ts +408 -406
  99. package/src/commands/code.ts +288 -287
  100. package/src/commands/index.ts +11 -10
  101. package/src/commands/models.ts +19 -18
  102. package/src/commands/users.ts +11 -10
  103. package/src/constants/command-structure.ts +159 -159
  104. package/src/services/api-key-service.ts +85 -85
  105. package/src/services/auth-service.ts +55 -54
  106. package/src/services/browser-auth.ts +62 -62
  107. package/src/services/chat-service.ts +169 -170
  108. package/src/services/cluster-service.ts +28 -28
  109. package/src/services/collaborator-service.ts +6 -6
  110. package/src/services/flux-service.ts +17 -17
  111. package/src/services/helm-service.ts +11 -11
  112. package/src/services/kubectl-service.ts +12 -12
  113. package/src/types/api.d.ts +1933 -1933
  114. package/src/types/json.d.ts +1 -1
  115. package/src/utils/config-checker.ts +6 -6
  116. package/src/utils/config-loader.ts +130 -129
  117. package/src/utils/default-api-key.ts +81 -80
  118. package/src/utils/env-manager.ts +37 -37
  119. package/src/utils/error-handler.ts +64 -64
  120. package/src/utils/logger.ts +72 -66
  121. package/src/utils/markdown-renderer.ts +28 -28
  122. package/src/utils/opencode-validator.ts +72 -71
  123. package/src/utils/token-manager.ts +69 -68
  124. package/tests/commands/chat.test.ts +32 -31
  125. package/tests/commands/code.test.ts +182 -181
  126. package/tests/utils/config-loader.test.ts +111 -110
  127. package/tests/utils/env-manager.test.ts +83 -79
  128. package/tests/utils/opencode-validator.test.ts +43 -42
  129. package/tsconfig.json +2 -1
  130. package/vitest.config.ts +2 -2
@@ -1,20 +1,20 @@
1
- import { createAuthenticatedClient } from "../client";
2
- import { logger } from "../utils/logger";
3
-
4
- export interface ChatMessage {
5
- role: "system" | "user" | "assistant";
6
- content: string;
7
- }
1
+ import { createAuthenticatedClient } from '../client';
2
+ import { logger } from '../utils/logger';
8
3
 
9
4
  export interface ChatCompletionOptions {
10
- model?: string;
11
- messages: ChatMessage[];
12
- temperature?: number;
5
+ apiKey?: string;
13
6
  max_tokens?: number;
7
+ messages: ChatMessage[];
8
+ model?: string;
9
+ onChunk?: (chunk: any) => void;
14
10
  stream?: boolean;
11
+ temperature?: number;
15
12
  top_p?: number;
16
- apiKey?: string;
17
- onChunk?: (chunk: any) => void;
13
+ }
14
+
15
+ export interface ChatMessage {
16
+ content: string;
17
+ role: 'assistant' | 'system' | 'user';
18
18
  }
19
19
 
20
20
  /**
@@ -22,18 +22,18 @@ export interface ChatCompletionOptions {
22
22
  * Command group: chat
23
23
  */
24
24
  export class ChatService {
25
- private static instance: ChatService;
26
- private client = createAuthenticatedClient();
27
-
28
25
  // Command group name for this service
29
- public static readonly COMMAND_GROUP = "chat";
30
-
26
+ public static readonly COMMAND_GROUP = 'chat';
31
27
  // Subcommands for this service
32
28
  public static readonly COMMANDS = {
33
- RUN: "run",
34
- LIST: "list",
29
+ LIST: 'list',
30
+ RUN: 'run',
35
31
  };
36
32
 
33
+ private static instance: ChatService;
34
+
35
+ private client = createAuthenticatedClient();
36
+
37
37
  private constructor() {}
38
38
 
39
39
  public static getInstance(): ChatService {
@@ -49,150 +49,149 @@ export class ChatService {
49
49
  */
50
50
  public async createCompletion(options: ChatCompletionOptions): Promise<any> {
51
51
  try {
52
- logger.debug("Starting createCompletion method");
52
+ logger.debug('Starting createCompletion method');
53
53
 
54
54
  // Initialize options if undefined
55
55
  const optionsCopy = options ? { ...options } : { messages: [] };
56
56
 
57
57
  // Check if messages are defined
58
58
  if (!optionsCopy.messages || !Array.isArray(optionsCopy.messages)) {
59
- logger.error("messages is undefined or not an array");
59
+ logger.error('messages is undefined or not an array');
60
60
  optionsCopy.messages = [];
61
61
  }
62
62
 
63
63
  // Log the options object
64
- logger.debug("Starting createCompletion with options:");
64
+ logger.debug('Starting createCompletion with options:');
65
65
  try {
66
66
  logger.debug(
67
67
  JSON.stringify(
68
68
  {
69
69
  ...optionsCopy,
70
- apiKey: optionsCopy.apiKey ? "***" : undefined,
70
+ apiKey: optionsCopy.apiKey ? '***' : undefined,
71
71
  messages: optionsCopy.messages
72
72
  ? `${optionsCopy.messages.length} messages`
73
- : "0 messages",
73
+ : '0 messages',
74
74
  },
75
75
  null,
76
- 2
77
- )
76
+ 2,
77
+ ),
78
78
  );
79
79
  } catch (error) {
80
- logger.error("Failed to stringify options:", error);
80
+ logger.error('Failed to stringify options:', error);
81
81
  }
82
82
 
83
83
  const headers: Record<string, string> = {};
84
84
 
85
85
  // First try to use the authenticated client (with refresh token support)
86
86
  // Only fall back to API key flow if explicitly requested or no auth tokens available
87
- const { TokenManager } = await import("../utils/token-manager");
87
+ const { TokenManager } = await import('../utils/token-manager');
88
88
  const tokenManagerInstance = TokenManager.getInstance();
89
89
  const hasValidAuth =
90
90
  tokenManagerInstance.getAccessToken() && !tokenManagerInstance.isTokenExpired();
91
91
 
92
- const envApiKeyForAuth = process.env.BERGET_API_KEY;
93
- const hasExplicitApiKey = !!optionsCopy.apiKey || !!envApiKeyForAuth;
92
+ const environmentApiKeyForAuth = process.env.BERGET_API_KEY;
93
+ const hasExplicitApiKey = !!optionsCopy.apiKey || !!environmentApiKeyForAuth;
94
94
 
95
95
  // If we have valid auth tokens and no explicit API key, use authenticated client
96
96
  if (hasValidAuth && !hasExplicitApiKey) {
97
- logger.debug("Using authenticated client with refresh token support");
97
+ logger.debug('Using authenticated client with refresh token support');
98
98
  // Create a copy without apiKey to let the authenticated client handle auth automatically
99
99
  const { apiKey: _apiKey, ...optionsWithoutKey } = optionsCopy;
100
100
  return this.executeCompletion(optionsWithoutKey, {});
101
101
  }
102
102
 
103
103
  // Check for environment variables first - prioritize this over everything else
104
- const envApiKey = process.env.BERGET_API_KEY;
105
- if (envApiKey) {
106
- logger.debug("Using API key from BERGET_API_KEY environment variable");
107
- optionsCopy.apiKey = envApiKey;
104
+ const environmentApiKey = process.env.BERGET_API_KEY;
105
+ if (environmentApiKey) {
106
+ logger.debug('Using API key from BERGET_API_KEY environment variable');
107
+ optionsCopy.apiKey = environmentApiKey;
108
108
  // Skip the default API key logic if we already have a key
109
109
  return this.executeCompletion(optionsCopy, headers);
110
110
  }
111
111
  // If API key is already provided, use it directly
112
112
  else if (optionsCopy.apiKey) {
113
- logger.debug("Using API key provided in options");
113
+ logger.debug('Using API key provided in options');
114
114
  // Skip the default API key logic if we already have a key
115
115
  return this.executeCompletion(optionsCopy, headers);
116
116
  }
117
117
  // Only try to get the default API key if no API key is provided and no env var is set
118
118
  else {
119
- logger.debug("No API key provided, trying to get default");
119
+ logger.debug('No API key provided, trying to get default');
120
120
 
121
121
  try {
122
122
  // Import the DefaultApiKeyManager directly
123
- logger.debug("Importing DefaultApiKeyManager");
123
+ logger.debug('Importing DefaultApiKeyManager');
124
124
 
125
- const DefaultApiKeyManager = (await import("../utils/default-api-key"))
126
- .DefaultApiKeyManager;
125
+ const { DefaultApiKeyManager } = await import('../utils/default-api-key');
127
126
  const defaultApiKeyManager = DefaultApiKeyManager.getInstance();
128
127
 
129
- logger.debug("Got DefaultApiKeyManager instance");
128
+ logger.debug('Got DefaultApiKeyManager instance');
130
129
 
131
130
  // Try to get the default API key
132
- logger.debug("Calling promptForDefaultApiKey");
131
+ logger.debug('Calling promptForDefaultApiKey');
133
132
 
134
133
  const defaultApiKeyData = defaultApiKeyManager.getDefaultApiKeyData();
135
134
  const apiKey =
136
135
  defaultApiKeyData?.key || (await defaultApiKeyManager.promptForDefaultApiKey());
137
136
 
138
137
  logger.debug(`Default API key data exists: ${!!defaultApiKeyData}`);
139
- logger.debug(`promptForDefaultApiKey returned: ${apiKey ? "a key" : "null"}`);
138
+ logger.debug(`promptForDefaultApiKey returned: ${apiKey ? 'a key' : 'null'}`);
140
139
 
141
140
  if (apiKey) {
142
- logger.debug("Using API key from default API key manager");
141
+ logger.debug('Using API key from default API key manager');
143
142
  optionsCopy.apiKey = apiKey;
144
143
  } else {
145
- logger.warn("No API key available. You need to either:");
144
+ logger.warn('No API key available. You need to either:');
146
145
  logger.warn('1. Create an API key with: berget api-keys create --name "My Key"');
147
- logger.warn("2. Set a default API key with: berget api-keys set-default <id>");
148
- logger.warn("3. Provide an API key with the --api-key option");
149
- logger.warn("4. Set the BERGET_API_KEY environment variable");
150
- logger.warn("\nExample:");
151
- logger.warn(" export BERGET_API_KEY=your_api_key_here");
152
- logger.warn(" # or for a single command:");
153
- logger.warn(" BERGET_API_KEY=your_api_key_here berget chat run google/gemma-3-27b-it");
154
- throw new Error("No API key provided and no default API key set");
146
+ logger.warn('2. Set a default API key with: berget api-keys set-default <id>');
147
+ logger.warn('3. Provide an API key with the --api-key option');
148
+ logger.warn('4. Set the BERGET_API_KEY environment variable');
149
+ logger.warn('\nExample:');
150
+ logger.warn(' export BERGET_API_KEY=your_api_key_here');
151
+ logger.warn(' # or for a single command:');
152
+ logger.warn(' BERGET_API_KEY=your_api_key_here berget chat run google/gemma-3-27b-it');
153
+ throw new Error('No API key provided and no default API key set');
155
154
  }
156
155
 
157
156
  // Set the API key in the options
158
- logger.debug("Setting API key in options");
157
+ logger.debug('Setting API key in options');
159
158
 
160
159
  // Only set the API key if it's not null
161
160
  if (apiKey) {
162
161
  optionsCopy.apiKey = apiKey;
163
162
  }
164
163
  } catch (error) {
165
- logger.error("Error getting API key:");
164
+ logger.error('Error getting API key:');
166
165
  if (error instanceof Error) {
167
166
  logger.error(error.message);
168
167
  }
169
168
  logger.warn('Please create an API key with: berget api-keys create --name "My Key"');
170
- throw new Error("Failed to get API key");
169
+ throw new Error('Failed to get API key');
171
170
  }
172
171
  }
173
172
 
174
173
  // Set default model if not provided
175
174
  if (!optionsCopy.model) {
176
- logger.debug("No model specified, using default: google/gemma-3-27b-it");
177
- optionsCopy.model = "google/gemma-3-27b-it";
175
+ logger.debug('No model specified, using default: google/gemma-3-27b-it');
176
+ optionsCopy.model = 'google/gemma-3-27b-it';
178
177
  }
179
178
 
180
- logger.debug("Chat completion options:");
179
+ logger.debug('Chat completion options:');
181
180
  logger.debug(
182
181
  JSON.stringify(
183
182
  {
184
183
  ...optionsCopy,
185
- apiKey: optionsCopy.apiKey ? "***" : undefined, // Hide the actual API key in debug output
184
+ apiKey: optionsCopy.apiKey ? '***' : undefined, // Hide the actual API key in debug output
186
185
  },
187
186
  null,
188
- 2
189
- )
187
+ 2,
188
+ ),
190
189
  );
191
190
 
192
191
  return this.executeCompletion(optionsCopy, headers);
193
192
  } catch (error) {
194
193
  // Improved error handling
195
- let errorMessage = "Failed to create chat completion";
194
+ let errorMessage = 'Failed to create chat completion';
196
195
 
197
196
  if (error instanceof Error) {
198
197
  try {
@@ -212,6 +211,55 @@ export class ChatService {
212
211
  }
213
212
  }
214
213
 
214
+ /**
215
+ * List available models
216
+ * Command: berget chat list
217
+ */
218
+ public async listModels(apiKey?: string): Promise<any> {
219
+ try {
220
+ // Check for environment variable first, then fallback to provided API key
221
+ const environmentApiKey = process.env.BERGET_API_KEY;
222
+ const effectiveApiKey = environmentApiKey || apiKey;
223
+
224
+ if (effectiveApiKey) {
225
+ const headers = {
226
+ Authorization: effectiveApiKey,
227
+ };
228
+
229
+ const { data, error } = await this.client.GET('/v1/models', { headers });
230
+ if (error) throw new Error(JSON.stringify(error));
231
+ return data;
232
+ } else {
233
+ const { data, error } = await this.client.GET('/v1/models');
234
+ if (error) throw new Error(JSON.stringify(error));
235
+ return data;
236
+ }
237
+ } catch (error) {
238
+ // Improved error handling
239
+ let errorMessage = 'Failed to list models';
240
+
241
+ if (error instanceof Error) {
242
+ try {
243
+ // Try to parse the error message as JSON
244
+ const parsedError = JSON.parse(error.message);
245
+ if (parsedError.error) {
246
+ errorMessage = `Models error: ${
247
+ typeof parsedError.error === 'string'
248
+ ? parsedError.error
249
+ : parsedError.error.message || JSON.stringify(parsedError.error)
250
+ }`;
251
+ }
252
+ } catch {
253
+ // If parsing fails, use the original error message
254
+ errorMessage = `Models error: ${error.message}`;
255
+ }
256
+ }
257
+
258
+ logger.error(errorMessage);
259
+ throw new Error(errorMessage);
260
+ }
261
+ }
262
+
215
263
  /**
216
264
  * Execute the completion request with the provided options
217
265
  * @param options The completion options
@@ -220,30 +268,30 @@ export class ChatService {
220
268
  */
221
269
  private async executeCompletion(
222
270
  options: ChatCompletionOptions,
223
- headers: Record<string, string> = {}
271
+ headers: Record<string, string> = {},
224
272
  ): Promise<any> {
225
273
  try {
226
274
  // If an API key is provided, use it for this request
227
275
  if (options.apiKey) {
228
276
  // API keys should be sent directly, not with Bearer prefix
229
- headers["Authorization"] = options.apiKey;
277
+ headers['Authorization'] = options.apiKey;
230
278
  }
231
279
 
232
280
  // Remove apiKey and onChunk from options before sending to API
233
- const { apiKey, onChunk, ...requestOptions } = options;
281
+ const { apiKey: _apiKey, onChunk, ...requestOptions } = options;
234
282
 
235
- logger.debug("Request options:");
283
+ logger.debug('Request options:');
236
284
  logger.debug(
237
285
  JSON.stringify(
238
286
  {
239
287
  ...requestOptions,
240
288
  messages: requestOptions.messages
241
289
  ? `${requestOptions.messages.length} messages`
242
- : "0 messages",
290
+ : '0 messages',
243
291
  },
244
292
  null,
245
- 2
246
- )
293
+ 2,
294
+ ),
247
295
  );
248
296
 
249
297
  // Handle streaming responses differently
@@ -253,14 +301,14 @@ export class ChatService {
253
301
  // Ensure model is always defined for the API call
254
302
  const requestBody = {
255
303
  ...requestOptions,
256
- model: requestOptions.model || "google/gemma-3-27b-it",
304
+ model: requestOptions.model || 'google/gemma-3-27b-it',
257
305
  };
258
306
 
259
307
  // Debug the headers being sent
260
- logger.debug("Headers being sent:");
308
+ logger.debug('Headers being sent:');
261
309
  logger.debug(JSON.stringify(headers, null, 2));
262
310
 
263
- const response = await this.client.POST("/v1/chat/completions", {
311
+ const response = await this.client.POST('/v1/chat/completions', {
264
312
  body: requestBody,
265
313
  headers,
266
314
  });
@@ -269,11 +317,11 @@ export class ChatService {
269
317
  const responseAny = response as any;
270
318
  if (responseAny && responseAny.error) throw new Error(JSON.stringify(responseAny.error));
271
319
 
272
- logger.debug("API response:");
320
+ logger.debug('API response:');
273
321
  logger.debug(JSON.stringify(response, null, 2));
274
322
 
275
323
  // Output the complete response data for debugging
276
- logger.debug("Complete response data:");
324
+ logger.debug('Complete response data:');
277
325
  logger.debug(JSON.stringify(response.data, null, 2));
278
326
 
279
327
  return response.data;
@@ -282,7 +330,7 @@ export class ChatService {
282
330
  logger.debug(
283
331
  `Request error: ${
284
332
  requestError instanceof Error ? requestError.message : String(requestError)
285
- }`
333
+ }`,
286
334
  );
287
335
  throw requestError;
288
336
  }
@@ -299,10 +347,10 @@ export class ChatService {
299
347
  */
300
348
  private async handleStreamingResponse(
301
349
  options: any,
302
- headers: Record<string, string>
350
+ headers: Record<string, string>,
303
351
  ): Promise<any> {
304
352
  // Use the same base URL as the client
305
- const baseUrl = process.env.API_BASE_URL || "https://api.berget.ai";
353
+ const baseUrl = process.env.API_BASE_URL || 'https://api.berget.ai';
306
354
  const url = new URL(`${baseUrl}/v1/chat/completions`);
307
355
 
308
356
  try {
@@ -312,19 +360,19 @@ export class ChatService {
312
360
 
313
361
  // Make fetch request directly to handle streaming
314
362
  const response = await fetch(url.toString(), {
315
- method: "POST",
363
+ body: JSON.stringify(options),
316
364
  headers: {
317
- "Content-Type": "application/json",
318
- Accept: "text/event-stream",
365
+ Accept: 'text/event-stream',
366
+ 'Content-Type': 'application/json',
319
367
  ...headers,
320
368
  },
321
- body: JSON.stringify(options),
369
+ method: 'POST',
322
370
  });
323
371
 
324
372
  logger.debug(`Response status: ${response.status}`);
325
373
  logger.debug(
326
374
  `Response headers:`,
327
- JSON.stringify(Object.fromEntries(response.headers.entries()), null, 2)
375
+ JSON.stringify(Object.fromEntries(response.headers.entries()), null, 2),
328
376
  );
329
377
 
330
378
  if (!response.ok) {
@@ -332,20 +380,20 @@ export class ChatService {
332
380
  logger.error(`Stream request failed: ${response.status} ${response.statusText}`);
333
381
  logger.error(`Error response: ${errorText}`);
334
382
  throw new Error(
335
- `Stream request failed: ${response.status} ${response.statusText} - ${errorText}`
383
+ `Stream request failed: ${response.status} ${response.statusText} - ${errorText}`,
336
384
  );
337
385
  }
338
386
 
339
387
  if (!response.body) {
340
- throw new Error("No response body received");
388
+ throw new Error('No response body received');
341
389
  }
342
390
 
343
391
  // Process the stream
344
392
  const reader = response.body.getReader();
345
393
  const decoder = new TextDecoder();
346
- let fullContent = "";
394
+ let fullContent = '';
347
395
  let fullResponse: any = null;
348
- let buffer = ""; // Buffer to accumulate partial JSON data
396
+ let buffer = ''; // Buffer to accumulate partial JSON data
349
397
 
350
398
  while (true) {
351
399
  const { done, value } = await reader.read();
@@ -359,31 +407,30 @@ export class ChatService {
359
407
  logger.debug(`Added chunk to buffer. Buffer length: ${buffer.length}`);
360
408
 
361
409
  // Process the buffer - it may contain multiple SSE events
362
- const lines = buffer.split("\n");
410
+ const lines = buffer.split('\n');
363
411
  logger.debug(`Processing ${lines.length} lines from buffer`);
364
412
 
365
413
  // Keep track of processed lines to update buffer
366
414
  let processedLines = 0;
367
415
 
368
- for (let i = 0; i < lines.length; i++) {
369
- const line = lines[i];
370
- logger.debug(`Line ${i}: "${line}"`);
416
+ for (const [index, line] of lines.entries()) {
417
+ logger.debug(`Line ${index}: "${line}"`);
371
418
 
372
- if (line.startsWith("data:")) {
419
+ if (line.startsWith('data:')) {
373
420
  const jsonData = line.slice(5).trim();
374
421
  logger.debug(`Extracted JSON data: "${jsonData}"`);
375
422
 
376
423
  // Skip empty data or [DONE] marker
377
- if (jsonData === "" || jsonData === "[DONE]") {
424
+ if (jsonData === '' || jsonData === '[DONE]') {
378
425
  logger.debug(`Skipping empty data or [DONE] marker`);
379
- processedLines = i + 1;
426
+ processedLines = index + 1;
380
427
  continue;
381
428
  }
382
429
 
383
430
  // Check if JSON looks complete (basic validation)
384
- if (!jsonData.startsWith("{")) {
431
+ if (!jsonData.startsWith('{')) {
385
432
  logger.warn(
386
- `JSON data doesn't start with '{', might be partial: "${jsonData.substring(0, 50)}..."`
433
+ `JSON data doesn't start with '{', might be partial: "${jsonData.slice(0, 50)}..."`,
387
434
  );
388
435
  // Don't process this line yet, keep it in buffer
389
436
  break;
@@ -394,13 +441,12 @@ export class ChatService {
394
441
  let inString = false;
395
442
  let escaped = false;
396
443
 
397
- for (let j = 0; j < jsonData.length; j++) {
398
- const char = jsonData[j];
444
+ for (const char of jsonData) {
399
445
  if (escaped) {
400
446
  escaped = false;
401
447
  continue;
402
448
  }
403
- if (char === "\\") {
449
+ if (char === '\\') {
404
450
  escaped = true;
405
451
  continue;
406
452
  }
@@ -408,16 +454,16 @@ export class ChatService {
408
454
  inString = !inString;
409
455
  continue;
410
456
  }
411
- if (!inString && char === "{") {
457
+ if (!inString && char === '{') {
412
458
  braceCount++;
413
- } else if (!inString && char === "}") {
459
+ } else if (!inString && char === '}') {
414
460
  braceCount--;
415
461
  }
416
462
  }
417
463
 
418
464
  if (braceCount !== 0) {
419
465
  logger.warn(
420
- `JSON braces don't balance (${braceCount}), treating as partial: "${jsonData.substring(0, 50)}..."`
466
+ `JSON braces don't balance (${braceCount}), treating as partial: "${jsonData.slice(0, 50)}..."`,
421
467
  );
422
468
  // Don't process this line yet, keep it in buffer
423
469
  break;
@@ -427,7 +473,7 @@ export class ChatService {
427
473
  logger.debug(`Attempting to parse JSON of length: ${jsonData.length}`);
428
474
  const parsedData = JSON.parse(jsonData);
429
475
  logger.debug(`Successfully parsed JSON: ${JSON.stringify(parsedData, null, 2)}`);
430
- processedLines = i + 1; // Mark this line as processed
476
+ processedLines = index + 1; // Mark this line as processed
431
477
 
432
478
  // Call the onChunk callback with the parsed data
433
479
  if (options.onChunk) {
@@ -440,34 +486,36 @@ export class ChatService {
440
486
  } else if (
441
487
  parsedData.choices &&
442
488
  parsedData.choices[0] &&
443
- parsedData.choices[0].delta
489
+ parsedData.choices[0].delta && // Accumulate content for the full response
490
+ parsedData.choices[0].delta.content
444
491
  ) {
445
- // Accumulate content for the full response
446
- if (parsedData.choices[0].delta.content) {
447
- fullContent += parsedData.choices[0].delta.content;
448
- }
492
+ fullContent += parsedData.choices[0].delta.content;
449
493
  }
450
- } catch (e) {
451
- logger.error(`Error parsing chunk: ${e}`);
494
+ } catch (error) {
495
+ logger.error(`Error parsing chunk: ${error}`);
452
496
  logger.error(
453
- `JSON parse error at position ${(e as any).message?.match(/position (\d+)/)?.[1] || "unknown"}`
497
+ `JSON parse error at position ${(error as any).message?.match(/position (\d+)/)?.[1] || 'unknown'}`,
454
498
  );
455
499
  logger.error(`Problematic chunk length: ${jsonData.length}`);
456
500
  logger.error(`Problematic chunk content: "${jsonData}"`);
457
- logger.error(`Chunk starts with: "${jsonData.substring(0, 50)}..."`);
458
- logger.error(`Chunk ends with: "...${jsonData.substring(jsonData.length - 50)}"`);
501
+ logger.error(`Chunk starts with: "${jsonData.slice(0, 50)}..."`);
502
+ logger.error(
503
+ `Chunk ends with: "...${jsonData.slice(Math.max(0, jsonData.length - 50))}"`,
504
+ );
459
505
 
460
506
  // Show character codes around the error position
461
- const errorPos = parseInt((e as any).message?.match(/position (\d+)/)?.[1] || "0");
507
+ const errorPos = Number.parseInt(
508
+ (error as any).message?.match(/position (\d+)/)?.[1] || '0',
509
+ );
462
510
  if (errorPos > 0) {
463
511
  const start = Math.max(0, errorPos - 20);
464
512
  const end = Math.min(jsonData.length, errorPos + 20);
465
513
  logger.error(`Context around error position ${errorPos}:`);
466
514
  logger.error(`"${jsonData.substring(start, end)}"`);
467
515
  logger.error(
468
- `Character codes: ${Array.from(jsonData.substring(start, end))
469
- .map(c => c.charCodeAt(0))
470
- .join(" ")}`
516
+ `Character codes: ${[...jsonData.substring(start, end)]
517
+ .map((c) => c.charCodeAt(0))
518
+ .join(' ')}`,
471
519
  );
472
520
  }
473
521
  }
@@ -477,9 +525,9 @@ export class ChatService {
477
525
  // Update buffer to only contain unprocessed lines
478
526
  if (processedLines > 0) {
479
527
  const remainingLines = lines.slice(processedLines);
480
- buffer = remainingLines.join("\n");
528
+ buffer = remainingLines.join('\n');
481
529
  logger.debug(
482
- `Updated buffer. Remaining lines: ${remainingLines.length}, Buffer length: ${buffer.length}`
530
+ `Updated buffer. Remaining lines: ${remainingLines.length}, Buffer length: ${buffer.length}`,
483
531
  );
484
532
  }
485
533
  }
@@ -488,68 +536,19 @@ export class ChatService {
488
536
  if (fullResponse) {
489
537
  if (fullContent) {
490
538
  fullResponse.choices[0].message = {
491
- role: "assistant",
492
539
  content: fullContent,
540
+ role: 'assistant',
493
541
  };
494
542
  }
495
543
  return fullResponse;
496
544
  }
497
545
 
498
546
  return {
499
- choices: [{ message: { role: "assistant", content: fullContent } }],
547
+ choices: [{ message: { content: fullContent, role: 'assistant' } }],
500
548
  };
501
549
  } catch (error) {
502
550
  logger.error(`Streaming error: ${error instanceof Error ? error.message : String(error)}`);
503
551
  throw error;
504
552
  }
505
553
  }
506
-
507
- /**
508
- * List available models
509
- * Command: berget chat list
510
- */
511
- public async listModels(apiKey?: string): Promise<any> {
512
- try {
513
- // Check for environment variable first, then fallback to provided API key
514
- const envApiKey = process.env.BERGET_API_KEY;
515
- const effectiveApiKey = envApiKey || apiKey;
516
-
517
- if (effectiveApiKey) {
518
- const headers = {
519
- Authorization: effectiveApiKey,
520
- };
521
-
522
- const { data, error } = await this.client.GET("/v1/models", { headers });
523
- if (error) throw new Error(JSON.stringify(error));
524
- return data;
525
- } else {
526
- const { data, error } = await this.client.GET("/v1/models");
527
- if (error) throw new Error(JSON.stringify(error));
528
- return data;
529
- }
530
- } catch (error) {
531
- // Improved error handling
532
- let errorMessage = "Failed to list models";
533
-
534
- if (error instanceof Error) {
535
- try {
536
- // Try to parse the error message as JSON
537
- const parsedError = JSON.parse(error.message);
538
- if (parsedError.error) {
539
- errorMessage = `Models error: ${
540
- typeof parsedError.error === "string"
541
- ? parsedError.error
542
- : parsedError.error.message || JSON.stringify(parsedError.error)
543
- }`;
544
- }
545
- } catch {
546
- // If parsing fails, use the original error message
547
- errorMessage = `Models error: ${error.message}`;
548
- }
549
- }
550
-
551
- logger.error(errorMessage);
552
- throw new Error(errorMessage);
553
- }
554
- }
555
554
  }