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.
@@ -36,13 +36,10 @@ export class AuthService {
36
36
  try {
37
37
  const { data: profile, error } = await this.client.GET('/v1/users/me')
38
38
  if (error) {
39
- throw new Error(
40
- error ? JSON.stringify(error) : 'Failed to get user profile',
41
- )
39
+ return null
42
40
  }
43
41
  return profile
44
42
  } catch (error) {
45
- handleError('Failed to get user profile', error)
46
43
  return null
47
44
  }
48
45
  }
@@ -386,6 +386,7 @@ export class ChatService {
386
386
  const decoder = new TextDecoder()
387
387
  let fullContent = ''
388
388
  let fullResponse: any = null
389
+ let buffer = '' // Buffer to accumulate partial JSON data
389
390
 
390
391
  while (true) {
391
392
  const { done, value } = await reader.read()
@@ -394,17 +395,76 @@ export class ChatService {
394
395
  const chunk = decoder.decode(value, { stream: true })
395
396
  logger.debug(`Received chunk: ${chunk.length} bytes`)
396
397
 
397
- // Process the chunk - it may contain multiple SSE events
398
- const lines = chunk.split('\n')
399
- for (const line of lines) {
398
+ // Add chunk to buffer
399
+ buffer += chunk
400
+ logger.debug(`Added chunk to buffer. Buffer length: ${buffer.length}`)
401
+
402
+ // 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
+
406
+ // Keep track of processed lines to update buffer
407
+ let processedLines = 0
408
+
409
+ for (let i = 0; i < lines.length; i++) {
410
+ const line = lines[i]
411
+ logger.debug(`Line ${i}: "${line}"`)
412
+
400
413
  if (line.startsWith('data:')) {
401
414
  const jsonData = line.slice(5).trim()
415
+ logger.debug(`Extracted JSON data: "${jsonData}"`)
402
416
 
403
417
  // Skip empty data or [DONE] marker
404
- if (jsonData === '' || jsonData === '[DONE]') continue
418
+ if (jsonData === '' || jsonData === '[DONE]') {
419
+ logger.debug(`Skipping empty data or [DONE] marker`)
420
+ processedLines = i + 1
421
+ continue
422
+ }
423
+
424
+ // Check if JSON looks complete (basic validation)
425
+ if (!jsonData.startsWith('{')) {
426
+ logger.warn(`JSON data doesn't start with '{', might be partial: "${jsonData.substring(0, 50)}..."`)
427
+ // Don't process this line yet, keep it in buffer
428
+ break
429
+ }
430
+
431
+ // Count braces to check if JSON is complete
432
+ let braceCount = 0
433
+ let inString = false
434
+ let escaped = false
435
+
436
+ for (let j = 0; j < jsonData.length; j++) {
437
+ const char = jsonData[j]
438
+ if (escaped) {
439
+ escaped = false
440
+ continue
441
+ }
442
+ if (char === '\\') {
443
+ escaped = true
444
+ continue
445
+ }
446
+ if (char === '"') {
447
+ inString = !inString
448
+ continue
449
+ }
450
+ if (!inString && char === '{') {
451
+ braceCount++
452
+ } else if (!inString && char === '}') {
453
+ braceCount--
454
+ }
455
+ }
456
+
457
+ if (braceCount !== 0) {
458
+ logger.warn(`JSON braces don't balance (${braceCount}), treating as partial: "${jsonData.substring(0, 50)}..."`)
459
+ // Don't process this line yet, keep it in buffer
460
+ break
461
+ }
405
462
 
406
463
  try {
464
+ logger.debug(`Attempting to parse JSON of length: ${jsonData.length}`)
407
465
  const parsedData = JSON.parse(jsonData)
466
+ logger.debug(`Successfully parsed JSON: ${JSON.stringify(parsedData, null, 2)}`)
467
+ processedLines = i + 1 // Mark this line as processed
408
468
 
409
469
  // Call the onChunk callback with the parsed data
410
470
  if (options.onChunk) {
@@ -426,10 +486,31 @@ export class ChatService {
426
486
  }
427
487
  } catch (e) {
428
488
  logger.error(`Error parsing chunk: ${e}`)
429
- logger.debug(`Problematic chunk: ${jsonData}`)
489
+ logger.error(`JSON parse error at position ${(e as any).message?.match(/position (\d+)/)?.[1] || 'unknown'}`)
490
+ logger.error(`Problematic chunk length: ${jsonData.length}`)
491
+ logger.error(`Problematic chunk content: "${jsonData}"`)
492
+ logger.error(`Chunk starts with: "${jsonData.substring(0, 50)}..."`)
493
+ logger.error(`Chunk ends with: "...${jsonData.substring(jsonData.length - 50)}"`)
494
+
495
+ // Show character codes around the error position
496
+ const errorPos = parseInt((e as any).message?.match(/position (\d+)/)?.[1] || '0')
497
+ 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(`Character codes: ${Array.from(jsonData.substring(start, end)).map(c => c.charCodeAt(0)).join(' ')}`)
503
+ }
430
504
  }
431
505
  }
432
506
  }
507
+
508
+ // Update buffer to only contain unprocessed lines
509
+ if (processedLines > 0) {
510
+ const remainingLines = lines.slice(processedLines)
511
+ buffer = remainingLines.join('\n')
512
+ logger.debug(`Updated buffer. Remaining lines: ${remainingLines.length}, Buffer length: ${buffer.length}`)
513
+ }
433
514
  }
434
515
 
435
516
  // Construct the final response object similar to non-streaming response
@@ -33,6 +33,10 @@ export interface ProviderModelConfig {
33
33
  output: number
34
34
  context: number
35
35
  }
36
+ modalities?: {
37
+ input: string[]
38
+ output: string[]
39
+ }
36
40
  }
37
41
 
38
42
  export interface OpenCodeConfig {
@@ -66,6 +70,13 @@ export class ConfigLoader {
66
70
  return ConfigLoader.instance
67
71
  }
68
72
 
73
+ /**
74
+ * Clear the singleton instance (for testing purposes)
75
+ */
76
+ public static clearInstance(): void {
77
+ ConfigLoader.instance = null as any
78
+ }
79
+
69
80
  /**
70
81
  * Load configuration from opencode.json
71
82
  */
@@ -81,12 +92,19 @@ export class ConfigLoader {
81
92
 
82
93
  const configContent = fs.readFileSync(this.configPath, 'utf8')
83
94
  this.config = JSON.parse(configContent) as OpenCodeConfig
84
-
95
+
85
96
  logger.debug(`Loaded configuration from ${this.configPath}`)
86
97
  return this.config
87
98
  } catch (error) {
88
- logger.error(`Failed to load configuration from ${this.configPath}:`, error)
89
- throw new Error(`Failed to load configuration: ${error instanceof Error ? error.message : String(error)}`)
99
+ logger.error(
100
+ `Failed to load configuration from ${this.configPath}:`,
101
+ error
102
+ )
103
+ throw new Error(
104
+ `Failed to load configuration: ${
105
+ error instanceof Error ? error.message : String(error)
106
+ }`
107
+ )
90
108
  }
91
109
  }
92
110
 
@@ -94,56 +112,85 @@ export class ConfigLoader {
94
112
  * Get agent configuration by name
95
113
  */
96
114
  public getAgentConfig(agentName: string): AgentConfig | null {
97
- const config = this.loadConfig()
98
- return config.agent?.[agentName] || null
115
+ try {
116
+ const config = this.loadConfig()
117
+ return config.agent?.[agentName] || null
118
+ } catch (error) {
119
+ // Config file doesn't exist, return null
120
+ return null
121
+ }
99
122
  }
100
123
 
101
124
  /**
102
125
  * Get all agent configurations
103
126
  */
104
127
  public getAllAgentConfigs(): Record<string, AgentConfig> {
105
- const config = this.loadConfig()
106
- return config.agent || {}
128
+ try {
129
+ const config = this.loadConfig()
130
+ return config.agent || {}
131
+ } catch (error) {
132
+ // Config file doesn't exist, return empty object
133
+ return {}
134
+ }
107
135
  }
108
136
 
109
137
  /**
110
138
  * Get model configuration
111
139
  */
112
140
  public getModelConfig(): ModelConfig {
113
- const config = this.loadConfig()
114
-
115
- // Extract from config or fall back to defaults
116
- const primary = config.model || 'berget/deepseek-r1'
117
- const small = config.small_model || 'berget/gpt-oss'
118
-
119
- return { primary, small }
141
+ try {
142
+ const config = this.loadConfig()
143
+
144
+ // Extract from config or fall back to defaults
145
+ const primary = config.model || 'berget/glm-4.7'
146
+ const small = config.small_model || 'berget/gpt-oss'
147
+
148
+ return { primary, small }
149
+ } catch (error) {
150
+ // Fallback to defaults when no config exists (init scenario)
151
+ return {
152
+ primary: 'berget/glm-4.7',
153
+ small: 'berget/gpt-oss',
154
+ }
155
+ }
120
156
  }
121
157
 
122
158
  /**
123
159
  * Get provider model configuration
124
160
  */
125
161
  public getProviderModels(): Record<string, ProviderModelConfig> {
126
- const config = this.loadConfig()
127
-
128
- // Extract from provider configuration
129
- if (config.provider?.berget?.models) {
130
- return config.provider.berget.models as Record<string, ProviderModelConfig>
162
+ try {
163
+ const config = this.loadConfig()
164
+
165
+ // Extract from provider configuration
166
+ if (config.provider?.berget?.models) {
167
+ return config.provider.berget.models as Record<
168
+ string,
169
+ ProviderModelConfig
170
+ >
171
+ }
172
+ } catch (error) {
173
+ // Config file doesn't exist, use fallback defaults
131
174
  }
132
-
175
+
133
176
  // Fallback to defaults
134
177
  return {
135
- 'deepseek-r1': {
136
- name: 'GLM-4.6',
137
- limit: { output: 4000, context: 90000 }
178
+ 'glm-4.7': {
179
+ name: 'GLM-4.7',
180
+ limit: { output: 4000, context: 90000 },
138
181
  },
139
182
  'gpt-oss': {
140
183
  name: 'GPT-OSS',
141
- limit: { output: 4000, context: 128000 }
184
+ limit: { output: 4000, context: 128000 },
185
+ modalities: {
186
+ input: ['text', 'image'],
187
+ output: ['text'],
188
+ },
142
189
  },
143
190
  'llama-8b': {
144
191
  name: 'llama-3.1-8b',
145
- limit: { output: 4000, context: 128000 }
146
- }
192
+ limit: { output: 4000, context: 128000 },
193
+ },
147
194
  }
148
195
  }
149
196
 
@@ -151,24 +198,43 @@ export class ConfigLoader {
151
198
  * Get command configurations
152
199
  */
153
200
  public getCommandConfigs(): Record<string, any> {
154
- const config = this.loadConfig()
155
- return config.command || {}
201
+ try {
202
+ const config = this.loadConfig()
203
+ return config.command || {}
204
+ } catch (error) {
205
+ // Config file doesn't exist, return empty object
206
+ return {}
207
+ }
156
208
  }
157
209
 
158
210
  /**
159
211
  * Get watcher configuration
160
212
  */
161
213
  public getWatcherConfig(): Record<string, any> {
162
- const config = this.loadConfig()
163
- return config.watcher || { ignore: ['node_modules', 'dist', '.git', 'coverage'] }
214
+ try {
215
+ const config = this.loadConfig()
216
+ return (
217
+ config.watcher || {
218
+ ignore: ['node_modules', 'dist', '.git', 'coverage'],
219
+ }
220
+ )
221
+ } catch (error) {
222
+ // Config file doesn't exist, return default watcher config
223
+ return { ignore: ['node_modules', 'dist', '.git', 'coverage'] }
224
+ }
164
225
  }
165
226
 
166
227
  /**
167
228
  * Get provider configuration
168
229
  */
169
230
  public getProviderConfig(): Record<string, any> {
170
- const config = this.loadConfig()
171
- return config.provider || {}
231
+ try {
232
+ const config = this.loadConfig()
233
+ return config.provider || {}
234
+ } catch (error) {
235
+ // Config file doesn't exist, return empty object
236
+ return {}
237
+ }
172
238
  }
173
239
 
174
240
  /**
@@ -190,7 +256,7 @@ export class ConfigLoader {
190
256
  */
191
257
  public getPrimaryAgentNames(): string[] {
192
258
  const agents = this.getAllAgentConfigs()
193
- return Object.keys(agents).filter(name => agents[name].mode === 'primary')
259
+ return Object.keys(agents).filter((name) => agents[name].mode === 'primary')
194
260
  }
195
261
 
196
262
  /**
@@ -198,7 +264,9 @@ export class ConfigLoader {
198
264
  */
199
265
  public getSubagentNames(): string[] {
200
266
  const agents = this.getAllAgentConfigs()
201
- return Object.keys(agents).filter(name => agents[name].mode === 'subagent')
267
+ return Object.keys(agents).filter(
268
+ (name) => agents[name].mode === 'subagent'
269
+ )
202
270
  }
203
271
 
204
272
  /**
@@ -235,14 +303,19 @@ export function getConfigLoader(configPath?: string): ConfigLoader {
235
303
  /**
236
304
  * Convenience function to get agent configuration
237
305
  */
238
- export function getAgentConfig(agentName: string, configPath?: string): AgentConfig | null {
306
+ export function getAgentConfig(
307
+ agentName: string,
308
+ configPath?: string
309
+ ): AgentConfig | null {
239
310
  return getConfigLoader(configPath).getAgentConfig(agentName)
240
311
  }
241
312
 
242
313
  /**
243
314
  * Convenience function to get all agent configurations
244
315
  */
245
- export function getAllAgentConfigs(configPath?: string): Record<string, AgentConfig> {
316
+ export function getAllAgentConfigs(
317
+ configPath?: string
318
+ ): Record<string, AgentConfig> {
246
319
  return getConfigLoader(configPath).getAllAgentConfigs()
247
320
  }
248
321
 
@@ -256,6 +329,8 @@ export function getModelConfig(configPath?: string): ModelConfig {
256
329
  /**
257
330
  * Convenience function to get provider models
258
331
  */
259
- export function getProviderModels(configPath?: string): Record<string, ProviderModelConfig> {
332
+ export function getProviderModels(
333
+ configPath?: string
334
+ ): Record<string, ProviderModelConfig> {
260
335
  return getConfigLoader(configPath).getProviderModels()
261
- }
336
+ }
@@ -46,7 +46,7 @@ describe('Chat Commands', () => {
46
46
  })
47
47
 
48
48
  describe('chat run command', () => {
49
- it('should use openai/gpt-oss as default model', () => {
49
+ it('should use berget/glm-4.7 as default model', () => {
50
50
  const chatCommand = program.commands.find((cmd) => cmd.name() === 'chat')
51
51
  const runCommand = chatCommand?.commands.find(
52
52
  (cmd) => cmd.name() === 'run',
@@ -56,7 +56,7 @@ describe('Chat Commands', () => {
56
56
 
57
57
  // Check the help text which contains the default model
58
58
  const helpText = runCommand?.helpInformation()
59
- expect(helpText).toContain('openai/gpt-oss')
59
+ expect(helpText).toContain('glm-4.7')
60
60
  })
61
61
 
62
62
  it('should have streaming enabled by default', () => {
@@ -194,7 +194,7 @@ describe('Code Commands', () => {
194
194
  it('should create opencode.json with correct structure', async () => {
195
195
  // This tests the expected config structure
196
196
  const expectedConfig = {
197
- model: 'berget/deepseek-r1',
197
+ model: 'berget/glm-4-6',
198
198
  apiKey: 'test-api-key',
199
199
  projectName: 'testproject',
200
200
  provider: 'berget',
@@ -202,7 +202,7 @@ describe('Code Commands', () => {
202
202
  version: '1.0.0',
203
203
  }
204
204
 
205
- expect(expectedConfig.model).toBe('berget/deepseek-r1')
205
+ expect(expectedConfig.model).toBe('berget/glm-4-6')
206
206
  expect(expectedConfig.provider).toBe('berget')
207
207
  expect(expectedConfig.version).toBe('1.0.0')
208
208
  })
@@ -259,7 +259,7 @@ describe('Code Commands', () => {
259
259
 
260
260
  it('should load configuration from opencode.json', async () => {
261
261
  const mockConfig = {
262
- model: 'berget/deepseek-r1',
262
+ model: 'berget/glm-4-6',
263
263
  apiKey: 'test-api-key',
264
264
  projectName: 'testproject',
265
265
  provider: 'berget',
@@ -284,7 +284,7 @@ describe('Code Commands', () => {
284
284
  })
285
285
 
286
286
  // Verify config structure expectations
287
- expect(mockConfig.model).toBe('berget/deepseek-r1')
287
+ expect(mockConfig.model).toBe('berget/glm-4-6')
288
288
  expect(mockConfig.apiKey).toBe('test-api-key')
289
289
  expect(mockConfig.projectName).toBe('testproject')
290
290
  })