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.
- package/AGENTS.md +29 -0
- package/dist/index.js +2 -0
- package/dist/package.json +1 -1
- package/dist/src/commands/api-keys.js +119 -24
- package/dist/src/commands/chat.js +6 -6
- package/dist/src/commands/code.js +103 -57
- package/dist/src/constants/command-structure.js +2 -0
- package/dist/src/services/api-key-service.js +64 -1
- package/dist/src/utils/config-loader.js +217 -0
- package/dist/src/utils/error-handler.js +98 -22
- package/dist/tests/utils/config-loader.test.js +182 -0
- package/index.ts +19 -19
- package/opencode.json +1 -1
- package/package.json +1 -1
- package/src/commands/api-keys.ts +156 -28
- package/src/commands/chat.ts +6 -6
- package/src/commands/code.ts +119 -58
- package/src/constants/command-structure.ts +2 -0
- package/src/services/api-key-service.ts +100 -2
- package/src/utils/config-loader.ts +261 -0
- package/src/utils/error-handler.ts +120 -23
|
@@ -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(
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
+
errorDetails = parsedError.error.message || parsedError.error
|
|
20
|
+
errorCode = parsedError.error.code || parsedError.code
|
|
21
|
+
errorType = parsedError.error.type || ''
|
|
19
22
|
} else {
|
|
20
|
-
|
|
23
|
+
errorDetails = error
|
|
21
24
|
}
|
|
22
25
|
} catch {
|
|
23
26
|
// If it's not valid JSON, just print the string
|
|
24
|
-
|
|
27
|
+
errorDetails = error
|
|
25
28
|
}
|
|
26
29
|
} else if (error && error.message) {
|
|
27
30
|
// If it's an Error object
|
|
28
|
-
|
|
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
|
-
//
|
|
81
|
+
// API key errors
|
|
32
82
|
if (
|
|
33
|
-
(
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
46
|
-
)
|
|
47
|
-
console.error(chalk.
|
|
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
|
}
|