portos-ai-toolkit 0.1.0 → 0.3.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.
package/README.md CHANGED
@@ -16,6 +16,10 @@ npm install portos-ai-toolkit
16
16
  - **React Components**: Ready-to-use React components and hooks for AI provider management
17
17
  - **Express Routes**: Pre-built Express route handlers for provider, prompt, and run management
18
18
 
19
+ ## Screenshot
20
+
21
+ ![AI Providers UI](screenshot.png)
22
+
19
23
  ## Usage
20
24
 
21
25
  ### Server-side
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portos-ai-toolkit",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Shared AI provider, model, and prompt template patterns for PortOS-style applications",
5
5
  "author": "Adam Eivy <adam@eivy.com> (https://atomantic.com)",
6
6
  "license": "MIT",
@@ -39,6 +39,7 @@
39
39
  "peerDependencies": {
40
40
  "express": "^4.21.2 || ^5.2.1",
41
41
  "socket.io": "^4.8.3",
42
+ "socket.io-client": "^4.8.3",
42
43
  "react": "^18.3.1",
43
44
  "react-dom": "^18.3.1"
44
45
  },
@@ -49,6 +50,9 @@
49
50
  "socket.io": {
50
51
  "optional": true
51
52
  },
53
+ "socket.io-client": {
54
+ "optional": true
55
+ },
52
56
  "react": {
53
57
  "optional": true
54
58
  },
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Error Detection Utility
3
+ *
4
+ * Detects and categorizes errors from AI provider responses,
5
+ * particularly rate limits and usage limits that require fallback handling.
6
+ */
7
+
8
+ /**
9
+ * Error categories and their characteristics
10
+ */
11
+ export const ERROR_CATEGORIES = {
12
+ RATE_LIMIT: 'rate-limit',
13
+ USAGE_LIMIT: 'usage-limit',
14
+ AUTH_ERROR: 'auth-error',
15
+ MODEL_NOT_FOUND: 'model-not-found',
16
+ NETWORK_ERROR: 'network-error',
17
+ TIMEOUT: 'timeout',
18
+ QUOTA_EXCEEDED: 'quota-exceeded',
19
+ UNKNOWN: 'unknown'
20
+ };
21
+
22
+ /**
23
+ * Error patterns for detection
24
+ * NOTE: Order matters! More specific patterns should come before general ones.
25
+ */
26
+ const ERROR_PATTERNS = [
27
+ // Quota exceeded (billing issues - check before usage limit since "quota" could match both)
28
+ {
29
+ pattern: /billing|payment|credit|insufficient funds/i,
30
+ category: ERROR_CATEGORIES.QUOTA_EXCEEDED,
31
+ requiresFallback: true,
32
+ actionable: true,
33
+ suggestedFix: 'Check billing status and add credits to the provider account'
34
+ },
35
+
36
+ // Rate limiting (temporary, short wait)
37
+ {
38
+ pattern: /API Error: 429|rate.?limit|too many requests/i,
39
+ category: ERROR_CATEGORIES.RATE_LIMIT,
40
+ requiresFallback: false, // Usually temporary
41
+ actionable: false,
42
+ suggestedFix: 'Wait and retry - temporary rate limiting'
43
+ },
44
+
45
+ // Usage limits (longer wait, fallback recommended)
46
+ {
47
+ pattern: /(?:hit your usage limit|You've hit your limit|usage limit|Upgrade to Pro)/i,
48
+ category: ERROR_CATEGORIES.USAGE_LIMIT,
49
+ requiresFallback: true,
50
+ actionable: true,
51
+ suggestedFix: 'Provider usage limit reached. Using fallback provider or wait for limit reset.',
52
+ extractWaitTime: true
53
+ },
54
+
55
+ // Authentication errors
56
+ {
57
+ pattern: /unauthorized|invalid.?api.?key|authentication|forbidden|401|403/i,
58
+ category: ERROR_CATEGORIES.AUTH_ERROR,
59
+ requiresFallback: true,
60
+ actionable: true,
61
+ suggestedFix: 'Check API key configuration for this provider'
62
+ },
63
+
64
+ // Model not found
65
+ {
66
+ pattern: /model.*(not found|does not exist|unavailable)|invalid model/i,
67
+ category: ERROR_CATEGORIES.MODEL_NOT_FOUND,
68
+ requiresFallback: true,
69
+ actionable: true,
70
+ suggestedFix: 'Check model name and availability in provider settings'
71
+ },
72
+
73
+ // Network errors
74
+ {
75
+ pattern: /ECONNREFUSED|ENOTFOUND|network error|connection refused|timeout|ETIMEDOUT/i,
76
+ category: ERROR_CATEGORIES.NETWORK_ERROR,
77
+ requiresFallback: false, // Often temporary
78
+ actionable: false,
79
+ suggestedFix: 'Check network connectivity and provider endpoint URL'
80
+ },
81
+
82
+ // Timeout
83
+ {
84
+ pattern: /timed out|timeout exceeded|SIGTERM/i,
85
+ category: ERROR_CATEGORIES.TIMEOUT,
86
+ requiresFallback: false,
87
+ actionable: false,
88
+ suggestedFix: 'Consider increasing timeout or reducing prompt complexity'
89
+ }
90
+ ];
91
+
92
+ /**
93
+ * Wait time extraction patterns
94
+ */
95
+ const WAIT_TIME_PATTERNS = [
96
+ // "resets 6am (America/Los_Angeles)" - extract timezone-aware time
97
+ /resets?\s+(\d{1,2}(?:am|pm)?)\s*\(([^)]+)\)/i,
98
+ // "try again in X day(s) X hour(s) X minute(s)"
99
+ /try again in\s+((?:\d+\s*(?:day|hour|minute|second)s?\s*)+)/i,
100
+ // "wait X minutes/hours/days"
101
+ /wait\s+((?:\d+\s*(?:day|hour|minute|second)s?\s*)+)/i,
102
+ // "in X hours", "in X minutes"
103
+ /in\s+(\d+)\s*(day|hour|minute|second)s?/i,
104
+ // Specific time format "1 day 1 hour 33 minutes"
105
+ /(\d+\s*day(?:s)?)?[,\s]*(\d+\s*hour(?:s)?)?[,\s]*(\d+\s*min(?:ute)?(?:s)?)?/i
106
+ ];
107
+
108
+ /**
109
+ * Extract wait time from error message
110
+ * @param {string} text - Error text to search
111
+ * @returns {string|null} - Human-readable wait time or null
112
+ */
113
+ export function extractWaitTime(text) {
114
+ if (!text) return null;
115
+
116
+ // Try each pattern
117
+ for (const pattern of WAIT_TIME_PATTERNS) {
118
+ const match = text.match(pattern);
119
+ if (match) {
120
+ // Clean up and return the matched time string
121
+ const timeStr = match.slice(1).filter(Boolean).join(' ').trim();
122
+ if (timeStr && timeStr !== ' ') {
123
+ return timeStr;
124
+ }
125
+ }
126
+ }
127
+
128
+ // Try to find any time-related content
129
+ const generalMatch = text.match(/(\d+)\s*(day|hour|min|sec)(?:ute)?(?:s)?/gi);
130
+ if (generalMatch) {
131
+ return generalMatch.join(' ');
132
+ }
133
+
134
+ return null;
135
+ }
136
+
137
+ /**
138
+ * Analyze error text and categorize it
139
+ *
140
+ * @param {string} errorText - The error message or output to analyze
141
+ * @param {number} exitCode - Optional exit code from CLI process
142
+ * @returns {Object} - Error analysis result
143
+ */
144
+ export function analyzeError(errorText, exitCode = null) {
145
+ if (!errorText && exitCode === 0) {
146
+ return {
147
+ hasError: false,
148
+ category: null,
149
+ message: null,
150
+ waitTime: null,
151
+ requiresFallback: false,
152
+ actionable: false,
153
+ suggestedFix: null
154
+ };
155
+ }
156
+
157
+ const text = String(errorText || '');
158
+
159
+ // Check each pattern
160
+ for (const errorPattern of ERROR_PATTERNS) {
161
+ if (errorPattern.pattern.test(text)) {
162
+ const result = {
163
+ hasError: true,
164
+ category: errorPattern.category,
165
+ message: extractErrorMessage(text),
166
+ waitTime: errorPattern.extractWaitTime ? extractWaitTime(text) : null,
167
+ requiresFallback: errorPattern.requiresFallback,
168
+ actionable: errorPattern.actionable,
169
+ suggestedFix: errorPattern.suggestedFix
170
+ };
171
+
172
+ return result;
173
+ }
174
+ }
175
+
176
+ // If we have an error but couldn't categorize it
177
+ if (exitCode !== 0 && exitCode !== null) {
178
+ return {
179
+ hasError: true,
180
+ category: ERROR_CATEGORIES.UNKNOWN,
181
+ message: extractErrorMessage(text) || `Process exited with code ${exitCode}`,
182
+ waitTime: null,
183
+ requiresFallback: false,
184
+ actionable: false,
185
+ suggestedFix: null
186
+ };
187
+ }
188
+
189
+ return {
190
+ hasError: false,
191
+ category: null,
192
+ message: null,
193
+ waitTime: null,
194
+ requiresFallback: false,
195
+ actionable: false,
196
+ suggestedFix: null
197
+ };
198
+ }
199
+
200
+ /**
201
+ * Extract the most relevant error message from text
202
+ * @param {string} text - Full error text
203
+ * @returns {string} - Extracted error message
204
+ */
205
+ function extractErrorMessage(text) {
206
+ if (!text) return '';
207
+
208
+ // Try to find common error message patterns
209
+ const patterns = [
210
+ /Error:\s*(.+?)(?:\n|$)/i,
211
+ /error":\s*"([^"]+)"/i,
212
+ /message":\s*"([^"]+)"/i,
213
+ /failed:\s*(.+?)(?:\n|$)/i
214
+ ];
215
+
216
+ for (const pattern of patterns) {
217
+ const match = text.match(pattern);
218
+ if (match) {
219
+ return match[1].trim();
220
+ }
221
+ }
222
+
223
+ // Return first meaningful line
224
+ const lines = text.split('\n').filter(line => line.trim());
225
+ return lines[0]?.substring(0, 200) || text.substring(0, 200);
226
+ }
227
+
228
+ /**
229
+ * Check if an HTTP status code indicates rate limiting
230
+ * @param {number} statusCode - HTTP status code
231
+ * @returns {boolean}
232
+ */
233
+ export function isRateLimitStatus(statusCode) {
234
+ return statusCode === 429;
235
+ }
236
+
237
+ /**
238
+ * Check if an HTTP status code indicates auth error
239
+ * @param {number} statusCode - HTTP status code
240
+ * @returns {boolean}
241
+ */
242
+ export function isAuthErrorStatus(statusCode) {
243
+ return statusCode === 401 || statusCode === 403;
244
+ }
245
+
246
+ /**
247
+ * Analyze an HTTP response for errors
248
+ * @param {Object} response - HTTP response object with status and body
249
+ * @returns {Object} - Error analysis result
250
+ */
251
+ export function analyzeHttpError(response) {
252
+ const { status, statusText, body } = response;
253
+
254
+ if (status >= 200 && status < 300) {
255
+ return {
256
+ hasError: false,
257
+ category: null,
258
+ message: null,
259
+ waitTime: null,
260
+ requiresFallback: false,
261
+ actionable: false,
262
+ suggestedFix: null
263
+ };
264
+ }
265
+
266
+ // Check status code first
267
+ if (isRateLimitStatus(status)) {
268
+ return {
269
+ hasError: true,
270
+ category: ERROR_CATEGORIES.RATE_LIMIT,
271
+ message: `Rate limit exceeded (${status})`,
272
+ waitTime: extractWaitTime(body),
273
+ requiresFallback: false,
274
+ actionable: false,
275
+ suggestedFix: 'Wait and retry - temporary rate limiting'
276
+ };
277
+ }
278
+
279
+ if (isAuthErrorStatus(status)) {
280
+ return {
281
+ hasError: true,
282
+ category: ERROR_CATEGORIES.AUTH_ERROR,
283
+ message: `Authentication failed (${status})`,
284
+ waitTime: null,
285
+ requiresFallback: true,
286
+ actionable: true,
287
+ suggestedFix: 'Check API key configuration for this provider'
288
+ };
289
+ }
290
+
291
+ // Analyze body for more specific errors
292
+ if (body) {
293
+ return analyzeError(body);
294
+ }
295
+
296
+ return {
297
+ hasError: true,
298
+ category: ERROR_CATEGORIES.UNKNOWN,
299
+ message: statusText || `HTTP ${status}`,
300
+ waitTime: null,
301
+ requiresFallback: false,
302
+ actionable: false,
303
+ suggestedFix: null
304
+ };
305
+ }
@@ -0,0 +1,180 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ analyzeError,
4
+ analyzeHttpError,
5
+ extractWaitTime,
6
+ ERROR_CATEGORIES
7
+ } from './errorDetection.js';
8
+
9
+ describe('Error Detection', () => {
10
+ describe('analyzeError', () => {
11
+ it('should detect rate limit errors', () => {
12
+ const result = analyzeError('API Error: 429 Too Many Requests');
13
+ expect(result.hasError).toBe(true);
14
+ expect(result.category).toBe(ERROR_CATEGORIES.RATE_LIMIT);
15
+ expect(result.requiresFallback).toBe(false);
16
+ });
17
+
18
+ it('should detect rate limit from "rate limit" text', () => {
19
+ const result = analyzeError('Rate limit exceeded. Please try again later.');
20
+ expect(result.hasError).toBe(true);
21
+ expect(result.category).toBe(ERROR_CATEGORIES.RATE_LIMIT);
22
+ });
23
+
24
+ it('should detect usage limit errors', () => {
25
+ const result = analyzeError("You've hit your usage limit. Upgrade to Pro or try again in 1 day 1 hour 33 minutes");
26
+ expect(result.hasError).toBe(true);
27
+ expect(result.category).toBe(ERROR_CATEGORIES.USAGE_LIMIT);
28
+ expect(result.requiresFallback).toBe(true);
29
+ });
30
+
31
+ it('should extract wait time from usage limit errors', () => {
32
+ const result = analyzeError("You've hit your usage limit. Upgrade to Pro or try again in 1 day 1 hour 33 minutes");
33
+ expect(result.waitTime).toBeTruthy();
34
+ expect(result.waitTime).toContain('day');
35
+ });
36
+
37
+ it('should detect authentication errors', () => {
38
+ const result = analyzeError('Error: 401 Unauthorized - Invalid API key');
39
+ expect(result.hasError).toBe(true);
40
+ expect(result.category).toBe(ERROR_CATEGORIES.AUTH_ERROR);
41
+ expect(result.requiresFallback).toBe(true);
42
+ });
43
+
44
+ it('should detect model not found errors', () => {
45
+ const result = analyzeError('Error: model "claude-9" does not exist');
46
+ expect(result.hasError).toBe(true);
47
+ expect(result.category).toBe(ERROR_CATEGORIES.MODEL_NOT_FOUND);
48
+ expect(result.requiresFallback).toBe(true);
49
+ });
50
+
51
+ it('should detect network errors', () => {
52
+ const result = analyzeError('Error: ECONNREFUSED 127.0.0.1:8080');
53
+ expect(result.hasError).toBe(true);
54
+ expect(result.category).toBe(ERROR_CATEGORIES.NETWORK_ERROR);
55
+ });
56
+
57
+ it('should detect timeout errors', () => {
58
+ const result = analyzeError('Process timed out after 300000ms');
59
+ expect(result.hasError).toBe(true);
60
+ expect(result.category).toBe(ERROR_CATEGORIES.TIMEOUT);
61
+ });
62
+
63
+ it('should detect quota exceeded errors', () => {
64
+ const result = analyzeError('Error: Billing quota exceeded. Please add credits.');
65
+ expect(result.hasError).toBe(true);
66
+ expect(result.category).toBe(ERROR_CATEGORIES.QUOTA_EXCEEDED);
67
+ expect(result.requiresFallback).toBe(true);
68
+ });
69
+
70
+ it('should return unknown for unrecognized errors with exit code', () => {
71
+ const result = analyzeError('Some unknown error occurred', 1);
72
+ expect(result.hasError).toBe(true);
73
+ expect(result.category).toBe(ERROR_CATEGORIES.UNKNOWN);
74
+ });
75
+
76
+ it('should return no error for success', () => {
77
+ const result = analyzeError('', 0);
78
+ expect(result.hasError).toBe(false);
79
+ expect(result.category).toBeNull();
80
+ });
81
+
82
+ it('should handle null/undefined input', () => {
83
+ const result = analyzeError(null, 0);
84
+ expect(result.hasError).toBe(false);
85
+ });
86
+ });
87
+
88
+ describe('extractWaitTime', () => {
89
+ it('should extract "X day X hour X minutes" format', () => {
90
+ const result = extractWaitTime('try again in 1 day 2 hours 30 minutes');
91
+ expect(result).toBeTruthy();
92
+ expect(result).toContain('day');
93
+ expect(result).toContain('hour');
94
+ expect(result).toContain('min');
95
+ });
96
+
97
+ it('should extract "in X hours" format', () => {
98
+ const result = extractWaitTime('Please wait, available in 3 hours');
99
+ expect(result).toBeTruthy();
100
+ expect(result).toMatch(/3\s*hour/i);
101
+ });
102
+
103
+ it('should extract "wait X minutes" format', () => {
104
+ const result = extractWaitTime('Wait 5 minutes before retrying');
105
+ expect(result).toBeTruthy();
106
+ expect(result).toMatch(/5\s*min/i);
107
+ });
108
+
109
+ it('should return null for no time found', () => {
110
+ const result = extractWaitTime('No time information here');
111
+ expect(result).toBeNull();
112
+ });
113
+
114
+ it('should handle null input', () => {
115
+ const result = extractWaitTime(null);
116
+ expect(result).toBeNull();
117
+ });
118
+ });
119
+
120
+ describe('analyzeHttpError', () => {
121
+ it('should detect 429 rate limit', () => {
122
+ const result = analyzeHttpError({
123
+ status: 429,
124
+ statusText: 'Too Many Requests',
125
+ body: ''
126
+ });
127
+ expect(result.hasError).toBe(true);
128
+ expect(result.category).toBe(ERROR_CATEGORIES.RATE_LIMIT);
129
+ });
130
+
131
+ it('should detect 401 auth error', () => {
132
+ const result = analyzeHttpError({
133
+ status: 401,
134
+ statusText: 'Unauthorized',
135
+ body: ''
136
+ });
137
+ expect(result.hasError).toBe(true);
138
+ expect(result.category).toBe(ERROR_CATEGORIES.AUTH_ERROR);
139
+ });
140
+
141
+ it('should detect 403 auth error', () => {
142
+ const result = analyzeHttpError({
143
+ status: 403,
144
+ statusText: 'Forbidden',
145
+ body: ''
146
+ });
147
+ expect(result.hasError).toBe(true);
148
+ expect(result.category).toBe(ERROR_CATEGORIES.AUTH_ERROR);
149
+ });
150
+
151
+ it('should return no error for 200 status', () => {
152
+ const result = analyzeHttpError({
153
+ status: 200,
154
+ statusText: 'OK',
155
+ body: ''
156
+ });
157
+ expect(result.hasError).toBe(false);
158
+ });
159
+
160
+ it('should analyze body for more specific errors', () => {
161
+ const result = analyzeHttpError({
162
+ status: 400,
163
+ statusText: 'Bad Request',
164
+ body: 'Error: model "invalid-model" does not exist'
165
+ });
166
+ expect(result.hasError).toBe(true);
167
+ expect(result.category).toBe(ERROR_CATEGORIES.MODEL_NOT_FOUND);
168
+ });
169
+
170
+ it('should extract wait time from 429 response body', () => {
171
+ const result = analyzeHttpError({
172
+ status: 429,
173
+ statusText: 'Too Many Requests',
174
+ body: 'Rate limit exceeded. Try again in 5 minutes.'
175
+ });
176
+ expect(result.hasError).toBe(true);
177
+ expect(result.waitTime).toBeTruthy();
178
+ });
179
+ });
180
+ });
@@ -1,5 +1,5 @@
1
1
  /**
2
- * TypeScript declarations for @portos/ai-toolkit/server
2
+ * TypeScript declarations for portos-ai-toolkit/server
3
3
  */
4
4
 
5
5
  export interface ProviderService {
@@ -6,13 +6,16 @@
6
6
  import { createProviderService } from './providers.js';
7
7
  import { createRunnerService } from './runner.js';
8
8
  import { createPromptsService } from './prompts.js';
9
+ import { createProviderStatusService } from './providerStatus.js';
9
10
  import { createProvidersRoutes } from './routes/providers.js';
10
11
  import { createRunsRoutes } from './routes/runs.js';
11
12
  import { createPromptsRoutes } from './routes/prompts.js';
13
+ import { createProviderStatusRoutes } from './routes/providerStatus.js';
12
14
 
13
15
  export * from './validation.js';
14
- export { createProviderService, createRunnerService, createPromptsService };
15
- export { createProvidersRoutes, createRunsRoutes, createPromptsRoutes };
16
+ export * from './errorDetection.js';
17
+ export { createProviderService, createRunnerService, createPromptsService, createProviderStatusService };
18
+ export { createProvidersRoutes, createRunsRoutes, createPromptsRoutes, createProviderStatusRoutes };
16
19
 
17
20
  /**
18
21
  * Create a complete AI toolkit instance with services and routes
@@ -21,6 +24,7 @@ export function createAIToolkit(config = {}) {
21
24
  const {
22
25
  dataDir = './data',
23
26
  providersFile = 'providers.json',
27
+ statusFile = 'provider-status.json',
24
28
  runsDir = 'runs',
25
29
  promptsDir = 'prompts',
26
30
  screenshotsDir = './data/screenshots',
@@ -36,7 +40,11 @@ export function createAIToolkit(config = {}) {
36
40
  hooks = {},
37
41
 
38
42
  // Runner config
39
- maxConcurrentRuns = 5
43
+ maxConcurrentRuns = 5,
44
+
45
+ // Provider status config
46
+ enableProviderStatus = true,
47
+ defaultFallbackPriority = ['claude-code', 'codex', 'lmstudio', 'local-lm-studio', 'ollama', 'gemini-cli']
40
48
  } = config;
41
49
 
42
50
  // Create services
@@ -46,12 +54,39 @@ export function createAIToolkit(config = {}) {
46
54
  sampleFile: sampleProvidersFile
47
55
  });
48
56
 
57
+ // Create provider status service if enabled
58
+ let providerStatusService = null;
59
+ if (enableProviderStatus) {
60
+ providerStatusService = createProviderStatusService({
61
+ dataDir,
62
+ statusFile,
63
+ defaultFallbackPriority,
64
+ onStatusChange: (eventData) => {
65
+ // Emit Socket.IO event if io is configured
66
+ io?.emit('provider:status:changed', eventData);
67
+ }
68
+ });
69
+
70
+ // Initialize status service
71
+ providerStatusService.init().catch(err => {
72
+ console.error(`❌ Failed to initialize provider status: ${err.message}`);
73
+ });
74
+ }
75
+
49
76
  const runnerService = createRunnerService({
50
77
  dataDir,
51
78
  runsDir,
52
79
  screenshotsDir,
53
80
  providerService,
54
- hooks,
81
+ providerStatusService,
82
+ hooks: {
83
+ ...hooks,
84
+ // Add hook to emit provider error events
85
+ onProviderError: (providerId, errorAnalysis, output) => {
86
+ io?.emit('provider:error', { providerId, errorAnalysis });
87
+ hooks.onProviderError?.(providerId, errorAnalysis, output);
88
+ }
89
+ },
55
90
  maxConcurrentRuns
56
91
  });
57
92
 
@@ -70,19 +105,27 @@ export function createAIToolkit(config = {}) {
70
105
  const runsRouter = createRunsRoutes(runnerService, { asyncHandler, io });
71
106
  const promptsRouter = createPromptsRoutes(promptsService, { asyncHandler });
72
107
 
108
+ // Create provider status routes if enabled
109
+ let providerStatusRouter = null;
110
+ if (providerStatusService) {
111
+ providerStatusRouter = createProviderStatusRoutes(providerStatusService, { asyncHandler });
112
+ }
113
+
73
114
  return {
74
115
  // Services
75
116
  services: {
76
117
  providers: providerService,
77
118
  runner: runnerService,
78
- prompts: promptsService
119
+ prompts: promptsService,
120
+ providerStatus: providerStatusService
79
121
  },
80
122
 
81
123
  // Routes
82
124
  routes: {
83
125
  providers: providersRouter,
84
126
  runs: runsRouter,
85
- prompts: promptsRouter
127
+ prompts: promptsRouter,
128
+ providerStatus: providerStatusRouter
86
129
  },
87
130
 
88
131
  // Convenience method to mount all routes
@@ -90,6 +133,9 @@ export function createAIToolkit(config = {}) {
90
133
  app.use(`${basePath}/providers`, providersRouter);
91
134
  app.use(`${basePath}/runs`, runsRouter);
92
135
  app.use(`${basePath}/prompts`, promptsRouter);
136
+ if (providerStatusRouter) {
137
+ app.use(`${basePath}/providers/status`, providerStatusRouter);
138
+ }
93
139
  }
94
140
  };
95
141
  }