@auto-engineer/ai-gateway 0.6.3 → 0.8.1

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 (47) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +17 -35
  3. package/.turbo/turbo-type-check.log +4 -5
  4. package/CHANGELOG.md +6 -0
  5. package/README.md +365 -0
  6. package/dist/config.d.ts +2 -0
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +31 -2
  9. package/dist/config.js.map +1 -1
  10. package/dist/config.specs.d.ts +2 -0
  11. package/dist/config.specs.d.ts.map +1 -0
  12. package/dist/config.specs.js +123 -0
  13. package/dist/config.specs.js.map +1 -0
  14. package/dist/constants.d.ts +20 -0
  15. package/dist/constants.d.ts.map +1 -0
  16. package/dist/constants.js +15 -0
  17. package/dist/constants.js.map +1 -0
  18. package/dist/index-custom.specs.d.ts +2 -0
  19. package/dist/index-custom.specs.d.ts.map +1 -0
  20. package/dist/index-custom.specs.js +161 -0
  21. package/dist/index-custom.specs.js.map +1 -0
  22. package/dist/index.d.ts +12 -13
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +93 -59
  25. package/dist/index.js.map +1 -1
  26. package/dist/index.specs.js +152 -11
  27. package/dist/index.specs.js.map +1 -1
  28. package/dist/providers/custom.d.ts +6 -0
  29. package/dist/providers/custom.d.ts.map +1 -0
  30. package/dist/providers/custom.js +16 -0
  31. package/dist/providers/custom.js.map +1 -0
  32. package/dist/providers/custom.specs.d.ts +2 -0
  33. package/dist/providers/custom.specs.d.ts.map +1 -0
  34. package/dist/providers/custom.specs.js +129 -0
  35. package/dist/providers/custom.specs.js.map +1 -0
  36. package/package.json +5 -5
  37. package/src/config.specs.ts +147 -0
  38. package/src/config.ts +46 -2
  39. package/src/constants.ts +21 -0
  40. package/src/index-custom.specs.ts +192 -0
  41. package/src/index.specs.ts +199 -10
  42. package/src/index.ts +99 -78
  43. package/src/providers/custom.specs.ts +161 -0
  44. package/src/providers/custom.ts +24 -0
  45. package/tsconfig.tsbuildinfo +1 -1
  46. package/.turbo/turbo-format.log +0 -14
  47. package/.turbo/turbo-lint.log +0 -5
package/src/config.ts CHANGED
@@ -2,6 +2,7 @@ import dotenv from 'dotenv';
2
2
  import { fileURLToPath } from 'url';
3
3
  import { dirname, resolve } from 'path';
4
4
  import createDebug from 'debug';
5
+ import { CustomProviderConfig } from './constants';
5
6
 
6
7
  const debug = createDebug('ai-gateway:config');
7
8
  const debugEnv = createDebug('ai-gateway:config:env');
@@ -25,6 +26,7 @@ export interface AIConfig {
25
26
  xai?: {
26
27
  apiKey: string;
27
28
  };
29
+ custom?: CustomProviderConfig;
28
30
  }
29
31
 
30
32
  // Helper to log provider configuration
@@ -34,6 +36,34 @@ function logProviderConfig(providerName: string, apiKey: string | undefined): vo
34
36
  }
35
37
  }
36
38
 
39
+ // Helper to build custom provider config
40
+ function buildCustomProviderConfig(): CustomProviderConfig | undefined {
41
+ const name = process.env.CUSTOM_PROVIDER_NAME;
42
+ const baseUrl = process.env.CUSTOM_PROVIDER_BASE_URL;
43
+ const apiKey = process.env.CUSTOM_PROVIDER_API_KEY;
44
+ const defaultModel = process.env.CUSTOM_PROVIDER_DEFAULT_MODEL;
45
+
46
+ if (
47
+ name != null &&
48
+ name.length > 0 &&
49
+ baseUrl != null &&
50
+ baseUrl.length > 0 &&
51
+ apiKey != null &&
52
+ apiKey.length > 0 &&
53
+ defaultModel != null &&
54
+ defaultModel.length > 0
55
+ ) {
56
+ return {
57
+ name,
58
+ baseUrl,
59
+ apiKey,
60
+ defaultModel,
61
+ };
62
+ }
63
+
64
+ return undefined;
65
+ }
66
+
37
67
  // Helper to build provider config
