@layer-ai/core 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/key-resolver.d.ts +16 -0
- package/dist/lib/key-resolver.d.ts.map +1 -0
- package/dist/lib/key-resolver.js +56 -0
- package/dist/lib/provider-factory.d.ts +3 -1
- package/dist/lib/provider-factory.d.ts.map +1 -1
- package/dist/lib/provider-factory.js +4 -2
- package/dist/routes/v2/complete.js +10 -10
- package/dist/routes/v2/tests/test-byok-completion.d.ts +10 -0
- package/dist/routes/v2/tests/test-byok-completion.d.ts.map +1 -0
- package/dist/routes/v2/tests/test-byok-completion.js +82 -0
- package/dist/routes/v2/tests/test-byok.d.ts +8 -0
- package/dist/routes/v2/tests/test-byok.d.ts.map +1 -0
- package/dist/routes/v2/tests/test-byok.js +73 -0
- package/dist/services/providers/anthropic-adapter.d.ts +1 -1
- package/dist/services/providers/anthropic-adapter.d.ts.map +1 -1
- package/dist/services/providers/anthropic-adapter.js +13 -5
- package/dist/services/providers/base-adapter.d.ts +2 -1
- package/dist/services/providers/base-adapter.d.ts.map +1 -1
- package/dist/services/providers/google-adapter.d.ts +1 -1
- package/dist/services/providers/google-adapter.d.ts.map +1 -1
- package/dist/services/providers/google-adapter.js +25 -17
- package/dist/services/providers/mistral-adapter.d.ts +1 -1
- package/dist/services/providers/mistral-adapter.d.ts.map +1 -1
- package/dist/services/providers/mistral-adapter.js +19 -11
- package/dist/services/providers/openai-adapter.d.ts +1 -1
- package/dist/services/providers/openai-adapter.d.ts.map +1 -1
- package/dist/services/providers/openai-adapter.js +22 -14
- package/package.json +1 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key Resolver
|
|
3
|
+
*
|
|
4
|
+
* Resolves API keys for providers with BYOK support.
|
|
5
|
+
* Priority: User's BYOK key → Platform key → Error
|
|
6
|
+
*/
|
|
7
|
+
import type { Provider } from './provider-constants.js';
|
|
8
|
+
/**
|
|
9
|
+
* Resolves the API key to use for a provider
|
|
10
|
+
* @param provider - The provider name
|
|
11
|
+
* @param userId - Optional user ID for BYOK lookup
|
|
12
|
+
* @param platformKey - The platform's API key (fallback)
|
|
13
|
+
* @returns The API key to use
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveApiKey(provider: Provider, userId: string | undefined, platformKey: string | undefined): Promise<string>;
|
|
16
|
+
//# sourceMappingURL=key-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-resolver.d.ts","sourceRoot":"","sources":["../../src/lib/key-resolver.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAGxD;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,OAAO,CAAC,MAAM,CAAC,CAoBjB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key Resolver
|
|
3
|
+
*
|
|
4
|
+
* Resolves API keys for providers with BYOK support.
|
|
5
|
+
* Priority: User's BYOK key → Platform key → Error
|
|
6
|
+
*/
|
|
7
|
+
import { decrypt } from './encryption.js';
|
|
8
|
+
/**
|
|
9
|
+
* Resolves the API key to use for a provider
|
|
10
|
+
* @param provider - The provider name
|
|
11
|
+
* @param userId - Optional user ID for BYOK lookup
|
|
12
|
+
* @param platformKey - The platform's API key (fallback)
|
|
13
|
+
* @returns The API key to use
|
|
14
|
+
*/
|
|
15
|
+
export async function resolveApiKey(provider, userId, platformKey) {
|
|
16
|
+
// If userId is provided, check for BYOK key
|
|
17
|
+
if (userId) {
|
|
18
|
+
try {
|
|
19
|
+
const byokKey = await getUserProviderKey(userId, provider);
|
|
20
|
+
if (byokKey) {
|
|
21
|
+
return byokKey;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
console.error(`Failed to fetch BYOK key for user ${userId}, provider ${provider}:`, error);
|
|
26
|
+
// Continue to fallback
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Fallback to platform key
|
|
30
|
+
if (platformKey) {
|
|
31
|
+
return platformKey;
|
|
32
|
+
}
|
|
33
|
+
throw new Error(`No API key available for provider: ${provider}`);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Fetches and decrypts user's BYOK key for a provider
|
|
37
|
+
* @param userId - The user ID
|
|
38
|
+
* @param provider - The provider name
|
|
39
|
+
* @returns The decrypted API key, or null if not found
|
|
40
|
+
*/
|
|
41
|
+
async function getUserProviderKey(userId, provider) {
|
|
42
|
+
// Dynamically import to avoid circular dependencies
|
|
43
|
+
const { db } = await import('./db/postgres.js');
|
|
44
|
+
const encryptionKey = process.env.ENCRYPTION_KEY;
|
|
45
|
+
if (!encryptionKey) {
|
|
46
|
+
console.warn('ENCRYPTION_KEY not set, BYOK disabled');
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const providerKey = await db.getProviderKey(userId, provider);
|
|
50
|
+
if (!providerKey) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
// Decrypt the key
|
|
54
|
+
const decryptedKey = decrypt(providerKey.encryptedKey, encryptionKey);
|
|
55
|
+
return decryptedKey;
|
|
56
|
+
}
|
|
@@ -20,6 +20,8 @@ export declare function getProviderForModel(model: SupportedModel): Provider;
|
|
|
20
20
|
/**
|
|
21
21
|
* Calls the appropriate provider adapter for the given request.
|
|
22
22
|
* This is the main entry point for executing AI model requests.
|
|
23
|
+
* @param request - The Layer request to execute
|
|
24
|
+
* @param userId - Optional user ID for BYOK key resolution
|
|
23
25
|
*/
|
|
24
|
-
export declare function callAdapter(request: LayerRequest): Promise<LayerResponse>;
|
|
26
|
+
export declare function callAdapter(request: LayerRequest, userId?: string): Promise<LayerResponse>;
|
|
25
27
|
//# sourceMappingURL=provider-factory.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-factory.d.ts","sourceRoot":"","sources":["../../src/lib/provider-factory.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEjF,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAG7E,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,CAAC;AAa9C;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAmBhE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,GAAG,QAAQ,CAMnE;AAED
|
|
1
|
+
{"version":3,"file":"provider-factory.d.ts","sourceRoot":"","sources":["../../src/lib/provider-factory.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEjF,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAG7E,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,CAAC;AAa9C;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAmBhE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,GAAG,QAAQ,CAMnE;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAWhG"}
|
|
@@ -58,8 +58,10 @@ export function getProviderForModel(model) {
|
|
|
58
58
|
/**
|
|
59
59
|
* Calls the appropriate provider adapter for the given request.
|
|
60
60
|
* This is the main entry point for executing AI model requests.
|
|
61
|
+
* @param request - The Layer request to execute
|
|
62
|
+
* @param userId - Optional user ID for BYOK key resolution
|
|
61
63
|
*/
|
|
62
|
-
export async function callAdapter(request) {
|
|
64
|
+
export async function callAdapter(request, userId) {
|
|
63
65
|
const normalizedModel = normalizeModelId(request.model);
|
|
64
66
|
const provider = getProviderForModel(normalizedModel);
|
|
65
67
|
const AdapterClass = PROVIDER_ADAPTERS[provider];
|
|
@@ -67,5 +69,5 @@ export async function callAdapter(request) {
|
|
|
67
69
|
throw new Error(`No adapter found for provider: ${provider}`);
|
|
68
70
|
}
|
|
69
71
|
const adapter = new AdapterClass();
|
|
70
|
-
return await adapter.call(request);
|
|
72
|
+
return await adapter.call(request, userId);
|
|
71
73
|
}
|
|
@@ -82,14 +82,14 @@ function getModelsToTry(gateConfig, primaryModel) {
|
|
|
82
82
|
}
|
|
83
83
|
return modelsToTry;
|
|
84
84
|
}
|
|
85
|
-
async function executeWithFallback(request, modelsToTry) {
|
|
85
|
+
async function executeWithFallback(request, modelsToTry, userId) {
|
|
86
86
|
let result = null;
|
|
87
87
|
let lastError = null;
|
|
88
88
|
let modelUsed = request.model;
|
|
89
89
|
for (const modelToTry of modelsToTry) {
|
|
90
90
|
try {
|
|
91
91
|
const modelRequest = { ...request, model: modelToTry };
|
|
92
|
-
result = await callAdapter(modelRequest);
|
|
92
|
+
result = await callAdapter(modelRequest, userId);
|
|
93
93
|
modelUsed = modelToTry;
|
|
94
94
|
break;
|
|
95
95
|
}
|
|
@@ -104,28 +104,28 @@ async function executeWithFallback(request, modelsToTry) {
|
|
|
104
104
|
}
|
|
105
105
|
return { result, modelUsed };
|
|
106
106
|
}
|
|
107
|
-
async function executeWithRoundRobin(gateConfig, request) {
|
|
107
|
+
async function executeWithRoundRobin(gateConfig, request, userId) {
|
|
108
108
|
if (!gateConfig.fallbackModels?.length) {
|
|
109
|
-
const result = await callAdapter(request);
|
|
109
|
+
const result = await callAdapter(request, userId);
|
|
110
110
|
return { result, modelUsed: request.model };
|
|
111
111
|
}
|
|
112
112
|
const allModels = [gateConfig.model, ...gateConfig.fallbackModels];
|
|
113
113
|
const modelIndex = Math.floor(Math.random() * allModels.length);
|
|
114
114
|
const selectedModel = allModels[modelIndex];
|
|
115
115
|
const modelRequest = { ...request, model: selectedModel };
|
|
116
|
-
const result = await callAdapter(modelRequest);
|
|
116
|
+
const result = await callAdapter(modelRequest, userId);
|
|
117
117
|
return { result, modelUsed: selectedModel };
|
|
118
118
|
}
|
|
119
|
-
async function executeWithRouting(gateConfig, request) {
|
|
119
|
+
async function executeWithRouting(gateConfig, request, userId) {
|
|
120
120
|
const modelsToTry = getModelsToTry(gateConfig, request.model);
|
|
121
121
|
switch (gateConfig.routingStrategy) {
|
|
122
122
|
case 'fallback':
|
|
123
|
-
return await executeWithFallback(request, modelsToTry);
|
|
123
|
+
return await executeWithFallback(request, modelsToTry, userId);
|
|
124
124
|
case 'round-robin':
|
|
125
|
-
return await executeWithRoundRobin(gateConfig, request);
|
|
125
|
+
return await executeWithRoundRobin(gateConfig, request, userId);
|
|
126
126
|
case 'single':
|
|
127
127
|
default:
|
|
128
|
-
const result = await callAdapter(request);
|
|
128
|
+
const result = await callAdapter(request, userId);
|
|
129
129
|
return { result, modelUsed: request.model };
|
|
130
130
|
}
|
|
131
131
|
}
|
|
@@ -163,7 +163,7 @@ router.post('/', authenticate, async (req, res) => {
|
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
const finalRequest = resolveFinalRequest(gateConfig, request);
|
|
166
|
-
const { result, modelUsed } = await executeWithRouting(gateConfig, finalRequest);
|
|
166
|
+
const { result, modelUsed } = await executeWithRouting(gateConfig, finalRequest, userId);
|
|
167
167
|
const latencyMs = Date.now() - startTime;
|
|
168
168
|
// Log request to database
|
|
169
169
|
db.logRequest({
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test BYOK completion flow
|
|
3
|
+
*
|
|
4
|
+
* This test verifies that when making a completion request:
|
|
5
|
+
* 1. The system checks for user's BYOK keys
|
|
6
|
+
* 2. Uses BYOK keys when available
|
|
7
|
+
* 3. Falls back to platform keys when BYOK not configured
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=test-byok-completion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-byok-completion.d.ts","sourceRoot":"","sources":["../../../../src/routes/v2/tests/test-byok-completion.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test BYOK completion flow
|
|
3
|
+
*
|
|
4
|
+
* This test verifies that when making a completion request:
|
|
5
|
+
* 1. The system checks for user's BYOK keys
|
|
6
|
+
* 2. Uses BYOK keys when available
|
|
7
|
+
* 3. Falls back to platform keys when BYOK not configured
|
|
8
|
+
*/
|
|
9
|
+
import { OpenAIAdapter } from '../../../services/providers/openai-adapter.js';
|
|
10
|
+
import { AnthropicAdapter } from '../../../services/providers/anthropic-adapter.js';
|
|
11
|
+
import { GoogleAdapter } from '../../../services/providers/google-adapter.js';
|
|
12
|
+
// Test user ID from the database
|
|
13
|
+
const TEST_USER_ID = 'ebd64998-465d-4211-ad67-87b4e01ad0da';
|
|
14
|
+
const SIMPLE_REQUEST = {
|
|
15
|
+
gate: 'byok-test',
|
|
16
|
+
model: 'gpt-4o-mini',
|
|
17
|
+
type: 'chat',
|
|
18
|
+
data: {
|
|
19
|
+
messages: [
|
|
20
|
+
{
|
|
21
|
+
role: 'user',
|
|
22
|
+
content: 'Say "BYOK test successful" and nothing else.',
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
maxTokens: 20,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
async function testBYOKCompletion() {
|
|
29
|
+
console.log('\n🧪 Testing BYOK Completion Flow...\n');
|
|
30
|
+
try {
|
|
31
|
+
// Test 1: OpenAI with BYOK (should use user's key)
|
|
32
|
+
console.log('Test 1: OpenAI completion with BYOK key');
|
|
33
|
+
console.log('----------------------------------------');
|
|
34
|
+
const openaiAdapter = new OpenAIAdapter();
|
|
35
|
+
const openaiRequest = { ...SIMPLE_REQUEST, model: 'gpt-4o-mini' };
|
|
36
|
+
console.log(`Making request to OpenAI with userId: ${TEST_USER_ID}`);
|
|
37
|
+
const openaiResult = await openaiAdapter.call(openaiRequest, TEST_USER_ID);
|
|
38
|
+
console.log('✓ OpenAI request successful');
|
|
39
|
+
console.log(`Response: ${openaiResult.content}`);
|
|
40
|
+
console.log('Note: This request used your BYOK OpenAI key\n');
|
|
41
|
+
// Test 2: Anthropic with BYOK (should use user's key)
|
|
42
|
+
console.log('Test 2: Anthropic completion with BYOK key');
|
|
43
|
+
console.log('------------------------------------------');
|
|
44
|
+
const anthropicAdapter = new AnthropicAdapter();
|
|
45
|
+
const anthropicRequest = { ...SIMPLE_REQUEST, model: 'claude-3-haiku-20240307' };
|
|
46
|
+
console.log(`Making request to Anthropic with userId: ${TEST_USER_ID}`);
|
|
47
|
+
const anthropicResult = await anthropicAdapter.call(anthropicRequest, TEST_USER_ID);
|
|
48
|
+
console.log('✓ Anthropic request successful');
|
|
49
|
+
console.log(`Response: ${anthropicResult.content}`);
|
|
50
|
+
console.log('Note: This request used your BYOK Anthropic key\n');
|
|
51
|
+
// Test 3: Google without BYOK (should use platform key)
|
|
52
|
+
console.log('Test 3: Google completion without BYOK key');
|
|
53
|
+
console.log('------------------------------------------');
|
|
54
|
+
const googleAdapter = new GoogleAdapter();
|
|
55
|
+
const googleRequest = { ...SIMPLE_REQUEST, model: 'gemini-2.0-flash' };
|
|
56
|
+
console.log(`Making request to Google with userId: ${TEST_USER_ID}`);
|
|
57
|
+
const googleResult = await googleAdapter.call(googleRequest, TEST_USER_ID);
|
|
58
|
+
console.log('✓ Google request successful');
|
|
59
|
+
console.log(`Response: ${googleResult.content}`);
|
|
60
|
+
console.log('Note: This request used the platform Google key (no BYOK configured)\n');
|
|
61
|
+
console.log('✅ All BYOK completion tests passed!\n');
|
|
62
|
+
console.log('Summary:');
|
|
63
|
+
console.log('- OpenAI: Used BYOK key ✓');
|
|
64
|
+
console.log('- Anthropic: Used BYOK key ✓');
|
|
65
|
+
console.log('- Google: Used platform key (fallback) ✓');
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
console.error('\n❌ BYOK completion test failed:', error);
|
|
69
|
+
if (error instanceof Error) {
|
|
70
|
+
console.error('Error message:', error.message);
|
|
71
|
+
console.error('Stack:', error.stack);
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Run the test
|
|
77
|
+
testBYOKCompletion()
|
|
78
|
+
.then(() => process.exit(0))
|
|
79
|
+
.catch((error) => {
|
|
80
|
+
console.error('Test failed:', error);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-byok.d.ts","sourceRoot":"","sources":["../../../../src/routes/v2/tests/test-byok.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test BYOK (Bring Your Own Keys) functionality
|
|
3
|
+
*
|
|
4
|
+
* This test verifies that when a user has configured their own provider keys,
|
|
5
|
+
* those keys are used instead of the platform keys.
|
|
6
|
+
*/
|
|
7
|
+
import { db } from '../../../lib/db/postgres.js';
|
|
8
|
+
import { decrypt } from '../../../lib/encryption.js';
|
|
9
|
+
async function testBYOK() {
|
|
10
|
+
console.log('\n🔑 Testing BYOK Key Resolution...\n');
|
|
11
|
+
// Test user ID from the database query
|
|
12
|
+
const userId = 'ebd64998-465d-4211-ad67-87b4e01ad0da';
|
|
13
|
+
try {
|
|
14
|
+
// Test 1: Retrieve OpenAI key
|
|
15
|
+
console.log('Test 1: Retrieving OpenAI BYOK key...');
|
|
16
|
+
const openaiKey = await db.getProviderKey(userId, 'openai');
|
|
17
|
+
if (openaiKey) {
|
|
18
|
+
console.log('✓ OpenAI key found in database');
|
|
19
|
+
console.log(` Provider: ${openaiKey.provider}`);
|
|
20
|
+
console.log(` Key Prefix: ${openaiKey.keyPrefix}`);
|
|
21
|
+
console.log(` Created: ${openaiKey.createdAt}`);
|
|
22
|
+
// Decrypt and verify the key
|
|
23
|
+
const encryptionKey = process.env.ENCRYPTION_KEY;
|
|
24
|
+
if (!encryptionKey) {
|
|
25
|
+
throw new Error('ENCRYPTION_KEY not found in environment');
|
|
26
|
+
}
|
|
27
|
+
const decryptedKey = decrypt(openaiKey.encryptedKey, encryptionKey);
|
|
28
|
+
console.log(` Decrypted key starts with: ${decryptedKey.substring(0, 10)}...`);
|
|
29
|
+
console.log(` Decrypted key length: ${decryptedKey.length} characters`);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
console.log('✗ No OpenAI key found');
|
|
33
|
+
}
|
|
34
|
+
console.log('\nTest 2: Retrieving Anthropic BYOK key...');
|
|
35
|
+
const anthropicKey = await db.getProviderKey(userId, 'anthropic');
|
|
36
|
+
if (anthropicKey) {
|
|
37
|
+
console.log('✓ Anthropic key found in database');
|
|
38
|
+
console.log(` Provider: ${anthropicKey.provider}`);
|
|
39
|
+
console.log(` Key Prefix: ${anthropicKey.keyPrefix}`);
|
|
40
|
+
console.log(` Created: ${anthropicKey.createdAt}`);
|
|
41
|
+
const encryptionKey = process.env.ENCRYPTION_KEY;
|
|
42
|
+
if (!encryptionKey) {
|
|
43
|
+
throw new Error('ENCRYPTION_KEY not found in environment');
|
|
44
|
+
}
|
|
45
|
+
const decryptedKey = decrypt(anthropicKey.encryptedKey, encryptionKey);
|
|
46
|
+
console.log(` Decrypted key starts with: ${decryptedKey.substring(0, 14)}...`);
|
|
47
|
+
console.log(` Decrypted key length: ${decryptedKey.length} characters`);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
console.log('✗ No Anthropic key found');
|
|
51
|
+
}
|
|
52
|
+
console.log('\nTest 3: Retrieving Google BYOK key (should not exist)...');
|
|
53
|
+
const googleKey = await db.getProviderKey(userId, 'google');
|
|
54
|
+
if (googleKey) {
|
|
55
|
+
console.log('✓ Google key found in database');
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
console.log('✓ No Google key found (as expected)');
|
|
59
|
+
}
|
|
60
|
+
console.log('\n✅ BYOK test completed successfully!\n');
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
console.error('\n❌ BYOK test failed:', error);
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Run the test
|
|
68
|
+
testBYOK()
|
|
69
|
+
.then(() => process.exit(0))
|
|
70
|
+
.catch((error) => {
|
|
71
|
+
console.error('Test failed:', error);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
});
|
|
@@ -7,7 +7,7 @@ export declare class AnthropicAdapter extends BaseProviderAdapter {
|
|
|
7
7
|
protected toolChoiceMappings: Record<string, string | object>;
|
|
8
8
|
protected finishReasonMappings: Record<string, FinishReason>;
|
|
9
9
|
protected mapToolChoice(choice: ToolChoice): string | object | undefined;
|
|
10
|
-
call(request: LayerRequest): Promise<LayerResponse>;
|
|
10
|
+
call(request: LayerRequest, userId?: string): Promise<LayerResponse>;
|
|
11
11
|
private handleChat;
|
|
12
12
|
}
|
|
13
13
|
//# sourceMappingURL=anthropic-adapter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"anthropic-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/anthropic-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAmB,MAAM,mBAAmB,CAAC;AACzE,OAAO,EACL,YAAY,EACZ,aAAa,EACb,IAAI,EACJ,YAAY,EACZ,UAAU,EACX,MAAM,eAAe,CAAC;AACvB,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"anthropic-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/anthropic-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAmB,MAAM,mBAAmB,CAAC;AACzE,OAAO,EACL,YAAY,EACZ,aAAa,EACb,IAAI,EACJ,YAAY,EACZ,UAAU,EACX,MAAM,eAAe,CAAC;AACvB,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAoB1E,qBAAa,gBAAiB,SAAQ,mBAAmB;IACvD,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAsB;IAElD,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAQ1C;IAEF,SAAS,CAAC,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAI3D;IAEF,SAAS,CAAC,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAM1D;IAEF,SAAS,CAAC,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS;IAalE,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;YAoB5D,UAAU;CA4JzB"}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import Anthropic from "@anthropic-ai/sdk";
|
|
2
2
|
import { BaseProviderAdapter, ADAPTER_HANDLED } from './base-adapter.js';
|
|
3
3
|
import { PROVIDER } from "../../lib/provider-constants.js";
|
|
4
|
+
import { resolveApiKey } from '../../lib/key-resolver.js';
|
|
4
5
|
let anthropic = null;
|
|
5
|
-
function getAnthropicClient() {
|
|
6
|
+
function getAnthropicClient(apiKey) {
|
|
7
|
+
// If custom API key provided, create new client
|
|
8
|
+
if (apiKey) {
|
|
9
|
+
return new Anthropic({ apiKey });
|
|
10
|
+
}
|
|
11
|
+
// Otherwise use singleton with platform key
|
|
6
12
|
if (!anthropic) {
|
|
7
13
|
anthropic = new Anthropic({
|
|
8
14
|
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
@@ -47,10 +53,12 @@ export class AnthropicAdapter extends BaseProviderAdapter {
|
|
|
47
53
|
// Handle string format using base mappings
|
|
48
54
|
return super.mapToolChoice(choice);
|
|
49
55
|
}
|
|
50
|
-
async call(request) {
|
|
56
|
+
async call(request, userId) {
|
|
57
|
+
// Resolve API key (BYOK → Platform key)
|
|
58
|
+
const apiKey = await resolveApiKey(this.provider, userId, process.env.ANTHROPIC_API_KEY);
|
|
51
59
|
switch (request.type) {
|
|
52
60
|
case 'chat':
|
|
53
|
-
return this.handleChat(request);
|
|
61
|
+
return this.handleChat(request, apiKey);
|
|
54
62
|
case 'image':
|
|
55
63
|
throw new Error('Image generation not yet supported by Anthropic');
|
|
56
64
|
case 'embeddings':
|
|
@@ -63,9 +71,9 @@ export class AnthropicAdapter extends BaseProviderAdapter {
|
|
|
63
71
|
throw new Error(`Unknown modality: ${request.type}`);
|
|
64
72
|
}
|
|
65
73
|
}
|
|
66
|
-
async handleChat(request) {
|
|
74
|
+
async handleChat(request, apiKey) {
|
|
67
75
|
const startTime = Date.now();
|
|
68
|
-
const client = getAnthropicClient();
|
|
76
|
+
const client = getAnthropicClient(apiKey);
|
|
69
77
|
const { data: chat, model } = request;
|
|
70
78
|
if (!model) {
|
|
71
79
|
throw new Error('Model is required for chat completions');
|
|
@@ -3,6 +3,7 @@ import type { Provider } from "../../lib/provider-constants.js";
|
|
|
3
3
|
export { ADAPTER_HANDLED };
|
|
4
4
|
export declare abstract class BaseProviderAdapter {
|
|
5
5
|
protected abstract provider: Provider;
|
|
6
|
+
protected userId?: string;
|
|
6
7
|
protected roleMappings?: Record<Role, string>;
|
|
7
8
|
protected imageDetailMappings?: Record<ImageDetail, string>;
|
|
8
9
|
protected toolChoiceMappings?: Record<string, string | object>;
|
|
@@ -15,7 +16,7 @@ export declare abstract class BaseProviderAdapter {
|
|
|
15
16
|
protected audioMimeTypeMappings?: Record<AudioMimeType, string>;
|
|
16
17
|
protected imageMimeTypeMappings?: Record<ImageMimeType, string>;
|
|
17
18
|
protected encodingFormatMappings?: Record<EncodingFormat, string>;
|
|
18
|
-
abstract call(request: LayerRequest): Promise<LayerResponse>;
|
|
19
|
+
abstract call(request: LayerRequest, userId?: string): Promise<LayerResponse>;
|
|
19
20
|
protected mapRole(role: Role): string;
|
|
20
21
|
protected mapImageDetail(detail: ImageDetail): string | undefined;
|
|
21
22
|
protected mapImageSize(size: ImageSize): string | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/base-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,aAAa,EACb,IAAI,EACJ,WAAW,EACX,SAAS,EACT,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,EACX,aAAa,EACb,aAAa,EACb,YAAY,EACZ,UAAU,EACV,cAAc,EACd,eAAe,EAGhB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAEhE,OAAO,EAAE,eAAe,EAAE,CAAC;AAE3B,8BAAsB,mBAAmB;IACvC,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"base-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/base-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,7 +11,7 @@ export declare class GoogleAdapter extends BaseProviderAdapter {
|
|
|
11
11
|
aspectRatio: string;
|
|
12
12
|
resolution: string;
|
|
13
13
|
}>;
|
|
14
|
-
call(request: LayerRequest): Promise<LayerResponse>;
|
|
14
|
+
call(request: LayerRequest, userId?: string): Promise<LayerResponse>;
|
|
15
15
|
private handleChat;
|
|
16
16
|
private handleImageGeneration;
|
|
17
17
|
private handleEmbeddings;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/google-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,yBAAyB,EAI1B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,IAAI,EACJ,YAAY,EACZ,SAAS,EACV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"google-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/google-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,yBAAyB,EAI1B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,IAAI,EACJ,YAAY,EACZ,SAAS,EACV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAkB1E,qBAAa,aAAc,SAAQ,mBAAmB;IACpD,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAmB;IAE/C,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAQ1C;IAGF,SAAS,CAAC,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAO1D;IAEF,SAAS,CAAC,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAIrE;IAGF,SAAS,CAAC,eAAe,EAAE,MAAM,CAC/B,SAAS,EACT;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAC5C,CAKC;IAEI,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;YAoB5D,UAAU;YAsLV,qBAAqB;YAmCrB,gBAAgB;YAoChB,qBAAqB;YAoHrB,kBAAkB;IAyChC,OAAO,CAAC,KAAK;CAGd"}
|
|
@@ -2,8 +2,14 @@ import { GoogleGenAI, FunctionCallingConfigMode, VideoGenerationReferenceType, }
|
|
|
2
2
|
import { BaseProviderAdapter } from './base-adapter.js';
|
|
3
3
|
import { ADAPTER_HANDLED } from './base-adapter.js';
|
|
4
4
|
import { PROVIDER } from "../../lib/provider-constants.js";
|
|
5
|
+
import { resolveApiKey } from '../../lib/key-resolver.js';
|
|
5
6
|
let client = null;
|
|
6
|
-
function getGoogleClient() {
|
|
7
|
+
function getGoogleClient(apiKey) {
|
|
8
|
+
// If custom API key provided, create new client
|
|
9
|
+
if (apiKey) {
|
|
10
|
+
return new GoogleGenAI({ apiKey });
|
|
11
|
+
}
|
|
12
|
+
// Otherwise use singleton with platform key
|
|
7
13
|
if (!client) {
|
|
8
14
|
client = new GoogleGenAI({ apiKey: process.env.GOOGLE_API_KEY || '' });
|
|
9
15
|
}
|
|
@@ -44,25 +50,27 @@ export class GoogleAdapter extends BaseProviderAdapter {
|
|
|
44
50
|
'1792x1024': { aspectRatio: '16:9', resolution: '1080p' },
|
|
45
51
|
};
|
|
46
52
|
}
|
|
47
|
-
async call(request) {
|
|
53
|
+
async call(request, userId) {
|
|
54
|
+
// Resolve API key (BYOK → Platform key)
|
|
55
|
+
const apiKey = await resolveApiKey(this.provider, userId, process.env.GOOGLE_API_KEY);
|
|
48
56
|
switch (request.type) {
|
|
49
57
|
case 'chat':
|
|
50
|
-
return this.handleChat(request);
|
|
58
|
+
return this.handleChat(request, apiKey);
|
|
51
59
|
case 'image':
|
|
52
|
-
return this.handleImageGeneration(request);
|
|
60
|
+
return this.handleImageGeneration(request, apiKey);
|
|
53
61
|
case 'embeddings':
|
|
54
|
-
return this.handleEmbeddings(request);
|
|
62
|
+
return this.handleEmbeddings(request, apiKey);
|
|
55
63
|
case 'tts':
|
|
56
|
-
return this.handleTextToSpeech(request);
|
|
64
|
+
return this.handleTextToSpeech(request, apiKey);
|
|
57
65
|
case 'video':
|
|
58
|
-
return this.handleVideoGeneration(request);
|
|
66
|
+
return this.handleVideoGeneration(request, apiKey);
|
|
59
67
|
default:
|
|
60
68
|
throw new Error(`Unknown modality: ${request.type}`);
|
|
61
69
|
}
|
|
62
70
|
}
|
|
63
|
-
async handleChat(request) {
|
|
71
|
+
async handleChat(request, apiKey) {
|
|
64
72
|
const startTime = Date.now();
|
|
65
|
-
const client = getGoogleClient();
|
|
73
|
+
const client = getGoogleClient(apiKey);
|
|
66
74
|
const { data: chat, model } = request;
|
|
67
75
|
if (!model) {
|
|
68
76
|
throw new Error('Model is required for chat completion');
|
|
@@ -215,9 +223,9 @@ export class GoogleAdapter extends BaseProviderAdapter {
|
|
|
215
223
|
raw: response,
|
|
216
224
|
};
|
|
217
225
|
}
|
|
218
|
-
async handleImageGeneration(request) {
|
|
226
|
+
async handleImageGeneration(request, apiKey) {
|
|
219
227
|
const startTime = Date.now();
|
|
220
|
-
const client = getGoogleClient();
|
|
228
|
+
const client = getGoogleClient(apiKey);
|
|
221
229
|
const { data: image, model } = request;
|
|
222
230
|
if (!model) {
|
|
223
231
|
throw new Error('Model is required for chat completion');
|
|
@@ -241,9 +249,9 @@ export class GoogleAdapter extends BaseProviderAdapter {
|
|
|
241
249
|
raw: response,
|
|
242
250
|
};
|
|
243
251
|
}
|
|
244
|
-
async handleEmbeddings(request) {
|
|
252
|
+
async handleEmbeddings(request, apiKey) {
|
|
245
253
|
const startTime = Date.now();
|
|
246
|
-
const client = getGoogleClient();
|
|
254
|
+
const client = getGoogleClient(apiKey);
|
|
247
255
|
const { data: embedding, model } = request;
|
|
248
256
|
if (!model) {
|
|
249
257
|
throw new Error('Model is required for chat completion');
|
|
@@ -268,9 +276,9 @@ export class GoogleAdapter extends BaseProviderAdapter {
|
|
|
268
276
|
raw: response,
|
|
269
277
|
};
|
|
270
278
|
}
|
|
271
|
-
async handleVideoGeneration(request) {
|
|
279
|
+
async handleVideoGeneration(request, apiKey) {
|
|
272
280
|
const startTime = Date.now();
|
|
273
|
-
const client = getGoogleClient();
|
|
281
|
+
const client = getGoogleClient(apiKey);
|
|
274
282
|
const { data: video, model } = request;
|
|
275
283
|
if (!model) {
|
|
276
284
|
throw new Error('Model is required for chat completion');
|
|
@@ -371,9 +379,9 @@ export class GoogleAdapter extends BaseProviderAdapter {
|
|
|
371
379
|
raw: operation.response,
|
|
372
380
|
};
|
|
373
381
|
}
|
|
374
|
-
async handleTextToSpeech(request) {
|
|
382
|
+
async handleTextToSpeech(request, apiKey) {
|
|
375
383
|
const startTime = Date.now();
|
|
376
|
-
const client = getGoogleClient();
|
|
384
|
+
const client = getGoogleClient(apiKey);
|
|
377
385
|
const { data: tts, model } = request;
|
|
378
386
|
if (!model) {
|
|
379
387
|
throw new Error('Model is required for chat completion');
|
|
@@ -6,7 +6,7 @@ export declare class MistralAdapter extends BaseProviderAdapter {
|
|
|
6
6
|
protected roleMappings: Record<Role, string>;
|
|
7
7
|
protected finishReasonMappings: Record<string, FinishReason>;
|
|
8
8
|
protected toolChoiceMappings: Record<string, string>;
|
|
9
|
-
call(request: LayerRequest): Promise<LayerResponse>;
|
|
9
|
+
call(request: LayerRequest, userId?: string): Promise<LayerResponse>;
|
|
10
10
|
private handleChat;
|
|
11
11
|
private handleEmbeddings;
|
|
12
12
|
private handleOCR;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mistral-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/mistral-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EACL,YAAY,EACZ,aAAa,EACb,IAAI,EACJ,YAAY,EAEb,MAAM,eAAe,CAAC;AACvB,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"mistral-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/mistral-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EACL,YAAY,EACZ,aAAa,EACb,IAAI,EACJ,YAAY,EAEb,MAAM,eAAe,CAAC;AACvB,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAoB1E,qBAAa,cAAe,SAAQ,mBAAmB;IACrD,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAoB;IAEhD,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAQ1C;IAGF,SAAS,CAAC,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAM1D;IAEF,SAAS,CAAC,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIlD;IAEI,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;YAsB5D,UAAU;YA+LV,gBAAgB;YAwChB,SAAS;CA4ExB"}
|
|
@@ -2,8 +2,14 @@ import { Mistral } from '@mistralai/mistralai';
|
|
|
2
2
|
import { BaseProviderAdapter } from './base-adapter.js';
|
|
3
3
|
import { ADAPTER_HANDLED, } from '@layer-ai/sdk';
|
|
4
4
|
import { PROVIDER } from "../../lib/provider-constants.js";
|
|
5
|
+
import { resolveApiKey } from '../../lib/key-resolver.js';
|
|
5
6
|
let client = null;
|
|
6
|
-
function getMistralClient() {
|
|
7
|
+
function getMistralClient(apiKey) {
|
|
8
|
+
// If custom API key provided, create new client
|
|
9
|
+
if (apiKey) {
|
|
10
|
+
return new Mistral({ apiKey });
|
|
11
|
+
}
|
|
12
|
+
// Otherwise use singleton with platform key
|
|
7
13
|
if (!client) {
|
|
8
14
|
client = new Mistral({
|
|
9
15
|
apiKey: process.env.MISTRAL_API_KEY || '',
|
|
@@ -38,14 +44,16 @@ export class MistralAdapter extends BaseProviderAdapter {
|
|
|
38
44
|
required: 'any',
|
|
39
45
|
};
|
|
40
46
|
}
|
|
41
|
-
async call(request) {
|
|
47
|
+
async call(request, userId) {
|
|
48
|
+
// Resolve API key (BYOK → Platform key)
|
|
49
|
+
const apiKey = await resolveApiKey(this.provider, userId, process.env.MISTRAL_API_KEY);
|
|
42
50
|
switch (request.type) {
|
|
43
51
|
case 'chat':
|
|
44
|
-
return this.handleChat(request);
|
|
52
|
+
return this.handleChat(request, apiKey);
|
|
45
53
|
case 'embeddings':
|
|
46
|
-
return this.handleEmbeddings(request);
|
|
54
|
+
return this.handleEmbeddings(request, apiKey);
|
|
47
55
|
case 'ocr':
|
|
48
|
-
return this.handleOCR(request);
|
|
56
|
+
return this.handleOCR(request, apiKey);
|
|
49
57
|
case 'image':
|
|
50
58
|
throw new Error('Image generation not supported by Mistral');
|
|
51
59
|
case 'tts':
|
|
@@ -56,9 +64,9 @@ export class MistralAdapter extends BaseProviderAdapter {
|
|
|
56
64
|
throw new Error(`Unknown modality: ${request.type}`);
|
|
57
65
|
}
|
|
58
66
|
}
|
|
59
|
-
async handleChat(request) {
|
|
67
|
+
async handleChat(request, apiKey) {
|
|
60
68
|
const startTime = Date.now();
|
|
61
|
-
const mistral = getMistralClient();
|
|
69
|
+
const mistral = getMistralClient(apiKey);
|
|
62
70
|
const { data: chat, model } = request;
|
|
63
71
|
if (!model) {
|
|
64
72
|
throw new Error('Model is required for chat completion');
|
|
@@ -209,9 +217,9 @@ export class MistralAdapter extends BaseProviderAdapter {
|
|
|
209
217
|
raw: response,
|
|
210
218
|
};
|
|
211
219
|
}
|
|
212
|
-
async handleEmbeddings(request) {
|
|
220
|
+
async handleEmbeddings(request, apiKey) {
|
|
213
221
|
const startTime = Date.now();
|
|
214
|
-
const mistral = getMistralClient();
|
|
222
|
+
const mistral = getMistralClient(apiKey);
|
|
215
223
|
const { data: embedding, model } = request;
|
|
216
224
|
if (!model) {
|
|
217
225
|
throw new Error('Model is required for embeddings');
|
|
@@ -239,9 +247,9 @@ export class MistralAdapter extends BaseProviderAdapter {
|
|
|
239
247
|
raw: response,
|
|
240
248
|
};
|
|
241
249
|
}
|
|
242
|
-
async handleOCR(request) {
|
|
250
|
+
async handleOCR(request, apiKey) {
|
|
243
251
|
const startTime = Date.now();
|
|
244
|
-
const mistral = getMistralClient();
|
|
252
|
+
const mistral = getMistralClient(apiKey);
|
|
245
253
|
const { data: ocr, model } = request;
|
|
246
254
|
const ocrModel = model || 'mistral-ocr-latest';
|
|
247
255
|
let document;
|
|
@@ -11,7 +11,7 @@ export declare class OpenAIAdapter extends BaseProviderAdapter {
|
|
|
11
11
|
protected imageStyleMappings: Record<ImageStyle, string>;
|
|
12
12
|
protected videoSizeMappings: Record<VideoSize, string>;
|
|
13
13
|
protected audioFormatMappings: Record<AudioFormat, string>;
|
|
14
|
-
call(request: LayerRequest): Promise<LayerResponse>;
|
|
14
|
+
call(request: LayerRequest, userId?: string): Promise<LayerResponse>;
|
|
15
15
|
private handleChat;
|
|
16
16
|
private handleImageGeneration;
|
|
17
17
|
private handleEmbeddings;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/openai-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EACL,YAAY,EACZ,aAAa,EACb,IAAI,EACJ,WAAW,EACX,SAAS,EACT,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACb,MAAM,eAAe,CAAC;AACvB,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"openai-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/openai-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EACL,YAAY,EACZ,aAAa,EACb,IAAI,EACJ,WAAW,EACX,SAAS,EACT,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACb,MAAM,eAAe,CAAC;AACvB,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAoB1E,qBAAa,aAAc,SAAQ,mBAAmB;IACpD,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAmB;IAE/C,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAQ1C;IAEF,SAAS,CAAC,mBAAmB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAIxD;IAEF,SAAS,CAAC,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAK1D;IAEF,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAQpD;IAEF,SAAS,CAAC,oBAAoB,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAG1D;IAEF,SAAS,CAAC,kBAAkB,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAGtD;IAEF,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAKpD;IAEF,SAAS,CAAC,mBAAmB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAOxD;IAEI,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;YAoB5D,UAAU;YA2GV,qBAAqB;YA6BrB,gBAAgB;YAiChB,kBAAkB;CA8BjC"}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import OpenAI from 'openai';
|
|
2
2
|
import { BaseProviderAdapter } from './base-adapter.js';
|
|
3
3
|
import { PROVIDER } from "../../lib/provider-constants.js";
|
|
4
|
+
import { resolveApiKey } from '../../lib/key-resolver.js';
|
|
4
5
|
let openai = null;
|
|
5
|
-
function getOpenAIClient() {
|
|
6
|
+
function getOpenAIClient(apiKey) {
|
|
7
|
+
// If custom API key provided, create new client
|
|
8
|
+
if (apiKey) {
|
|
9
|
+
return new OpenAI({ apiKey });
|
|
10
|
+
}
|
|
11
|
+
// Otherwise use singleton with platform key
|
|
6
12
|
if (!openai) {
|
|
7
13
|
openai = new OpenAI({
|
|
8
14
|
apiKey: process.env.OPENAI_API_KEY,
|
|
@@ -66,25 +72,27 @@ export class OpenAIAdapter extends BaseProviderAdapter {
|
|
|
66
72
|
pcm: 'pcm',
|
|
67
73
|
};
|
|
68
74
|
}
|
|
69
|
-
async call(request) {
|
|
75
|
+
async call(request, userId) {
|
|
76
|
+
// Resolve API key (BYOK → Platform key)
|
|
77
|
+
const apiKey = await resolveApiKey(this.provider, userId, process.env.OPENAI_API_KEY);
|
|
70
78
|
switch (request.type) {
|
|
71
79
|
case 'chat':
|
|
72
|
-
return this.handleChat(request);
|
|
80
|
+
return this.handleChat(request, apiKey);
|
|
73
81
|
case 'image':
|
|
74
|
-
return this.handleImageGeneration(request);
|
|
82
|
+
return this.handleImageGeneration(request, apiKey);
|
|
75
83
|
case 'embeddings':
|
|
76
|
-
return this.handleEmbeddings(request);
|
|
84
|
+
return this.handleEmbeddings(request, apiKey);
|
|
77
85
|
case 'tts':
|
|
78
|
-
return this.handleTextToSpeech(request);
|
|
86
|
+
return this.handleTextToSpeech(request, apiKey);
|
|
79
87
|
case 'video':
|
|
80
88
|
throw new Error('Video generation not yet supported by OpenAI');
|
|
81
89
|
default:
|
|
82
90
|
throw new Error(`Unknown modality: ${request.type}`);
|
|
83
91
|
}
|
|
84
92
|
}
|
|
85
|
-
async handleChat(request) {
|
|
93
|
+
async handleChat(request, apiKey) {
|
|
86
94
|
const startTime = Date.now();
|
|
87
|
-
const client = getOpenAIClient();
|
|
95
|
+
const client = getOpenAIClient(apiKey);
|
|
88
96
|
const { data: chat, model } = request;
|
|
89
97
|
if (!model) {
|
|
90
98
|
throw new Error('Model is required for chat completion');
|
|
@@ -176,9 +184,9 @@ export class OpenAIAdapter extends BaseProviderAdapter {
|
|
|
176
184
|
raw: response,
|
|
177
185
|
};
|
|
178
186
|
}
|
|
179
|
-
async handleImageGeneration(request) {
|
|
187
|
+
async handleImageGeneration(request, apiKey) {
|
|
180
188
|
const startTime = Date.now();
|
|
181
|
-
const client = getOpenAIClient();
|
|
189
|
+
const client = getOpenAIClient(apiKey);
|
|
182
190
|
const { data: image, model } = request;
|
|
183
191
|
if (!model) {
|
|
184
192
|
throw new Error('Model is required for image generation');
|
|
@@ -201,9 +209,9 @@ export class OpenAIAdapter extends BaseProviderAdapter {
|
|
|
201
209
|
raw: response,
|
|
202
210
|
};
|
|
203
211
|
}
|
|
204
|
-
async handleEmbeddings(request) {
|
|
212
|
+
async handleEmbeddings(request, apiKey) {
|
|
205
213
|
const startTime = Date.now();
|
|
206
|
-
const client = getOpenAIClient();
|
|
214
|
+
const client = getOpenAIClient(apiKey);
|
|
207
215
|
const { data: embedding, model } = request;
|
|
208
216
|
if (!model) {
|
|
209
217
|
throw new Error('Model is required for embeddings');
|
|
@@ -229,9 +237,9 @@ export class OpenAIAdapter extends BaseProviderAdapter {
|
|
|
229
237
|
raw: response,
|
|
230
238
|
};
|
|
231
239
|
}
|
|
232
|
-
async handleTextToSpeech(request) {
|
|
240
|
+
async handleTextToSpeech(request, apiKey) {
|
|
233
241
|
const startTime = Date.now();
|
|
234
|
-
const client = getOpenAIClient();
|
|
242
|
+
const client = getOpenAIClient(apiKey);
|
|
235
243
|
const { data: tts, model } = request;
|
|
236
244
|
if (!model) {
|
|
237
245
|
throw new Error('Model is required for tts');
|