berget 2.0.6 → 2.1.1
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/AGENTS.md +163 -2
- package/dist/package.json +1 -1
- package/dist/src/commands/chat.js +1 -2
- package/dist/src/commands/code.js +152 -87
- package/dist/src/services/auth-service.js +1 -2
- package/dist/src/services/chat-service.js +77 -5
- package/dist/src/utils/config-loader.js +83 -27
- package/dist/tests/commands/chat.test.js +2 -2
- package/dist/tests/commands/code.test.js +4 -4
- package/dist/tests/utils/config-loader.test.js +248 -163
- package/opencode.json +31 -71
- package/package.json +1 -1
- package/src/commands/chat.ts +68 -61
- package/src/commands/code.ts +304 -213
- package/src/services/auth-service.ts +1 -4
- package/src/services/chat-service.ts +86 -5
- package/src/utils/config-loader.ts +113 -38
- package/tests/commands/chat.test.ts +2 -2
- package/tests/commands/code.test.ts +4 -4
- package/tests/utils/config-loader.test.ts +320 -0
- package/blog-post.md +0 -176
|
@@ -269,6 +269,7 @@ class ChatService {
|
|
|
269
269
|
* @returns A promise that resolves when the stream is complete
|
|
270
270
|
*/
|
|
271
271
|
handleStreamingResponse(options, headers) {
|
|
272
|
+
var _a, _b, _c, _d;
|
|
272
273
|
return __awaiter(this, void 0, void 0, function* () {
|
|
273
274
|
// Use the same base URL as the client
|
|
274
275
|
const baseUrl = process.env.API_BASE_URL || 'https://api.berget.ai';
|
|
@@ -299,22 +300,74 @@ class ChatService {
|
|
|
299
300
|
const decoder = new TextDecoder();
|
|
300
301
|
let fullContent = '';
|
|
301
302
|
let fullResponse = null;
|
|
303
|
+
let buffer = ''; // Buffer to accumulate partial JSON data
|
|
302
304
|
while (true) {
|
|
303
305
|
const { done, value } = yield reader.read();
|
|
304
306
|
if (done)
|
|
305
307
|
break;
|
|
306
308
|
const chunk = decoder.decode(value, { stream: true });
|
|
307
309
|
logger_1.logger.debug(`Received chunk: ${chunk.length} bytes`);
|
|
308
|
-
//
|
|
309
|
-
|
|
310
|
-
|
|
310
|
+
// Add chunk to buffer
|
|
311
|
+
buffer += chunk;
|
|
312
|
+
logger_1.logger.debug(`Added chunk to buffer. Buffer length: ${buffer.length}`);
|
|
313
|
+
// Process the buffer - it may contain multiple SSE events
|
|
314
|
+
const lines = buffer.split('\n');
|
|
315
|
+
logger_1.logger.debug(`Processing ${lines.length} lines from buffer`);
|
|
316
|
+
// Keep track of processed lines to update buffer
|
|
317
|
+
let processedLines = 0;
|
|
318
|
+
for (let i = 0; i < lines.length; i++) {
|
|
319
|
+
const line = lines[i];
|
|
320
|
+
logger_1.logger.debug(`Line ${i}: "${line}"`);
|
|
311
321
|
if (line.startsWith('data:')) {
|
|
312
322
|
const jsonData = line.slice(5).trim();
|
|
323
|
+
logger_1.logger.debug(`Extracted JSON data: "${jsonData}"`);
|
|
313
324
|
// Skip empty data or [DONE] marker
|
|
314
|
-
if (jsonData === '' || jsonData === '[DONE]')
|
|
325
|
+
if (jsonData === '' || jsonData === '[DONE]') {
|
|
326
|
+
logger_1.logger.debug(`Skipping empty data or [DONE] marker`);
|
|
327
|
+
processedLines = i + 1;
|
|
315
328
|
continue;
|
|
329
|
+
}
|
|
330
|
+
// Check if JSON looks complete (basic validation)
|
|
331
|
+
if (!jsonData.startsWith('{')) {
|
|
332
|
+
logger_1.logger.warn(`JSON data doesn't start with '{', might be partial: "${jsonData.substring(0, 50)}..."`);
|
|
333
|
+
// Don't process this line yet, keep it in buffer
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
// Count braces to check if JSON is complete
|
|
337
|
+
let braceCount = 0;
|
|
338
|
+
let inString = false;
|
|
339
|
+
let escaped = false;
|
|
340
|
+
for (let j = 0; j < jsonData.length; j++) {
|
|
341
|
+
const char = jsonData[j];
|
|
342
|
+
if (escaped) {
|
|
343
|
+
escaped = false;
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (char === '\\') {
|
|
347
|
+
escaped = true;
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (char === '"') {
|
|
351
|
+
inString = !inString;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
if (!inString && char === '{') {
|
|
355
|
+
braceCount++;
|
|
356
|
+
}
|
|
357
|
+
else if (!inString && char === '}') {
|
|
358
|
+
braceCount--;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (braceCount !== 0) {
|
|
362
|
+
logger_1.logger.warn(`JSON braces don't balance (${braceCount}), treating as partial: "${jsonData.substring(0, 50)}..."`);
|
|
363
|
+
// Don't process this line yet, keep it in buffer
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
316
366
|
try {
|
|
367
|
+
logger_1.logger.debug(`Attempting to parse JSON of length: ${jsonData.length}`);
|
|
317
368
|
const parsedData = JSON.parse(jsonData);
|
|
369
|
+
logger_1.logger.debug(`Successfully parsed JSON: ${JSON.stringify(parsedData, null, 2)}`);
|
|
370
|
+
processedLines = i + 1; // Mark this line as processed
|
|
318
371
|
// Call the onChunk callback with the parsed data
|
|
319
372
|
if (options.onChunk) {
|
|
320
373
|
options.onChunk(parsedData);
|
|
@@ -334,10 +387,29 @@ class ChatService {
|
|
|
334
387
|
}
|
|
335
388
|
catch (e) {
|
|
336
389
|
logger_1.logger.error(`Error parsing chunk: ${e}`);
|
|
337
|
-
logger_1.logger.
|
|
390
|
+
logger_1.logger.error(`JSON parse error at position ${((_b = (_a = e.message) === null || _a === void 0 ? void 0 : _a.match(/position (\d+)/)) === null || _b === void 0 ? void 0 : _b[1]) || 'unknown'}`);
|
|
391
|
+
logger_1.logger.error(`Problematic chunk length: ${jsonData.length}`);
|
|
392
|
+
logger_1.logger.error(`Problematic chunk content: "${jsonData}"`);
|
|
393
|
+
logger_1.logger.error(`Chunk starts with: "${jsonData.substring(0, 50)}..."`);
|
|
394
|
+
logger_1.logger.error(`Chunk ends with: "...${jsonData.substring(jsonData.length - 50)}"`);
|
|
395
|
+
// Show character codes around the error position
|
|
396
|
+
const errorPos = parseInt(((_d = (_c = e.message) === null || _c === void 0 ? void 0 : _c.match(/position (\d+)/)) === null || _d === void 0 ? void 0 : _d[1]) || '0');
|
|
397
|
+
if (errorPos > 0) {
|
|
398
|
+
const start = Math.max(0, errorPos - 20);
|
|
399
|
+
const end = Math.min(jsonData.length, errorPos + 20);
|
|
400
|
+
logger_1.logger.error(`Context around error position ${errorPos}:`);
|
|
401
|
+
logger_1.logger.error(`"${jsonData.substring(start, end)}"`);
|
|
402
|
+
logger_1.logger.error(`Character codes: ${Array.from(jsonData.substring(start, end)).map(c => c.charCodeAt(0)).join(' ')}`);
|
|
403
|
+
}
|
|
338
404
|
}
|
|
339
405
|
}
|
|
340
406
|
}
|
|
407
|
+
// Update buffer to only contain unprocessed lines
|
|
408
|
+
if (processedLines > 0) {
|
|
409
|
+
const remainingLines = lines.slice(processedLines);
|
|
410
|
+
buffer = remainingLines.join('\n');
|
|
411
|
+
logger_1.logger.debug(`Updated buffer. Remaining lines: ${remainingLines.length}, Buffer length: ${buffer.length}`);
|
|
412
|
+
}
|
|
341
413
|
}
|
|
342
414
|
// Construct the final response object similar to non-streaming response
|
|
343
415
|
if (fullResponse) {
|
|
@@ -39,6 +39,12 @@ class ConfigLoader {
|
|
|
39
39
|
}
|
|
40
40
|
return ConfigLoader.instance;
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Clear the singleton instance (for testing purposes)
|
|
44
|
+
*/
|
|
45
|
+
static clearInstance() {
|
|
46
|
+
ConfigLoader.instance = null;
|
|
47
|
+
}
|
|
42
48
|
/**
|
|
43
49
|
* Load configuration from opencode.json
|
|
44
50
|
*/
|
|
@@ -65,72 +71,122 @@ class ConfigLoader {
|
|
|
65
71
|
*/
|
|
66
72
|
getAgentConfig(agentName) {
|
|
67
73
|
var _a;
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
try {
|
|
75
|
+
const config = this.loadConfig();
|
|
76
|
+
return ((_a = config.agent) === null || _a === void 0 ? void 0 : _a[agentName]) || null;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
// Config file doesn't exist, return null
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
70
82
|
}
|
|
71
83
|
/**
|
|
72
84
|
* Get all agent configurations
|
|
73
85
|
*/
|
|
74
86
|
getAllAgentConfigs() {
|
|
75
|
-
|
|
76
|
-
|
|
87
|
+
try {
|
|
88
|
+
const config = this.loadConfig();
|
|
89
|
+
return config.agent || {};
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
// Config file doesn't exist, return empty object
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
77
95
|
}
|
|
78
96
|
/**
|
|
79
97
|
* Get model configuration
|
|
80
98
|
*/
|
|
81
99
|
getModelConfig() {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
100
|
+
try {
|
|
101
|
+
const config = this.loadConfig();
|
|
102
|
+
// Extract from config or fall back to defaults
|
|
103
|
+
const primary = config.model || 'berget/glm-4.7';
|
|
104
|
+
const small = config.small_model || 'berget/gpt-oss';
|
|
105
|
+
return { primary, small };
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
// Fallback to defaults when no config exists (init scenario)
|
|
109
|
+
return {
|
|
110
|
+
primary: 'berget/glm-4.7',
|
|
111
|
+
small: 'berget/gpt-oss',
|
|
112
|
+
};
|
|
113
|
+
}
|
|
87
114
|
}
|
|
88
115
|
/**
|
|
89
116
|
* Get provider model configuration
|
|
90
117
|
*/
|
|
91
118
|
getProviderModels() {
|
|
92
119
|
var _a, _b;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
120
|
+
try {
|
|
121
|
+
const config = this.loadConfig();
|
|
122
|
+
// Extract from provider configuration
|
|
123
|
+
if ((_b = (_a = config.provider) === null || _a === void 0 ? void 0 : _a.berget) === null || _b === void 0 ? void 0 : _b.models) {
|
|
124
|
+
return config.provider.berget.models;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
// Config file doesn't exist, use fallback defaults
|
|
97
129
|
}
|
|
98
130
|
// Fallback to defaults
|
|
99
131
|
return {
|
|
100
|
-
'
|
|
101
|
-
name: 'GLM-4.
|
|
102
|
-
limit: { output: 4000, context: 90000 }
|
|
132
|
+
'glm-4.7': {
|
|
133
|
+
name: 'GLM-4.7',
|
|
134
|
+
limit: { output: 4000, context: 90000 },
|
|
103
135
|
},
|
|
104
136
|
'gpt-oss': {
|
|
105
137
|
name: 'GPT-OSS',
|
|
106
|
-
limit: { output: 4000, context: 128000 }
|
|
138
|
+
limit: { output: 4000, context: 128000 },
|
|
139
|
+
modalities: {
|
|
140
|
+
input: ['text', 'image'],
|
|
141
|
+
output: ['text'],
|
|
142
|
+
},
|
|
107
143
|
},
|
|
108
144
|
'llama-8b': {
|
|
109
145
|
name: 'llama-3.1-8b',
|
|
110
|
-
limit: { output: 4000, context: 128000 }
|
|
111
|
-
}
|
|
146
|
+
limit: { output: 4000, context: 128000 },
|
|
147
|
+
},
|
|
112
148
|
};
|
|
113
149
|
}
|
|
114
150
|
/**
|
|
115
151
|
* Get command configurations
|
|
116
152
|
*/
|
|
117
153
|
getCommandConfigs() {
|
|
118
|
-
|
|
119
|
-
|
|
154
|
+
try {
|
|
155
|
+
const config = this.loadConfig();
|
|
156
|
+
return config.command || {};
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
// Config file doesn't exist, return empty object
|
|
160
|
+
return {};
|
|
161
|
+
}
|
|
120
162
|
}
|
|
121
163
|
/**
|
|
122
164
|
* Get watcher configuration
|
|
123
165
|
*/
|
|
124
166
|
getWatcherConfig() {
|
|
125
|
-
|
|
126
|
-
|
|
167
|
+
try {
|
|
168
|
+
const config = this.loadConfig();
|
|
169
|
+
return (config.watcher || {
|
|
170
|
+
ignore: ['node_modules', 'dist', '.git', 'coverage'],
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
// Config file doesn't exist, return default watcher config
|
|
175
|
+
return { ignore: ['node_modules', 'dist', '.git', 'coverage'] };
|
|
176
|
+
}
|
|
127
177
|
}
|
|
128
178
|
/**
|
|
129
179
|
* Get provider configuration
|
|
130
180
|
*/
|
|
131
181
|
getProviderConfig() {
|
|
132
|
-
|
|
133
|
-
|
|
182
|
+
try {
|
|
183
|
+
const config = this.loadConfig();
|
|
184
|
+
return config.provider || {};
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
// Config file doesn't exist, return empty object
|
|
188
|
+
return {};
|
|
189
|
+
}
|
|
134
190
|
}
|
|
135
191
|
/**
|
|
136
192
|
* Check if an agent exists
|
|
@@ -149,14 +205,14 @@ class ConfigLoader {
|
|
|
149
205
|
*/
|
|
150
206
|
getPrimaryAgentNames() {
|
|
151
207
|
const agents = this.getAllAgentConfigs();
|
|
152
|
-
return Object.keys(agents).filter(name => agents[name].mode === 'primary');
|
|
208
|
+
return Object.keys(agents).filter((name) => agents[name].mode === 'primary');
|
|
153
209
|
}
|
|
154
210
|
/**
|
|
155
211
|
* Get list of subagents (mode: 'subagent')
|
|
156
212
|
*/
|
|
157
213
|
getSubagentNames() {
|
|
158
214
|
const agents = this.getAllAgentConfigs();
|
|
159
|
-
return Object.keys(agents).filter(name => agents[name].mode === 'subagent');
|
|
215
|
+
return Object.keys(agents).filter((name) => agents[name].mode === 'subagent');
|
|
160
216
|
}
|
|
161
217
|
/**
|
|
162
218
|
* Reload configuration from file
|
|
@@ -47,13 +47,13 @@ vitest_1.vi.mock('readline', () => ({
|
|
|
47
47
|
vitest_1.vi.clearAllMocks();
|
|
48
48
|
});
|
|
49
49
|
(0, vitest_1.describe)('chat run command', () => {
|
|
50
|
-
(0, vitest_1.it)('should use
|
|
50
|
+
(0, vitest_1.it)('should use berget/glm-4.7 as default model', () => {
|
|
51
51
|
const chatCommand = program.commands.find((cmd) => cmd.name() === 'chat');
|
|
52
52
|
const runCommand = chatCommand === null || chatCommand === void 0 ? void 0 : chatCommand.commands.find((cmd) => cmd.name() === 'run');
|
|
53
53
|
(0, vitest_1.expect)(runCommand).toBeDefined();
|
|
54
54
|
// Check the help text which contains the default model
|
|
55
55
|
const helpText = runCommand === null || runCommand === void 0 ? void 0 : runCommand.helpInformation();
|
|
56
|
-
(0, vitest_1.expect)(helpText).toContain('
|
|
56
|
+
(0, vitest_1.expect)(helpText).toContain('glm-4.7');
|
|
57
57
|
});
|
|
58
58
|
(0, vitest_1.it)('should have streaming enabled by default', () => {
|
|
59
59
|
const chatCommand = program.commands.find((cmd) => cmd.name() === 'chat');
|
|
@@ -188,14 +188,14 @@ vitest_1.vi.mock('readline', () => ({
|
|
|
188
188
|
(0, vitest_1.it)('should create opencode.json with correct structure', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
189
189
|
// This tests the expected config structure
|
|
190
190
|
const expectedConfig = {
|
|
191
|
-
model: 'berget/
|
|
191
|
+
model: 'berget/glm-4-6',
|
|
192
192
|
apiKey: 'test-api-key',
|
|
193
193
|
projectName: 'testproject',
|
|
194
194
|
provider: 'berget',
|
|
195
195
|
created: vitest_1.expect.any(String),
|
|
196
196
|
version: '1.0.0',
|
|
197
197
|
};
|
|
198
|
-
(0, vitest_1.expect)(expectedConfig.model).toBe('berget/
|
|
198
|
+
(0, vitest_1.expect)(expectedConfig.model).toBe('berget/glm-4-6');
|
|
199
199
|
(0, vitest_1.expect)(expectedConfig.provider).toBe('berget');
|
|
200
200
|
(0, vitest_1.expect)(expectedConfig.version).toBe('1.0.0');
|
|
201
201
|
}));
|
|
@@ -230,7 +230,7 @@ vitest_1.vi.mock('readline', () => ({
|
|
|
230
230
|
});
|
|
231
231
|
(0, vitest_1.it)('should load configuration from opencode.json', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
232
232
|
const mockConfig = {
|
|
233
|
-
model: 'berget/
|
|
233
|
+
model: 'berget/glm-4-6',
|
|
234
234
|
apiKey: 'test-api-key',
|
|
235
235
|
projectName: 'testproject',
|
|
236
236
|
provider: 'berget',
|
|
@@ -253,7 +253,7 @@ vitest_1.vi.mock('readline', () => ({
|
|
|
253
253
|
return { on: vitest_1.vi.fn() };
|
|
254
254
|
});
|
|
255
255
|
// Verify config structure expectations
|
|
256
|
-
(0, vitest_1.expect)(mockConfig.model).toBe('berget/
|
|
256
|
+
(0, vitest_1.expect)(mockConfig.model).toBe('berget/glm-4-6');
|
|
257
257
|
(0, vitest_1.expect)(mockConfig.apiKey).toBe('test-api-key');
|
|
258
258
|
(0, vitest_1.expect)(mockConfig.projectName).toBe('testproject');
|
|
259
259
|
}));
|