@layer-ai/core 0.1.10 → 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 (35) 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/gates.d.ts.map +1 -1
  7. package/dist/routes/gates.js +8 -13
  8. package/dist/routes/v2/complete.d.ts +4 -0
  9. package/dist/routes/v2/complete.d.ts.map +1 -0
  10. package/dist/routes/v2/complete.js +214 -0
  11. package/dist/routes/v2/tests/test-complete-anthropic.d.ts +2 -0
  12. package/dist/routes/v2/tests/test-complete-anthropic.d.ts.map +1 -0
  13. package/dist/routes/v2/tests/test-complete-anthropic.js +132 -0
  14. package/dist/routes/v2/tests/test-complete-openai.d.ts +2 -0
  15. package/dist/routes/v2/tests/test-complete-openai.d.ts.map +1 -0
  16. package/dist/routes/v2/tests/test-complete-openai.js +178 -0
  17. package/dist/routes/v2/tests/test-complete-routing.d.ts +2 -0
  18. package/dist/routes/v2/tests/test-complete-routing.d.ts.map +1 -0
  19. package/dist/routes/v2/tests/test-complete-routing.js +192 -0
  20. package/dist/services/providers/anthropic-adapter.d.ts +12 -0
  21. package/dist/services/providers/anthropic-adapter.d.ts.map +1 -0
  22. package/dist/services/providers/anthropic-adapter.js +203 -0
  23. package/dist/services/providers/base-adapter.d.ts +1 -1
  24. package/dist/services/providers/base-adapter.d.ts.map +1 -1
  25. package/dist/services/providers/base-adapter.js +1 -1
  26. package/dist/services/providers/openai-adapter.d.ts +2 -2
  27. package/dist/services/providers/openai-adapter.d.ts.map +1 -1
  28. package/dist/services/providers/openai-adapter.js +15 -3
  29. package/dist/services/providers/tests/test-anthropic-adapter.d.ts +2 -0
  30. package/dist/services/providers/tests/test-anthropic-adapter.d.ts.map +1 -0
  31. package/dist/services/providers/tests/test-anthropic-adapter.js +104 -0
  32. package/dist/services/providers/tests/test-openai-adapter.d.ts +2 -0
  33. package/dist/services/providers/tests/test-openai-adapter.d.ts.map +1 -0
  34. package/dist/services/providers/tests/test-openai-adapter.js +118 -0
  35. package/package.json +2 -2
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:
@@ -1 +1 @@
1
- {"version":3,"file":"gates.d.ts","sourceRoot":"","sources":["../../src/routes/gates.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAOpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AA+SpC,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"gates.d.ts","sourceRoot":"","sources":["../../src/routes/gates.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAOpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AAwSpC,eAAe,MAAM,CAAC"}
@@ -228,30 +228,25 @@ router.delete('/:id', async (req, res) => {
228
228
  res.status(500).json({ error: 'internal_error', message: 'Failed to delete gate' });
229
229
  }
230
230
  });
231
- // GET /:name/suggestions - Get AI-powered model suggestions for a gate
232
- router.get('/:name/suggestions', async (req, res) => {
231
+ // POST /suggestions - Get AI-powered model suggestions for a gate
232
+ router.post('/suggestions', async (req, res) => {
233
233
  if (!req.userId) {
234
234
  res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
235
235
  return;
236
236
  }
237
237
  try {
238
- const { name } = req.params;
239
- const gate = await db.getGateByUserAndName(req.userId, name);
240
- if (!gate) {
241
- res.status(404).json({ error: 'not_found', message: 'Gate not found' });
242
- return;
243
- }
244
- if (!gate.description) {
238
+ const { description, costWeight, latencyWeight, qualityWeight } = req.body;
239
+ if (!description) {
245
240
  res.status(400).json({ error: 'bad_request', message: 'Gate must have a description for AI recommendations' });
246
241
  return;
247
242
  }
248
243
  const userPreferences = {
249
- costWeight: gate.costWeight,
250
- latencyWeight: gate.latencyWeight,
251
- qualityWeight: gate.qualityWeight
244
+ costWeight: parseFloat(costWeight ?? '0.33'),
245
+ latencyWeight: parseFloat(latencyWeight ?? '0.33'),
246
+ qualityWeight: parseFloat(qualityWeight ?? '0.34'),
252
247
  };
253
248
  const { analyzeTask } = await import('../services/task-analysis.js');
254
- const suggestions = await analyzeTask(gate.description, userPreferences);
249
+ const suggestions = await analyzeTask(description, userPreferences);
255
250
  res.json(suggestions);
256
251
  }
257
252
  catch (error) {
@@ -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":""}