myshell-tools 1.0.0 → 2.0.0

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 (153) hide show
  1. package/CHANGELOG.md +44 -69
  2. package/LICENSE +21 -21
  3. package/README.md +178 -318
  4. package/dist/cli.d.ts +8 -0
  5. package/dist/cli.js +130 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/cost.d.ts +36 -0
  8. package/dist/commands/cost.js +103 -0
  9. package/dist/commands/cost.js.map +1 -0
  10. package/dist/commands/doctor.d.ts +36 -0
  11. package/dist/commands/doctor.js +115 -0
  12. package/dist/commands/doctor.js.map +1 -0
  13. package/dist/commands/login.d.ts +20 -0
  14. package/dist/commands/login.js +60 -0
  15. package/dist/commands/login.js.map +1 -0
  16. package/dist/core/assess.d.ts +25 -0
  17. package/dist/core/assess.js +142 -0
  18. package/dist/core/assess.js.map +1 -0
  19. package/dist/core/classify.d.ts +19 -0
  20. package/dist/core/classify.js +80 -0
  21. package/dist/core/classify.js.map +1 -0
  22. package/dist/core/escalate.d.ts +32 -0
  23. package/dist/core/escalate.js +57 -0
  24. package/dist/core/escalate.js.map +1 -0
  25. package/dist/core/index.d.ts +13 -0
  26. package/dist/core/index.js +12 -0
  27. package/dist/core/index.js.map +1 -0
  28. package/dist/core/orchestrate.d.ts +42 -0
  29. package/dist/core/orchestrate.js +439 -0
  30. package/dist/core/orchestrate.js.map +1 -0
  31. package/dist/core/policy.d.ts +9 -0
  32. package/dist/core/policy.js +27 -0
  33. package/dist/core/policy.js.map +1 -0
  34. package/dist/core/prompt.d.ts +26 -0
  35. package/dist/core/prompt.js +125 -0
  36. package/dist/core/prompt.js.map +1 -0
  37. package/dist/core/review.d.ts +46 -0
  38. package/dist/core/review.js +148 -0
  39. package/dist/core/review.js.map +1 -0
  40. package/dist/core/route.d.ts +28 -0
  41. package/dist/core/route.js +52 -0
  42. package/dist/core/route.js.map +1 -0
  43. package/dist/core/types.d.ts +141 -0
  44. package/dist/core/types.js +14 -0
  45. package/dist/core/types.js.map +1 -0
  46. package/dist/infra/atomic.d.ts +53 -0
  47. package/dist/infra/atomic.js +171 -0
  48. package/dist/infra/atomic.js.map +1 -0
  49. package/dist/infra/clock.d.ts +9 -0
  50. package/dist/infra/clock.js +15 -0
  51. package/dist/infra/clock.js.map +1 -0
  52. package/dist/infra/index.d.ts +9 -0
  53. package/dist/infra/index.js +7 -0
  54. package/dist/infra/index.js.map +1 -0
  55. package/dist/infra/ledger.d.ts +49 -0
  56. package/dist/infra/ledger.js +90 -0
  57. package/dist/infra/ledger.js.map +1 -0
  58. package/dist/infra/paths.d.ts +28 -0
  59. package/dist/infra/paths.js +38 -0
  60. package/dist/infra/paths.js.map +1 -0
  61. package/dist/infra/pricing.d.ts +47 -0
  62. package/dist/infra/pricing.js +151 -0
  63. package/dist/infra/pricing.js.map +1 -0
  64. package/dist/infra/session.d.ts +28 -0
  65. package/dist/infra/session.js +61 -0
  66. package/dist/infra/session.js.map +1 -0
  67. package/dist/interface/render.d.ts +27 -0
  68. package/dist/interface/render.js +134 -0
  69. package/dist/interface/render.js.map +1 -0
  70. package/dist/interface/repl.d.ts +23 -0
  71. package/dist/interface/repl.js +90 -0
  72. package/dist/interface/repl.js.map +1 -0
  73. package/dist/interface/run.d.ts +20 -0
  74. package/dist/interface/run.js +31 -0
  75. package/dist/interface/run.js.map +1 -0
  76. package/dist/providers/claude-parse.d.ts +24 -0
  77. package/dist/providers/claude-parse.js +113 -0
  78. package/dist/providers/claude-parse.js.map +1 -0
  79. package/dist/providers/claude.d.ts +45 -0
  80. package/dist/providers/claude.js +122 -0
  81. package/dist/providers/claude.js.map +1 -0
  82. package/dist/providers/codex-parse.d.ts +32 -0
  83. package/dist/providers/codex-parse.js +145 -0
  84. package/dist/providers/codex-parse.js.map +1 -0
  85. package/dist/providers/codex.d.ts +44 -0
  86. package/dist/providers/codex.js +124 -0
  87. package/dist/providers/codex.js.map +1 -0
  88. package/dist/providers/detect.d.ts +49 -0
  89. package/dist/providers/detect.js +125 -0
  90. package/dist/providers/detect.js.map +1 -0
  91. package/dist/providers/errors.d.ts +49 -0
  92. package/dist/providers/errors.js +189 -0
  93. package/dist/providers/errors.js.map +1 -0
  94. package/dist/providers/index.d.ts +9 -0
  95. package/dist/providers/index.js +7 -0
  96. package/dist/providers/index.js.map +1 -0
  97. package/dist/providers/port.d.ts +74 -0
  98. package/dist/providers/port.js +16 -0
  99. package/dist/providers/port.js.map +1 -0
  100. package/dist/providers/registry.d.ts +21 -0
  101. package/dist/providers/registry.js +34 -0
  102. package/dist/providers/registry.js.map +1 -0
  103. package/dist/ui/banner.d.ts +19 -0
  104. package/dist/ui/banner.js +32 -0
  105. package/dist/ui/banner.js.map +1 -0
  106. package/dist/ui/spinner.d.ts +27 -0
  107. package/dist/ui/spinner.js +67 -0
  108. package/dist/ui/spinner.js.map +1 -0
  109. package/dist/ui/theme.d.ts +32 -0
  110. package/dist/ui/theme.js +56 -0
  111. package/dist/ui/theme.js.map +1 -0
  112. package/package.json +55 -49
  113. package/data/orchestrator.json +0 -113
  114. package/src/auth/recovery.mjs +0 -328
  115. package/src/auth/refresh.mjs +0 -373
  116. package/src/chef.mjs +0 -348
  117. package/src/cli/doctor.mjs +0 -568
  118. package/src/cli/reset.mjs +0 -447
  119. package/src/cli/status.mjs +0 -379
  120. package/src/cli.mjs +0 -429
  121. package/src/commands/doctor.mjs +0 -375
  122. package/src/commands/help.mjs +0 -324
  123. package/src/commands/status.mjs +0 -331
  124. package/src/monitor/health.mjs +0 -486
  125. package/src/monitor/performance.mjs +0 -442
  126. package/src/monitor/report.mjs +0 -535
  127. package/src/orchestrator/classify.mjs +0 -391
  128. package/src/orchestrator/confidence.mjs +0 -151
  129. package/src/orchestrator/handoffs.mjs +0 -231
  130. package/src/orchestrator/review.mjs +0 -222
  131. package/src/providers/balance.mjs +0 -201
  132. package/src/providers/claude.mjs +0 -236
  133. package/src/providers/codex.mjs +0 -255
  134. package/src/providers/detect.mjs +0 -185
  135. package/src/providers/errors.mjs +0 -373
  136. package/src/providers/select.mjs +0 -162
  137. package/src/repl-enhanced.mjs +0 -417
  138. package/src/repl.mjs +0 -321
  139. package/src/state/archive.mjs +0 -366
  140. package/src/state/atomic.mjs +0 -116
  141. package/src/state/cleanup.mjs +0 -440
  142. package/src/state/recovery.mjs +0 -461
  143. package/src/state/session.mjs +0 -147
  144. package/src/ui/errors.mjs +0 -456
  145. package/src/ui/formatter.mjs +0 -327
  146. package/src/ui/icons.mjs +0 -318
  147. package/src/ui/progress.mjs +0 -468
  148. package/templates/prompts/confidence-format.txt +0 -14
  149. package/templates/prompts/ic-with-feedback.txt +0 -41
  150. package/templates/prompts/ic.txt +0 -13
  151. package/templates/prompts/manager-review.txt +0 -40
  152. package/templates/prompts/manager.txt +0 -14
  153. package/templates/prompts/worker.txt +0 -12
