@layer-ai/core 0.1.12 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -0
  4. package/dist/routes/complete.d.ts.map +1 -1
  5. package/dist/routes/complete.js +24 -4
  6. package/dist/routes/v2/complete.d.ts +4 -0
  7. package/dist/routes/v2/complete.d.ts.map +1 -0
  8. package/dist/routes/v2/complete.js +214 -0
  9. package/dist/routes/v2/tests/test-complete-anthropic.d.ts +2 -0
  10. package/dist/routes/v2/tests/test-complete-anthropic.d.ts.map +1 -0
  11. package/dist/routes/v2/tests/test-complete-anthropic.js +132 -0
  12. package/dist/routes/v2/tests/test-complete-openai.d.ts +2 -0
  13. package/dist/routes/v2/tests/test-complete-openai.d.ts.map +1 -0
  14. package/dist/routes/v2/tests/test-complete-openai.js +178 -0
  15. package/dist/routes/v2/tests/test-complete-routing.d.ts +2 -0
  16. package/dist/routes/v2/tests/test-complete-routing.d.ts.map +1 -0
  17. package/dist/routes/v2/tests/test-complete-routing.js +192 -0
  18. package/dist/services/providers/anthropic-adapter.d.ts +12 -0
  19. package/dist/services/providers/anthropic-adapter.d.ts.map +1 -0
  20. package/dist/services/providers/anthropic-adapter.js +203 -0
  21. package/dist/services/providers/base-adapter.d.ts +1 -1
  22. package/dist/services/providers/base-adapter.d.ts.map +1 -1
  23. package/dist/services/providers/base-adapter.js +1 -1
  24. package/dist/services/providers/openai-adapter.d.ts +2 -2
  25. package/dist/services/providers/openai-adapter.d.ts.map +1 -1
  26. package/dist/services/providers/openai-adapter.js +15 -3
  27. package/dist/services/providers/tests/test-anthropic-adapter.d.ts +2 -0
  28. package/dist/services/providers/tests/test-anthropic-adapter.d.ts.map +1 -0
  29. package/dist/services/providers/tests/test-anthropic-adapter.js +104 -0
  30. package/dist/services/providers/tests/test-openai-adapter.d.ts +2 -0
  31. package/dist/services/providers/tests/test-openai-adapter.d.ts.map +1 -0
  32. package/dist/services/providers/tests/test-openai-adapter.js +118 -0
  33. package/package.json +9 -9
  34. package/LICENSE +0 -21
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export { default as gatesRouter } from './routes/gates.js';
3
3
  export { default as keysRouter } from './routes/keys.js';
4
4
  export { default as logsRouter } from './routes/logs.js';
5
5
  export { default as completeRouter } from './routes/complete.js';
6
+ export { default as completeV2Router } from './routes/v2/complete.js';
6
7
  export { authenticate } from './middleware/auth.js';
7
8
  export { db } from './lib/db/postgres.js';
8
9
  export { default as redis } from './lib/db/redis.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGjE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGpD,OAAO,EAAE,EAAE,EAAE,MAAM,sBAAsB,CAAC;AAC1C,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAGrD,eAAO,MAAM,gBAAgB,GAAU,QAAQ,MAAM,KAAG,OAAO,CAAC,MAAM,CAGrE,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAU,QAAQ,MAAM,KAAG,OAAO,CAAC,IAAI,CAG3E,CAAC;AAGF,OAAO,EAAE,gBAAgB,IAAI,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAC5F,OAAO,EAAE,gBAAgB,IAAI,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAClG,OAAO,EAAE,gBAAgB,IAAI,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAC5F,YAAY,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAGtE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGpD,OAAO,EAAE,EAAE,EAAE,MAAM,sBAAsB,CAAC;AAC1C,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAGrD,eAAO,MAAM,gBAAgB,GAAU,QAAQ,MAAM,KAAG,OAAO,CAAC,MAAM,CAGrE,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAU,QAAQ,MAAM,KAAG,OAAO,CAAC,IAAI,CAG3E,CAAC;AAGF,OAAO,EAAE,gBAAgB,IAAI,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAC5F,OAAO,EAAE,gBAAgB,IAAI,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAClG,OAAO,EAAE,gBAAgB,IAAI,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAC5F,YAAY,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC"}
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ export { default as gatesRouter } from './routes/gates.js';
4
4
  export { default as keysRouter } from './routes/keys.js';
