@axplusb/kepler 2.3.1 → 2.3.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axplusb/kepler",
3
- "version": "2.3.1",
3
+ "version": "2.3.3",
4
4
  "description": "Kepler — AI coding agent with operating brief, preflight planning, and sub-agents. SWE-bench Lite evaluated.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,308 @@
1
+ function compact(value) {
2
+ return String(value || '').trim();
3
+ }
4
+
5
+ function lower(value) {
6
+ return compact(value).toLowerCase();
7
+ }
8
+
9
+ const PROVIDER_ALIASES = new Map([
10
+ ['openrouter', 'openrouter'],
11
+ ['openrouterv2', 'openrouter'],
12
+ ['openroutergateway', 'openrouter'],
13
+ ['openrouterv2gateway', 'openrouter'],
14
+ ['anthropic', 'anthropic'],
15
+ ['claude', 'anthropic'],
16
+ ['claudegateway', 'anthropic'],
17
+ ['openai', 'openai'],
18
+ ['openaigateway', 'openai'],
19
+ ['google', 'googleai'],
20
+ ['googleai', 'googleai'],
21
+ ['googlegateway', 'googleai'],
22
+ ['googleaigateway', 'googleai'],
23
+ ['gemini', 'googleai'],
24
+ ['azure', 'azureopenai'],
25
+ ['azureopenai', 'azureopenai'],
26
+ ['azureopenaigateway', 'azureopenai'],
27
+ ['bedrock', 'bedrock'],
28
+ ['aws', 'bedrock'],
29
+ ['bedrockgateway', 'bedrock'],
30
+ ['databricks', 'databricks'],
31
+ ['databricksgateway', 'databricks'],
32
+ ['custom', 'custom'],
33
+ ['byom', 'custom'],
34
+ ['openai-compatible', 'custom'],
35
+ ['deepseek', 'deepseek'],
36
+ ['deepseekgateway', 'deepseek'],
37
+ ['dashscope', 'dashscope'],
38
+ ['dashscopegateway', 'dashscope'],
39
+ ['qwen', 'dashscope'],
40
+ ['zhipu', 'zhipu'],
41
+ ['zhipugateway', 'zhipu'],
42
+ ['glm', 'zhipu'],
43
+ ['moonshot', 'moonshot'],
44
+ ['moonshotgateway', 'moonshot'],
45
+ ['kimi', 'moonshot'],
46
+ ['xai', 'xai'],
47
+ ['xaigateway', 'xai'],
48
+ ['grok', 'xai'],
49
+ ['mistral', 'mistral'],
50
+ ['mistralgateway', 'mistral'],
51
+ ]);
52
+
53
+ const PROVIDER_GUIDANCE = {
54
+ openrouter: {
55
+ label: 'OpenRouter',
56
+ lines: [
57
+ 'Check that your OpenRouter API key is saved and has enough credits/quota.',
58
+ 'Verify the selected model is available on OpenRouter and that provider routing is allowed for it.',
59
+ 'If settings were just updated, run /login or restart the CLI so provider settings sync again.',
60
+ ],
61
+ },
62
+ anthropic: {
63
+ label: 'Anthropic',
64
+ lines: [
65
+ 'Check that your Anthropic API key is saved and active.',
66
+ 'Verify the selected Claude model is enabled for your Anthropic account and region.',
67
+ 'If settings were just updated, run /login or restart the CLI so provider settings sync again.',
68
+ ],
69
+ },
70
+ openai: {
71
+ label: 'OpenAI',
72
+ lines: [
73
+ 'Check that your OpenAI API key is saved and has access to the selected model.',
74
+ 'Verify project billing, model permissions, and rate limits in your OpenAI account.',
75
+ 'If settings were just updated, run /login or restart the CLI so provider settings sync again.',
76
+ ],
77
+ },
78
+ googleai: {
79
+ label: 'Google AI',
80
+ lines: [
81
+ 'Check that your Google AI API key is saved and active.',
82
+ 'Verify the selected Gemini model is available for that key and region.',
83
+ 'If settings were just updated, run /login or restart the CLI so provider settings sync again.',
84
+ ],
85
+ },
86
+ azureopenai: {
87
+ label: 'Azure OpenAI',
88
+ lines: [
89
+ 'Check that Azure OpenAI API key, endpoint, API version, and deployment/model name are saved.',
90
+ 'Verify the deployment exists in Azure AI Foundry and the key has access to that resource.',
91
+ 'If settings were just updated, run /login or restart the CLI so provider settings sync again.',
92
+ ],
93
+ },
94
+ bedrock: {
95
+ label: 'AWS Bedrock',
96
+ lines: [
97
+ 'Check that AWS Access Key ID, Secret Access Key, optional Session Token, and Region are saved.',
98
+ 'Verify the IAM principal can call bedrock-runtime:InvokeModel for the selected model and region.',
99
+ 'If settings were just updated, run /login or restart the CLI so provider settings sync again.',
100
+ ],
101
+ },
102
+ databricks: {
103
+ label: 'Databricks',
104
+ lines: [
105
+ 'Check that your Databricks host/workspace URL and token are saved.',
106
+ 'Verify the selected serving endpoint exists and the token can query it.',
107
+ 'If settings were just updated, run /login or restart the CLI so provider settings sync again.',
108
+ ],
109
+ },
110
+ custom: {
111
+ label: 'custom OpenAI-compatible provider',
112
+ lines: [
113
+ 'Check that base URL, API key, and model name are saved for the custom provider.',
114
+ 'Verify the endpoint supports OpenAI-compatible chat completions for the selected model.',
115
+ 'If settings were just updated, run /login or restart the CLI so provider settings sync again.',
116
+ ],
117
+ },
118
+ deepseek: {
119
+ label: 'DeepSeek',
120
+ lines: [
121
+ 'Check that your DeepSeek API key is saved and active.',
122
+ 'Verify the selected DeepSeek model is available and your account has quota.',
123
+ 'If settings were just updated, run /login or restart the CLI so provider settings sync again.',
124
+ ],
125
+ },
126
+ dashscope: {
127
+ label: 'DashScope/Qwen',
128
+ lines: [
129
+ 'Check that your DashScope API key is saved and active.',
130
+ 'Verify the selected Qwen model is available for your account and region.',
131
+ 'If settings were just updated, run /login or restart the CLI so provider settings sync again.',
132
+ ],
133
+ },
134
+ zhipu: {
135
+ label: 'Zhipu/GLM',
136
+ lines: [
137
+ 'Check that your Zhipu API key is saved and active.',
138
+ 'Verify the selected GLM model is enabled and your account has quota.',
139
+ 'If settings were just updated, run /login or restart the CLI so provider settings sync again.',
140
+ ],
141
+ },
142
+ moonshot: {
143
+ label: 'Moonshot/Kimi',
144
+ lines: [
145
+ 'Check that your Moonshot API key is saved and active.',
146
+ 'Verify the selected Kimi model is enabled and your account has quota.',
147
+ 'If settings were just updated, run /login or restart the CLI so provider settings sync again.',
148
+ ],
149
+ },
150
+ xai: {
151
+ label: 'xAI',
152
+ lines: [
153
+ 'Check that your xAI API key is saved and active.',
154
+ 'Verify the selected Grok model is enabled and your account has quota.',
155
+ 'If settings were just updated, run /login or restart the CLI so provider settings sync again.',
156
+ ],
157
+ },
158
+ mistral: {
159
+ label: 'Mistral',
160
+ lines: [
161
+ 'Check that your Mistral API key is saved and active.',
162
+ 'Verify the selected Mistral model is enabled and your account has quota.',
163
+ 'If settings were just updated, run /login or restart the CLI so provider settings sync again.',
164
+ ],
165
+ },
166
+ };
167
+
168
+ export function isBedrockMissingCredentials(data = {}) {
169
+ const haystack = [
170
+ data.message,
171
+ data.error,
172
+ data.code,
173
+ data.phase,
174
+ data.provider,
175
+ data.gateway,
176
+ data.gateway_type,
177
+ data.exception_type,
178
+ ].map(lower).join(' ');
179
+
180
+ const mentionsBedrock = haystack.includes('bedrock');
181
+ const mentionsAwsCreds = haystack.includes('aws credentials')
182
+ || haystack.includes('aws credential')
183
+ || haystack.includes('aws_access_key')
184
+ || haystack.includes('aws_secret')
185
+ || haystack.includes('access key')
186
+ || haystack.includes('secret access key');
187
+
188
+ return mentionsBedrock && mentionsAwsCreds;
189
+ }
190
+
191
+ export function normalizeGatewayProvider(data = {}) {
192
+ const direct = [
193
+ data.provider,
194
+ data.gateway,
195
+ data.gateway_type,
196
+ data.gatewayType,
197
+ ].map(normalizeProviderToken).find(Boolean);
198
+ if (direct) return direct;
199
+
200
+ const haystack = [
201
+ data.message,
202
+ data.error,
203
+ data.code,
204
+ data.phase,
205
+ data.operation,
206
+ data.exception_type,
207
+ data.model,
208
+ ].map(lower).join(' ');
209
+
210
+ for (const [alias, provider] of PROVIDER_ALIASES.entries()) {
211
+ if (haystack.includes(alias)) return provider;
212
+ }
213
+ return '';
214
+ }
215
+
216
+ export function formatAgentErrorGuidance(data = {}) {
217
+ const message = compact(data.message || data.error || 'Agent execution failed.');
218
+ const code = compact(data.code);
219
+ const phase = compact(data.phase);
220
+ const provider = normalizeGatewayProvider(data);
221
+ const providerMeta = provider || compact(data.provider || data.gateway || data.gateway_type);
222
+ const taskId = compact(data.task_id);
223
+ const retryAfter = data.retry_after != null ? compact(data.retry_after) : '';
224
+ const retryLabel = retryAfter ? formatRetryAfterLabel(retryAfter) : '';
225
+ const retryable = data.retryable === true;
226
+
227
+ if (isBedrockMissingCredentials(data)) {
228
+ return {
229
+ title: 'AWS Bedrock credentials are missing.',
230
+ lines: [
231
+ 'Kepler reached the Bedrock gateway, but the backend did not receive AWS Access Key ID and Secret Access Key.',
232
+ 'Open Kepler/AppStak model settings, re-save the AWS Bedrock provider with Access Key ID, Secret Access Key, and Region, then retry.',
233
+ 'If settings were just updated, run /login or restart the CLI so provider settings sync again.',
234
+ ],
235
+ meta: buildMeta({ code, phase, provider: provider || 'bedrock', taskId, retryAfter, retryable }),
236
+ };
237
+ }
238
+
239
+ const lines = [];
240
+ const providerGuidance = PROVIDER_GUIDANCE[provider];
241
+ if (code === 'credit_balance_exhausted') {
242
+ lines.push('Add credits or upgrade your plan to continue using platform-hosted models.');
243
+ lines.push('If you have your own provider key, switch to BYOK in Settings to keep working without Kepler credit charges.');
244
+ if (data.pricing_url) lines.push(`Open ${data.pricing_url} to top up or compare plans.`);
245
+ } else if (code === 'message_limit_reached') {
246
+ const retry = retryLabel ? ` Wait ${retryLabel} for the rolling window to recover.` : ' Wait for the rolling message window to reset.';
247
+ lines.push(`You reached the message limit for this plan.${retry}`);
248
+ lines.push('Upgrade your plan for a larger 5-hour message window, or switch to BYOK if available.');
249
+ if (data.pricing_url) lines.push(`Open ${data.pricing_url} to upgrade.`);
250
+ } else if (phase === 'gateway' || code.includes('gateway')) {
251
+ const label = providerGuidance?.label || 'provider';
252
+ lines.push(`The ${label} gateway failed before the agent could respond.`);
253
+ if (providerGuidance) {
254
+ lines.push(...providerGuidance.lines);
255
+ } else {
256
+ lines.push('Check the selected provider, model, and BYOK credentials in settings, then retry.');
257
+ }
258
+ } else if (/authentication|token/i.test(message)) {
259
+ lines.push('Run /login to re-authenticate.');
260
+ } else if (/api key|openrouter/i.test(message)) {
261
+ lines.push('Run /config to set up or refresh your provider settings.');
262
+ } else if (/backend|network/i.test(message)) {
263
+ lines.push('Check that the backend is reachable, then retry.');
264
+ }
265
+
266
+ if (retryable && retryAfter) {
267
+ lines.push(`This looks retryable after ${retryAfter}s.`);
268
+ } else if (retryable) {
269
+ lines.push('This looks retryable.');
270
+ }
271
+
272
+ return {
273
+ title: message,
274
+ lines,
275
+ meta: buildMeta({ code, phase, provider: providerMeta, taskId, retryAfter, retryable }),
276
+ };
277
+ }
278
+
279
+ function formatRetryAfterLabel(value) {
280
+ const secs = Math.max(0, Math.ceil(Number(value) || 0));
281
+ if (!secs) return '';
282
+ const hours = Math.floor(secs / 3600);
283
+ const minutes = Math.ceil((secs % 3600) / 60);
284
+ if (hours > 0 && minutes > 0) return `${hours}h ${minutes}m`;
285
+ if (hours > 0) return `${hours}h`;
286
+ if (minutes > 0) return `${minutes}m`;
287
+ return `${secs}s`;
288
+ }
289
+
290
+ function normalizeProviderToken(value) {
291
+ const raw = lower(value);
292
+ if (!raw) return '';
293
+ const compacted = raw.replace(/[^a-z0-9]/g, '');
294
+ return PROVIDER_ALIASES.get(raw)
295
+ || PROVIDER_ALIASES.get(compacted)
296
+ || '';
297
+ }
298
+
299
+ function buildMeta({ code, phase, provider, taskId, retryAfter, retryable }) {
300
+ const parts = [];
301
+ if (provider) parts.push(`provider=${provider}`);
302
+ if (phase) parts.push(`phase=${phase}`);
303
+ if (code) parts.push(`code=${code}`);
304
+ if (retryable) parts.push(`retryable=true`);
305
+ if (retryAfter) parts.push(`retry_after=${retryAfter}s`);
306
+ if (taskId) parts.push(`task=${taskId}`);
307
+ return parts;
308
+ }
@@ -83,13 +83,28 @@ export function lowWindowStatus(rateLimit) {
83
83
  }