@@ -1,373 +0,0 @@
1
- /**
2
- * errors.mjs — Comprehensive error handling and recovery for provider operations
3
- */
4
-
5
- import { spawnSync } from 'child_process';
6
- import { handleAuthFailure, getRecoverySuggestions } from '../auth/recovery.mjs';
7
-
8
- /**
9
- * Custom error class for CLI operations
10
- */
11
- export class CliError extends Error {
12
- constructor(message, details = {}) {
13
- super(message);
14
- this.name = 'CliError';
15
- this.details = details;
16
- this.isRecoverable = details.isRecoverable || false;
17
- this.provider = details.provider;
18
- this.command = details.command;
19
- this.args = details.args;
20
- this.exitCode = details.exitCode;
21
- this.stderr = details.stderr;
22
- this.originalError = details.originalError;
23
- }
24
- }
25
-
26
- /**
27
- * Detailed error for subprocess failures
28
- */
29
- export class DetailedCliError extends CliError {
30
- constructor(error, command, args, provider) {
31
- const details = {
32
- command,
33
- args: args?.slice() || [],
34
- provider,
35
- originalError: error,
36
- isRecoverable: isRecoverableError(error),
37
- exitCode: error.status || error.code || -1,
38
- stderr: error.stderr || error.message || ''
39
- };
40
-
41
- let message = `${provider?.toUpperCase() || 'CLI'} command failed`;
42
-
43
- if (command && args) {
44
- message += `: ${command} ${args.join(' ')}`;
45
- }
46
-
47
- if (error.message) {
48
- message += ` - ${error.message}`;
49
- }
50
-
51
- super(message, details);
52
- }
53
- }
54
-
55
- /**
56
- * Check if an error is recoverable with retries
57
- */
58
- export function isRecoverableError(error) {
59
- if (!error) return false;
60
-
61
- const errorString = error.toString().toLowerCase();
62
- const stderr = error.stderr?.toLowerCase() || '';
63
- const combined = errorString + ' ' + stderr;
64
-
65
- // Network-related errors (recoverable)
66
- if (combined.includes('timeout') ||
67
- combined.includes('network') ||
68
- combined.includes('enotfound') ||
69
- combined.includes('econnreset') ||
70
- combined.includes('econnrefused')) {
71
- return true;
72
- }
73
-
74
- // Temporary server issues (recoverable)
75
- if (combined.includes('502') ||
76
- combined.includes('503') ||
77
- combined.includes('504') ||
78
- combined.includes('internal server error')) {
79
- return true;
80
- }
81
-
82
- // Rate limiting (recoverable with delay)
83
- if (combined.includes('rate limit') ||
84
- combined.includes('429') ||
85
- combined.includes('too many requests')) {
86
- return true;
87
- }
88
-
89
- // Temporary auth token issues (recoverable with refresh)
90
- if (combined.includes('token expired') ||
91
- combined.includes('invalid token') ||
92
- combined.includes('token not found')) {
93
- return true;
94
- }
95
-
96
- // Process spawning issues (sometimes recoverable)
97
- if (error.code === 'EAGAIN' || error.code === 'EMFILE') {
98
- return true;
99
- }
100
-
101
- return false;
102
- }
103
-
104
- /**
105
- * Sleep with jitter for backoff
106
- */
107
- function sleep(ms) {
108
- const jitter = Math.floor(Math.random() * (ms * 0.1)); // 10% jitter
109
- return new Promise(resolve => setTimeout(resolve, ms + jitter));
110
- }
111
-
112
- /**
113
- * Robust subprocess execution with retry and recovery
114
- */
115
- export async function executeWithRecovery(command, args, options = {}) {
116
- const {
117
- provider,
118
- maxRetries = 3,
119
- timeoutMs = 120000,
120
- backoffMs = [1000, 2000, 4000],
121
- cwd = process.cwd(),
122
- onRetry
123
- } = options;
124
-
125
- let lastError;
126
-
127
- for (let attempt = 0; attempt < maxRetries; attempt++) {
128
- try {
129
- const proc = spawnSync(command, args, {
130
- encoding: 'utf8',
131
- stdio: ['pipe', 'pipe', 'pipe'],
132
- timeout: timeoutMs,
133
- cwd,
134
- });
135
-
136
- // Success case
137
- if (proc.status === 0) {
138
- return proc;
139
- }
140
-
141
- // Create detailed error for failed command
142
- const error = new Error(`Process exited with code ${proc.status}`);
143
- error.status = proc.status;
144
- error.stderr = proc.stderr;
145
- lastError = new DetailedCliError(error, command, args, provider);
146
-
147
- // Check if this is an auth error
148
- if (isAuthError(proc.stderr)) {
149
- const recovery = await handleAuthFailure(provider, proc.stderr);
150
- if (recovery.recovered) {
151
- continue; // Retry after successful recovery
152
- }
153
- }
154
-
155
- // Check if recoverable
156
- if (!isRecoverableError(lastError)) {
157
- throw lastError;
158
- }
159
-
160
- // Backoff before retry
161
- if (attempt < maxRetries - 1) {
162
- const backoff = backoffMs[Math.min(attempt, backoffMs.length - 1)];
163
- if (onRetry) {
164
- onRetry(attempt + 1, lastError, backoff);
165
- }
166
- await sleep(backoff);
167
- }
168
-
169
- } catch (spawnError) {
170
- lastError = new DetailedCliError(spawnError, command, args, provider);
171
-
172
- // Handle specific spawn errors
173
- if (spawnError.code === 'ENOENT') {
174
- // CLI not found - not recoverable with retries
175
- throw lastError;
176
- }
177
-
178
- if (!isRecoverableError(spawnError) || attempt === maxRetries - 1) {
179
- throw lastError;
180
- }
181
-
182
- // Retry for recoverable spawn errors
183
- if (attempt < maxRetries - 1) {
184
- const backoff = backoffMs[Math.min(attempt, backoffMs.length - 1)];
185
- if (onRetry) {
186
- onRetry(attempt + 1, lastError, backoff);
187
- }
188
- await sleep(backoff);
189
- }
190
- }
191
- }
192
-
193
- throw lastError;
194
- }
195
-
196
- /**
197
- * Check if error is authentication-related
198
- */
199
- function isAuthError(stderr) {
200
- if (!stderr) return false;
201
- const lower = stderr.toLowerCase();
202
-
203
- return lower.includes('authentication') ||
204
- lower.includes('unauthorized') ||
205
- lower.includes('401') ||
206
- lower.includes('invalid credentials') ||
207
- lower.includes('login required') ||
208
- lower.includes('access denied') ||
209
- lower.includes('forbidden') ||
210
- lower.includes('token') && (lower.includes('expired') || lower.includes('invalid'));
211
- }
212
-
213
- /**
214
- * Check if error is a rate limit
215
- */
216
- function isRateLimitError(stderr) {
217
- if (!stderr) return false;
218
- const lower = stderr.toLowerCase();
219
-
220
- return lower.includes('rate limit') ||
221
- lower.includes('429') ||
222
- lower.includes('too many requests') ||
223
- lower.includes('quota') && lower.includes('exceeded');
224
- }
225
-
226
- /**
227
- * Parse and classify CLI output for better error handling
228
- */
229
- export function parseCliOutput(stdout, stderr, exitCode) {
230
- const result = {
231
- success: exitCode === 0,
232
- output: stdout || '',
233
- stderr: stderr || '',
234
- exitCode,
235
- error: null,
236
- errorType: null,
237
- isRecoverable: false,
238
- suggestions: []
239
- };
240
-
241
- if (exitCode !== 0 || stderr) {
242
- const errorText = stderr || 'Process failed';
243
- result.error = errorText;
244
- result.isRecoverable = isRecoverableError({ stderr, status: exitCode });
245
-
246
- // Classify error type
247
- if (isAuthError(stderr)) {
248
- result.errorType = 'auth';
249
- result.suggestions.push('Re-authenticate with the CLI');
250
- } else if (isRateLimitError(stderr)) {
251
- result.errorType = 'rate_limit';
252
- result.suggestions.push('Wait before retrying');
253
- result.suggestions.push('Consider using a different model tier');
254
- } else if (stderr.toLowerCase().includes('timeout')) {
255
- result.errorType = 'timeout';
256
- result.suggestions.push('Increase timeout or simplify the request');
257
- } else if (stderr.toLowerCase().includes('network')) {
258
- result.errorType = 'network';
259
- result.suggestions.push('Check internet connection');
260
- } else if (exitCode === 127 || stderr.toLowerCase().includes('command not found')) {
261
- result.errorType = 'cli_missing';
262
- result.suggestions.push('Install the required CLI tool');
263
- } else {
264
- result.errorType = 'unknown';
265
- result.suggestions = getRecoverySuggestions(stderr);
266
- }
267
- }
268
-
269
- return result;
270
- }
271
-
272
- /**
273
- * Create user-friendly error message
274
- */
275
- export function createFriendlyErrorMessage(error, provider) {
276
- let message = `❌ ${provider?.toUpperCase() || 'CLI'} Error`;
277
-
278
- if (error instanceof CliError) {
279
- switch (error.details.errorType || 'unknown') {
280
- case 'cli_missing':
281
- message += '\n\n📦 CLI not installed or not found in PATH';
282
- if (provider === 'claude') {
283
- message += '\n Install: pip install anthropic-cli';
284
- message += '\n Then: claude auth login';
285
- } else if (provider === 'codex') {
286
- message += '\n Install: npm install -g @openai/codex';
287
- message += '\n Then: codex login';
288
- }
289
- break;
290
-
291
- case 'auth':
292
- message += '\n\n🔐 Authentication failed';
293
- message += `\n Run: ${provider === 'claude' ? 'claude auth login' : 'codex login'}`;
294
- break;
295
-
296
- case 'rate_limit':
297
- message += '\n\n⏱️ Rate limit exceeded';
298
- message += '\n Wait a few minutes before retrying';
299
- message += '\n Consider using a different model tier';
300
- break;
301
-
302
- case 'timeout':
303
- message += '\n\n⏰ Request timed out';
304
- message += '\n Try simplifying your request or increasing timeout';
305
- break;
306
-
307
- case 'network':
308
- message += '\n\n🌐 Network connectivity issue';
309
- message += '\n Check your internet connection and try again';
310
- break;
311
-
312
- default:
313
- message += '\n\n💥 Unexpected error occurred';
314
- if (error.message) {
315
- message += `\n ${error.message}`;
316
- }
317
- }
318
-
319
- if (error.details.suggestions?.length > 0) {
320
- message += '\n\n💡 Suggestions:';
321
- for (const suggestion of error.details.suggestions) {
322
- message += `\n • ${suggestion}`;
323
- }
324
- }
325
-
326
- if (error.details.isRecoverable) {
327
- message += '\n\n🔄 This error might be temporary. Cortex will retry automatically.';
328
- }
329
-
330
- } else {
331
- message += `\n ${error.message || 'Unknown error occurred'}`;
332
- }
333
-
334
- return message;
335
- }
336
-
337
- /**
338
- * Graceful error handling wrapper for provider functions
339
- */
340
- export function withErrorHandling(fn, provider) {
341
- return async (...args) => {
342
- try {
343
- return await fn(...args);
344
- } catch (error) {
345
- if (error instanceof CliError) {
346
- throw error; // Already handled
347
- }
348
-
349
- // Convert generic errors to CLI errors
350
- const cliError = new CliError(error.message || 'Operation failed', {
351
- provider,
352
- originalError: error,
353
- isRecoverable: isRecoverableError(error)
354
- });
355
-
356
- throw cliError;
357
- }
358
- };
359
- }
360
-
361
- /**
362
- * Progress callback for retries
363
- */
364
- export function defaultRetryCallback(attempt, error, backoffMs) {
365
- const provider = error.provider?.toUpperCase() || 'CLI';
366
- console.log(`⚠️ ${provider} attempt ${attempt} failed, retrying in ${backoffMs}ms...`);
367
-
368
- if (error.details?.errorType === 'rate_limit') {
369
- console.log(' Rate limit detected, backing off...');
370
- } else if (error.details?.errorType === 'network') {
371
- console.log(' Network issue detected, retrying...');
372
- }
373
- }
@@ -1,162 +0,0 @@
1
- /**
2
- * select.mjs — Intelligent provider selection and load balancing
3
- */
4
-
5
- import { getRecentHandoffs, getHandoffStats } from '../orchestrator/handoffs.mjs';
6
-
7
- /**
8
- * Select the best provider for a task based on load balancing and context
9
- */
10
- export function selectProvider(tier, context = {}) {
11
- const { availableModels, sessionId } = context;
12
-
13
- if (!availableModels) {
14
- console.warn('No available models provided to selectProvider');
15
- return null;
16
- }
17
-
18
- const tierModels = availableModels[tier] || [];
19
- if (tierModels.length === 0) {
20
- return null;
21
- }
22
-
23
- // If only one provider available, use it
24
- if (tierModels.length === 1) {
25
- return tierModels[0];
26
- }
27
-
28
- // Get load balancing data
29
- const loadBalance = getProviderLoadBalance(sessionId);
30
- const modelStrengths = getModelStrengths(tier);
31
-
32
- // Score each available model
33
- const scoredModels = tierModels.map(model => {
34
- let score = 0;
35
-
36
- // Base score from model strength for this tier
37
- score += modelStrengths[model.provider] || 0.5;
38
-
39
- // Load balancing bonus (prefer less-used providers)
40
- const providerLoad = loadBalance[model.provider] || 0;
41
- const avgLoad = Object.values(loadBalance).reduce((sum, load) => sum + load, 0) / Object.keys(loadBalance).length || 0;
42
-
43
- if (providerLoad < avgLoad - 2) {
44
- score += 0.3; // Bonus for underused provider
45
- } else if (providerLoad > avgLoad + 2) {
46
- score -= 0.2; // Penalty for overused provider
47
- }
48
-
49
- // Prefer models that haven't failed recently
50
- const recentFailures = getRecentFailures(model.provider, 0.5); // Last 30 min
51
- score -= recentFailures * 0.1;
52
-
53
- return {
54
- ...model,
55
- score,
56
- load: providerLoad,
57
- failures: recentFailures
58
- };
59
- });
60
-
61
- // Sort by score (highest first) and return best
62
- scoredModels.sort((a, b) => b.score - a.score);
63
-
64
- const selected = scoredModels[0];
65
-
66
- console.log(` 🎯 Provider selection for ${tier}: ${selected.provider}/${selected.model} (score: ${selected.score.toFixed(2)})`);
67
-
68
- return selected;
69
- }
70
-
71
- /**
72
- * Get provider load balance statistics
73
- */
74
- function getProviderLoadBalance(sessionId, timeWindowHours = 1) {
75
- const handoffs = getRecentHandoffs(timeWindowHours, sessionId);
76
-
77
- const loadBalance = {};
78
-
79
- for (const handoff of handoffs) {
80
- if (handoff.provider_from) {
81
- loadBalance[handoff.provider_from] = (loadBalance[handoff.provider_from] || 0) + 1;
82
- }
83
- if (handoff.provider_to) {
84
- loadBalance[handoff.provider_to] = (loadBalance[handoff.provider_to] || 0) + 1;
85
- }
86
- }
87
-
88
- return loadBalance;
89
- }
90
-
91
- /**
92
- * Get model strengths by tier based on empirical performance
93
- */
94
- function getModelStrengths(tier) {
95
- const strengths = {
96
- worker: {
97
- claude: 0.8, // Excellent for search and analysis
98
- codex: 0.6 // Good for quick lookups
99
- },
100
- ic: {
101
- codex: 0.8, // Excellent for implementation
102
- claude: 0.7 // Good for code generation
103
- },
104
- manager: {
105
- claude: 0.9, // Excellent for architecture and review
106
- codex: 0.7 // Good for complex reasoning
107
- }
108
- };
109
-
110
- return strengths[tier] || { claude: 0.5, codex: 0.5 };
111
- }
112
-
113
- /**
114
- * Get recent failure count for a provider
115
- */
116
- function getRecentFailures(provider, timeWindowHours = 0.5) {
117
- const handoffs = getRecentHandoffs(timeWindowHours);
118
-
119
- return handoffs.filter(handoff =>
120
- handoff.provider_from === provider &&
121
- handoff.op === 'escalate_up' &&
122
- handoff.reason.includes('failure')
123
- ).length;
124
- }
125
-
126
- /**
127
- * Check if provider is currently available
128
- */
129
- export function checkProviderHealth(provider) {
130
- const recentFailures = getRecentFailures(provider, 0.25); // Last 15 minutes
131
-
132
- return {
133
- available: recentFailures < 3, // Healthy if fewer than 3 failures in 15 min
134
- failures: recentFailures,
135
- status: recentFailures >= 3 ? 'degraded' : 'healthy'
136
- };
137
- }
138
-
139
- /**
140
- * Get provider usage statistics for debugging
141
- */
142
- export function getProviderStats(timeWindowHours = 1) {
143
- const stats = getHandoffStats(timeWindowHours);
144
-
145
- const providerStats = {};
146
-
147
- for (const [provider, count] of Object.entries(stats.by_provider)) {
148
- providerStats[provider] = {
149
- handoffs: count,
150
- percentage: Math.round((count / stats.total) * 100),
151
- health: checkProviderHealth(provider)
152
- };
153
- }
154
-
155
- return {
156
- total_handoffs: stats.total,
157
- time_window_hours: timeWindowHours,
158
- providers: providerStats,
159
- avg_duration_ms: stats.avg_duration_ms,
160
- success_rate: Math.round(stats.success_rate * 100)
161
- };
162
- }