berget 2.2.7 → 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.
Files changed (130) hide show
  1. package/.github/workflows/publish.yml +6 -6
  2. package/.github/workflows/test.yml +1 -1
  3. package/.prettierrc +5 -3
  4. package/dist/index.js +24 -25
  5. package/dist/package.json +5 -3
  6. package/dist/src/agents/app.js +8 -8
  7. package/dist/src/agents/backend.js +3 -3
  8. package/dist/src/agents/devops.js +8 -8
  9. package/dist/src/agents/frontend.js +3 -3
  10. package/dist/src/agents/fullstack.js +3 -3
  11. package/dist/src/agents/index.js +18 -18
  12. package/dist/src/agents/quality.js +8 -8
  13. package/dist/src/agents/security.js +8 -8
  14. package/dist/src/client.js +115 -127
  15. package/dist/src/commands/api-keys.js +195 -202
  16. package/dist/src/commands/auth.js +16 -25
  17. package/dist/src/commands/autocomplete.js +8 -8
  18. package/dist/src/commands/billing.js +10 -19
  19. package/dist/src/commands/chat.js +139 -170
  20. package/dist/src/commands/clusters.js +21 -30
  21. package/dist/src/commands/code/__tests__/auth-sync.test.js +189 -186
  22. package/dist/src/commands/code/__tests__/fake-api-key-service.js +3 -13
  23. package/dist/src/commands/code/__tests__/fake-auth-service.js +21 -29
  24. package/dist/src/commands/code/__tests__/fake-command-runner.js +22 -33
  25. package/dist/src/commands/code/__tests__/fake-file-store.js +19 -41
  26. package/dist/src/commands/code/__tests__/fake-prompter.js +81 -97
  27. package/dist/src/commands/code/__tests__/setup-flow.test.js +295 -295
  28. package/dist/src/commands/code/adapters/clack-prompter.js +15 -32
  29. package/dist/src/commands/code/adapters/fs-file-store.js +25 -44
  30. package/dist/src/commands/code/adapters/spawn-command-runner.js +27 -41
  31. package/dist/src/commands/code/auth-sync.js +215 -228
  32. package/dist/src/commands/code/errors.js +15 -12
  33. package/dist/src/commands/code/setup.js +390 -425
  34. package/dist/src/commands/code.js +279 -294
  35. package/dist/src/commands/index.js +5 -5
  36. package/dist/src/commands/models.js +16 -25
  37. package/dist/src/commands/users.js +9 -18
  38. package/dist/src/constants/command-structure.js +138 -138
  39. package/dist/src/services/api-key-service.js +132 -152
  40. package/dist/src/services/auth-service.js +81 -95
  41. package/dist/src/services/browser-auth.js +121 -131
  42. package/dist/src/services/chat-service.js +369 -386
  43. package/dist/src/services/cluster-service.js +47 -62
  44. package/dist/src/services/collaborator-service.js +9 -21
  45. package/dist/src/services/flux-service.js +13 -25
  46. package/dist/src/services/helm-service.js +9 -21
  47. package/dist/src/services/kubectl-service.js +15 -29
  48. package/dist/src/utils/config-checker.js +7 -7
  49. package/dist/src/utils/config-loader.js +109 -109
  50. package/dist/src/utils/default-api-key.js +129 -139
  51. package/dist/src/utils/env-manager.js +55 -66
  52. package/dist/src/utils/error-handler.js +62 -62
  53. package/dist/src/utils/logger.js +74 -67
  54. package/dist/src/utils/markdown-renderer.js +28 -28
  55. package/dist/src/utils/opencode-validator.js +67 -69
  56. package/dist/src/utils/token-manager.js +67 -65
  57. package/dist/tests/commands/chat.test.js +30 -39
  58. package/dist/tests/commands/code.test.js +186 -195
  59. package/dist/tests/utils/config-loader.test.js +107 -107
  60. package/dist/tests/utils/env-manager.test.js +81 -90
  61. package/dist/tests/utils/opencode-validator.test.js +42 -41
  62. package/dist/vitest.config.js +1 -1
  63. package/eslint.config.mjs +50 -30
  64. package/index.ts +30 -31
  65. package/package.json +5 -3
  66. package/src/agents/app.ts +9 -9
  67. package/src/agents/backend.ts +4 -4
  68. package/src/agents/devops.ts +9 -9
  69. package/src/agents/frontend.ts +4 -4
  70. package/src/agents/fullstack.ts +4 -4
  71. package/src/agents/index.ts +27 -25
  72. package/src/agents/quality.ts +9 -9
  73. package/src/agents/security.ts +9 -9
  74. package/src/agents/types.ts +10 -10
  75. package/src/client.ts +85 -77
  76. package/src/commands/api-keys.ts +190 -185
  77. package/src/commands/auth.ts +15 -14
  78. package/src/commands/autocomplete.ts +10 -10
  79. package/src/commands/billing.ts +13 -12
  80. package/src/commands/chat.ts +145 -142
  81. package/src/commands/clusters.ts +20 -19
  82. package/src/commands/code/__tests__/auth-sync.test.ts +176 -175
  83. package/src/commands/code/__tests__/fake-api-key-service.ts +2 -2
  84. package/src/commands/code/__tests__/fake-auth-service.ts +18 -18
  85. package/src/commands/code/__tests__/fake-command-runner.ts +28 -22
  86. package/src/commands/code/__tests__/fake-file-store.ts +15 -15
  87. package/src/commands/code/__tests__/fake-prompter.ts +86 -85
  88. package/src/commands/code/__tests__/setup-flow.test.ts +253 -251
  89. package/src/commands/code/adapters/clack-prompter.ts +32 -30
  90. package/src/commands/code/adapters/fs-file-store.ts +18 -17
  91. package/src/commands/code/adapters/spawn-command-runner.ts +20 -15
  92. package/src/commands/code/auth-sync.ts +210 -210
  93. package/src/commands/code/errors.ts +11 -11
  94. package/src/commands/code/ports/auth-services.ts +7 -7
  95. package/src/commands/code/ports/command-runner.ts +2 -2
  96. package/src/commands/code/ports/file-store.ts +3 -3
  97. package/src/commands/code/ports/prompter.ts +13 -13
  98. package/src/commands/code/setup.ts +408 -406
  99. package/src/commands/code.ts +288 -287
  100. package/src/commands/index.ts +11 -10
  101. package/src/commands/models.ts +19 -18
  102. package/src/commands/users.ts +11 -10
  103. package/src/constants/command-structure.ts +159 -159
  104. package/src/services/api-key-service.ts +85 -85
  105. package/src/services/auth-service.ts +55 -54
  106. package/src/services/browser-auth.ts +62 -62
  107. package/src/services/chat-service.ts +169 -170
  108. package/src/services/cluster-service.ts +28 -28
  109. package/src/services/collaborator-service.ts +6 -6
  110. package/src/services/flux-service.ts +17 -17
  111. package/src/services/helm-service.ts +11 -11
  112. package/src/services/kubectl-service.ts +12 -12
  113. package/src/types/api.d.ts +1933 -1933
  114. package/src/types/json.d.ts +1 -1
  115. package/src/utils/config-checker.ts +6 -6
  116. package/src/utils/config-loader.ts +130 -129
  117. package/src/utils/default-api-key.ts +81 -80
  118. package/src/utils/env-manager.ts +37 -37
  119. package/src/utils/error-handler.ts +64 -64
  120. package/src/utils/logger.ts +72 -66
  121. package/src/utils/markdown-renderer.ts +28 -28
  122. package/src/utils/opencode-validator.ts +72 -71
  123. package/src/utils/token-manager.ts +69 -68
  124. package/tests/commands/chat.test.ts +32 -31
  125. package/tests/commands/code.test.ts +182 -181
  126. package/tests/utils/config-loader.test.ts +111 -110
  127. package/tests/utils/env-manager.test.ts +83 -79
  128. package/tests/utils/opencode-validator.test.ts +43 -42
  129. package/tsconfig.json +2 -1
  130. package/vitest.config.ts +2 -2
