berget 2.0.3 → 2.0.5

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.
@@ -0,0 +1,261 @@
1
+ import * as fs from 'fs'
2
+ import * as path from 'path'
3
+ import { logger } from './logger'
4
+
5
+ /**
6
+ * Centralized agent configuration loader
7
+ * Loads all agent configurations from opencode.json as the single source of truth
8
+ */
9
+
10
+ export interface AgentConfig {
11
+ model: string
12
+ temperature: number
13
+ top_p: number
14
+ mode: 'primary' | 'subagent'
15
+ permission: {
16
+ edit: 'allow' | 'deny'
17
+ bash: 'allow' | 'deny'
18
+ webfetch: 'allow' | 'deny'
19
+ }
20
+ description?: string
21
+ prompt?: string
22
+ note?: string
23
+ }
24
+
25
+ export interface ModelConfig {
26
+ primary: string
27
+ small: string
28
+ }
29
+
30
+ export interface ProviderModelConfig {
31
+ name: string
32
+ limit: {
33
+ output: number
34
+ context: number
35
+ }
36
+ }
37
+
38
+ export interface OpenCodeConfig {
39
+ $schema?: string
40
+ username?: string
41
+ theme?: string
42
+ share?: string
43
+ autoupdate?: boolean
44
+ model?: string
45
+ small_model?: string
46
+ agent?: Record<string, AgentConfig>
47
+ command?: Record<string, any>
48
+ watcher?: Record<string, any>
49
+ provider?: Record<string, any>
50
+ }
51
+
52
+ export class ConfigLoader {
53
+ private static instance: ConfigLoader
54
+ private config: OpenCodeConfig | null = null
55
+ private configPath: string
56
+
57
+ private constructor(configPath?: string) {
58
+ // Default to opencode.json in current working directory
59
+ this.configPath = configPath || path.join(process.cwd(), 'opencode.json')
60
+ }
61
+
62
+ public static getInstance(configPath?: string): ConfigLoader {
63
+ if (!ConfigLoader.instance) {
64
+ ConfigLoader.instance = new ConfigLoader(configPath)
65
+ }
66
+ return ConfigLoader.instance
67
+ }
68
+
69
+ /**
70
+ * Load configuration from opencode.json
71
+ */
72
+ public loadConfig(): OpenCodeConfig {
73
+ if (this.config) {
74
+ return this.config
75
+ }
76
+
77
+ try {
78
+ if (!fs.existsSync(this.configPath)) {
79
+ throw new Error(`Configuration file not found: ${this.configPath}`)
80
+ }
81
+
82
+ const configContent = fs.readFileSync(this.configPath, 'utf8')
83
+ this.config = JSON.parse(configContent) as OpenCodeConfig
84
+
85
+ logger.debug(`Loaded configuration from ${this.configPath}`)
86
+ return this.config
87
+ } 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)}`)
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Get agent configuration by name
95
+ */
96
+ public getAgentConfig(agentName: string): AgentConfig | null {
97
+ const config = this.loadConfig()
98
+ return config.agent?.[agentName] || null
99
+ }
100
+
101
+ /**
102
+ * Get all agent configurations
103
+ */
104
+ public getAllAgentConfigs(): Record<string, AgentConfig> {
105
+ const config = this.loadConfig()
106
+ return config.agent || {}
107
+ }
108
+
109
+ /**
110
+ * Get model configuration
111
+ */
112
+ 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 }
120
+ }
121
+
122
+ /**
123
+ * Get provider model configuration
124
+ */
125
+ 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>
131
+ }
132
+
133
+ // Fallback to defaults
134
+ return {
135
+ 'deepseek-r1': {
136
+ name: 'GLM-4.6',
137
+ limit: { output: 4000, context: 90000 }
138
+ },
139
+ 'gpt-oss': {
140
+ name: 'GPT-OSS',
141
+ limit: { output: 4000, context: 128000 }
142
+ },
143
+ 'llama-8b': {
144
+ name: 'llama-3.1-8b',
145
+ limit: { output: 4000, context: 128000 }
146
+ }
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Get command configurations
152
+ */
153
+ public getCommandConfigs(): Record<string, any> {
154
+ const config = this.loadConfig()
155
+ return config.command || {}
156
+ }
157
+
158
+ /**
159
+ * Get watcher configuration
160
+ */
161
+ public getWatcherConfig(): Record<string, any> {
162
+ const config = this.loadConfig()
163
+ return config.watcher || { ignore: ['node_modules', 'dist', '.git', 'coverage'] }
164
+ }
165
+
166
+ /**
167
+ * Get provider configuration
168
+ */
169
+ public getProviderConfig(): Record<string, any> {
170
+ const config = this.loadConfig()
171
+ return config.provider || {}
172
+ }
173
+
174
+ /**
175
+ * Check if an agent exists
176
+ */
177
+ public hasAgent(agentName: string): boolean {
178
+ return agentName in this.getAllAgentConfigs()
179
+ }
180
+
181
+ /**
182
+ * Get list of all available agent names
183
+ */
184
+ public getAgentNames(): string[] {
185
+ return Object.keys(this.getAllAgentConfigs())
186
+ }
187
+
188
+ /**
189
+ * Get list of primary agents (mode: 'primary')
190
+ */
191
+ public getPrimaryAgentNames(): string[] {
192
+ const agents = this.getAllAgentConfigs()
193
+ return Object.keys(agents).filter(name => agents[name].mode === 'primary')
194
+ }
195
+
196
+ /**
197
+ * Get list of subagents (mode: 'subagent')
198
+ */
199
+ public getSubagentNames(): string[] {
200
+ const agents = this.getAllAgentConfigs()
201
+ return Object.keys(agents).filter(name => agents[name].mode === 'subagent')
202
+ }
203
+
204
+ /**
205
+ * Reload configuration from file
206
+ */
207
+ public reloadConfig(): OpenCodeConfig {
208
+ this.config = null
209
+ return this.loadConfig()
210
+ }
211
+
212
+ /**
213
+ * Set custom configuration path (for testing or different environments)
214
+ */
215
+ public setConfigPath(configPath: string): void {
216
+ this.configPath = configPath
217
+ this.config = null // Force reload
218
+ }
219
+
220
+ /**
221
+ * Get the current configuration path
222
+ */
223
+ public getConfigPath(): string {
224
+ return this.configPath
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Convenience function to get the config loader instance
230
+ */
231
+ export function getConfigLoader(configPath?: string): ConfigLoader {
232
+ return ConfigLoader.getInstance(configPath)
233
+ }
234
+
235
+ /**
236
+ * Convenience function to get agent configuration
237
+ */
238
+ export function getAgentConfig(agentName: string, configPath?: string): AgentConfig | null {
239
+ return getConfigLoader(configPath).getAgentConfig(agentName)
240
+ }
241
+
242
+ /**
243
+ * Convenience function to get all agent configurations
244
+ */
245
+ export function getAllAgentConfigs(configPath?: string): Record<string, AgentConfig> {
246
+ return getConfigLoader(configPath).getAllAgentConfigs()
247
+ }
248
+
249
+ /**
250
+ * Convenience function to get model configuration
251
+ */
252
+ export function getModelConfig(configPath?: string): ModelConfig {
253
+ return getConfigLoader(configPath).getModelConfig()
254
+ }
255
+
256
+ /**
257
+ * Convenience function to get provider models
258
+ */
259
+ export function getProviderModels(configPath?: string): Record<string, ProviderModelConfig> {
260
+ return getConfigLoader(configPath).getProviderModels()
261
+ }
@@ -4,7 +4,11 @@ import chalk from 'chalk'
4
4
  * Formats and prints error messages in a consistent way
5
5
  */
6
6
  export function handleError(message: string, error: any): void {
7
- console.error(chalk.red(`Error: ${message}`))
7
+ console.error(chalk.red(`āŒ Error: ${message}`))
8
+
9
+ let errorDetails = ''
10
+ let errorCode = ''
11
+ let errorType = ''
8
12
 
9
13
  // If the error is a string (like JSON.stringify(error))
10
14
  if (typeof error === 'string') {
@@ -12,38 +16,131 @@ export function handleError(message: string, error: any): void {
12
16
  // Try to parse it as JSON
13
17
  const parsedError = JSON.parse(error)
14
18
  if (parsedError.error) {
15
- console.error(chalk.dim(`Details: ${parsedError.error}`))
16
- if (parsedError.code) {
17
- console.error(chalk.dim(`Code: ${parsedError.code}`))
18
- }
19
+ errorDetails = parsedError.error.message || parsedError.error
20
+ errorCode = parsedError.error.code || parsedError.code
21
+ errorType = parsedError.error.type || ''
19
22
  } else {
20
- console.error(chalk.dim(`Details: ${error}`))
23
+ errorDetails = error
21
24
  }
22
25
  } catch {
23
26
  // If it's not valid JSON, just print the string
24
- console.error(chalk.dim(`Details: ${error}`))
27
+ errorDetails = error
25
28
  }
26
29
  } else if (error && error.message) {
27
30
  // If it's an Error object
28
- console.error(chalk.dim(`Details: ${error.message}`))
31
+ errorDetails = error.message
32
+ errorCode = error.code
33
+ errorType = error.type
34
+ }
35
+
36
+ // Print error details
37
+ if (errorDetails) {
38
+ console.error(chalk.dim(`šŸ“ Details: ${errorDetails}`))
39
+ }
40
+ if (errorCode) {
41
+ console.error(chalk.dim(`šŸ”¢ Code: ${errorCode}`))
42
+ }
43
+
44
+ // Provide helpful troubleshooting based on error type
45
+ provideTroubleshootingTips(errorType, errorCode, errorDetails)
46
+ }
47
+
48
+ /**
49
+ * Provides helpful troubleshooting tips based on error type
50
+ */
51
+ function provideTroubleshootingTips(errorType: string, errorCode: string, errorDetails: string): void {
52
+ console.error(chalk.blue('\nšŸ’” Troubleshooting tips:'))
53
+
54
+ // Authentication errors
55
+ if (
56
+ errorType === 'authentication_error' ||
57
+ errorCode === 'AUTH_FAILED' ||
58
+ errorDetails?.includes('Unauthorized') ||
59
+ errorDetails?.includes('Authentication failed')
60
+ ) {
61
+ console.error(chalk.yellow(' šŸ” Authentication issue detected:'))
62
+ console.error(chalk.white(' • Run `berget auth login` to log in'))
63
+ console.error(chalk.white(' • Check if your session has expired'))
64
+ console.error(chalk.white(' • Verify you have the correct permissions'))
65
+ }
66
+
67
+ // Network/connection errors
68
+ if (
69
+ errorDetails?.includes('fetch failed') ||
70
+ errorDetails?.includes('ECONNREFUSED') ||
71
+ errorDetails?.includes('ENOTFOUND') ||
72
+ errorDetails?.includes('network')
73
+ ) {
74
+ console.error(chalk.yellow(' 🌐 Network issue detected:'))
75
+ console.error(chalk.white(' • Check your internet connection'))
76
+ console.error(chalk.white(' • Verify you can reach api.berget.ai'))
77
+ console.error(chalk.white(' • Try again in a few minutes'))
78
+ console.error(chalk.white(' • Check if any firewall is blocking the request'))
29
79
  }
30
80
 
31
- // Check for authentication errors
81
+ // API key errors
32
82
  if (
33
- (typeof error === 'string' &&
34
- (error.includes('Unauthorized') ||
35
- error.includes('Authentication failed'))) ||
36
- (error &&
37
- error.message &&
38
- (error.message.includes('Unauthorized') ||
39
- error.message.includes('Authentication failed'))) ||
40
- (error &&
41
- error.code &&
42
- (error.code === 401 || error.code === 'AUTH_FAILED'))
83
+ errorCode?.includes('API_KEY') ||
84
+ errorDetails?.includes('API key') ||
85
+ errorType === 'invalid_request_error'
43
86
  ) {
44
- console.error(
45
- chalk.yellow('\nYou need to be logged in to use this command.'),
46
- )
47
- console.error(chalk.yellow('Run `berget auth login` to authenticate.'))
87
+ console.error(chalk.yellow(' šŸ”‘ API key issue detected:'))
88
+ console.error(chalk.white(' • Run `berget api-keys list` to check your keys'))
89
+ console.error(chalk.white(' • Create a new key with `berget api-keys create --name "My Key"`'))
90
+ console.error(chalk.white(' • Set a default key with `berget api-keys set-default <id>`'))
91
+ console.error(chalk.white(' • Check if your API key has expired'))
48
92
  }
93
+
94
+ // Rate limiting
95
+ if (
96
+ errorCode === 'RATE_LIMIT_EXCEEDED' ||
97
+ errorDetails?.includes('rate limit') ||
98
+ errorDetails?.includes('too many requests')
99
+ ) {
100
+ console.error(chalk.yellow(' ā±ļø Rate limit exceeded:'))
101
+ console.error(chalk.white(' • Wait a few minutes before trying again'))
102
+ console.error(chalk.white(' • Consider upgrading your plan for higher limits'))
103
+ console.error(chalk.white(' • Use `berget billing get-usage` to check your usage'))
104
+ }
105
+
106
+ // Server errors
107
+ if (
108
+ errorCode?.includes('SERVER_ERROR') ||
109
+ errorType === 'server_error' ||
110
+ (errorCode && parseInt(errorCode) >= 500)
111
+ ) {
112
+ console.error(chalk.yellow(' šŸ–„ļø Server issue detected:'))
113
+ console.error(chalk.white(' • This is a temporary problem on our end'))
114
+ console.error(chalk.white(' • Try again in a few minutes'))
115
+ console.error(chalk.white(' • Check status.berget.ai for service status'))
116
+ console.error(chalk.white(' • Contact support if the problem persists'))
117
+ }
118
+
119
+ // Cluster errors
120
+ if (
121
+ errorCode?.includes('CLUSTERS') ||
122
+ errorDetails?.includes('cluster')
123
+ ) {
124
+ console.error(chalk.yellow(' šŸ—ļø Cluster issue detected:'))
125
+ console.error(chalk.white(' • Clusters may be temporarily unavailable'))
126
+ console.error(chalk.white(' • Try again later or contact support'))
127
+ console.error(chalk.white(' • Check your cluster permissions'))
128
+ }
129
+
130
+ // Generic fallback
131
+ if (
132
+ !errorType?.includes('authentication') &&
133
+ !errorDetails?.includes('fetch failed') &&
134
+ !errorCode?.includes('API_KEY') &&
135
+ !errorCode?.includes('RATE_LIMIT') &&
136
+ !errorCode?.includes('SERVER_ERROR') &&
137
+ !errorCode?.includes('CLUSTERS')
138
+ ) {
139
+ console.error(chalk.yellow(' ā“ General issue:'))
140
+ console.error(chalk.white(' • Try running the command with --debug for more info'))
141
+ console.error(chalk.white(' • Check your configuration with `berget auth whoami`'))
142
+ console.error(chalk.white(' • Contact support if the problem persists'))
143
+ }
144
+
145
+ console.error(chalk.dim('\nNeed more help? Visit https://docs.berget.ai or contact support@berget.ai'))
49
146
  }