38
68
  function buildProviderConfig(): AIConfig {
39
69
  return {
@@ -41,6 +71,7 @@ function buildProviderConfig(): AIConfig {
41
71
  anthropic: process.env.ANTHROPIC_API_KEY != null ? { apiKey: process.env.ANTHROPIC_API_KEY } : undefined,
42
72
  google: process.env.GEMINI_API_KEY != null ? { apiKey: process.env.GEMINI_API_KEY } : undefined,
43
73
  xai: process.env.XAI_API_KEY != null ? { apiKey: process.env.XAI_API_KEY } : undefined,
74
+ custom: buildCustomProviderConfig(),
44
75
  };
45
76
  }
46
77
 
@@ -54,6 +85,7 @@ export function configureAIProvider(): AIConfig {
54
85
  debugEnv('Anthropic configured: %s', config.anthropic != null);
55
86
  debugEnv('Google configured: %s', config.google != null);
56
87
  debugEnv('XAI configured: %s', config.xai != null);
88
+ debugEnv('Custom configured: %s', config.custom != null);
57
89
 
58
90
  // Log provider configurations
59
91
  logProviderConfig('OpenAI', config.openai?.apiKey);
@@ -61,12 +93,24 @@ export function configureAIProvider(): AIConfig {
61
93
  logProviderConfig('Google', config.google?.apiKey);
62
94
  logProviderConfig('XAI', config.xai?.apiKey);
63
95
 
64
- const configuredProviders = [config.openai, config.anthropic, config.google, config.xai].filter((p) => p != null);
96
+ if (config.custom != null) {
97
+ debug(
98
+ 'Custom provider configured: %s at %s with model %s',
99
+ config.custom.name,
100
+ config.custom.baseUrl,
101
+ config.custom.defaultModel,
102
+ );
103
+ logProviderConfig('Custom', config.custom.apiKey);
104
+ }
105
+
106
+ const configuredProviders = [config.openai, config.anthropic, config.google, config.xai, config.custom].filter(
107
+ (p) => p != null,
108
+ );
65
109
 
66
110
  if (configuredProviders.length === 0) {
67
111
  debug('ERROR: No AI providers configured');
68
112
  throw new Error(
69
- 'At least one AI provider must be configured. Please set OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, or XAI_API_KEY environment variables.',
113
+ 'At least one AI provider must be configured. Please set OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, XAI_API_KEY environment variables, or configure a custom provider with CUSTOM_PROVIDER_NAME, CUSTOM_PROVIDER_BASE_URL, CUSTOM_PROVIDER_API_KEY, and CUSTOM_PROVIDER_DEFAULT_MODEL.',
70
114
  );
71
115
  }
72
116
 
@@ -0,0 +1,21 @@
1
+ export enum AIProvider {
2
+ OpenAI = 'openai',
3
+ Anthropic = 'anthropic',
4
+ Google = 'google',
5
+ XAI = 'xai',
6
+ Custom = 'custom',
7
+ }
8
+
9
+ export interface CustomProviderConfig {
10
+ name: string;
11
+ baseUrl: string;
12
+ apiKey: string;
13
+ defaultModel: string;
14
+ }
15
+
16
+ export const DEFAULT_MODELS = {
17
+ [AIProvider.OpenAI]: 'gpt-4o-mini',
18
+ [AIProvider.Anthropic]: 'claude-sonnet-4-20250514',
19
+ [AIProvider.Google]: 'gemini-2.5-pro',
20
+ [AIProvider.XAI]: 'grok-4',
21
+ } as const;
@@ -0,0 +1,192 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { AIProvider } from './constants';
3
+ import { getAvailableProviders, getDefaultModel, getDefaultAIProvider } from './index';
4
+
5
+ // Mock the config module
6
+ vi.mock('./config', () => ({
7
+ configureAIProvider: vi.fn(),
8
+ }));
9
+
10
+ // Mock the custom provider
11
+ vi.mock('./providers/custom', () => ({
12
+ createCustomProvider: vi.fn(() => ({
13
+ languageModel: vi.fn(),
14
+ })),
15
+ }));
16
+
17
+ describe('Index Integration with Custom Providers', () => {
18
+ const originalEnv = process.env;
19
+
20
+ beforeEach(() => {
21
+ vi.resetModules();
22
+ process.env = { ...originalEnv };
23
+ vi.clearAllMocks();
24
+ });
25
+
26
+ afterEach(() => {
27
+ process.env = originalEnv;
28
+ });
29
+
30
+ describe('getAvailableProviders', () => {
31
+ it('should include custom provider when configured', async () => {
32
+ const { configureAIProvider } = await import('./config');
33
+ vi.mocked(configureAIProvider).mockReturnValue({
34
+ custom: {
35
+ name: 'litellm',
36
+ baseUrl: 'https://api.litellm.ai',
37
+ apiKey: 'sk-key',
38
+ defaultModel: 'claude-3-sonnet',
39
+ },
40
+ anthropic: { apiKey: 'sk-anthropic' },
41
+ });
42
+
43
+ const providers = getAvailableProviders();
44
+
45
+ expect(providers).toContain(AIProvider.Custom);
46
+ expect(providers).toContain(AIProvider.Anthropic);
47
+ });
48
+
49
+ it('should not include custom provider when not configured', async () => {
50
+ const { configureAIProvider } = await import('./config');
51
+ vi.mocked(configureAIProvider).mockReturnValue({
52
+ anthropic: { apiKey: 'sk-anthropic' },
53
+ });
54
+
55
+ const providers = getAvailableProviders();
56
+
57
+ expect(providers).not.toContain(AIProvider.Custom);
58
+ expect(providers).toContain(AIProvider.Anthropic);
59
+ });
60
+
61
+ it('should handle multiple providers including custom', async () => {
62
+ const { configureAIProvider } = await import('./config');
63
+ vi.mocked(configureAIProvider).mockReturnValue({
64
+ openai: { apiKey: 'sk-openai' },
65
+ anthropic: { apiKey: 'sk-anthropic' },
66
+ google: { apiKey: 'sk-google' },
67
+ xai: { apiKey: 'sk-xai' },
68
+ custom: {
69
+ name: 'custom',
70
+ baseUrl: 'https://api.custom.com',
71
+ apiKey: 'sk-custom',
72
+ defaultModel: 'custom-model',
73
+ },
74
+ });
75
+
76
+ const providers = getAvailableProviders();
77
+
78
+ expect(providers).toHaveLength(5);
79
+ expect(providers).toContain(AIProvider.OpenAI);
80
+ expect(providers).toContain(AIProvider.Anthropic);
81
+ expect(providers).toContain(AIProvider.Google);
82
+ expect(providers).toContain(AIProvider.XAI);
83
+ expect(providers).toContain(AIProvider.Custom);
84
+ });
85
+ });
86
+
87
+ describe('getDefaultModel', () => {
88
+ it('should return custom provider default model when provider is Custom', async () => {
89
+ const { configureAIProvider } = await import('./config');
90
+ vi.mocked(configureAIProvider).mockReturnValue({
91
+ custom: {
92
+ name: 'litellm',
93
+ baseUrl: 'https://api.litellm.ai',
94
+ apiKey: 'sk-key',
95
+ defaultModel: 'claude-3-sonnet',
96
+ },
97
+ });
98
+
99
+ const model = getDefaultModel(AIProvider.Custom);
100
+ expect(model).toBe('claude-3-sonnet');
101
+ });
102
+
103
+ it('should throw error when custom provider not configured but requested', async () => {
104
+ const { configureAIProvider } = await import('./config');
105
+ vi.mocked(configureAIProvider).mockReturnValue({
106
+ anthropic: { apiKey: 'sk-anthropic' },
107
+ });
108
+
109
+ expect(() => getDefaultModel(AIProvider.Custom)).toThrow('Custom provider not configured');
110
+ });
111
+
112
+ it('should use environment variable DEFAULT_AI_MODEL for custom provider', async () => {
113
+ process.env.DEFAULT_AI_MODEL = 'env-override-model';
114
+
115
+ const { configureAIProvider } = await import('./config');
116
+ vi.mocked(configureAIProvider).mockReturnValue({
117
+ custom: {
118
+ name: 'custom',
119
+ baseUrl: 'https://api.custom.com',
120
+ apiKey: 'sk-key',
121
+ defaultModel: 'config-model',
122
+ },
123
+ });
124
+
125
+ const model = getDefaultModel(AIProvider.Custom);
126
+ expect(model).toBe('env-override-model');
127
+ });
128
+
129
+ it('should handle different custom provider models', async () => {
130
+ const testCases = ['gpt-4o', 'claude-3-opus', 'llama3.1:70b', 'mistral-large', 'gemini-1.5-pro'];
131
+
132
+ const { configureAIProvider } = await import('./config');
133
+
134
+ testCases.forEach((testModel) => {
135
+ vi.mocked(configureAIProvider).mockReturnValue({
136
+ custom: {
137
+ name: 'test',
138
+ baseUrl: 'https://api.test.com',
139
+ apiKey: 'sk-key',
140
+ defaultModel: testModel,
141
+ },
142
+ });
143
+
144
+ const model = getDefaultModel(AIProvider.Custom);
145
+ expect(model).toBe(testModel);
146
+ });
147
+ });
148
+ });
149
+
150
+ describe('getDefaultAIProvider', () => {
151
+ it('should use custom provider as fallback when available', async () => {
152
+ process.env.DEFAULT_AI_PROVIDER = 'nonexistent';
153
+
154
+ const { configureAIProvider } = await import('./config');
155
+ vi.mocked(configureAIProvider).mockReturnValue({
156
+ custom: {
157
+ name: 'custom',
158
+ baseUrl: 'https://api.custom.com',
159
+ apiKey: 'sk-key',
160
+ defaultModel: 'custom-model',
161
+ },
162
+ });
163
+
164
+ // Mock getAvailableProviders to return custom
165
+ const mockGetAvailableProviders = vi.fn(() => [AIProvider.Custom]);
166
+ vi.doMock('./index', async () => ({
167
+ ...(await vi.importActual('./index')),
168
+ getAvailableProviders: mockGetAvailableProviders,
169
+ }));
170
+
171
+ const { getDefaultAIProvider: getDefaultAIProviderMocked } = await import('./index');
172
+ const provider = getDefaultAIProviderMocked();
173
+
174
+ expect(provider).toBe(AIProvider.Custom);
175
+ });
176
+
177
+ it('should respect explicit DEFAULT_AI_PROVIDER environment variable', () => {
178
+ const testCases = [
179
+ { env: 'openai', expected: AIProvider.OpenAI },
180
+ { env: 'anthropic', expected: AIProvider.Anthropic },
181
+ { env: 'google', expected: AIProvider.Google },
182
+ { env: 'xai', expected: AIProvider.XAI },
183
+ ];
184
+
185
+ testCases.forEach(({ env, expected }) => {
186
+ process.env.DEFAULT_AI_PROVIDER = env;
187
+ const provider = getDefaultAIProvider();
188
+ expect(provider).toBe(expected);
189
+ });
190
+ });
191
+ });
192
+ });
@@ -1,16 +1,205 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { generateTextWithAI, streamTextWithAI, AIProvider } from './index';
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { getAvailableProviders, getDefaultAIProvider, getDefaultModel } from './index';
3
+ import { DEFAULT_MODELS, AIProvider } from './constants';
4
+ import type { AIConfig } from './config';
3
5
 
4
- describe('AI Integration', () => {
5
- process.env.OPENAI_API_KEY = 'test';
6
+ // Mock the config module
7
+ vi.mock('./config', () => ({
8
+ configureAIProvider: vi.fn(
9
+ () =>
10
+ ({
11
+ anthropic: { apiKey: 'test-anthropic-key' },
12
+ openai: { apiKey: 'test-openai-key' },
13
+ google: { apiKey: 'test-google-key' },
14
+ xai: { apiKey: 'test-xai-key' },
15
+ }) as AIConfig,
16
+ ),
17
+ }));
6
18
 
7
- it('should export the correct types', () => {
8
- expect(typeof generateTextWithAI).toBe('function');
9
- expect(typeof streamTextWithAI).toBe('function');
19
+ describe('Provider Selection Logic', () => {
20
+ describe('getDefaultAIProvider', () => {
21
+ let originalProvider: string | undefined;
22
+
23
+ beforeEach(() => {
24
+ originalProvider = process.env.DEFAULT_AI_PROVIDER;
25
+ });
26
+
27
+ afterEach(() => {
28
+ if (originalProvider !== undefined) {
29
+ process.env.DEFAULT_AI_PROVIDER = originalProvider;
30
+ } else {
31
+ delete process.env.DEFAULT_AI_PROVIDER;
32
+ }
33
+ });
34
+
35
+ it('should respect DEFAULT_AI_PROVIDER environment variable when set to anthropic', () => {
36
+ process.env.DEFAULT_AI_PROVIDER = 'anthropic';
37
+
38
+ const provider = getDefaultAIProvider();
39
+
40
+ expect(provider).toBe(AIProvider.Anthropic);
41
+ });
42
+
43
+ it('should respect DEFAULT_AI_PROVIDER environment variable when set to xai', () => {
44
+ process.env.DEFAULT_AI_PROVIDER = 'xai';
45
+
46
+ const provider = getDefaultAIProvider();
47
+
48
+ expect(provider).toBe(AIProvider.XAI);
49
+ });
50
+
51
+ it('should respect DEFAULT_AI_PROVIDER environment variable when set to google', () => {
52
+ process.env.DEFAULT_AI_PROVIDER = 'google';
53
+
54
+ const provider = getDefaultAIProvider();
55
+
56
+ expect(provider).toBe(AIProvider.Google);
57
+ });
58
+
59
+ it('should be case insensitive for DEFAULT_AI_PROVIDER', () => {
60
+ process.env.DEFAULT_AI_PROVIDER = 'ANTHROPIC';
61
+
62
+ const provider = getDefaultAIProvider();
63
+
64
+ expect(provider).toBe(AIProvider.Anthropic);
65
+ });
66
+
67
+ it('should handle invalid DEFAULT_AI_PROVIDER values by falling back to available providers', () => {
68
+ process.env.DEFAULT_AI_PROVIDER = 'invalid_provider';
69
+
70
+ const provider = getDefaultAIProvider();
71
+
72
+ expect([AIProvider.Anthropic, AIProvider.OpenAI, AIProvider.Google, AIProvider.XAI]).toContain(provider);
73
+ });
74
+
75
+ it('should fallback to available providers when DEFAULT_AI_PROVIDER is not set', () => {
76
+ delete process.env.DEFAULT_AI_PROVIDER;
77
+
78
+ const provider = getDefaultAIProvider();
79
+
80
+ expect([AIProvider.Anthropic, AIProvider.OpenAI, AIProvider.Google, AIProvider.XAI]).toContain(provider);
81
+ });
82
+ });
83
+
84
+ describe('getAvailableProviders', () => {
85
+ it('should return array of available providers', () => {
86
+ const providers = getAvailableProviders();
87
+
88
+ expect(Array.isArray(providers)).toBe(true);
89
+ expect(providers.length).toBeGreaterThan(0);
90
+
91
+ providers.forEach((provider) => {
92
+ expect([AIProvider.Anthropic, AIProvider.OpenAI, AIProvider.Google, AIProvider.XAI]).toContain(provider);
93
+ });
94
+ });
95
+
96
+ it('should maintain priority order when multiple providers are available', () => {
97
+ const providers = getAvailableProviders();
98
+ const expectedOrder = [AIProvider.Anthropic, AIProvider.OpenAI, AIProvider.Google, AIProvider.XAI];
99
+
100
+ let lastIndex = -1;
101
+ providers.forEach((provider) => {
102
+ const currentIndex = expectedOrder.indexOf(provider);
103
+ expect(currentIndex).toBeGreaterThan(lastIndex);
104
+ lastIndex = currentIndex;
105
+ });
106
+ });
107
+ });
108
+
109
+ describe('getDefaultModel', () => {
110
+ let originalModel: string | undefined;
111
+
112
+ beforeEach(() => {
113
+ originalModel = process.env.DEFAULT_AI_MODEL;
114
+ });
115
+
116
+ afterEach(() => {
117
+ if (originalModel !== undefined) {
118
+ process.env.DEFAULT_AI_MODEL = originalModel;
119
+ } else {
120
+ delete process.env.DEFAULT_AI_MODEL;
121
+ }
122
+ });
123
+
124
+ it('should use DEFAULT_AI_MODEL when set in environment', () => {
125
+ process.env.DEFAULT_AI_MODEL = 'custom-model-name';
126
+
127
+ const model = getDefaultModel(AIProvider.OpenAI);
128
+
129
+ expect(model).toBe('custom-model-name');
130
+ });
131
+
132
+ it('should use DEFAULT_AI_MODEL regardless of provider when set', () => {
133
+ process.env.DEFAULT_AI_MODEL = 'universal-model';
134
+
135
+ expect(getDefaultModel(AIProvider.OpenAI)).toBe('universal-model');
136
+ expect(getDefaultModel(AIProvider.Anthropic)).toBe('universal-model');
137
+ expect(getDefaultModel(AIProvider.Google)).toBe('universal-model');
138
+ expect(getDefaultModel(AIProvider.XAI)).toBe('universal-model');
139
+ });
140
+
141
+ it('should trim whitespace from DEFAULT_AI_MODEL', () => {
142
+ process.env.DEFAULT_AI_MODEL = ' model-with-spaces ';
143
+
144
+ const model = getDefaultModel(AIProvider.OpenAI);
145
+
146
+ expect(model).toBe('model-with-spaces');
147
+ });
148
+
149
+ it('should fallback to provider-specific defaults when DEFAULT_AI_MODEL is not set', () => {
150
+ delete process.env.DEFAULT_AI_MODEL;
151
+
152
+ expect(getDefaultModel(AIProvider.OpenAI)).toBe(DEFAULT_MODELS[AIProvider.OpenAI]);
153
+ expect(getDefaultModel(AIProvider.Anthropic)).toBe(DEFAULT_MODELS[AIProvider.Anthropic]);
154
+ expect(getDefaultModel(AIProvider.Google)).toBe(DEFAULT_MODELS[AIProvider.Google]);
155
+ expect(getDefaultModel(AIProvider.XAI)).toBe(DEFAULT_MODELS[AIProvider.XAI]);
156
+ });
157
+
158
+ it('should fallback to provider-specific defaults when DEFAULT_AI_MODEL is empty', () => {
159
+ process.env.DEFAULT_AI_MODEL = '';
160
+
161
+ expect(getDefaultModel(AIProvider.OpenAI)).toBe(DEFAULT_MODELS[AIProvider.OpenAI]);
162
+ expect(getDefaultModel(AIProvider.Anthropic)).toBe(DEFAULT_MODELS[AIProvider.Anthropic]);
163
+ expect(getDefaultModel(AIProvider.Google)).toBe(DEFAULT_MODELS[AIProvider.Google]);
164
+ expect(getDefaultModel(AIProvider.XAI)).toBe(DEFAULT_MODELS[AIProvider.XAI]);
165
+ });
166
+
167
+ it('should fallback to provider-specific defaults when DEFAULT_AI_MODEL is only whitespace', () => {
168
+ process.env.DEFAULT_AI_MODEL = ' ';
169
+
170
+ expect(getDefaultModel(AIProvider.OpenAI)).toBe(DEFAULT_MODELS[AIProvider.OpenAI]);
171
+ expect(getDefaultModel(AIProvider.Anthropic)).toBe(DEFAULT_MODELS[AIProvider.Anthropic]);
172
+ expect(getDefaultModel(AIProvider.Google)).toBe(DEFAULT_MODELS[AIProvider.Google]);
173
+ expect(getDefaultModel(AIProvider.XAI)).toBe(DEFAULT_MODELS[AIProvider.XAI]);
174
+ });
10
175
  });
11
176
 
12
- it('should have valid AIProvider type', () => {
13
- const providers: AIProvider[] = [AIProvider.OpenAI, AIProvider.Anthropic, AIProvider.Google, AIProvider.XAI];
14
- expect(providers).toContain(AIProvider.OpenAI);
177
+ describe('Integration: Provider Selection with Priority', () => {
178
+ it('should demonstrate overrides when keys are not set', () => {
179
+ const availableProviders = getAvailableProviders();
180
+
181
+ if (availableProviders.includes(AIProvider.XAI)) {
182
+ expect(availableProviders).toContain(AIProvider.XAI);
183
+ }
184
+ });
185
+
186
+ it('should demonstrate priority order logic', () => {
187
+ const availableProviders = getAvailableProviders();
188
+ const defaultProvider = getDefaultAIProvider();
189
+ const priorityOrder = [AIProvider.Anthropic, AIProvider.OpenAI, AIProvider.Google, AIProvider.XAI];
190
+
191
+ let expectedProvider = AIProvider.OpenAI;
192
+ for (const provider of priorityOrder) {
193
+ if (availableProviders.includes(provider)) {
194
+ expectedProvider = provider;
195
+ break;
196
+ }
197
+ }
198
+
199
+ const envProvider = process.env.DEFAULT_AI_PROVIDER;
200
+ if (envProvider === undefined || envProvider === null || envProvider.toLowerCase() === 'invalid_provider') {
201
+ expect(defaultProvider).toBe(expectedProvider);
202
+ }
203
+ });
15
204
  });
16
205
  });