84
84
 
85
85
  export function rateLimitErrorMessage(payload, fallback = 'Message limit reached.') {
86
- const detail = payload?.detail && typeof payload.detail === 'object' ? payload.detail : payload;
86
+ const detail = quotaErrorDetail(payload);
87
87
  const retryAfter = detail?.retry_after ?? detail?.rate_limit?.retry_after ?? detail?.rate_limit?.retry_after_seconds;
88
+ if (detail?.code === 'credit_balance_exhausted') {
89
+ return detail.message || 'Credit balance exhausted — add credits, upgrade your plan, or switch to BYOK in Settings.';
90
+ }
91
+ if (detail?.code === 'message_limit_reached') {
92
+ if (detail.message) return detail.message;
93
+ if (retryAfter != null) return `Message window exhausted — try again in ${formatRetryAfter(retryAfter)}, or upgrade your plan.`;
94
+ return 'Message window exhausted — wait for the window to reset or upgrade your plan.';
95
+ }
88
96
  if (detail?.message) return detail.message;
89
- if (retryAfter != null) return `Message limit reached — try again in ${formatRetryAfter(retryAfter)}.`;
97
+ if (retryAfter != null) return `Message window exhausted — try again in ${formatRetryAfter(retryAfter)}, or upgrade your plan.`;
90
98
  return fallback;
91
99
  }
