berget 2.2.7 → 2.2.9
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/.github/workflows/publish.yml +6 -6
- package/.github/workflows/test.yml +1 -1
- package/.prettierrc +5 -3
- package/dist/index.js +24 -25
- package/dist/package.json +7 -3
- package/dist/src/agents/app.js +8 -8
- package/dist/src/agents/backend.js +3 -3
- package/dist/src/agents/devops.js +8 -8
- package/dist/src/agents/frontend.js +3 -3
- package/dist/src/agents/fullstack.js +3 -3
- package/dist/src/agents/index.js +18 -18
- package/dist/src/agents/quality.js +8 -8
- package/dist/src/agents/security.js +8 -8
- package/dist/src/client.js +115 -127
- package/dist/src/commands/api-keys.js +181 -202
- package/dist/src/commands/auth.js +16 -25
- package/dist/src/commands/autocomplete.js +8 -8
- package/dist/src/commands/billing.js +10 -19
- package/dist/src/commands/chat.js +139 -170
- package/dist/src/commands/clusters.js +21 -30
- package/dist/src/commands/code/__tests__/auth-sync.test.js +189 -186
- package/dist/src/commands/code/__tests__/fake-api-key-service.js +3 -13
- package/dist/src/commands/code/__tests__/fake-auth-service.js +21 -29
- package/dist/src/commands/code/__tests__/fake-command-runner.js +22 -33
- package/dist/src/commands/code/__tests__/fake-file-store.js +19 -41
- package/dist/src/commands/code/__tests__/fake-prompter.js +81 -97
- package/dist/src/commands/code/__tests__/setup-flow.test.js +295 -295
- package/dist/src/commands/code/adapters/clack-prompter.js +15 -32
- package/dist/src/commands/code/adapters/fs-file-store.js +25 -44
- package/dist/src/commands/code/adapters/spawn-command-runner.js +27 -41
- package/dist/src/commands/code/auth-sync.js +215 -228
- package/dist/src/commands/code/errors.js +15 -12
- package/dist/src/commands/code/setup.js +390 -425
- package/dist/src/commands/code.js +279 -294
- package/dist/src/commands/index.js +5 -5
- package/dist/src/commands/models.js +16 -25
- package/dist/src/commands/users.js +9 -18
- package/dist/src/constants/command-structure.js +138 -138
- package/dist/src/services/api-key-service.js +132 -152
- package/dist/src/services/auth-service.js +81 -95
- package/dist/src/services/browser-auth.js +121 -131
- package/dist/src/services/chat-service.js +369 -386
- package/dist/src/services/cluster-service.js +47 -62
- package/dist/src/services/collaborator-service.js +9 -21
- package/dist/src/services/flux-service.js +13 -25
- package/dist/src/services/helm-service.js +9 -21
- package/dist/src/services/kubectl-service.js +15 -29
- package/dist/src/utils/config-checker.js +8 -8
- package/dist/src/utils/config-loader.js +109 -109
- package/dist/src/utils/default-api-key.js +129 -139
- package/dist/src/utils/env-manager.js +55 -66
- package/dist/src/utils/error-handler.js +62 -62
- package/dist/src/utils/logger.js +74 -67
- package/dist/src/utils/markdown-renderer.js +28 -28
- package/dist/src/utils/opencode-validator.js +67 -69
- package/dist/src/utils/token-manager.js +67 -65
- package/dist/tests/commands/chat.test.js +30 -39
- package/dist/tests/commands/code.test.js +186 -195
- package/dist/tests/utils/config-loader.test.js +107 -107
- package/dist/tests/utils/env-manager.test.js +81 -90
- package/dist/tests/utils/opencode-validator.test.js +42 -41
- package/dist/vitest.config.js +1 -1
- package/eslint.config.mjs +65 -30
- package/index.ts +30 -31
- package/package.json +7 -3
- package/src/agents/app.ts +9 -9
- package/src/agents/backend.ts +4 -4
- package/src/agents/devops.ts +9 -9
- package/src/agents/frontend.ts +4 -4
- package/src/agents/fullstack.ts +4 -4
- package/src/agents/index.ts +27 -25
- package/src/agents/quality.ts +9 -9
- package/src/agents/security.ts +9 -9
- package/src/agents/types.ts +10 -10
- package/src/client.ts +85 -77
- package/src/commands/api-keys.ts +180 -185
- package/src/commands/auth.ts +15 -14
- package/src/commands/autocomplete.ts +10 -10
- package/src/commands/billing.ts +13 -12
- package/src/commands/chat.ts +145 -142
- package/src/commands/clusters.ts +20 -19
- package/src/commands/code/__tests__/auth-sync.test.ts +176 -175
- package/src/commands/code/__tests__/fake-api-key-service.ts +2 -2
- package/src/commands/code/__tests__/fake-auth-service.ts +18 -18
- package/src/commands/code/__tests__/fake-command-runner.ts +28 -22
- package/src/commands/code/__tests__/fake-file-store.ts +15 -15
- package/src/commands/code/__tests__/fake-prompter.ts +86 -85
- package/src/commands/code/__tests__/setup-flow.test.ts +253 -251
- package/src/commands/code/adapters/clack-prompter.ts +32 -30
- package/src/commands/code/adapters/fs-file-store.ts +18 -17
- package/src/commands/code/adapters/spawn-command-runner.ts +20 -15
- package/src/commands/code/auth-sync.ts +210 -210
- package/src/commands/code/errors.ts +11 -11
- package/src/commands/code/ports/auth-services.ts +7 -7
- package/src/commands/code/ports/command-runner.ts +2 -2
- package/src/commands/code/ports/file-store.ts +3 -3
- package/src/commands/code/ports/prompter.ts +13 -13
- package/src/commands/code/setup.ts +408 -406
- package/src/commands/code.ts +288 -287
- package/src/commands/index.ts +11 -10
- package/src/commands/models.ts +19 -18
- package/src/commands/users.ts +11 -10
- package/src/constants/command-structure.ts +159 -159
- package/src/services/api-key-service.ts +85 -85
- package/src/services/auth-service.ts +55 -54
- package/src/services/browser-auth.ts +62 -62
- package/src/services/chat-service.ts +170 -171
- package/src/services/cluster-service.ts +28 -28
- package/src/services/collaborator-service.ts +6 -6
- package/src/services/flux-service.ts +17 -17
- package/src/services/helm-service.ts +11 -11
- package/src/services/kubectl-service.ts +12 -12
- package/src/types/api.d.ts +1933 -1933
- package/src/types/json.d.ts +1 -1
- package/src/utils/config-checker.ts +7 -7
- package/src/utils/config-loader.ts +130 -129
- package/src/utils/default-api-key.ts +81 -80
- package/src/utils/env-manager.ts +37 -37
- package/src/utils/error-handler.ts +64 -64
- package/src/utils/logger.ts +72 -66
- package/src/utils/markdown-renderer.ts +28 -28
- package/src/utils/opencode-validator.ts +72 -71
- package/src/utils/token-manager.ts +69 -68
- package/tests/commands/chat.test.ts +32 -31
- package/tests/commands/code.test.ts +182 -181
- package/tests/utils/config-loader.test.ts +111 -110
- package/tests/utils/env-manager.test.ts +83 -79
- package/tests/utils/opencode-validator.test.ts +43 -42
- package/tsconfig.json +2 -1
- package/vitest.config.ts +2 -2
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { createAuthenticatedClient } from
|
|
2
|
-
import { logger } from
|
|
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
|
-
|
|
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
|
-
|
|
17
|
-
|
|
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 =
|
|
30
|
-
|
|
26
|
+
public static readonly COMMAND_GROUP = 'chat';
|
|
31
27
|
// Subcommands for this service
|
|
32
28
|
public static readonly COMMANDS = {
|
|
33
|
-
|
|
34
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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 ?
|
|
70
|
+
apiKey: optionsCopy.apiKey ? '***' : undefined,
|
|
71
71
|
messages: optionsCopy.messages
|
|
72
72
|
? `${optionsCopy.messages.length} messages`
|
|
73
|
-
:
|
|
73
|
+
: '0 messages',
|
|
74
74
|
},
|
|
75
75
|
null,
|
|
76
|
-
2
|
|
77
|
-
)
|
|
76
|
+
2,
|
|
77
|
+
),
|
|
78
78
|
);
|
|
79
79
|
} catch (error) {
|
|
80
|
-
logger.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(
|
|
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
|
|
93
|
-
const hasExplicitApiKey = !!optionsCopy.apiKey || !!
|
|
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(
|
|
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
|
-
const { apiKey:
|
|
99
|
+
const { 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
|
|
105
|
-
if (
|
|
106
|
-
logger.debug(
|
|
107
|
-
optionsCopy.apiKey =
|
|
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(
|
|
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(
|
|
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(
|
|
123
|
+
logger.debug('Importing DefaultApiKeyManager');
|
|
124
124
|
|
|
125
|
-
const DefaultApiKeyManager =
|
|
126
|
-
.DefaultApiKeyManager;
|
|
125
|
+
const { DefaultApiKeyManager } = await import('../utils/default-api-key');
|
|
127
126
|
const defaultApiKeyManager = DefaultApiKeyManager.getInstance();
|
|
128
127
|
|
|
129
|
-
logger.debug(
|
|
128
|
+
logger.debug('Got DefaultApiKeyManager instance');
|
|
130
129
|
|
|
131
130
|
// Try to get the default API key
|
|
132
|
-
logger.debug(
|
|
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 ?
|
|
138
|
+
logger.debug(`promptForDefaultApiKey returned: ${apiKey ? 'a key' : 'null'}`);
|
|
140
139
|
|
|
141
140
|
if (apiKey) {
|
|
142
|
-
logger.debug(
|
|
141
|
+
logger.debug('Using API key from default API key manager');
|
|
143
142
|
optionsCopy.apiKey = apiKey;
|
|
144
143
|
} else {
|
|
145
|
-
logger.warn(
|
|
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(
|
|
148
|
-
logger.warn(
|
|
149
|
-
logger.warn(
|
|
150
|
-
logger.warn(
|
|
151
|
-
logger.warn(
|
|
152
|
-
logger.warn(
|
|
153
|
-
logger.warn(
|
|
154
|
-
throw new Error(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
177
|
-
optionsCopy.model =
|
|
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(
|
|
179
|
+
logger.debug('Chat completion options:');
|
|
181
180
|
logger.debug(
|
|
182
181
|
JSON.stringify(
|
|
183
182
|
{
|
|
184
183
|
...optionsCopy,
|
|
185
|
-
apiKey: optionsCopy.apiKey ?
|
|
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 =
|
|
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[
|
|
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: _, onChunk, ...requestOptions } = options;
|
|
234
282
|
|
|
235
|
-
logger.debug(
|
|
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
|
-
:
|
|
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 ||
|
|
304
|
+
model: requestOptions.model || 'google/gemma-3-27b-it',
|
|
257
305
|
};
|
|
258
306
|
|
|
259
307
|
// Debug the headers being sent
|
|
260
|
-
logger.debug(
|
|
308
|
+
logger.debug('Headers being sent:');
|
|
261
309
|
logger.debug(JSON.stringify(headers, null, 2));
|
|
262
310
|
|
|
263
|
-
const response = await this.client.POST(
|
|
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(
|
|
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(
|
|
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 ||
|
|
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
|
-
|
|
363
|
+
body: JSON.stringify(options),
|
|
316
364
|
headers: {
|
|
317
|
-
|
|
318
|
-
|
|
365
|
+
Accept: 'text/event-stream',
|
|
366
|
+
'Content-Type': 'application/json',
|
|
319
367
|
...headers,
|
|
320
368
|
},
|
|
321
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
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 (
|
|
369
|
-
|
|
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(
|
|
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 ===
|
|
424
|
+
if (jsonData === '' || jsonData === '[DONE]') {
|
|
378
425
|
logger.debug(`Skipping empty data or [DONE] marker`);
|
|
379
|
-
processedLines =
|
|
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.
|
|
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 (
|
|
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.
|
|
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 =
|
|
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
|
-
|
|
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 (
|
|
451
|
-
logger.error(`Error parsing chunk: ${
|
|
494
|
+
} catch (error) {
|
|
495
|
+
logger.error(`Error parsing chunk: ${error}`);
|
|
452
496
|
logger.error(
|
|
453
|
-
`JSON parse error at position ${(
|
|
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.
|
|
458
|
-
logger.error(
|
|
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(
|
|
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: ${
|
|
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(
|
|
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: {
|
|
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
|
}
|