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