92
100
 
101
+ export function quotaErrorDetail(payload) {
102
+ if (!payload || typeof payload !== 'object') return {};
103
+ if (typeof payload.detail === 'string') return { message: payload.detail };
104
+ if (payload.detail && typeof payload.detail === 'object') return payload.detail;
105
+ return payload;
106
+ }
107
+
93
108
  function numberOrNull(value) {
94
109
  if (value == null) return null;
95
110
  const n = Number(value);
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { sendCallback, sendSkippedCallback, sendApprovalDecision } from './callback-client.mjs';
13
13
  import { ApprovalManager } from './approval.mjs';
14
- import { rateLimitErrorMessage } from './rate-limit-display.mjs';
14
+ import { quotaErrorDetail, rateLimitErrorMessage } from './rate-limit-display.mjs';
15
15
 
16
16
  export const EVENT_TYPES = Object.freeze({
17
17
  // Phase 1 — handled
@@ -134,7 +134,7 @@ export class TarangStreamClient {
134
134
  } catch {
135
135
  payload = { detail: { message: text } };
136
136
  }
137
- const detail = payload?.detail && typeof payload.detail === 'object' ? payload.detail : payload;
137
+ const detail = quotaErrorDetail(payload);
138
138
  yield {
139
139
  type: EVENT_TYPES.ERROR,
140
140
  data: {
@@ -142,6 +142,8 @@ export class TarangStreamClient {
142
142
  code: detail?.code || 'rate_limited',
143
143
  retry_after: detail?.retry_after ?? detail?.rate_limit?.retry_after,
144
144
  rate_limit: detail?.rate_limit || null,
145
+ action: detail?.action || null,
146
+ pricing_url: detail?.pricing_url || null,
145
147
  fatal: true,
146
148
  },
147
149
  };
@@ -40,6 +40,7 @@ import { TarangAuth } from '../auth/tarang-auth.mjs';
40
40
  import { ApprovalManager } from '../core/approval.mjs';
41
41
  import { resolveBackendUrl } from '../core/backend-url.mjs';
42
42
  import { formatMessageWindow, lowWindowStatus, messagesRemaining } from '../core/rate-limit-display.mjs';
43
+ import { formatAgentErrorGuidance } from '../core/error-guidance.mjs';
43
44
  import { BUILTIN_AGENTS, runAgent } from './agents.mjs';
44
45
  import { SessionManager } from '../core/session-manager.mjs';
45
46
  import { parseArgs } from '../config/cli-args.mjs';
@@ -1908,9 +1909,15 @@ function renderEvent(event) {
1908
1909
  case 'error':
1909
1910
  stopSpinner();
1910
1911
  flushContent();
1911
- process.stderr.write(`\n ${c.red('✗')} ${data?.message || 'Unknown error'}\n`);
1912
- if ((data?.message || '').includes('Authentication')) {
1913
- process.stderr.write(` ${c.dim('Run /login to re-authenticate')}\n`);
1912
+ {
1913
+ const guidance = formatAgentErrorGuidance(data || {});
1914
+ process.stderr.write(`\n ${c.red('')} ${guidance.title}\n`);
1915
+ for (const line of guidance.lines) {
1916
+ process.stderr.write(` ${c.dim(line)}\n`);
1917
+ }
1918
+ if (guidance.meta.length) {
1919
+ process.stderr.write(` ${c.dim(guidance.meta.join(' · '))}\n`);
1920
+ }
1914
1921
  }
1915
1922
  break;
1916
1923
 
@@ -10,6 +10,7 @@
10
10
 
11
11
  import { toolDisplayLabel, toolDisplaySummary } from '../terminal/tool-display.mjs';
12
12
  import { formatMessageWindow } from '../core/rate-limit-display.mjs';
13
+ import { formatAgentErrorGuidance } from '../core/error-guidance.mjs';
13
14
 
14
15
  const RESET = '\x1b[0m';
15
16
  const BOLD = '\x1b[1m';
@@ -309,16 +310,13 @@ export class EventFormatter {
309
310
  }
310
311
 
311
312
  _error(data) {
312
- const msg = data?.message || 'Unknown error';
313
- process.stderr.write(`\n ${RED}✗ ${msg}${RESET}\n`);
314
-
315
- // Helpful suggestions for common errors
316
- if (msg.includes('Authentication') || msg.includes('token')) {
317
- process.stderr.write(` ${DIM}Run /login to re-authenticate${RESET}\n`);
318
- } else if (msg.includes('API key') || msg.includes('OpenRouter')) {
319
- process.stderr.write(` ${DIM}Run /config to set up your provider${RESET}\n`);
320
- } else if (msg.includes('Backend') || msg.includes('Network')) {
321
- process.stderr.write(` ${DIM}Check if the backend is running at ${this.sessionInfo?.backend || 'localhost:8150'}${RESET}\n`);
313
+ const guidance = formatAgentErrorGuidance(data || {});
314
+ process.stderr.write(`\n ${RED}✗ ${guidance.title}${RESET}\n`);
315
+ for (const line of guidance.lines) {
316
+ process.stderr.write(` ${DIM}${line}${RESET}\n`);
317
+ }
318
+ if (guidance.meta.length) {
319
+ process.stderr.write(` ${DIM}${guidance.meta.join(' · ')}${RESET}\n`);
322
320
  }
323
321
  }
324
322