@auto-engineer/ai-gateway 0.7.0 → 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.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +8 -8
- package/.turbo/turbo-type-check.log +1 -1
- package/README.md +365 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +31 -2
- package/dist/config.js.map +1 -1
- package/dist/config.specs.d.ts +2 -0
- package/dist/config.specs.d.ts.map +1 -0
- package/dist/config.specs.js +123 -0
- package/dist/config.specs.js.map +1 -0
- package/dist/constants.d.ts +20 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +15 -0
- package/dist/constants.js.map +1 -0
- package/dist/index-custom.specs.d.ts +2 -0
- package/dist/index-custom.specs.d.ts.map +1 -0
- package/dist/index-custom.specs.js +161 -0
- package/dist/index-custom.specs.js.map +1 -0
- package/dist/index.d.ts +12 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +93 -59
- package/dist/index.js.map +1 -1
- package/dist/index.specs.js +152 -11
- package/dist/index.specs.js.map +1 -1
- package/dist/providers/custom.d.ts +6 -0
- package/dist/providers/custom.d.ts.map +1 -0
- package/dist/providers/custom.js +16 -0
- package/dist/providers/custom.js.map +1 -0
- package/dist/providers/custom.specs.d.ts +2 -0
- package/dist/providers/custom.specs.d.ts.map +1 -0
- package/dist/providers/custom.specs.js +129 -0
- package/dist/providers/custom.specs.js.map +1 -0
- package/package.json +5 -5
- package/src/config.specs.ts +147 -0
- package/src/config.ts +46 -2
- package/src/constants.ts +21 -0
- package/src/index-custom.specs.ts +192 -0
- package/src/index.specs.ts +199 -10
- package/src/index.ts +99 -78
- package/src/providers/custom.specs.ts +161 -0
- package/src/providers/custom.ts +24 -0
- package/tsconfig.tsbuildinfo +1 -1
package/dist/index.specs.js
CHANGED
|
@@ -1,14 +1,155 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { getAvailableProviders, getDefaultAIProvider, getDefaultModel } from './index.js';
|
|
3
|
+
import { DEFAULT_MODELS, AIProvider } from './constants.js';
|
|
4
|
+
// Mock the config module
|
|
5
|
+
vi.mock('./config', () => ({
|
|
6
|
+
configureAIProvider: vi.fn(() => ({
|
|
7
|
+
anthropic: { apiKey: 'test-anthropic-key' },
|
|
8
|
+
openai: { apiKey: 'test-openai-key' },
|
|
9
|
+
google: { apiKey: 'test-google-key' },
|
|
10
|
+
xai: { apiKey: 'test-xai-key' },
|
|
11
|
+
})),
|
|
12
|
+
}));
|
|
13
|
+
describe('Provider Selection Logic', () => {
|
|
14
|
+
describe('getDefaultAIProvider', () => {
|
|
15
|
+
let originalProvider;
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
originalProvider = process.env.DEFAULT_AI_PROVIDER;
|
|
18
|
+
});
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
if (originalProvider !== undefined) {
|
|
21
|
+
process.env.DEFAULT_AI_PROVIDER = originalProvider;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
delete process.env.DEFAULT_AI_PROVIDER;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
it('should respect DEFAULT_AI_PROVIDER environment variable when set to anthropic', () => {
|
|
28
|
+
process.env.DEFAULT_AI_PROVIDER = 'anthropic';
|
|
29
|
+
const provider = getDefaultAIProvider();
|
|
30
|
+
expect(provider).toBe(AIProvider.Anthropic);
|
|
31
|
+
});
|
|
32
|
+
it('should respect DEFAULT_AI_PROVIDER environment variable when set to xai', () => {
|
|
33
|
+
process.env.DEFAULT_AI_PROVIDER = 'xai';
|
|
34
|
+
const provider = getDefaultAIProvider();
|
|
35
|
+
expect(provider).toBe(AIProvider.XAI);
|
|
36
|
+
});
|
|
37
|
+
it('should respect DEFAULT_AI_PROVIDER environment variable when set to google', () => {
|
|
38
|
+
process.env.DEFAULT_AI_PROVIDER = 'google';
|
|
39
|
+
const provider = getDefaultAIProvider();
|
|
40
|
+
expect(provider).toBe(AIProvider.Google);
|
|
41
|
+
});
|
|
42
|
+
it('should be case insensitive for DEFAULT_AI_PROVIDER', () => {
|
|
43
|
+
process.env.DEFAULT_AI_PROVIDER = 'ANTHROPIC';
|
|
44
|
+
const provider = getDefaultAIProvider();
|
|
45
|
+
expect(provider).toBe(AIProvider.Anthropic);
|
|
46
|
+
});
|
|
47
|
+
it('should handle invalid DEFAULT_AI_PROVIDER values by falling back to available providers', () => {
|
|
48
|
+
process.env.DEFAULT_AI_PROVIDER = 'invalid_provider';
|
|
49
|
+
const provider = getDefaultAIProvider();
|
|
50
|
+
expect([AIProvider.Anthropic, AIProvider.OpenAI, AIProvider.Google, AIProvider.XAI]).toContain(provider);
|
|
51
|
+
});
|
|
52
|
+
it('should fallback to available providers when DEFAULT_AI_PROVIDER is not set', () => {
|
|
53
|
+
delete process.env.DEFAULT_AI_PROVIDER;
|
|
54
|
+
const provider = getDefaultAIProvider();
|
|
55
|
+
expect([AIProvider.Anthropic, AIProvider.OpenAI, AIProvider.Google, AIProvider.XAI]).toContain(provider);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
describe('getAvailableProviders', () => {
|
|
59
|
+
it('should return array of available providers', () => {
|
|
60
|
+
const providers = getAvailableProviders();
|
|
61
|
+
expect(Array.isArray(providers)).toBe(true);
|
|
62
|
+
expect(providers.length).toBeGreaterThan(0);
|
|
63
|
+
providers.forEach((provider) => {
|
|
64
|
+
expect([AIProvider.Anthropic, AIProvider.OpenAI, AIProvider.Google, AIProvider.XAI]).toContain(provider);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
it('should maintain priority order when multiple providers are available', () => {
|
|
68
|
+
const providers = getAvailableProviders();
|
|
69
|
+
const expectedOrder = [AIProvider.Anthropic, AIProvider.OpenAI, AIProvider.Google, AIProvider.XAI];
|
|
70
|
+
let lastIndex = -1;
|
|
71
|
+
providers.forEach((provider) => {
|
|
72
|
+
const currentIndex = expectedOrder.indexOf(provider);
|
|
73
|
+
expect(currentIndex).toBeGreaterThan(lastIndex);
|
|
74
|
+
lastIndex = currentIndex;
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
describe('getDefaultModel', () => {
|
|
79
|
+
let originalModel;
|
|
80
|
+
beforeEach(() => {
|
|
81
|
+
originalModel = process.env.DEFAULT_AI_MODEL;
|
|
82
|
+
});
|
|
83
|
+
afterEach(() => {
|
|
84
|
+
if (originalModel !== undefined) {
|
|
85
|
+
process.env.DEFAULT_AI_MODEL = originalModel;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
delete process.env.DEFAULT_AI_MODEL;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
it('should use DEFAULT_AI_MODEL when set in environment', () => {
|
|
92
|
+
process.env.DEFAULT_AI_MODEL = 'custom-model-name';
|
|
93
|
+
const model = getDefaultModel(AIProvider.OpenAI);
|
|
94
|
+
expect(model).toBe('custom-model-name');
|
|
95
|
+
});
|
|
96
|
+
it('should use DEFAULT_AI_MODEL regardless of provider when set', () => {
|
|
97
|
+
process.env.DEFAULT_AI_MODEL = 'universal-model';
|
|
98
|
+
expect(getDefaultModel(AIProvider.OpenAI)).toBe('universal-model');
|
|
99
|
+
expect(getDefaultModel(AIProvider.Anthropic)).toBe('universal-model');
|
|
100
|
+
expect(getDefaultModel(AIProvider.Google)).toBe('universal-model');
|
|
101
|
+
expect(getDefaultModel(AIProvider.XAI)).toBe('universal-model');
|
|
102
|
+
});
|
|
103
|
+
it('should trim whitespace from DEFAULT_AI_MODEL', () => {
|
|
104
|
+
process.env.DEFAULT_AI_MODEL = ' model-with-spaces ';
|
|
105
|
+
const model = getDefaultModel(AIProvider.OpenAI);
|
|
106
|
+
expect(model).toBe('model-with-spaces');
|
|
107
|
+
});
|
|
108
|
+
it('should fallback to provider-specific defaults when DEFAULT_AI_MODEL is not set', () => {
|
|
109
|
+
delete process.env.DEFAULT_AI_MODEL;
|
|
110
|
+
expect(getDefaultModel(AIProvider.OpenAI)).toBe(DEFAULT_MODELS[AIProvider.OpenAI]);
|
|
111
|
+
expect(getDefaultModel(AIProvider.Anthropic)).toBe(DEFAULT_MODELS[AIProvider.Anthropic]);
|
|
112
|
+
expect(getDefaultModel(AIProvider.Google)).toBe(DEFAULT_MODELS[AIProvider.Google]);
|
|
113
|
+
expect(getDefaultModel(AIProvider.XAI)).toBe(DEFAULT_MODELS[AIProvider.XAI]);
|
|
114
|
+
});
|
|
115
|
+
it('should fallback to provider-specific defaults when DEFAULT_AI_MODEL is empty', () => {
|
|
116
|
+
process.env.DEFAULT_AI_MODEL = '';
|
|
117
|
+
expect(getDefaultModel(AIProvider.OpenAI)).toBe(DEFAULT_MODELS[AIProvider.OpenAI]);
|
|
118
|
+
expect(getDefaultModel(AIProvider.Anthropic)).toBe(DEFAULT_MODELS[AIProvider.Anthropic]);
|
|
119
|
+
expect(getDefaultModel(AIProvider.Google)).toBe(DEFAULT_MODELS[AIProvider.Google]);
|
|
120
|
+
expect(getDefaultModel(AIProvider.XAI)).toBe(DEFAULT_MODELS[AIProvider.XAI]);
|
|
121
|
+
});
|
|
122
|
+
it('should fallback to provider-specific defaults when DEFAULT_AI_MODEL is only whitespace', () => {
|
|
123
|
+
process.env.DEFAULT_AI_MODEL = ' ';
|
|
124
|
+
expect(getDefaultModel(AIProvider.OpenAI)).toBe(DEFAULT_MODELS[AIProvider.OpenAI]);
|
|
125
|
+
expect(getDefaultModel(AIProvider.Anthropic)).toBe(DEFAULT_MODELS[AIProvider.Anthropic]);
|
|
126
|
+
expect(getDefaultModel(AIProvider.Google)).toBe(DEFAULT_MODELS[AIProvider.Google]);
|
|
127
|
+
expect(getDefaultModel(AIProvider.XAI)).toBe(DEFAULT_MODELS[AIProvider.XAI]);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
describe('Integration: Provider Selection with Priority', () => {
|
|
131
|
+
it('should demonstrate overrides when keys are not set', () => {
|
|
132
|
+
const availableProviders = getAvailableProviders();
|
|
133
|
+
if (availableProviders.includes(AIProvider.XAI)) {
|
|
134
|
+
expect(availableProviders).toContain(AIProvider.XAI);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
it('should demonstrate priority order logic', () => {
|
|
138
|
+
const availableProviders = getAvailableProviders();
|
|
139
|
+
const defaultProvider = getDefaultAIProvider();
|
|
140
|
+
const priorityOrder = [AIProvider.Anthropic, AIProvider.OpenAI, AIProvider.Google, AIProvider.XAI];
|
|
141
|
+
let expectedProvider = AIProvider.OpenAI;
|
|
142
|
+
for (const provider of priorityOrder) {
|
|
143
|
+
if (availableProviders.includes(provider)) {
|
|
144
|
+
expectedProvider = provider;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const envProvider = process.env.DEFAULT_AI_PROVIDER;
|
|
149
|
+
if (envProvider === undefined || envProvider === null || envProvider.toLowerCase() === 'invalid_provider') {
|
|
150
|
+
expect(defaultProvider).toBe(expectedProvider);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
12
153
|
});
|
|
13
154
|
});
|
|
14
155
|
//# sourceMappingURL=index.specs.js.map
|
package/dist/index.specs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.specs.js","sourceRoot":"","sources":["../src/index.specs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"index.specs.js","sourceRoot":"","sources":["../src/index.specs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { CustomProviderConfig } from '../constants';
|
|
2
|
+
export interface CustomProviderOptions {
|
|
3
|
+
config: CustomProviderConfig;
|
|
4
|
+
}
|
|
5
|
+
export declare function createCustomProvider(config: CustomProviderConfig): import("@ai-sdk/openai").OpenAIProvider;
|
|
6
|
+
//# sourceMappingURL=custom.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom.d.ts","sourceRoot":"","sources":["../../src/providers/custom.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAKpD,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,oBAAoB,CAAC;CAC9B;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,2CAahE"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createOpenAI } from '@ai-sdk/openai';
|
|
2
|
+
import createDebug from 'debug';
|
|
3
|
+
const debug = createDebug('ai-gateway:custom');
|
|
4
|
+
export function createCustomProvider(config) {
|
|
5
|
+
debug('Creating custom provider: %s with baseUrl: %s', config.name, config.baseUrl);
|
|
6
|
+
// Use OpenAI's provider implementation but with custom baseUrl
|
|
7
|
+
// This leverages the existing, battle-tested OpenAI provider while allowing custom endpoints
|
|
8
|
+
const customProvider = createOpenAI({
|
|
9
|
+
name: config.name,
|
|
10
|
+
baseURL: config.baseUrl,
|
|
11
|
+
apiKey: config.apiKey,
|
|
12
|
+
});
|
|
13
|
+
debug('Custom provider created successfully: %s', config.name);
|
|
14
|
+
return customProvider;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=custom.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom.js","sourceRoot":"","sources":["../../src/providers/custom.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,WAAW,MAAM,OAAO,CAAC;AAEhC,MAAM,KAAK,GAAG,WAAW,CAAC,mBAAmB,CAAC,CAAC;AAM/C,MAAM,UAAU,oBAAoB,CAAC,MAA4B;IAC/D,KAAK,CAAC,+CAA+C,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAEpF,+DAA+D;IAC/D,6FAA6F;IAC7F,MAAM,cAAc,GAAG,YAAY,CAAC;QAClC,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,MAAM,EAAE,MAAM,CAAC,MAAM;KACtB,CAAC,CAAC;IAEH,KAAK,CAAC,0CAA0C,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;IAC/D,OAAO,cAAc,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom.specs.d.ts","sourceRoot":"","sources":["../../src/providers/custom.specs.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { createCustomProvider } from './custom.js';
|
|
3
|
+
// Mock the createOpenAI function
|
|
4
|
+
vi.mock('@ai-sdk/openai', () => ({
|
|
5
|
+
createOpenAI: vi.fn((config) => ({
|
|
6
|
+
languageModel: vi.fn((modelId) => ({
|
|
7
|
+
modelId,
|
|
8
|
+
provider: config.name,
|
|
9
|
+
specificationVersion: 'v1',
|
|
10
|
+
defaultObjectGenerationMode: 'json',
|
|
11
|
+
})),
|
|
12
|
+
})),
|
|
13
|
+
}));
|
|
14
|
+
describe('Custom Provider', () => {
|
|
15
|
+
const mockConfig = {
|
|
16
|
+
name: 'test-provider',
|
|
17
|
+
baseUrl: 'https://api.example.com/v1',
|
|
18
|
+
apiKey: 'test-api-key',
|
|
19
|
+
defaultModel: 'test-model',
|
|
20
|
+
};
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
vi.clearAllMocks();
|
|
23
|
+
});
|
|
24
|
+
describe('createCustomProvider', () => {
|
|
25
|
+
it('should create a custom provider with the correct configuration', () => {
|
|
26
|
+
const provider = createCustomProvider(mockConfig);
|
|
27
|
+
expect(provider).toBeDefined();
|
|
28
|
+
// Provider creation should succeed with valid config
|
|
29
|
+
});
|
|
30
|
+
it('should call createOpenAI with the correct parameters', async () => {
|
|
31
|
+
const { createOpenAI } = await import('@ai-sdk/openai');
|
|
32
|
+
createCustomProvider(mockConfig);
|
|
33
|
+
expect(createOpenAI).toHaveBeenCalledWith({
|
|
34
|
+
name: mockConfig.name,
|
|
35
|
+
baseURL: mockConfig.baseUrl,
|
|
36
|
+
apiKey: mockConfig.apiKey,
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
it('should create a provider that can create language models', () => {
|
|
40
|
+
const provider = createCustomProvider(mockConfig);
|
|
41
|
+
const model = provider.languageModel('test-model');
|
|
42
|
+
expect(model).toBeDefined();
|
|
43
|
+
expect(model.modelId).toBe('test-model');
|
|
44
|
+
expect(model.provider).toBe(mockConfig.name);
|
|
45
|
+
});
|
|
46
|
+
it('should handle different base URLs correctly', () => {
|
|
47
|
+
const configs = [
|
|
48
|
+
{ ...mockConfig, baseUrl: 'https://api.litellm.ai' },
|
|
49
|
+
{ ...mockConfig, baseUrl: 'http://localhost:8000' },
|
|
50
|
+
{ ...mockConfig, baseUrl: 'https://custom-llm.company.com/api' },
|
|
51
|
+
];
|
|
52
|
+
configs.forEach((config) => {
|
|
53
|
+
const provider = createCustomProvider(config);
|
|
54
|
+
expect(provider).toBeDefined();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
it('should handle different provider names', () => {
|
|
58
|
+
const configs = [
|
|
59
|
+
{ ...mockConfig, name: 'litellm' },
|
|
60
|
+
{ ...mockConfig, name: 'local-llm' },
|
|
61
|
+
{ ...mockConfig, name: 'company-custom-llm' },
|
|
62
|
+
];
|
|
63
|
+
configs.forEach((config) => {
|
|
64
|
+
const provider = createCustomProvider(config);
|
|
65
|
+
expect(provider).toBeDefined();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
describe('provider compatibility', () => {
|
|
70
|
+
it('should be compatible with OpenAI-style endpoints', () => {
|
|
71
|
+
const litellmConfig = {
|
|
72
|
+
name: 'litellm',
|
|
73
|
+
baseUrl: 'https://api.litellm.ai/chat/completions',
|
|
74
|
+
apiKey: 'sk-litellm-key',
|
|
75
|
+
defaultModel: 'claude-3-sonnet',
|
|
76
|
+
};
|
|
77
|
+
const provider = createCustomProvider(litellmConfig);
|
|
78
|
+
expect(provider).toBeDefined();
|
|
79
|
+
});
|
|
80
|
+
it('should work with localhost endpoints for development', () => {
|
|
81
|
+
const localConfig = {
|
|
82
|
+
name: 'local-dev',
|
|
83
|
+
baseUrl: 'http://localhost:8000',
|
|
84
|
+
apiKey: 'local-key',
|
|
85
|
+
defaultModel: 'local-model',
|
|
86
|
+
};
|
|
87
|
+
const provider = createCustomProvider(localConfig);
|
|
88
|
+
expect(provider).toBeDefined();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe('error handling', () => {
|
|
92
|
+
it('should pass through any errors from createOpenAI', async () => {
|
|
93
|
+
const { createOpenAI } = await import('@ai-sdk/openai');
|
|
94
|
+
const mockError = new Error('Invalid API key');
|
|
95
|
+
vi.mocked(createOpenAI).mockImplementationOnce(() => {
|
|
96
|
+
throw mockError;
|
|
97
|
+
});
|
|
98
|
+
expect(() => createCustomProvider(mockConfig)).toThrow('Invalid API key');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe('configuration validation', () => {
|
|
102
|
+
it('should handle minimal required configuration', () => {
|
|
103
|
+
const minimalConfig = {
|
|
104
|
+
name: 'minimal',
|
|
105
|
+
baseUrl: 'https://api.example.com',
|
|
106
|
+
apiKey: 'key',
|
|
107
|
+
defaultModel: 'model',
|
|
108
|
+
};
|
|
109
|
+
const provider = createCustomProvider(minimalConfig);
|
|
110
|
+
expect(provider).toBeDefined();
|
|
111
|
+
});
|
|
112
|
+
it('should preserve all configuration properties', async () => {
|
|
113
|
+
const fullConfig = {
|
|
114
|
+
name: 'full-config',
|
|
115
|
+
baseUrl: 'https://api.example.com/v1',
|
|
116
|
+
apiKey: 'sk-test-key-123',
|
|
117
|
+
defaultModel: 'gpt-4o',
|
|
118
|
+
};
|
|
119
|
+
createCustomProvider(fullConfig);
|
|
120
|
+
const { createOpenAI } = await import('@ai-sdk/openai');
|
|
121
|
+
expect(createOpenAI).toHaveBeenCalledWith({
|
|
122
|
+
name: fullConfig.name,
|
|
123
|
+
baseURL: fullConfig.baseUrl,
|
|
124
|
+
apiKey: fullConfig.apiKey,
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
//# sourceMappingURL=custom.specs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom.specs.js","sourceRoot":"","sources":["../../src/providers/custom.specs.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@auto-engineer/ai-gateway",
|
|
3
|
-
"version": "0.7.0",
|
|
4
3
|
"type": "module",
|
|
5
4
|
"main": "./dist/index.js",
|
|
6
5
|
"types": "./dist/index.d.ts",
|
|
7
6
|
"scripts": {
|
|
8
7
|
"build": "tsc && tsx ../../scripts/fix-esm-imports.ts",
|
|
9
8
|
"dev": "tsc --watch",
|
|
10
|
-
"test": "vitest run",
|
|
9
|
+
"test": "vitest run --reporter=dot",
|
|
11
10
|
"lint": "eslint 'src/**/*.ts' --max-warnings 0 --config ../../eslint.config.ts",
|
|
12
11
|
"lint:fix": "eslint 'src/**/*.ts' --fix --config ../../eslint.config.ts",
|
|
13
12
|
"type-check": "tsc --noEmit",
|
|
14
|
-
"format": "prettier --write \"**/*.{js,ts,json,md,yml,yaml}\" --ignore-path ../../.prettierignore",
|
|
15
|
-
"format:fix": "prettier --write \"**/*.{js,ts,json,md,yml,yaml}\" --ignore-path ../../.prettierignore",
|
|
13
|
+
"format": "prettier --write \"**/*.{js,ts,json,md,yml,yaml}\" --ignore-path ../../.prettierignore --log-level warn",
|
|
14
|
+
"format:fix": "prettier --write \"**/*.{js,ts,json,md,yml,yaml}\" --ignore-path ../../.prettierignore --log-level warn",
|
|
16
15
|
"link:dev": "pnpm build && pnpm link --global",
|
|
17
16
|
"unlink:dev": "pnpm unlink --global",
|
|
18
17
|
"prepublishOnly": "npm run build"
|
|
@@ -30,5 +29,6 @@
|
|
|
30
29
|
},
|
|
31
30
|
"publishConfig": {
|
|
32
31
|
"access": "public"
|
|
33
|
-
}
|
|
32
|
+
},
|
|
33
|
+
"version": "0.8.1"
|
|
34
34
|
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { configureAIProvider } from './config';
|
|
3
|
+
|
|
4
|
+
// Mock environment variables
|
|
5
|
+
const originalEnv = process.env;
|
|
6
|
+
|
|
7
|
+
describe('AI Configuration with Custom Providers', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
vi.resetModules();
|
|
10
|
+
// Clear all AI-related environment variables for clean test state
|
|
11
|
+
process.env = {};
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
process.env = originalEnv;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('configureAIProvider', () => {
|
|
19
|
+
it('should configure custom provider when all environment variables are set', () => {
|
|
20
|
+
process.env.CUSTOM_PROVIDER_NAME = 'litellm';
|
|
21
|
+
process.env.CUSTOM_PROVIDER_BASE_URL = 'https://api.litellm.ai';
|
|
22
|
+
process.env.CUSTOM_PROVIDER_API_KEY = 'sk-litellm-key';
|
|
23
|
+
process.env.CUSTOM_PROVIDER_DEFAULT_MODEL = 'claude-3-sonnet';
|
|
24
|
+
|
|
25
|
+
const config = configureAIProvider();
|
|
26
|
+
|
|
27
|
+
expect(config.custom).toBeDefined();
|
|
28
|
+
expect(config.custom?.name).toBe('litellm');
|
|
29
|
+
expect(config.custom?.baseUrl).toBe('https://api.litellm.ai');
|
|
30
|
+
expect(config.custom?.apiKey).toBe('sk-litellm-key');
|
|
31
|
+
expect(config.custom?.defaultModel).toBe('claude-3-sonnet');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should not configure custom provider when environment variables are missing', () => {
|
|
35
|
+
// Only set some environment variables
|
|
36
|
+
process.env.CUSTOM_PROVIDER_NAME = 'incomplete';
|
|
37
|
+
process.env.CUSTOM_PROVIDER_BASE_URL = 'https://api.example.com';
|
|
38
|
+
process.env.ANTHROPIC_API_KEY = 'sk-test'; // Need at least one provider
|
|
39
|
+
// Missing CUSTOM_PROVIDER_API_KEY and CUSTOM_PROVIDER_DEFAULT_MODEL
|
|
40
|
+
|
|
41
|
+
const config = configureAIProvider();
|
|
42
|
+
expect(config.custom).toBeUndefined();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should handle multiple providers including custom', () => {
|
|
46
|
+
process.env.OPENAI_API_KEY = 'sk-openai-key';
|
|
47
|
+
process.env.ANTHROPIC_API_KEY = 'sk-anthropic-key';
|
|
48
|
+
process.env.CUSTOM_PROVIDER_NAME = 'custom';
|
|
49
|
+
process.env.CUSTOM_PROVIDER_BASE_URL = 'https://api.custom.com';
|
|
50
|
+
process.env.CUSTOM_PROVIDER_API_KEY = 'custom-key';
|
|
51
|
+
process.env.CUSTOM_PROVIDER_DEFAULT_MODEL = 'custom-model';
|
|
52
|
+
|
|
53
|
+
const config = configureAIProvider();
|
|
54
|
+
|
|
55
|
+
expect(config.openai).toBeDefined();
|
|
56
|
+
expect(config.anthropic).toBeDefined();
|
|
57
|
+
expect(config.custom).toBeDefined();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should work with only custom provider configured', () => {
|
|
61
|
+
process.env.CUSTOM_PROVIDER_NAME = 'only-custom';
|
|
62
|
+
process.env.CUSTOM_PROVIDER_BASE_URL = 'https://api.only-custom.com';
|
|
63
|
+
process.env.CUSTOM_PROVIDER_API_KEY = 'only-custom-key';
|
|
64
|
+
process.env.CUSTOM_PROVIDER_DEFAULT_MODEL = 'only-custom-model';
|
|
65
|
+
|
|
66
|
+
const config = configureAIProvider();
|
|
67
|
+
|
|
68
|
+
expect(config.custom).toBeDefined();
|
|
69
|
+
expect(config.openai).toBeUndefined();
|
|
70
|
+
expect(config.anthropic).toBeUndefined();
|
|
71
|
+
expect(config.google).toBeUndefined();
|
|
72
|
+
expect(config.xai).toBeUndefined();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should throw error when no providers are configured', () => {
|
|
76
|
+
// Clear all environment variables
|
|
77
|
+
delete process.env.OPENAI_API_KEY;
|
|
78
|
+
delete process.env.ANTHROPIC_API_KEY;
|
|
79
|
+
delete process.env.GEMINI_API_KEY;
|
|
80
|
+
delete process.env.XAI_API_KEY;
|
|
81
|
+
delete process.env.CUSTOM_PROVIDER_NAME;
|
|
82
|
+
delete process.env.CUSTOM_PROVIDER_BASE_URL;
|
|
83
|
+
delete process.env.CUSTOM_PROVIDER_API_KEY;
|
|
84
|
+
delete process.env.CUSTOM_PROVIDER_DEFAULT_MODEL;
|
|
85
|
+
|
|
86
|
+
expect(() => configureAIProvider()).toThrow(/At least one AI provider must be configured/);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should handle custom provider with different configurations', () => {
|
|
90
|
+
const testCases = [
|
|
91
|
+
{
|
|
92
|
+
name: 'litellm-proxy',
|
|
93
|
+
baseUrl: 'https://litellm-proxy.company.com/v1',
|
|
94
|
+
model: 'gpt-4o-mini',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'local-ollama',
|
|
98
|
+
baseUrl: 'http://localhost:11434/v1',
|
|
99
|
+
model: 'llama3.1:8b',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'azure-openai',
|
|
103
|
+
baseUrl: 'https://company.openai.azure.com/openai/deployments',
|
|
104
|
+
model: 'gpt-4',
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
testCases.forEach((testCase) => {
|
|
109
|
+
process.env.CUSTOM_PROVIDER_NAME = testCase.name;
|
|
110
|
+
process.env.CUSTOM_PROVIDER_BASE_URL = testCase.baseUrl;
|
|
111
|
+
process.env.CUSTOM_PROVIDER_API_KEY = 'test-key';
|
|
112
|
+
process.env.CUSTOM_PROVIDER_DEFAULT_MODEL = testCase.model;
|
|
113
|
+
|
|
114
|
+
const config = configureAIProvider();
|
|
115
|
+
|
|
116
|
+
expect(config.custom?.name).toBe(testCase.name);
|
|
117
|
+
expect(config.custom?.baseUrl).toBe(testCase.baseUrl);
|
|
118
|
+
expect(config.custom?.defaultModel).toBe(testCase.model);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should handle empty string environment variables as undefined', () => {
|
|
123
|
+
process.env.CUSTOM_PROVIDER_NAME = '';
|
|
124
|
+
process.env.CUSTOM_PROVIDER_BASE_URL = '';
|
|
125
|
+
process.env.CUSTOM_PROVIDER_API_KEY = '';
|
|
126
|
+
process.env.CUSTOM_PROVIDER_DEFAULT_MODEL = '';
|
|
127
|
+
process.env.ANTHROPIC_API_KEY = 'sk-test'; // Need at least one provider
|
|
128
|
+
|
|
129
|
+
const config = configureAIProvider();
|
|
130
|
+
expect(config.custom).toBeUndefined();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should handle whitespace in environment variables', () => {
|
|
134
|
+
process.env.CUSTOM_PROVIDER_NAME = ' litellm ';
|
|
135
|
+
process.env.CUSTOM_PROVIDER_BASE_URL = ' https://api.litellm.ai ';
|
|
136
|
+
process.env.CUSTOM_PROVIDER_API_KEY = ' sk-key ';
|
|
137
|
+
process.env.CUSTOM_PROVIDER_DEFAULT_MODEL = ' claude-3-sonnet ';
|
|
138
|
+
|
|
139
|
+
const config = configureAIProvider();
|
|
140
|
+
|
|
141
|
+
expect(config.custom?.name).toBe(' litellm ');
|
|
142
|
+
expect(config.custom?.baseUrl).toBe(' https://api.litellm.ai ');
|
|
143
|
+
expect(config.custom?.apiKey).toBe(' sk-key ');
|
|
144
|
+
expect(config.custom?.defaultModel).toBe(' claude-3-sonnet ');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
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
|
-
|
|
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,
|
|
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
|
|