@@ -22,26 +22,6 @@ var __importStar = (this && this.__importStar) || function (mod) {
22
22
  __setModuleDefault(result, mod);
23
23
  return result;
24
24
  };
25
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
- return new (P || (P = Promise))(function (resolve, reject) {
28
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
- step((generator = generator.apply(thisArg, _arguments || [])).next());
32
- });
33
- };
34
- var __rest = (this && this.__rest) || function (s, e) {
35
- var t = {};
36
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
37
- t[p] = s[p];
38
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
39
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
40
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
41
- t[p[i]] = s[p[i]];
42
- }
43
- return t;
44
- };
45
25
  Object.defineProperty(exports, "__esModule", { value: true });
46
26
  exports.ChatService = void 0;
47
27
  const client_1 = require("../client");
@@ -51,9 +31,16 @@ const logger_1 = require("../utils/logger");
51
31
  * Command group: chat
52
32
  */
53
33
  class ChatService {
54
- constructor() {
55
- this.client = (0, client_1.createAuthenticatedClient)();
56
- }
34
+ // Command group name for this service
35
+ static COMMAND_GROUP = 'chat';
36
+ // Subcommands for this service
37
+ static COMMANDS = {
38
+ LIST: 'list',
39
+ RUN: 'run',
40
+ };
41
+ static instance;
42
+ client = (0, client_1.createAuthenticatedClient)();
43
+ constructor() { }
57
44
  static getInstance() {
58
45
  if (!ChatService.instance) {
59
46
  ChatService.instance = new ChatService();
@@ -64,133 +51,184 @@ class ChatService {
64
51
  * Create a chat completion
65
52
  * Command: berget chat completion
66
53
  */
67
- createCompletion(options) {
68
- return __awaiter(this, void 0, void 0, function* () {
54
+ async createCompletion(options) {
55
+ try {
56
+ logger_1.logger.debug('Starting createCompletion method');
57
+ // Initialize options if undefined
58
+ const optionsCopy = options ? { ...options } : { messages: [] };
59
+ // Check if messages are defined
60
+ if (!optionsCopy.messages || !Array.isArray(optionsCopy.messages)) {
61
+ logger_1.logger.error('messages is undefined or not an array');
62
+ optionsCopy.messages = [];
63
+ }
64
+ // Log the options object
65
+ logger_1.logger.debug('Starting createCompletion with options:');
69
66
  try {
70
- logger_1.logger.debug("Starting createCompletion method");
71
- // Initialize options if undefined
72
- const optionsCopy = options ? Object.assign({}, options) : { messages: [] };
73
- // Check if messages are defined
74
- if (!optionsCopy.messages || !Array.isArray(optionsCopy.messages)) {
75
- logger_1.logger.error("messages is undefined or not an array");
76
- optionsCopy.messages = [];
77
- }
78
- // Log the options object
79
- logger_1.logger.debug("Starting createCompletion with options:");
67
+ logger_1.logger.debug(JSON.stringify({
68
+ ...optionsCopy,
69
+ apiKey: optionsCopy.apiKey ? '***' : undefined,
70
+ messages: optionsCopy.messages
71
+ ? `${optionsCopy.messages.length} messages`
72
+ : '0 messages',
73
+ }, null, 2));
74
+ }
75
+ catch (error) {
76
+ logger_1.logger.error('Failed to stringify options:', error);
77
+ }
78
+ const headers = {};
79
+ // First try to use the authenticated client (with refresh token support)
80
+ // Only fall back to API key flow if explicitly requested or no auth tokens available
81
+ const { TokenManager } = await Promise.resolve().then(() => __importStar(require('../utils/token-manager')));
82
+ const tokenManagerInstance = TokenManager.getInstance();
83
+ const hasValidAuth = tokenManagerInstance.getAccessToken() && !tokenManagerInstance.isTokenExpired();
84
+ const environmentApiKeyForAuth = process.env.BERGET_API_KEY;
85
+ const hasExplicitApiKey = !!optionsCopy.apiKey || !!environmentApiKeyForAuth;
86
+ // If we have valid auth tokens and no explicit API key, use authenticated client
87
+ if (hasValidAuth && !hasExplicitApiKey) {
88
+ logger_1.logger.debug('Using authenticated client with refresh token support');
89
+ // Create a copy without apiKey to let the authenticated client handle auth automatically
90
+ const { apiKey: _apiKey, ...optionsWithoutKey } = optionsCopy;
91
+ return this.executeCompletion(optionsWithoutKey, {});
92
+ }
93
+ // Check for environment variables first - prioritize this over everything else
94
+ const environmentApiKey = process.env.BERGET_API_KEY;
95
+ if (environmentApiKey) {
96
+ logger_1.logger.debug('Using API key from BERGET_API_KEY environment variable');
97
+ optionsCopy.apiKey = environmentApiKey;
98
+ // Skip the default API key logic if we already have a key
99
+ return this.executeCompletion(optionsCopy, headers);
100
+ }
101
+ // If API key is already provided, use it directly
102
+ else if (optionsCopy.apiKey) {
103
+ logger_1.logger.debug('Using API key provided in options');
104
+ // Skip the default API key logic if we already have a key
105
+ return this.executeCompletion(optionsCopy, headers);
106
+ }
107
+ // Only try to get the default API key if no API key is provided and no env var is set
108
+ else {
109
+ logger_1.logger.debug('No API key provided, trying to get default');
80
110
  try {
81
- logger_1.logger.debug(JSON.stringify(Object.assign(Object.assign({}, optionsCopy), { apiKey: optionsCopy.apiKey ? "***" : undefined, messages: optionsCopy.messages
82
- ? `${optionsCopy.messages.length} messages`
83
- : "0 messages" }), null, 2));
111
+ // Import the DefaultApiKeyManager directly
112
+ logger_1.logger.debug('Importing DefaultApiKeyManager');
113
+ const { DefaultApiKeyManager } = await Promise.resolve().then(() => __importStar(require('../utils/default-api-key')));
114
+ const defaultApiKeyManager = DefaultApiKeyManager.getInstance();
115
+ logger_1.logger.debug('Got DefaultApiKeyManager instance');
116
+ // Try to get the default API key
117
+ logger_1.logger.debug('Calling promptForDefaultApiKey');
118
+ const defaultApiKeyData = defaultApiKeyManager.getDefaultApiKeyData();
119
+ const apiKey = defaultApiKeyData?.key || (await defaultApiKeyManager.promptForDefaultApiKey());
120
+ logger_1.logger.debug(`Default API key data exists: ${!!defaultApiKeyData}`);
121
+ logger_1.logger.debug(`promptForDefaultApiKey returned: ${apiKey ? 'a key' : 'null'}`);
122
+ if (apiKey) {
123
+ logger_1.logger.debug('Using API key from default API key manager');
124
+ optionsCopy.apiKey = apiKey;
125
+ }
126
+ else {
127
+ logger_1.logger.warn('No API key available. You need to either:');
128
+ logger_1.logger.warn('1. Create an API key with: berget api-keys create --name "My Key"');
129
+ logger_1.logger.warn('2. Set a default API key with: berget api-keys set-default <id>');
130
+ logger_1.logger.warn('3. Provide an API key with the --api-key option');
131
+ logger_1.logger.warn('4. Set the BERGET_API_KEY environment variable');
132
+ logger_1.logger.warn('\nExample:');
133
+ logger_1.logger.warn(' export BERGET_API_KEY=your_api_key_here');
134
+ logger_1.logger.warn(' # or for a single command:');
135
+ logger_1.logger.warn(' BERGET_API_KEY=your_api_key_here berget chat run google/gemma-3-27b-it');
136
+ throw new Error('No API key provided and no default API key set');
137
+ }
138
+ // Set the API key in the options
139
+ logger_1.logger.debug('Setting API key in options');
140
+ // Only set the API key if it's not null
141
+ if (apiKey) {
142
+ optionsCopy.apiKey = apiKey;
143
+ }
84
144
  }
85
145
  catch (error) {
86
- logger_1.logger.error("Failed to stringify options:", error);
87
- }
88
- const headers = {};
89
- // First try to use the authenticated client (with refresh token support)
90
- // Only fall back to API key flow if explicitly requested or no auth tokens available
91
- const { TokenManager } = yield Promise.resolve().then(() => __importStar(require("../utils/token-manager")));
92
- const tokenManagerInstance = TokenManager.getInstance();
93
- const hasValidAuth = tokenManagerInstance.getAccessToken() && !tokenManagerInstance.isTokenExpired();
94
- const envApiKeyForAuth = process.env.BERGET_API_KEY;
95
- const hasExplicitApiKey = !!optionsCopy.apiKey || !!envApiKeyForAuth;
96
- // If we have valid auth tokens and no explicit API key, use authenticated client
97
- if (hasValidAuth && !hasExplicitApiKey) {
98
- logger_1.logger.debug("Using authenticated client with refresh token support");
99
- // Create a copy without apiKey to let the authenticated client handle auth automatically
100
- const { apiKey: _apiKey } = optionsCopy, optionsWithoutKey = __rest(optionsCopy, ["apiKey"]);
101
- return this.executeCompletion(optionsWithoutKey, {});
102
- }
103
- // Check for environment variables first - prioritize this over everything else
104
- const envApiKey = process.env.BERGET_API_KEY;
105
- if (envApiKey) {
106
- logger_1.logger.debug("Using API key from BERGET_API_KEY environment variable");
107
- optionsCopy.apiKey = envApiKey;
108
- // Skip the default API key logic if we already have a key
109
- return this.executeCompletion(optionsCopy, headers);
110
- }
111
- // If API key is already provided, use it directly
112
- else if (optionsCopy.apiKey) {
113
- logger_1.logger.debug("Using API key provided in options");
114
- // Skip the default API key logic if we already have a key
115
- return this.executeCompletion(optionsCopy, headers);
116
- }
117
- // Only try to get the default API key if no API key is provided and no env var is set
118
- else {
119
- logger_1.logger.debug("No API key provided, trying to get default");
120
- try {
121
- // Import the DefaultApiKeyManager directly
122
- logger_1.logger.debug("Importing DefaultApiKeyManager");
123
- const DefaultApiKeyManager = (yield Promise.resolve().then(() => __importStar(require("../utils/default-api-key"))))
124
- .DefaultApiKeyManager;
125
- const defaultApiKeyManager = DefaultApiKeyManager.getInstance();
126
- logger_1.logger.debug("Got DefaultApiKeyManager instance");
127
- // Try to get the default API key
128
- logger_1.logger.debug("Calling promptForDefaultApiKey");
129
- const defaultApiKeyData = defaultApiKeyManager.getDefaultApiKeyData();
130
- const apiKey = (defaultApiKeyData === null || defaultApiKeyData === void 0 ? void 0 : defaultApiKeyData.key) || (yield defaultApiKeyManager.promptForDefaultApiKey());
131
- logger_1.logger.debug(`Default API key data exists: ${!!defaultApiKeyData}`);
132
- logger_1.logger.debug(`promptForDefaultApiKey returned: ${apiKey ? "a key" : "null"}`);
133
- if (apiKey) {
134
- logger_1.logger.debug("Using API key from default API key manager");
135
- optionsCopy.apiKey = apiKey;
136
- }
137
- else {
138
- logger_1.logger.warn("No API key available. You need to either:");
139
- logger_1.logger.warn('1. Create an API key with: berget api-keys create --name "My Key"');
140
- logger_1.logger.warn("2. Set a default API key with: berget api-keys set-default <id>");
141
- logger_1.logger.warn("3. Provide an API key with the --api-key option");
142
- logger_1.logger.warn("4. Set the BERGET_API_KEY environment variable");
143
- logger_1.logger.warn("\nExample:");
144
- logger_1.logger.warn(" export BERGET_API_KEY=your_api_key_here");
145
- logger_1.logger.warn(" # or for a single command:");
146
- logger_1.logger.warn(" BERGET_API_KEY=your_api_key_here berget chat run google/gemma-3-27b-it");
147
- throw new Error("No API key provided and no default API key set");
148
- }
149
- // Set the API key in the options
150
- logger_1.logger.debug("Setting API key in options");
151
- // Only set the API key if it's not null
152
- if (apiKey) {
153
- optionsCopy.apiKey = apiKey;
154
- }
146
+ logger_1.logger.error('Error getting API key:');
147
+ if (error instanceof Error) {
148
+ logger_1.logger.error(error.message);
155
149
  }
156
- catch (error) {
157
- logger_1.logger.error("Error getting API key:");
158
- if (error instanceof Error) {
159
- logger_1.logger.error(error.message);
160
- }
161
- logger_1.logger.warn('Please create an API key with: berget api-keys create --name "My Key"');
162
- throw new Error("Failed to get API key");
150
+ logger_1.logger.warn('Please create an API key with: berget api-keys create --name "My Key"');
151
+ throw new Error('Failed to get API key');
152
+ }
153
+ }
154
+ // Set default model if not provided
155
+ if (!optionsCopy.model) {
156
+ logger_1.logger.debug('No model specified, using default: google/gemma-3-27b-it');
157
+ optionsCopy.model = 'google/gemma-3-27b-it';
158
+ }
159
+ logger_1.logger.debug('Chat completion options:');
160
+ logger_1.logger.debug(JSON.stringify({
161
+ ...optionsCopy,
162
+ apiKey: optionsCopy.apiKey ? '***' : undefined, // Hide the actual API key in debug output
163
+ }, null, 2));
164
+ return this.executeCompletion(optionsCopy, headers);
165
+ }
166
+ catch (error) {
167
+ // Improved error handling
168
+ let errorMessage = 'Failed to create chat completion';
169
+ if (error instanceof Error) {
170
+ try {
171
+ // Try to parse the error message as JSON
172
+ const parsedError = JSON.parse(error.message);
173
+ if (parsedError.error && parsedError.error.message) {
174
+ errorMessage = `Chat error: ${parsedError.error.message}`;
163
175
  }
164
176
  }
165
- // Set default model if not provided
166
- if (!optionsCopy.model) {
167
- logger_1.logger.debug("No model specified, using default: google/gemma-3-27b-it");
168
- optionsCopy.model = "google/gemma-3-27b-it";
177
+ catch {
178
+ // If parsing fails, use the original error message
179
+ errorMessage = `Chat error: ${error.message}`;
169
180
  }
170
- logger_1.logger.debug("Chat completion options:");
171
- logger_1.logger.debug(JSON.stringify(Object.assign(Object.assign({}, optionsCopy), { apiKey: optionsCopy.apiKey ? "***" : undefined }), null, 2));
172
- return this.executeCompletion(optionsCopy, headers);
173
181
  }
174
- catch (error) {
175
- // Improved error handling
176
- let errorMessage = "Failed to create chat completion";
177
- if (error instanceof Error) {
178
- try {
179
- // Try to parse the error message as JSON
180
- const parsedError = JSON.parse(error.message);
181
- if (parsedError.error && parsedError.error.message) {
182
- errorMessage = `Chat error: ${parsedError.error.message}`;
183
- }
184
- }
185
- catch (_a) {
186
- // If parsing fails, use the original error message
187
- errorMessage = `Chat error: ${error.message}`;
182
+ logger_1.logger.error(errorMessage);
183
+ throw new Error(errorMessage);
184
+ }
185
+ }
186
+ /**
187
+ * List available models
188
+ * Command: berget chat list
189
+ */
190
+ async listModels(apiKey) {
191
+ try {
192
+ // Check for environment variable first, then fallback to provided API key
193
+ const environmentApiKey = process.env.BERGET_API_KEY;
194
+ const effectiveApiKey = environmentApiKey || apiKey;
195
+ if (effectiveApiKey) {
196
+ const headers = {
197
+ Authorization: effectiveApiKey,
198
+ };
199
+ const { data, error } = await this.client.GET('/v1/models', { headers });
200
+ if (error)
201
+ throw new Error(JSON.stringify(error));
202
+ return data;
203
+ }
204
+ else {
205
+ const { data, error } = await this.client.GET('/v1/models');
206
+ if (error)
207
+ throw new Error(JSON.stringify(error));
208
+ return data;
209
+ }
210
+ }
211
+ catch (error) {
212
+ // Improved error handling
213
+ let errorMessage = 'Failed to list models';
214
+ if (error instanceof Error) {
215
+ try {
216
+ // Try to parse the error message as JSON
217
+ const parsedError = JSON.parse(error.message);
218
+ if (parsedError.error) {
219
+ errorMessage = `Models error: ${typeof parsedError.error === 'string'
220
+ ? parsedError.error
221
+ : parsedError.error.message || JSON.stringify(parsedError.error)}`;
188
222
  }
189
223
  }
190
- logger_1.logger.error(errorMessage);
191
- throw new Error(errorMessage);
224
+ catch {
225
+ // If parsing fails, use the original error message
226
+ errorMessage = `Models error: ${error.message}`;
227
+ }
192
228
  }
193
- });
229
+ logger_1.logger.error(errorMessage);
230
+ throw new Error(errorMessage);
231
+ }
194
232
  }
195
233
  /**
196
234
  * Execute the completion request with the provided options
@@ -198,51 +236,55 @@ class ChatService {
198
236
  * @param headers Additional headers to include
199
237
  * @returns The completion response
200
238
  */
201
- executeCompletion(options, headers = {}) {
202
- return __awaiter(this, void 0, void 0, function* () {
203
- try {
204
- // If an API key is provided, use it for this request
205
- if (options.apiKey) {
206
- // API keys should be sent directly, not with Bearer prefix
207
- headers["Authorization"] = options.apiKey;
208
- }
209
- // Remove apiKey and onChunk from options before sending to API
210
- const { apiKey, onChunk } = options, requestOptions = __rest(options, ["apiKey", "onChunk"]);
211
- logger_1.logger.debug("Request options:");
212
- logger_1.logger.debug(JSON.stringify(Object.assign(Object.assign({}, requestOptions), { messages: requestOptions.messages
213
- ? `${requestOptions.messages.length} messages`
214
- : "0 messages" }), null, 2));
215
- // Handle streaming responses differently
216
- if (requestOptions.stream && onChunk) {
217
- return yield this.handleStreamingResponse(Object.assign(Object.assign({}, requestOptions), { onChunk }), headers);
218
- }
219
- else {
220
- // Ensure model is always defined for the API call
221
- const requestBody = Object.assign(Object.assign({}, requestOptions), { model: requestOptions.model || "google/gemma-3-27b-it" });
222
- // Debug the headers being sent
223
- logger_1.logger.debug("Headers being sent:");
224
- logger_1.logger.debug(JSON.stringify(headers, null, 2));
225
- const response = yield this.client.POST("/v1/chat/completions", {
226
- body: requestBody,
227
- headers,
228
- });
229
- // Check if response has an error property
230
- const responseAny = response;
231
- if (responseAny && responseAny.error)
232
- throw new Error(JSON.stringify(responseAny.error));
233
- logger_1.logger.debug("API response:");
234
- logger_1.logger.debug(JSON.stringify(response, null, 2));
235
- // Output the complete response data for debugging
236
- logger_1.logger.debug("Complete response data:");
237
- logger_1.logger.debug(JSON.stringify(response.data, null, 2));
238
- return response.data;
239
- }
239
+ async executeCompletion(options, headers = {}) {
240
+ try {
241
+ // If an API key is provided, use it for this request
242
+ if (options.apiKey) {
243
+ // API keys should be sent directly, not with Bearer prefix
244
+ headers['Authorization'] = options.apiKey;
245
+ }
246
+ // Remove apiKey and onChunk from options before sending to API
247
+ const { apiKey: _apiKey, onChunk, ...requestOptions } = options;
248
+ logger_1.logger.debug('Request options:');
249
+ logger_1.logger.debug(JSON.stringify({
250
+ ...requestOptions,
251
+ messages: requestOptions.messages
252
+ ? `${requestOptions.messages.length} messages`
253
+ : '0 messages',
254
+ }, null, 2));
255
+ // Handle streaming responses differently
256
+ if (requestOptions.stream && onChunk) {
257
+ return await this.handleStreamingResponse({ ...requestOptions, onChunk }, headers);
240
258
  }
241
- catch (requestError) {
242
- logger_1.logger.debug(`Request error: ${requestError instanceof Error ? requestError.message : String(requestError)}`);
243
- throw requestError;
259
+ else {
260
+ // Ensure model is always defined for the API call
261
+ const requestBody = {
262
+ ...requestOptions,
263
+ model: requestOptions.model || 'google/gemma-3-27b-it',
264
+ };
265
+ // Debug the headers being sent
266
+ logger_1.logger.debug('Headers being sent:');
267
+ logger_1.logger.debug(JSON.stringify(headers, null, 2));
268
+ const response = await this.client.POST('/v1/chat/completions', {
269
+ body: requestBody,
270
+ headers,
271
+ });
272
+ // Check if response has an error property
273
+ const responseAny = response;
274
+ if (responseAny && responseAny.error)
275
+ throw new Error(JSON.stringify(responseAny.error));
276
+ logger_1.logger.debug('API response:');
277
+ logger_1.logger.debug(JSON.stringify(response, null, 2));
278
+ // Output the complete response data for debugging
279
+ logger_1.logger.debug('Complete response data:');
280
+ logger_1.logger.debug(JSON.stringify(response.data, null, 2));
281
+ return response.data;
244
282
  }
245
- });
283
+ }
284
+ catch (requestError) {
285
+ logger_1.logger.debug(`Request error: ${requestError instanceof Error ? requestError.message : String(requestError)}`);
286
+ throw requestError;
287
+ }
246
288
  }
247
289
  /**
248
290
 
@@ -253,226 +295,167 @@ class ChatService {
253
295
  * @param headers Request headers
254
296
  * @returns A promise that resolves when the stream is complete
255
297
  */
256
- handleStreamingResponse(options, headers) {
257
- var _a, _b, _c, _d;
258
- return __awaiter(this, void 0, void 0, function* () {
259
- // Use the same base URL as the client
260
- const baseUrl = process.env.API_BASE_URL || "https://api.berget.ai";
261
- const url = new URL(`${baseUrl}/v1/chat/completions`);
262
- try {
263
- logger_1.logger.debug(`Making streaming request to: ${url.toString()}`);
264
- logger_1.logger.debug(`Headers:`, JSON.stringify(headers, null, 2));
265
- logger_1.logger.debug(`Body:`, JSON.stringify(options, null, 2));
266
- // Make fetch request directly to handle streaming
267
- const response = yield fetch(url.toString(), {
268
- method: "POST",
269
- headers: Object.assign({ "Content-Type": "application/json", Accept: "text/event-stream" }, headers),
270
- body: JSON.stringify(options),
271
- });
272
- logger_1.logger.debug(`Response status: ${response.status}`);
273
- logger_1.logger.debug(`Response headers:`, JSON.stringify(Object.fromEntries(response.headers.entries()), null, 2));
274
- if (!response.ok) {
275
- const errorText = yield response.text();
276
- logger_1.logger.error(`Stream request failed: ${response.status} ${response.statusText}`);
277
- logger_1.logger.error(`Error response: ${errorText}`);
278
- throw new Error(`Stream request failed: ${response.status} ${response.statusText} - ${errorText}`);
279
- }
280
- if (!response.body) {
281
- throw new Error("No response body received");
282
- }
283
- // Process the stream
284
- const reader = response.body.getReader();
285
- const decoder = new TextDecoder();
286
- let fullContent = "";
287
- let fullResponse = null;
288
- let buffer = ""; // Buffer to accumulate partial JSON data
289
- while (true) {
290
- const { done, value } = yield reader.read();
291
- if (done)
292
- break;
293
- const chunk = decoder.decode(value, { stream: true });
294
- logger_1.logger.debug(`Received chunk: ${chunk.length} bytes`);
295
- // Add chunk to buffer
296
- buffer += chunk;
297
- logger_1.logger.debug(`Added chunk to buffer. Buffer length: ${buffer.length}`);
298
- // Process the buffer - it may contain multiple SSE events
299
- const lines = buffer.split("\n");
300
- logger_1.logger.debug(`Processing ${lines.length} lines from buffer`);
301
- // Keep track of processed lines to update buffer
302
- let processedLines = 0;
303
- for (let i = 0; i < lines.length; i++) {
304
- const line = lines[i];
305
- logger_1.logger.debug(`Line ${i}: "${line}"`);
306
- if (line.startsWith("data:")) {
307
- const jsonData = line.slice(5).trim();
308
- logger_1.logger.debug(`Extracted JSON data: "${jsonData}"`);
309
- // Skip empty data or [DONE] marker
310
- if (jsonData === "" || jsonData === "[DONE]") {
311
- logger_1.logger.debug(`Skipping empty data or [DONE] marker`);
312
- processedLines = i + 1;
298
+ async handleStreamingResponse(options, headers) {
299
+ // Use the same base URL as the client
300
+ const baseUrl = process.env.API_BASE_URL || 'https://api.berget.ai';
301
+ const url = new URL(`${baseUrl}/v1/chat/completions`);
302
+ try {
303
+ logger_1.logger.debug(`Making streaming request to: ${url.toString()}`);
304
+ logger_1.logger.debug(`Headers:`, JSON.stringify(headers, null, 2));
305
+ logger_1.logger.debug(`Body:`, JSON.stringify(options, null, 2));
306
+ // Make fetch request directly to handle streaming
307
+ const response = await fetch(url.toString(), {
308
+ body: JSON.stringify(options),
309
+ headers: {
310
+ Accept: 'text/event-stream',
311
+ 'Content-Type': 'application/json',
312
+ ...headers,
313
+ },
314
+ method: 'POST',
315
+ });
316
+ logger_1.logger.debug(`Response status: ${response.status}`);
317
+ logger_1.logger.debug(`Response headers:`, JSON.stringify(Object.fromEntries(response.headers.entries()), null, 2));
318
+ if (!response.ok) {
319
+ const errorText = await response.text();
320
+ logger_1.logger.error(`Stream request failed: ${response.status} ${response.statusText}`);
321
+ logger_1.logger.error(`Error response: ${errorText}`);
322
+ throw new Error(`Stream request failed: ${response.status} ${response.statusText} - ${errorText}`);
323
+ }
324
+ if (!response.body) {
325
+ throw new Error('No response body received');
326
+ }
327
+ // Process the stream
328
+ const reader = response.body.getReader();
329
+ const decoder = new TextDecoder();
330
+ let fullContent = '';
331
+ let fullResponse = null;
332
+ let buffer = ''; // Buffer to accumulate partial JSON data
333
+ while (true) {
334
+ const { done, value } = await reader.read();
335
+ if (done)
336
+ break;
337
+ const chunk = decoder.decode(value, { stream: true });
338
+ logger_1.logger.debug(`Received chunk: ${chunk.length} bytes`);
339
+ // Add chunk to buffer
340
+ buffer += chunk;
341
+ logger_1.logger.debug(`Added chunk to buffer. Buffer length: ${buffer.length}`);
342
+ // Process the buffer - it may contain multiple SSE events
343
+ const lines = buffer.split('\n');
344
+ logger_1.logger.debug(`Processing ${lines.length} lines from buffer`);
345
+ // Keep track of processed lines to update buffer
346
+ let processedLines = 0;
347
+ for (const [index, line] of lines.entries()) {
348
+ logger_1.logger.debug(`Line ${index}: "${line}"`);
349
+ if (line.startsWith('data:')) {
350
+ const jsonData = line.slice(5).trim();
351
+ logger_1.logger.debug(`Extracted JSON data: "${jsonData}"`);
352
+ // Skip empty data or [DONE] marker
353
+ if (jsonData === '' || jsonData === '[DONE]') {
354
+ logger_1.logger.debug(`Skipping empty data or [DONE] marker`);
355
+ processedLines = index + 1;
356
+ continue;
357
+ }
358
+ // Check if JSON looks complete (basic validation)
359
+ if (!jsonData.startsWith('{')) {
360
+ logger_1.logger.warn(`JSON data doesn't start with '{', might be partial: "${jsonData.slice(0, 50)}..."`);
361
+ // Don't process this line yet, keep it in buffer
362
+ break;
363
+ }
364
+ // Count braces to check if JSON is complete
365
+ let braceCount = 0;
366
+ let inString = false;
367
+ let escaped = false;
368
+ for (const char of jsonData) {
369
+ if (escaped) {
370
+ escaped = false;
313
371
  continue;
314
372
  }
315
- // Check if JSON looks complete (basic validation)
316
- if (!jsonData.startsWith("{")) {
317
- logger_1.logger.warn(`JSON data doesn't start with '{', might be partial: "${jsonData.substring(0, 50)}..."`);
318
- // Don't process this line yet, keep it in buffer
319
- break;
373
+ if (char === '\\') {
374
+ escaped = true;
375
+ continue;
320
376
  }
321
- // Count braces to check if JSON is complete
322
- let braceCount = 0;
323
- let inString = false;
324
- let escaped = false;
325
- for (let j = 0; j < jsonData.length; j++) {
326
- const char = jsonData[j];
327
- if (escaped) {
328
- escaped = false;
329
- continue;
330
- }
331
- if (char === "\\") {
332
- escaped = true;
333
- continue;
334
- }
335
- if (char === '"') {
336
- inString = !inString;
337
- continue;
338
- }
339
- if (!inString && char === "{") {
340
- braceCount++;
341
- }
342
- else if (!inString && char === "}") {
343
- braceCount--;
344
- }
377
+ if (char === '"') {
378
+ inString = !inString;
379
+ continue;
380
+ }
381
+ if (!inString && char === '{') {
382
+ braceCount++;
383
+ }
384
+ else if (!inString && char === '}') {
385
+ braceCount--;
386
+ }
387
+ }
388
+ if (braceCount !== 0) {
389
+ logger_1.logger.warn(`JSON braces don't balance (${braceCount}), treating as partial: "${jsonData.slice(0, 50)}..."`);
390
+ // Don't process this line yet, keep it in buffer
391
+ break;
392
+ }
393
+ try {
394
+ logger_1.logger.debug(`Attempting to parse JSON of length: ${jsonData.length}`);
395
+ const parsedData = JSON.parse(jsonData);
396
+ logger_1.logger.debug(`Successfully parsed JSON: ${JSON.stringify(parsedData, null, 2)}`);
397
+ processedLines = index + 1; // Mark this line as processed
398
+ // Call the onChunk callback with the parsed data
399
+ if (options.onChunk) {
400
+ options.onChunk(parsedData);
345
401
  }
346
- if (braceCount !== 0) {
347
- logger_1.logger.warn(`JSON braces don't balance (${braceCount}), treating as partial: "${jsonData.substring(0, 50)}..."`);
348
- // Don't process this line yet, keep it in buffer
349
- break;
402
+ // Keep track of the full response
403
+ if (!fullResponse) {
404
+ fullResponse = parsedData;
350
405
  }
351
- try {
352
- logger_1.logger.debug(`Attempting to parse JSON of length: ${jsonData.length}`);
353
- const parsedData = JSON.parse(jsonData);
354
- logger_1.logger.debug(`Successfully parsed JSON: ${JSON.stringify(parsedData, null, 2)}`);
355
- processedLines = i + 1; // Mark this line as processed
356
- // Call the onChunk callback with the parsed data
357
- if (options.onChunk) {
358
- options.onChunk(parsedData);
359
- }
360
- // Keep track of the full response
361
- if (!fullResponse) {
362
- fullResponse = parsedData;
363
- }
364
- else if (parsedData.choices &&
365
- parsedData.choices[0] &&
366
- parsedData.choices[0].delta) {
367
- // Accumulate content for the full response
368
- if (parsedData.choices[0].delta.content) {
369
- fullContent += parsedData.choices[0].delta.content;
370
- }
371
- }
406
+ else if (parsedData.choices &&
407
+ parsedData.choices[0] &&
408
+ parsedData.choices[0].delta && // Accumulate content for the full response
409
+ parsedData.choices[0].delta.content) {
410
+ fullContent += parsedData.choices[0].delta.content;
372
411
  }
373
- catch (e) {
374
- logger_1.logger.error(`Error parsing chunk: ${e}`);
375
- 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"}`);
376
- logger_1.logger.error(`Problematic chunk length: ${jsonData.length}`);
377
- logger_1.logger.error(`Problematic chunk content: "${jsonData}"`);
378
- logger_1.logger.error(`Chunk starts with: "${jsonData.substring(0, 50)}..."`);
379
- logger_1.logger.error(`Chunk ends with: "...${jsonData.substring(jsonData.length - 50)}"`);
380
- // Show character codes around the error position
381
- 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");
382
- if (errorPos > 0) {
383
- const start = Math.max(0, errorPos - 20);
384
- const end = Math.min(jsonData.length, errorPos + 20);
385
- logger_1.logger.error(`Context around error position ${errorPos}:`);
386
- logger_1.logger.error(`"${jsonData.substring(start, end)}"`);
387
- logger_1.logger.error(`Character codes: ${Array.from(jsonData.substring(start, end))
388
- .map(c => c.charCodeAt(0))
389
- .join(" ")}`);
390
- }
412
+ }
413
+ catch (error) {
414
+ logger_1.logger.error(`Error parsing chunk: ${error}`);
415
+ logger_1.logger.error(`JSON parse error at position ${error.message?.match(/position (\d+)/)?.[1] || 'unknown'}`);
416
+ logger_1.logger.error(`Problematic chunk length: ${jsonData.length}`);
417
+ logger_1.logger.error(`Problematic chunk content: "${jsonData}"`);
418
+ logger_1.logger.error(`Chunk starts with: "${jsonData.slice(0, 50)}..."`);
419
+ logger_1.logger.error(`Chunk ends with: "...${jsonData.slice(Math.max(0, jsonData.length - 50))}"`);
420
+ // Show character codes around the error position
421
+ const errorPos = Number.parseInt(error.message?.match(/position (\d+)/)?.[1] || '0');
422
+ if (errorPos > 0) {
423
+ const start = Math.max(0, errorPos - 20);
424
+ const end = Math.min(jsonData.length, errorPos + 20);
425
+ logger_1.logger.error(`Context around error position ${errorPos}:`);
426
+ logger_1.logger.error(`"${jsonData.substring(start, end)}"`);
427
+ logger_1.logger.error(`Character codes: ${[...jsonData.substring(start, end)]
428
+ .map((c) => c.charCodeAt(0))
429
+ .join(' ')}`);
391
430
  }
392
431
  }
393
432
  }
394
- // Update buffer to only contain unprocessed lines
395
- if (processedLines > 0) {
396
- const remainingLines = lines.slice(processedLines);
397
- buffer = remainingLines.join("\n");
398
- logger_1.logger.debug(`Updated buffer. Remaining lines: ${remainingLines.length}, Buffer length: ${buffer.length}`);
399
- }
400
433
  }
401
- // Construct the final response object similar to non-streaming response
402
- if (fullResponse) {
403
- if (fullContent) {
404
- fullResponse.choices[0].message = {
405
- role: "assistant",
406
- content: fullContent,
407
- };
408
- }
409
- return fullResponse;
434
+ // Update buffer to only contain unprocessed lines
435
+ if (processedLines > 0) {
436
+ const remainingLines = lines.slice(processedLines);
437
+ buffer = remainingLines.join('\n');
438
+ logger_1.logger.debug(`Updated buffer. Remaining lines: ${remainingLines.length}, Buffer length: ${buffer.length}`);
410
439
  }
411
- return {
412
- choices: [{ message: { role: "assistant", content: fullContent } }],
413
- };
414
- }
415
- catch (error) {
416
- logger_1.logger.error(`Streaming error: ${error instanceof Error ? error.message : String(error)}`);
417
- throw error;
418
440
  }
419
- });
420
- }
421
- /**
422
- * List available models
423
- * Command: berget chat list
424
- */
425
- listModels(apiKey) {
426
- return __awaiter(this, void 0, void 0, function* () {
427
- try {
428
- // Check for environment variable first, then fallback to provided API key
429
- const envApiKey = process.env.BERGET_API_KEY;
430
- const effectiveApiKey = envApiKey || apiKey;
431
- if (effectiveApiKey) {
432
- const headers = {
433
- Authorization: effectiveApiKey,
441
+ // Construct the final response object similar to non-streaming response
442
+ if (fullResponse) {
443
+ if (fullContent) {
444
+ fullResponse.choices[0].message = {
445
+ content: fullContent,
446
+ role: 'assistant',
434
447
  };
435
- const { data, error } = yield this.client.GET("/v1/models", { headers });
436
- if (error)
437
- throw new Error(JSON.stringify(error));
438
- return data;
439
- }
440
- else {
441
- const { data, error } = yield this.client.GET("/v1/models");
442
- if (error)
443
- throw new Error(JSON.stringify(error));
444
- return data;
445
448
  }
449
+ return fullResponse;
446
450
  }
447
- catch (error) {
448
- // Improved error handling
449
- let errorMessage = "Failed to list models";
450
- if (error instanceof Error) {
451
- try {
452
- // Try to parse the error message as JSON
453
- const parsedError = JSON.parse(error.message);
454
- if (parsedError.error) {
455
- errorMessage = `Models error: ${typeof parsedError.error === "string"
456
- ? parsedError.error
457
- : parsedError.error.message || JSON.stringify(parsedError.error)}`;
458
- }
459
- }
460
- catch (_a) {
461
- // If parsing fails, use the original error message
462
- errorMessage = `Models error: ${error.message}`;
463
- }
464
- }
465
- logger_1.logger.error(errorMessage);
466
- throw new Error(errorMessage);
467
- }
468
- });
451
+ return {
452
+ choices: [{ message: { content: fullContent, role: 'assistant' } }],
453
+ };
454
+ }
455
+ catch (error) {
456
+ logger_1.logger.error(`Streaming error: ${error instanceof Error ? error.message : String(error)}`);
457
+ throw error;
458
+ }
469
459
  }
470
460
  }
471
461
  exports.ChatService = ChatService;
472
- // Command group name for this service
473
- ChatService.COMMAND_GROUP = "chat";
474
- // Subcommands for this service
475
- ChatService.COMMANDS = {
476
- RUN: "run",
477
- LIST: "list",
478
- };