@layer-ai/core 2.0.33 → 2.0.34
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/middleware/auth.d.ts +5 -0
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +25 -1
- package/dist/routes/tests/test-anthropic-endpoint.d.ts +3 -0
- package/dist/routes/tests/test-anthropic-endpoint.d.ts.map +1 -0
- package/dist/routes/tests/test-anthropic-endpoint.js +346 -0
- package/dist/routes/v1/messages.js +2 -2
- package/package.json +2 -2
|
@@ -20,6 +20,11 @@ declare global {
|
|
|
20
20
|
* Authorization: Bearer layer_abc123...
|
|
21
21
|
*/
|
|
22
22
|
export declare function authenticate(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Auth middleware that supports both Layer format (Authorization: Bearer) and Anthropic format (x-api-key)
|
|
25
|
+
* Used for the /v1/messages endpoint to support Anthropic SDK clients
|
|
26
|
+
*/
|
|
27
|
+
export declare function authenticateAnthropicCompatible(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
23
28
|
/**
|
|
24
29
|
* Optional middleware for endpoints that don't require auth
|
|
25
30
|
* like the health check public endpoints etc.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAK1D,oBAAY,QAAQ;IAClB,OAAO,YAAY;IACnB,OAAO,YAAY;CACpB;AAGD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO,CAAC;QAChB,UAAU,OAAO;YACf,MAAM,CAAC,EAAE,MAAM,CAAC;YAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;YAClB,UAAU,CAAC,EAAE,MAAM,CAAC;YACpB,QAAQ,CAAC,EAAE,QAAQ,CAAC;SACrB;KACF;CACF;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC,CA4Jf;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,GACjB,IAAI,CAWN"}
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAK1D,oBAAY,QAAQ;IAClB,OAAO,YAAY;IACnB,OAAO,YAAY;CACpB;AAGD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO,CAAC;QAChB,UAAU,OAAO;YACf,MAAM,CAAC,EAAE,MAAM,CAAC;YAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;YAClB,UAAU,CAAC,EAAE,MAAM,CAAC;YACpB,QAAQ,CAAC,EAAE,QAAQ,CAAC;SACrB;KACF;CACF;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC,CA4Jf;AAED;;;GAGG;AACH,wBAAsB,+BAA+B,CACnD,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,GACjB,IAAI,CAWN"}
|
package/dist/middleware/auth.js
CHANGED
|
@@ -144,6 +144,30 @@ export async function authenticate(req, res, next) {
|
|
|
144
144
|
});
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Auth middleware that supports both Layer format (Authorization: Bearer) and Anthropic format (x-api-key)
|
|
149
|
+
* Used for the /v1/messages endpoint to support Anthropic SDK clients
|
|
150
|
+
*/
|
|
151
|
+
export async function authenticateAnthropicCompatible(req, res, next) {
|
|
152
|
+
try {
|
|
153
|
+
// Check for x-api-key header first (Anthropic SDK format)
|
|
154
|
+
const xApiKey = req.headers['x-api-key'];
|
|
155
|
+
if (xApiKey) {
|
|
156
|
+
// Convert x-api-key to Authorization header format and process
|
|
157
|
+
req.headers.authorization = `Bearer ${xApiKey}`;
|
|
158
|
+
return authenticate(req, res, next);
|
|
159
|
+
}
|
|
160
|
+
// Fall back to standard Authorization header
|
|
161
|
+
return authenticate(req, res, next);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
console.error('Authentication error:', error);
|
|
165
|
+
res.status(500).json({
|
|
166
|
+
error: 'internal_error',
|
|
167
|
+
message: 'Authentication failed'
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
147
171
|
/**
|
|
148
172
|
* Optional middleware for endpoints that don't require auth
|
|
149
173
|
* like the health check public endpoints etc.
|
|
@@ -155,6 +179,6 @@ export function optionalAuth(req, res, next) {
|
|
|
155
179
|
next();
|
|
156
180
|
return;
|
|
157
181
|
}
|
|
158
|
-
// if auth header exists, validate it
|
|
182
|
+
// if auth header exists, validate it
|
|
159
183
|
authenticate(req, res, next);
|
|
160
184
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-anthropic-endpoint.d.ts","sourceRoot":"","sources":["../../../src/routes/tests/test-anthropic-endpoint.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
console.log('='.repeat(80));
|
|
3
|
+
console.log('ANTHROPIC MESSAGES API ENDPOINT TESTS');
|
|
4
|
+
console.log('='.repeat(80));
|
|
5
|
+
console.log('');
|
|
6
|
+
const BASE_URL = process.env.API_URL || 'http://localhost:3001';
|
|
7
|
+
const API_KEY = process.env.LAYER_API_KEY;
|
|
8
|
+
const GATE_ID = process.env.TEST_GATE_ID;
|
|
9
|
+
if (!API_KEY) {
|
|
10
|
+
console.error('❌ Error: LAYER_API_KEY environment variable not set');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
if (!GATE_ID) {
|
|
14
|
+
console.error('❌ Error: TEST_GATE_ID environment variable not set');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
async function testNonStreamingBasic() {
|
|
18
|
+
console.log('Test 1: Non-streaming basic message');
|
|
19
|
+
console.log('-'.repeat(80));
|
|
20
|
+
const request = {
|
|
21
|
+
model: 'claude-3-5-sonnet-20241022',
|
|
22
|
+
max_tokens: 100,
|
|
23
|
+
messages: [
|
|
24
|
+
{ role: 'user', content: 'Say "test passed" and nothing else.' }
|
|
25
|
+
],
|
|
26
|
+
gateId: GATE_ID,
|
|
27
|
+
};
|
|
28
|
+
const response = await fetch(`${BASE_URL}/v1/messages`, {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: {
|
|
31
|
+
'Content-Type': 'application/json',
|
|
32
|
+
'Authorization': `Bearer ${API_KEY}`,
|
|
33
|
+
},
|
|
34
|
+
body: JSON.stringify(request),
|
|
35
|
+
});
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
const error = await response.json();
|
|
38
|
+
throw new Error(`Request failed: ${JSON.stringify(error)}`);
|
|
39
|
+
}
|
|
40
|
+
const data = await response.json();
|
|
41
|
+
console.log(' Response ID:', data.id);
|
|
42
|
+
console.log(' Model:', data.model);
|
|
43
|
+
console.log(' Content:', data.content);
|
|
44
|
+
console.log(' Stop reason:', data.stop_reason);
|
|
45
|
+
console.log(' Usage:', data.usage);
|
|
46
|
+
console.log(' ✅ Non-streaming basic test passed\n');
|
|
47
|
+
}
|
|
48
|
+
async function testNonStreamingWithGateIdInHeader() {
|
|
49
|
+
console.log('Test 2: Non-streaming with gateId in header');
|
|
50
|
+
console.log('-'.repeat(80));
|
|
51
|
+
const request = {
|
|
52
|
+
model: 'claude-3-5-sonnet-20241022',
|
|
53
|
+
max_tokens: 100,
|
|
54
|
+
messages: [
|
|
55
|
+
{ role: 'user', content: 'Say "header test passed" and nothing else.' }
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
const response = await fetch(`${BASE_URL}/v1/messages`, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: {
|
|
61
|
+
'Content-Type': 'application/json',
|
|
62
|
+
'Authorization': `Bearer ${API_KEY}`,
|
|
63
|
+
'X-Layer-Gate-Id': GATE_ID,
|
|
64
|
+
},
|
|
65
|
+
body: JSON.stringify(request),
|
|
66
|
+
});
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
const error = await response.json();
|
|
69
|
+
throw new Error(`Request failed: ${JSON.stringify(error)}`);
|
|
70
|
+
}
|
|
71
|
+
const data = await response.json();
|
|
72
|
+
const textBlock = data.content.find(block => block.type === 'text');
|
|
73
|
+
console.log(' Content:', textBlock ? textBlock.text : 'No text content');
|
|
74
|
+
console.log(' ✅ Header gateId test passed\n');
|
|
75
|
+
}
|
|
76
|
+
async function testStreamingBasic() {
|
|
77
|
+
console.log('Test 3: Streaming basic message');
|
|
78
|
+
console.log('-'.repeat(80));
|
|
79
|
+
const request = {
|
|
80
|
+
model: 'claude-3-5-sonnet-20241022',
|
|
81
|
+
max_tokens: 100,
|
|
82
|
+
messages: [
|
|
83
|
+
{ role: 'user', content: 'Count from 1 to 3, one number per line.' }
|
|
84
|
+
],
|
|
85
|
+
stream: true,
|
|
86
|
+
gateId: GATE_ID,
|
|
87
|
+
};
|
|
88
|
+
const response = await fetch(`${BASE_URL}/v1/messages`, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers: {
|
|
91
|
+
'Content-Type': 'application/json',
|
|
92
|
+
'Authorization': `Bearer ${API_KEY}`,
|
|
93
|
+
},
|
|
94
|
+
body: JSON.stringify(request),
|
|
95
|
+
});
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
const error = await response.json();
|
|
98
|
+
throw new Error(`Request failed: ${JSON.stringify(error)}`);
|
|
99
|
+
}
|
|
100
|
+
let eventCount = 0;
|
|
101
|
+
let fullContent = '';
|
|
102
|
+
let inputTokens = 0;
|
|
103
|
+
let outputTokens = 0;
|
|
104
|
+
const reader = response.body?.getReader();
|
|
105
|
+
const decoder = new TextDecoder();
|
|
106
|
+
if (!reader) {
|
|
107
|
+
throw new Error('No response body reader');
|
|
108
|
+
}
|
|
109
|
+
while (true) {
|
|
110
|
+
const { done, value } = await reader.read();
|
|
111
|
+
if (done)
|
|
112
|
+
break;
|
|
113
|
+
const text = decoder.decode(value);
|
|
114
|
+
const lines = text.split('\n');
|
|
115
|
+
let currentEvent = '';
|
|
116
|
+
for (const line of lines) {
|
|
117
|
+
if (line.startsWith('event: ')) {
|
|
118
|
+
currentEvent = line.replace('event: ', '').trim();
|
|
119
|
+
}
|
|
120
|
+
else if (line.startsWith('data: ')) {
|
|
121
|
+
const data = line.replace('data: ', '').trim();
|
|
122
|
+
try {
|
|
123
|
+
const event = JSON.parse(data);
|
|
124
|
+
eventCount++;
|
|
125
|
+
if (event.type === 'message_start' && 'message' in event) {
|
|
126
|
+
inputTokens = event.message.usage.input_tokens;
|
|
127
|
+
}
|
|
128
|
+
else if (event.type === 'content_block_delta' && 'delta' in event) {
|
|
129
|
+
if (event.delta.type === 'text_delta' && event.delta.text) {
|
|
130
|
+
fullContent += event.delta.text;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else if (event.type === 'message_delta' && 'usage' in event) {
|
|
134
|
+
outputTokens = event.usage.output_tokens;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
// Skip invalid JSON
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
console.log(' Events received:', eventCount);
|
|
144
|
+
console.log(' Full content:', fullContent.trim());
|
|
145
|
+
console.log(' Input tokens:', inputTokens);
|
|
146
|
+
console.log(' Output tokens:', outputTokens);
|
|
147
|
+
console.log(' ✅ Streaming basic test passed\n');
|
|
148
|
+
}
|
|
149
|
+
async function testWithToolCalls() {
|
|
150
|
+
console.log('Test 4: Non-streaming with tool calls');
|
|
151
|
+
console.log('-'.repeat(80));
|
|
152
|
+
const request = {
|
|
153
|
+
model: 'claude-3-5-sonnet-20241022',
|
|
154
|
+
max_tokens: 1024,
|
|
155
|
+
messages: [
|
|
156
|
+
{ role: 'user', content: 'What is the weather in Paris?' }
|
|
157
|
+
],
|
|
158
|
+
tools: [
|
|
159
|
+
{
|
|
160
|
+
name: 'get_weather',
|
|
161
|
+
description: 'Get the current weather for a location',
|
|
162
|
+
input_schema: {
|
|
163
|
+
type: 'object',
|
|
164
|
+
properties: {
|
|
165
|
+
location: {
|
|
166
|
+
type: 'string',
|
|
167
|
+
description: 'The city and state, e.g. Paris, France',
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
required: ['location'],
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
tool_choice: { type: 'auto' },
|
|
175
|
+
gateId: GATE_ID,
|
|
176
|
+
};
|
|
177
|
+
const response = await fetch(`${BASE_URL}/v1/messages`, {
|
|
178
|
+
method: 'POST',
|
|
179
|
+
headers: {
|
|
180
|
+
'Content-Type': 'application/json',
|
|
181
|
+
'Authorization': `Bearer ${API_KEY}`,
|
|
182
|
+
},
|
|
183
|
+
body: JSON.stringify(request),
|
|
184
|
+
});
|
|
185
|
+
if (!response.ok) {
|
|
186
|
+
const error = await response.json();
|
|
187
|
+
throw new Error(`Request failed: ${JSON.stringify(error)}`);
|
|
188
|
+
}
|
|
189
|
+
const data = await response.json();
|
|
190
|
+
console.log(' Stop reason:', data.stop_reason);
|
|
191
|
+
const toolUseBlocks = data.content.filter(block => block.type === 'tool_use');
|
|
192
|
+
if (toolUseBlocks.length > 0) {
|
|
193
|
+
console.log(' Tool calls:', JSON.stringify(toolUseBlocks, null, 2));
|
|
194
|
+
console.log(' ✅ Tool calls test passed\n');
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
console.log(' ⚠️ No tool calls received (model may have chosen not to use tools)\n');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async function testWithSystemPrompt() {
|
|
201
|
+
console.log('Test 5: Message with system prompt');
|
|
202
|
+
console.log('-'.repeat(80));
|
|
203
|
+
const request = {
|
|
204
|
+
model: 'claude-3-5-sonnet-20241022',
|
|
205
|
+
max_tokens: 100,
|
|
206
|
+
system: 'You are a pirate. Always respond in pirate speak.',
|
|
207
|
+
messages: [
|
|
208
|
+
{ role: 'user', content: 'Hello, how are you?' }
|
|
209
|
+
],
|
|
210
|
+
gateId: GATE_ID,
|
|
211
|
+
};
|
|
212
|
+
const response = await fetch(`${BASE_URL}/v1/messages`, {
|
|
213
|
+
method: 'POST',
|
|
214
|
+
headers: {
|
|
215
|
+
'Content-Type': 'application/json',
|
|
216
|
+
'Authorization': `Bearer ${API_KEY}`,
|
|
217
|
+
},
|
|
218
|
+
body: JSON.stringify(request),
|
|
219
|
+
});
|
|
220
|
+
if (!response.ok) {
|
|
221
|
+
const error = await response.json();
|
|
222
|
+
throw new Error(`Request failed: ${JSON.stringify(error)}`);
|
|
223
|
+
}
|
|
224
|
+
const data = await response.json();
|
|
225
|
+
const textBlock = data.content.find(block => block.type === 'text');
|
|
226
|
+
const content = textBlock ? textBlock.text : 'No text content';
|
|
227
|
+
console.log(' Content:', content);
|
|
228
|
+
console.log(' ✅ System prompt test passed\n');
|
|
229
|
+
}
|
|
230
|
+
async function testMultiTurnConversation() {
|
|
231
|
+
console.log('Test 6: Multi-turn conversation');
|
|
232
|
+
console.log('-'.repeat(80));
|
|
233
|
+
const request = {
|
|
234
|
+
model: 'claude-3-5-sonnet-20241022',
|
|
235
|
+
max_tokens: 100,
|
|
236
|
+
messages: [
|
|
237
|
+
{ role: 'user', content: 'My name is Alice.' },
|
|
238
|
+
{ role: 'assistant', content: 'Hello Alice! Nice to meet you.' },
|
|
239
|
+
{ role: 'user', content: 'What is my name?' }
|
|
240
|
+
],
|
|
241
|
+
gateId: GATE_ID,
|
|
242
|
+
};
|
|
243
|
+
const response = await fetch(`${BASE_URL}/v1/messages`, {
|
|
244
|
+
method: 'POST',
|
|
245
|
+
headers: {
|
|
246
|
+
'Content-Type': 'application/json',
|
|
247
|
+
'Authorization': `Bearer ${API_KEY}`,
|
|
248
|
+
},
|
|
249
|
+
body: JSON.stringify(request),
|
|
250
|
+
});
|
|
251
|
+
if (!response.ok) {
|
|
252
|
+
const error = await response.json();
|
|
253
|
+
throw new Error(`Request failed: ${JSON.stringify(error)}`);
|
|
254
|
+
}
|
|
255
|
+
const data = await response.json();
|
|
256
|
+
const textBlock = data.content.find(block => block.type === 'text');
|
|
257
|
+
const content = textBlock ? textBlock.text : 'No text content';
|
|
258
|
+
console.log(' Content:', content);
|
|
259
|
+
if (content.toLowerCase().includes('alice')) {
|
|
260
|
+
console.log(' ✅ Multi-turn conversation test passed (remembered name)\n');
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
console.log(' ⚠️ Multi-turn conversation test unclear (name not found in response)\n');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
async function testTemperatureParameter() {
|
|
267
|
+
console.log('Test 7: Temperature parameter');
|
|
268
|
+
console.log('-'.repeat(80));
|
|
269
|
+
const request = {
|
|
270
|
+
model: 'claude-3-5-sonnet-20241022',
|
|
271
|
+
max_tokens: 50,
|
|
272
|
+
temperature: 0.1,
|
|
273
|
+
messages: [
|
|
274
|
+
{ role: 'user', content: 'Say "temperature test passed"' }
|
|
275
|
+
],
|
|
276
|
+
gateId: GATE_ID,
|
|
277
|
+
};
|
|
278
|
+
const response = await fetch(`${BASE_URL}/v1/messages`, {
|
|
279
|
+
method: 'POST',
|
|
280
|
+
headers: {
|
|
281
|
+
'Content-Type': 'application/json',
|
|
282
|
+
'Authorization': `Bearer ${API_KEY}`,
|
|
283
|
+
},
|
|
284
|
+
body: JSON.stringify(request),
|
|
285
|
+
});
|
|
286
|
+
if (!response.ok) {
|
|
287
|
+
const error = await response.json();
|
|
288
|
+
throw new Error(`Request failed: ${JSON.stringify(error)}`);
|
|
289
|
+
}
|
|
290
|
+
const data = await response.json();
|
|
291
|
+
const textBlock = data.content.find(block => block.type === 'text');
|
|
292
|
+
const content = textBlock ? textBlock.text : 'No text content';
|
|
293
|
+
console.log(' Content:', content);
|
|
294
|
+
console.log(' ✅ Temperature parameter test passed\n');
|
|
295
|
+
}
|
|
296
|
+
async function testErrorHandlingMissingMaxTokens() {
|
|
297
|
+
console.log('Test 8: Error handling - missing max_tokens');
|
|
298
|
+
console.log('-'.repeat(80));
|
|
299
|
+
const request = {
|
|
300
|
+
model: 'claude-3-5-sonnet-20241022',
|
|
301
|
+
messages: [
|
|
302
|
+
{ role: 'user', content: 'Hello' }
|
|
303
|
+
],
|
|
304
|
+
gateId: GATE_ID,
|
|
305
|
+
// Intentionally omit max_tokens
|
|
306
|
+
};
|
|
307
|
+
const response = await fetch(`${BASE_URL}/v1/messages`, {
|
|
308
|
+
method: 'POST',
|
|
309
|
+
headers: {
|
|
310
|
+
'Content-Type': 'application/json',
|
|
311
|
+
'Authorization': `Bearer ${API_KEY}`,
|
|
312
|
+
},
|
|
313
|
+
body: JSON.stringify(request),
|
|
314
|
+
});
|
|
315
|
+
if (response.ok) {
|
|
316
|
+
throw new Error('Expected error for missing max_tokens, but request succeeded');
|
|
317
|
+
}
|
|
318
|
+
const error = await response.json();
|
|
319
|
+
console.log(' Error response:', error);
|
|
320
|
+
if (error.type === 'error' && error.error.message.includes('max_tokens')) {
|
|
321
|
+
console.log(' ✅ Error handling test passed (correctly rejected missing max_tokens)\n');
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
console.log(' ⚠️ Error handling test unclear (unexpected error format)\n');
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
(async () => {
|
|
328
|
+
try {
|
|
329
|
+
await testNonStreamingBasic();
|
|
330
|
+
await testNonStreamingWithGateIdInHeader();
|
|
331
|
+
await testStreamingBasic();
|
|
332
|
+
await testWithToolCalls();
|
|
333
|
+
await testWithSystemPrompt();
|
|
334
|
+
await testMultiTurnConversation();
|
|
335
|
+
await testTemperatureParameter();
|
|
336
|
+
await testErrorHandlingMissingMaxTokens();
|
|
337
|
+
console.log('='.repeat(80));
|
|
338
|
+
console.log('✅ ALL ANTHROPIC MESSAGES API ENDPOINT TESTS PASSED');
|
|
339
|
+
console.log('='.repeat(80));
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
console.error('❌ Test failed:', error);
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
})();
|
|
346
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import { db } from '../../lib/db/postgres.js';
|
|
3
|
-
import {
|
|
3
|
+
import { authenticateAnthropicCompatible } from '../../middleware/auth.js';
|
|
4
4
|
import { spendingTracker } from '../../lib/spending-tracker.js';
|
|
5
5
|
import { convertAnthropicRequestToLayer, convertLayerResponseToAnthropic, convertLayerStreamToAnthropicEvents, } from '../../lib/anthropic-conversion.js';
|
|
6
6
|
import { resolveFinalRequest } from '../v3/chat.js';
|
|
@@ -13,7 +13,7 @@ async function executeWithRouting(gateConfig, request, userId) {
|
|
|
13
13
|
const result = await callAdapter(request, userId);
|
|
14
14
|
return { result, modelUsed: request.model };
|
|
15
15
|
}
|
|
16
|
-
router.post('/',
|
|
16
|
+
router.post('/', authenticateAnthropicCompatible, async (req, res) => {
|
|
17
17
|
const startTime = Date.now();
|
|
18
18
|
if (!req.userId) {
|
|
19
19
|
const error = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@layer-ai/core",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.34",
|
|
4
4
|
"description": "Core API routes and services for Layer AI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"nanoid": "^5.0.4",
|
|
37
37
|
"openai": "^4.24.0",
|
|
38
38
|
"pg": "^8.11.3",
|
|
39
|
-
"@layer-ai/sdk": "^2.5.
|
|
39
|
+
"@layer-ai/sdk": "^2.5.10"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/bcryptjs": "^2.4.6",
|