5
5
  export { default as logsRouter } from './routes/logs.js';
6
6
  export { default as completeRouter } from './routes/complete.js';
7
+ export { default as completeV2Router } from './routes/v2/complete.js';
7
8
  // Middleware
8
9
  export { authenticate } from './middleware/auth.js';
9
10
  // Database
@@ -1 +1 @@
1
- {"version":3,"file":"complete.d.ts","sourceRoot":"","sources":["../../src/routes/complete.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAWpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AA2QpC,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"complete.d.ts","sourceRoot":"","sources":["../../src/routes/complete.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAWpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AAgSpC,eAAe,MAAM,CAAC"}
@@ -3,7 +3,7 @@ import { db } from '../lib/db/postgres.js';
3
3
  import { cache } from '../lib/db/redis.js';
4
4
  import { authenticate } from '../middleware/auth.js';
5
5
  import { OpenAIAdapter } from '../services/providers/openai-adapter.js';
6
- import * as anthropic from '../services/providers/anthropic.js';
6
+ import { AnthropicAdapter } from '../services/providers/anthropic-adapter.js';
7
7
  import * as google from '../services/providers/google.js';
8
8
  import { MODEL_REGISTRY, OverrideField } from '@layer-ai/sdk';
9
9
  const router = Router();
@@ -54,7 +54,7 @@ function resolveFinalParams(gateConfig, requestParams) {
54
54
  }
55
55
  /**
56
56
  * MIGRATION IN PROGRESS: Moving to normalized adapter pattern.
57
- * OpenAI now uses the new adapter. Other providers will follow.
57
+ * OpenAI and Anthropic now use the new adapter. Other providers will follow.
58
58
  * This temporary conversion layer will be removed after all providers are migrated.
59
59
  */
60
60
  async function callProvider(params) {
@@ -82,8 +82,28 @@ async function callProvider(params) {
82
82
  costUsd: layerResponse.cost || 0,
83
83
  };
84
84
  }
85
- case 'anthropic':
86
- return await anthropic.createCompletion(params);
85
+ case 'anthropic': {
86
+ const adapter = new AnthropicAdapter();
87
+ const layerResponse = await adapter.call({
88
+ gate: 'internal',
89
+ model: params.model,
90
+ type: 'chat',
91
+ data: {
92
+ messages: params.messages,
93
+ systemPrompt: params.systemPrompt,
94
+ temperature: params.temperature,
95
+ maxTokens: params.maxTokens,
96
+ topP: params.topP,
97
+ },
98
+ });
99
+ return {
100
+ content: layerResponse.content || '',
101
+ promptTokens: layerResponse.usage?.promptTokens || 0,
102
+ completionTokens: layerResponse.usage?.completionTokens || 0,
103
+ totalTokens: layerResponse.usage?.totalTokens || 0,
104
+ costUsd: layerResponse.cost || 0,
105
+ };
106
+ }
87
107
  case 'google':
88
108
  return await google.createCompletion(params);
89
109
  default:
@@ -0,0 +1,4 @@
1
+ import type { Router as RouterType } from 'express';
2
+ declare const router: RouterType;
3
+ export default router;
4
+ //# sourceMappingURL=complete.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"complete.d.ts","sourceRoot":"","sources":["../../../src/routes/v2/complete.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAUpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AAgQpC,eAAe,MAAM,CAAC"}
@@ -0,0 +1,214 @@
1
+ import { Router } from 'express';
2
+ import { db } from '../../lib/db/postgres.js';
3
+ import { cache } from '../../lib/db/redis.js';
4
+ import { authenticate } from '../../middleware/auth.js';
5
+ import { OpenAIAdapter } from '../../services/providers/openai-adapter.js';
6
+ import { AnthropicAdapter } from '../../services/providers/anthropic-adapter.js';
7
+ import { MODEL_REGISTRY, OverrideField } from '@layer-ai/sdk';
8
+ const router = Router();
9
+ // MARK:- Helper Functions
10
+ function isOverrideAllowed(allowOverrides, field) {
11
+ if (allowOverrides === undefined || allowOverrides === null || allowOverrides === true)
12
+ return true;
13
+ if (allowOverrides === false)
14
+ return false;
15
+ return allowOverrides[field] ?? false;
16
+ }
17
+ async function getGateConfig(userId, gateName) {
18
+ let gateConfig = await cache.getGate(userId, gateName);
19
+ if (!gateConfig) {
20
+ gateConfig = await db.getGateByUserAndName(userId, gateName);
21
+ if (gateConfig) {
22
+ await cache.setGate(userId, gateName, gateConfig);
23
+ }
24
+ }
25
+ return gateConfig;
26
+ }
27
+ function resolveFinalRequest(gateConfig, request) {
28
+ const finalRequest = { ...request };
29
+ let finalModel = gateConfig.model;
30
+ if (request.model && isOverrideAllowed(gateConfig.allowOverrides, OverrideField.Model) && MODEL_REGISTRY[request.model]) {
31
+ finalModel = request.model;
32
+ }
33
+ finalRequest.model = finalModel;
34
+ if (request.type === 'chat') {
35
+ const chatData = { ...request.data };
36
+ if (!chatData.systemPrompt && gateConfig.systemPrompt) {
37
+ chatData.systemPrompt = gateConfig.systemPrompt;
38
+ }
39
+ if (chatData.temperature === undefined && gateConfig.temperature !== undefined) {
40
+ chatData.temperature = gateConfig.temperature;
41
+ }
42
+ else if (chatData.temperature !== undefined && !isOverrideAllowed(gateConfig.allowOverrides, OverrideField.Temperature)) {
43
+ chatData.temperature = gateConfig.temperature;
44
+ }
45
+ if (chatData.maxTokens === undefined && gateConfig.maxTokens !== undefined) {
46
+ chatData.maxTokens = gateConfig.maxTokens;
47
+ }
48
+ else if (chatData.maxTokens !== undefined && !isOverrideAllowed(gateConfig.allowOverrides, OverrideField.MaxTokens)) {
49
+ chatData.maxTokens = gateConfig.maxTokens;
50
+ }
51
+ if (chatData.topP === undefined && gateConfig.topP !== undefined) {
52
+ chatData.topP = gateConfig.topP;
53
+ }
54
+ else if (chatData.topP !== undefined && !isOverrideAllowed(gateConfig.allowOverrides, OverrideField.TopP)) {
55
+ chatData.topP = gateConfig.topP;
56
+ }
57
+ finalRequest.data = chatData;
58
+ }
59
+ return finalRequest;
60
+ }
61
+ async function callProvider(request) {
62
+ const provider = MODEL_REGISTRY[request.model].provider;
63
+ switch (provider) {
64
+ case 'openai': {
65
+ const adapter = new OpenAIAdapter();
66
+ return await adapter.call(request);
67
+ }
68
+ case 'anthropic': {
69
+ const adapter = new AnthropicAdapter();
70
+ return await adapter.call(request);
71
+ }
72
+ case 'google':
73
+ // TODO: Migrate Google to use adapter pattern
74
+ throw new Error('Google provider not yet migrated to v2 API');
75
+ default:
76
+ throw new Error(`Unknown provider: ${provider}`);
77
+ }
78
+ }
79
+ function getModelsToTry(gateConfig, primaryModel) {
80
+ const modelsToTry = [primaryModel];
81
+ if (gateConfig.routingStrategy === 'fallback' && gateConfig.fallbackModels?.length) {
82
+ modelsToTry.push(...gateConfig.fallbackModels);
83
+ }
84
+ return modelsToTry;
85
+ }
86
+ async function executeWithFallback(request, modelsToTry) {
87
+ let result = null;
88
+ let lastError = null;
89
+ let modelUsed = request.model;
90
+ for (const modelToTry of modelsToTry) {
91
+ try {
92
+ const modelRequest = { ...request, model: modelToTry };
93
+ result = await callProvider(modelRequest);
94
+ modelUsed = modelToTry;
95
+ break;
96
+ }
97
+ catch (error) {
98
+ lastError = error;
99
+ console.log(`Model ${modelToTry} failed, trying next fallback...`, error instanceof Error ? error.message : error);
100
+ continue;
101
+ }
102
+ }
103
+ if (!result) {
104
+ throw lastError || new Error('All models failed');
105
+ }
106
+ return { result, modelUsed };
107
+ }
108
+ async function executeWithRoundRobin(gateConfig, request) {
109
+ if (!gateConfig.fallbackModels?.length) {
110
+ const result = await callProvider(request);
111
+ return { result, modelUsed: request.model };
112
+ }
113
+ const allModels = [gateConfig.model, ...gateConfig.fallbackModels];
114
+ const modelIndex = Math.floor(Math.random() * allModels.length);
115
+ const selectedModel = allModels[modelIndex];
116
+ const modelRequest = { ...request, model: selectedModel };
117
+ const result = await callProvider(modelRequest);
118
+ return { result, modelUsed: selectedModel };
119
+ }
120
+ async function executeWithRouting(gateConfig, request) {
121
+ const modelsToTry = getModelsToTry(gateConfig, request.model);
122
+ switch (gateConfig.routingStrategy) {
123
+ case 'fallback':
124
+ return await executeWithFallback(request, modelsToTry);
125
+ case 'round-robin':
126
+ return await executeWithRoundRobin(gateConfig, request);
127
+ case 'single':
128
+ default:
129
+ const result = await callProvider(request);
130
+ return { result, modelUsed: request.model };
131
+ }
132
+ }
133
+ // MARK:- Route Handler
134
+ router.post('/', authenticate, async (req, res) => {
135
+ const startTime = Date.now();
136
+ if (!req.userId) {
137
+ res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
138
+ return;
139
+ }
140
+ const userId = req.userId;
141
+ try {
142
+ const request = req.body;
143
+ if (!request.gate) {
144
+ res.status(400).json({ error: 'bad_request', message: 'Missing required field: gate' });
145
+ return;
146
+ }
147
+ if (!request.type) {
148
+ res.status(400).json({ error: 'bad_request', message: 'Missing required field: type' });
149
+ return;
150
+ }
151
+ // Validate chat-specific requirements
152
+ if (request.type === 'chat') {
153
+ if (!request.data.messages || !Array.isArray(request.data.messages) || request.data.messages.length === 0) {
154
+ res.status(400).json({ error: 'bad_request', message: 'Missing required field: data.messages' });
155
+ return;
156
+ }
157
+ }
158
+ const gateConfig = await getGateConfig(userId, request.gate);
159
+ if (!gateConfig) {
160
+ res.status(404).json({ error: 'not_found', message: `Gate "${request.gate}" not found` });
161
+ return;
162
+ }
163
+ const finalRequest = resolveFinalRequest(gateConfig, request);
164
+ const { result, modelUsed } = await executeWithRouting(gateConfig, finalRequest);
165
+ const latencyMs = Date.now() - startTime;
166
+ // Log request to database
167
+ db.logRequest({
168
+ userId,
169
+ gateId: gateConfig.id,
170
+ gateName: request.gate,
171
+ modelRequested: request.model || gateConfig.model,
172
+ modelUsed: modelUsed,
173
+ promptTokens: result.usage?.promptTokens || 0,
174
+ completionTokens: result.usage?.completionTokens || 0,
175
+ totalTokens: result.usage?.totalTokens || 0,
176
+ costUsd: result.cost || 0,
177
+ latencyMs,
178
+ success: true,
179
+ errorMessage: null,
180
+ userAgent: req.headers['user-agent'] || null,
181
+ ipAddress: req.ip || null,
182
+ }).catch(err => console.error('Failed to log request:', err));
183
+ // Return LayerResponse with additional metadata
184
+ const response = {
185
+ ...result,
186
+ model: modelUsed,
187
+ latencyMs,
188
+ };
189
+ res.json(response);
190
+ }
191
+ catch (error) {
192
+ const latencyMs = Date.now() - startTime;
193
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
194
+ db.logRequest({
195
+ userId,
196
+ gateId: null,
197
+ gateName: req.body?.gate || null,
198
+ modelRequested: null,
199
+ modelUsed: null,
200
+ promptTokens: 0,
201
+ completionTokens: 0,
202
+ totalTokens: 0,
203
+ costUsd: 0,
204
+ latencyMs,
205
+ success: false,
206
+ errorMessage,
207
+ userAgent: req.headers['user-agent'] || null,
208
+ ipAddress: req.ip || null,
209
+ }).catch(err => console.error('Failed to log request:', err));
210
+ console.error('Completion error:', error);
211
+ res.status(500).json({ error: 'internal_error', message: errorMessage });
212
+ }
213
+ });
214
+ export default router;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=test-complete-anthropic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-complete-anthropic.d.ts","sourceRoot":"","sources":["../../../../src/routes/v2/tests/test-complete-anthropic.ts"],"names":[],"mappings":""}
@@ -0,0 +1,132 @@
1
+ // Test v2 complete route with Anthropic adapter
2
+ // This demonstrates the full LayerRequest format with all features
3
+ async function testBasicChat() {
4
+ console.log('Test 1: Basic chat completion with Anthropic\n');
5
+ const request = {
6
+ gate: 'test-gate',
7
+ model: 'claude-sonnet-4-5-20250929',
8
+ type: 'chat',
9
+ data: {
10
+ messages: [
11
+ { role: 'user', content: 'Say "Hello from v2 API" and nothing else.' }
12
+ ],
13
+ temperature: 0.7,
14
+ maxTokens: 20,
15
+ }
16
+ };
17
+ console.log('Request:', JSON.stringify(request, null, 2));
18
+ console.log('\nExpected response includes:');
19
+ console.log('- content: string');
20
+ console.log('- model: string');
21
+ console.log('- finishReason: "completed" | "length_limit" | "tool_call" | "filtered" | "error"');
22
+ console.log('- usage: { promptTokens, completionTokens, totalTokens }');
23
+ console.log('- cost: number');
24
+ console.log('- latencyMs: number');
25
+ console.log('- raw: original provider response');
26
+ }
27
+ async function testVision() {
28
+ console.log('\n\nTest 2: Vision with Anthropic (not supported in v1)\n');
29
+ const request = {
30
+ gate: 'test-gate',
31
+ model: 'claude-sonnet-4-5-20250929',
32
+ type: 'chat',
33
+ data: {
34
+ messages: [
35
+ {
36
+ role: 'user',
37
+ content: 'What color is the sky in this image?',
38
+ images: [{
39
+ url: 'https://images.unsplash.com/photo-1765202659641-9ad9facfe5cf?q=80&w=1364&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
40
+ }]
41
+ }
42
+ ],
43
+ maxTokens: 50,
44
+ }
45
+ };
46
+ console.log('Request includes image URL in message');
47
+ console.log('This feature is only available in v2 API');
48
+ }
49
+ async function testToolCalls() {
50
+ console.log('\n\nTest 3: Tool calls with Anthropic (not supported in v1)\n');
51
+ const request = {
52
+ gate: 'test-gate',
53
+ model: 'claude-sonnet-4-5-20250929',
54
+ type: 'chat',
55
+ data: {
56
+ messages: [
57
+ { role: 'user', content: 'What is the weather in San Francisco?' }
58
+ ],
59
+ tools: [
60
+ {
61
+ type: 'function',
62
+ function: {
63
+ name: 'get_weather',
64
+ description: 'Get the current weather for a location',
65
+ parameters: {
66
+ type: 'object',
67
+ properties: {
68
+ location: {
69
+ type: 'string',
70
+ description: 'The city and state, e.g. San Francisco, CA',
71
+ },
72
+ },
73
+ required: ['location'],
74
+ },
75
+ },
76
+ },
77
+ ],
78
+ toolChoice: 'auto',
79
+ maxTokens: 100,
80
+ }
81
+ };
82
+ console.log('Request includes tools array and toolChoice');
83
+ console.log('Response will include toolCalls array if Claude wants to call a tool');
84
+ console.log('This feature is only available in v2 API');
85
+ }
86
+ async function testSystemPrompt() {
87
+ console.log('\n\nTest 4: System prompt and advanced params\n');
88
+ const request = {
89
+ gate: 'test-gate',
90
+ model: 'claude-sonnet-4-5-20250929',
91
+ type: 'chat',
92
+ data: {
93
+ messages: [
94
+ { role: 'user', content: 'Write a haiku about coding' }
95
+ ],
96
+ systemPrompt: 'You are a poetic AI that loves to write haikus.',
97
+ temperature: 1.0,
98
+ topP: 0.9,
99
+ maxTokens: 100,
100
+ stopSequences: ['END'],
101
+ }
102
+ };
103
+ console.log('Request includes:');
104
+ console.log('- systemPrompt: custom system instruction');
105
+ console.log('- temperature: controls randomness');
106
+ console.log('- topP: nucleus sampling');
107
+ console.log('- stopSequences: custom stop sequences');
108
+ console.log('All these features are available in v2');
109
+ }
110
+ async function runTests() {
111
+ console.log('='.repeat(60));
112
+ console.log('V2 Complete Route - Feature Showcase');
113
+ console.log('='.repeat(60));
114
+ console.log('\nTo test manually:');
115
+ console.log('1. Start server: pnpm --filter @layer-ai/api dev');
116
+ console.log('2. Create test gate in database with Claude model');
117
+ console.log('3. POST to /v2/complete with LayerRequest format');
118
+ console.log('='.repeat(60));
119
+ await testBasicChat();
120
+ await testVision();
121
+ await testToolCalls();
122
+ await testSystemPrompt();
123
+ console.log('\n' + '='.repeat(60));
124
+ console.log('Key differences from v1:');
125
+ console.log('- v1: Simple CompletionRequest (messages, temp, maxTokens, topP)');
126
+ console.log('- v2: Full LayerRequest (all above + tools, images, stopSeqs, etc)');
127
+ console.log('- v1: Returns CompletionResponse (content, usage)');
128
+ console.log('- v2: Returns LayerResponse (content, usage, toolCalls, cost, etc)');
129
+ console.log('='.repeat(60));
130
+ }
131
+ runTests();
132
+ export {};
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=test-complete-openai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-complete-openai.d.ts","sourceRoot":"","sources":["../../../../src/routes/v2/tests/test-complete-openai.ts"],"names":[],"mappings":""}
@@ -0,0 +1,178 @@
1
+ // Test v2 complete route with OpenAI adapter
2
+ // This demonstrates LayerRequest format with OpenAI features
3
+ async function testBasicChat() {
4
+ console.log('Test 1: Basic chat completion with OpenAI\n');
5
+ const request = {
6
+ gate: 'test-gate',
7
+ model: 'gpt-4o-mini',
8
+ type: 'chat',
9
+ data: {
10
+ messages: [
11
+ { role: 'user', content: 'Say "Hello from v2 API" and nothing else.' }
12
+ ],
13
+ temperature: 0.7,
14
+ maxTokens: 20,
15
+ }
16
+ };
17
+ console.log('Request:', JSON.stringify(request, null, 2));
18
+ console.log('\nExpected response includes:');
19
+ console.log('- content: string');
20
+ console.log('- model: string');
21
+ console.log('- finishReason: "completed" | "length_limit" | "tool_call" | "filtered" | "error"');
22
+ console.log('- usage: { promptTokens, completionTokens, totalTokens }');
23
+ console.log('- cost: number');
24
+ console.log('- latencyMs: number');
25
+ }
26
+ async function testVision() {
27
+ console.log('\n\nTest 2: Vision with GPT-4o\n');
28
+ const request = {
29
+ gate: 'test-gate',
30
+ model: 'gpt-4o',
31
+ type: 'chat',
32
+ data: {
33
+ messages: [
34
+ {
35
+ role: 'user',
36
+ content: 'What color is the sky in this image?',
37
+ images: [{
38
+ url: 'https://images.unsplash.com/photo-1765202659641-9ad9facfe5cf?q=80&w=1364&auto=format&fit=crop&ixlib=rb-4.1.0',
39
+ detail: 'auto'
40
+ }]
41
+ }
42
+ ],
43
+ maxTokens: 50,
44
+ }
45
+ };
46
+ console.log('Request includes image with detail level (auto/low/high)');
47
+ console.log('OpenAI supports image detail parameter for cost optimization');
48
+ }
49
+ async function testToolCalls() {
50
+ console.log('\n\nTest 3: Tool calls with GPT-4\n');
51
+ const request = {
52
+ gate: 'test-gate',
53
+ model: 'gpt-4o-mini',
54
+ type: 'chat',
55
+ data: {
56
+ messages: [
57
+ { role: 'user', content: 'What is the weather in San Francisco?' }
58
+ ],
59
+ tools: [
60
+ {
61
+ type: 'function',
62
+ function: {
63
+ name: 'get_weather',
64
+ description: 'Get the current weather for a location',
65
+ parameters: {
66
+ type: 'object',
67
+ properties: {
68
+ location: {
69
+ type: 'string',
70
+ description: 'The city and state, e.g. San Francisco, CA',
71
+ },
72
+ },
73
+ required: ['location'],
74
+ },
75
+ },
76
+ },
77
+ ],
78
+ toolChoice: 'auto',
79
+ maxTokens: 100,
80
+ }
81
+ };
82
+ console.log('Request includes tools array and toolChoice');
83
+ console.log('OpenAI will return toolCalls in response when needed');
84
+ }
85
+ async function testImageGeneration() {
86
+ console.log('\n\nTest 4: Image generation with DALL-E\n');
87
+ const request = {
88
+ gate: 'test-gate',
89
+ model: 'dall-e-3',
90
+ type: 'image',
91
+ data: {
92
+ prompt: 'A cute cat playing with yarn',
93
+ size: '1024x1024',
94
+ quality: 'standard',
95
+ count: 1,
96
+ }
97
+ };
98
+ console.log('Image generation request format');
99
+ console.log('Response will include images array with URLs');
100
+ console.log('This is a unique feature to v2 - multiple modalities in one endpoint');
101
+ }
102
+ async function testEmbeddings() {
103
+ console.log('\n\nTest 5: Text embeddings\n');
104
+ const request = {
105
+ gate: 'test-gate',
106
+ model: 'text-embedding-3-small',
107
+ type: 'embeddings',
108
+ data: {
109
+ input: 'The quick brown fox jumps over the lazy dog',
110
+ dimensions: 1536,
111
+ }
112
+ };
113
+ console.log('Embeddings request format');
114
+ console.log('Response will include embeddings array (number[][])');
115
+ console.log('Another unique v2 feature - embeddings through the same endpoint');
116
+ }
117
+ async function testTextToSpeech() {
118
+ console.log('\n\nTest 6: Text-to-speech\n');
119
+ const request = {
120
+ gate: 'test-gate',
121
+ model: 'tts-1',
122
+ type: 'tts',
123
+ data: {
124
+ input: 'Hello, this is a test of the text to speech system.',
125
+ voice: 'alloy',
126
+ responseFormat: 'mp3',
127
+ }
128
+ };
129
+ console.log('Text-to-speech request format');
130
+ console.log('Response will include audio with base64 data');
131
+ console.log('Yet another v2 exclusive - TTS through unified API');
132
+ }
133
+ async function testResponseFormat() {
134
+ console.log('\n\nTest 7: JSON response format\n');
135
+ const request = {
136
+ gate: 'test-gate',
137
+ model: 'gpt-4o-mini',
138
+ type: 'chat',
139
+ data: {
140
+ messages: [
141
+ { role: 'user', content: 'Generate a JSON object with name and age fields for a person.' }
142
+ ],
143
+ responseFormat: 'json_object',
144
+ maxTokens: 100,
145
+ }
146
+ };
147
+ console.log('Request with JSON response format');
148
+ console.log('OpenAI will ensure the response is valid JSON');
149
+ console.log('Available in v2 with responseFormat parameter');
150
+ }
151
+ async function runTests() {
152
+ console.log('='.repeat(60));
153
+ console.log('V2 Complete Route - OpenAI Feature Showcase');
154
+ console.log('='.repeat(60));
155
+ console.log('\nTo test manually:');
156
+ console.log('1. Start server: pnpm --filter @layer-ai/api dev');
157
+ console.log('2. Create test gate in database with OpenAI model');
158
+ console.log('3. POST to /v2/complete with LayerRequest format');
159
+ console.log('='.repeat(60));
160
+ await testBasicChat();
161
+ await testVision();
162
+ await testToolCalls();
163
+ await testImageGeneration();
164
+ await testEmbeddings();
165
+ await testTextToSpeech();
166
+ await testResponseFormat();
167
+ console.log('\n' + '='.repeat(60));
168
+ console.log('V2 API supports ALL modalities through one endpoint:');
169
+ console.log('- chat: Text completion and multimodal chat');
170
+ console.log('- image: DALL-E image generation');
171
+ console.log('- embeddings: Text embeddings');
172
+ console.log('- tts: Text-to-speech');
173
+ console.log('- video: (Future) Video generation');
174
+ console.log('\nAll with consistent LayerRequest/LayerResponse format');
175
+ console.log('='.repeat(60));
176
+ }
177
+ runTests();
178
+ export {};
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=test-complete-routing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-complete-routing.d.ts","sourceRoot":"","sources":["../../../../src/routes/v2/tests/test-complete-routing.ts"],"names":[],"mappings":""}