@layer-ai/core 0.5.4 → 0.5.6

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/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export { default as authRouter } from './routes/auth.js';
2
- export { default as gatesRouter } from './routes/gates.js';
3
- export { default as keysRouter } from './routes/keys.js';
4
- export { default as logsRouter } from './routes/logs.js';
1
+ export { default as authRouter } from './routes/v1/auth.js';
2
+ export { default as gatesRouter } from './routes/v1/gates.js';
3
+ export { default as keysRouter } from './routes/v1/keys.js';
4
+ export { default as logsRouter } from './routes/v1/logs.js';
5
5
  export { default as completeRouter } from './routes/v2/complete.js';
6
6
  export { authenticate } from './middleware/auth.js';
7
7
  export type {} from './middleware/auth.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,yBAAyB,CAAC;AAGpE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAG3C,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,cAAc,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAGpE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAG3C,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,cAAc,6BAA6B,CAAC"}
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  // Routes
2
- export { default as authRouter } from './routes/auth.js';
3
- export { default as gatesRouter } from './routes/gates.js';
4
- export { default as keysRouter } from './routes/keys.js';
5
- export { default as logsRouter } from './routes/logs.js';
2
+ export { default as authRouter } from './routes/v1/auth.js';
3
+ export { default as gatesRouter } from './routes/v1/gates.js';
4
+ export { default as keysRouter } from './routes/v1/keys.js';
5
+ export { default as logsRouter } from './routes/v1/logs.js';
6
6
  export { default as completeRouter } from './routes/v2/complete.js';
7
7
  // Middleware
8
8
  export { authenticate } from './middleware/auth.js';
@@ -0,0 +1,4 @@
1
+ import type { Router as RouterType } from 'express';
2
+ declare const router: RouterType;
3
+ export default router;
4
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/routes/v1/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAKpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AA4FpC,eAAe,MAAM,CAAC"}
@@ -0,0 +1,82 @@
1
+ import { Router } from 'express';
2
+ import bcrypt from 'bcryptjs';
3
+ import crypto from 'crypto';
4
+ import { db } from '../../lib/db/postgres.js';
5
+ const router = Router();
6
+ // POST /auth/signup
7
+ router.post('/signup', async (req, res) => {
8
+ try {
9
+ const { email, password } = req.body;
10
+ if (!email || !password) {
11
+ res.status(400).json({ error: 'bad_request', message: 'Email and password required' });
12
+ return;
13
+ }
14
+ const existing = await db.getUserByEmail(email);
15
+ if (existing) {
16
+ res.status(409).json({ error: 'conflict', message: 'Email already registered' });
17
+ return;
18
+ }
19
+ const passwordHash = await bcrypt.hash(password, 10);
20
+ const user = await db.createUser(email, passwordHash);
21
+ res.status(201).json({ id: user.id, email: user.email });
22
+ }
23
+ catch (error) {
24
+ console.error('Signup error:', error);
25
+ res.status(500).json({ error: 'internal_error', message: 'Failed to create account ' });
26
+ }
27
+ });
28
+ // POST /auth/login
29
+ router.post('/login', async (req, res) => {
30
+ try {
31
+ const { email, password } = req.body;
32
+ if (!email || !password) {
33
+ res.status(400).json({ error: 'bad_request', message: 'Email and password required' });
34
+ return;
35
+ }
36
+ const user = await db.getUserByEmail(email);
37
+ if (!user) {
38
+ res.status(401).json({ error: 'unauthorized', message: 'Invalid credentials' });
39
+ return;
40
+ }
41
+ const valid = await bcrypt.compare(password, user.passwordHash);
42
+ if (!valid) {
43
+ res.status(401).json({ error: 'unauthorized', message: 'Invalid credentials' });
44
+ return;
45
+ }
46
+ res.json({ id: user.id, email: user.email });
47
+ }
48
+ catch (error) {
49
+ console.error('Login error', error);
50
+ res.status(500).json({ error: 'internal_error', message: 'Failed to login' });
51
+ }
52
+ });
53
+ // POST /auth/token
54
+ router.post('/token', async (req, res) => {
55
+ try {
56
+ const { email, password } = req.body;
57
+ if (!email || !password) {
58
+ res.status(400).json({ error: 'bad_request', message: 'Email and password required' });
59
+ return;
60
+ }
61
+ const user = await db.getUserByEmail(email);
62
+ if (!user) {
63
+ res.status(401).json({ error: 'unauthorized', message: 'Invalid credentials' });
64
+ return;
65
+ }
66
+ const valid = await bcrypt.compare(password, user.passwordHash);
67
+ if (!valid) {
68
+ res.status(401).json({ error: 'unauthorized', message: 'Invalid credentials' });
69
+ return;
70
+ }
71
+ const rawKey = `layer_${crypto.randomBytes(32).toString('hex')}`;
72
+ const keyHash = crypto.createHash('sha256').update(rawKey).digest('hex');
73
+ const keyPrefix = rawKey.substring(0, 12); // "layer_xxxxxx"
74
+ await db.createApiKey(user.id, keyHash, keyPrefix, 'CLI');
75
+ res.status(201).json({ apiKey: rawKey });
76
+ }
77
+ catch (error) {
78
+ console.error('api key creation error', error);
79
+ res.status(500).json({ error: 'internal_error', message: 'Failed to create api key' });
80
+ }
81
+ });
82
+ export default router;
@@ -0,0 +1,4 @@
1
+ import type { Router as RouterType } from 'express';
2
+ declare const router: RouterType;
3
+ export default router;
4
+ //# sourceMappingURL=gates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gates.d.ts","sourceRoot":"","sources":["../../../src/routes/v1/gates.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAOpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AA0TpC,eAAe,MAAM,CAAC"}
@@ -0,0 +1,275 @@
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 { MODEL_REGISTRY } from '@layer-ai/sdk';
6
+ const router = Router();
7
+ // All routes require authentication (SDK auth with Bearer token)
8
+ router.use(authenticate);
9
+ // POST / - Create a new gate
10
+ router.post('/', async (req, res) => {
11
+ if (!req.userId) {
12
+ res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
13
+ return;
14
+ }
15
+ try {
16
+ const { name, description, taskType, model, systemPrompt, allowOverrides, temperature, maxTokens, topP, tags, routingStrategy, fallbackModels, costWeight, latencyWeight, qualityWeight, reanalysisPeriod, taskAnalysis } = req.body;
17
+ if (!name || !model) {
18
+ res.status(400).json({ error: 'bad_request', message: 'Missing required fields: name and model' });
19
+ return;
20
+ }
21
+ if (!MODEL_REGISTRY[model]) {
22
+ res.status(400).json({ error: 'bad_request', message: `Unsupported model: ${model}` });
23
+ return;
24
+ }
25
+ const existing = await db.getGateByUserAndName(req.userId, name);
26
+ if (existing) {
27
+ res.status(409).json({ error: 'conflict', message: `Gate "${name}" already exists` });
28
+ return;
29
+ }
30
+ const gate = await db.createGate(req.userId, {
31
+ name,
32
+ description,
33
+ taskType,
34
+ model,
35
+ systemPrompt,
36
+ allowOverrides,
37
+ temperature,
38
+ maxTokens,
39
+ topP,
40
+ tags,
41
+ routingStrategy,
42
+ fallbackModels,
43
+ costWeight,
44
+ latencyWeight,
45
+ qualityWeight,
46
+ reanalysisPeriod,
47
+ taskAnalysis,
48
+ });
49
+ res.status(201).json(gate);
50
+ }
51
+ catch (error) {
52
+ console.error('Create gate error:', error);
53
+ res.status(500).json({ error: 'internal_error', message: 'Failed to create gate' });
54
+ }
55
+ });
56
+ // GET / - List all the gates for user
57
+ router.get('/', async (req, res) => {
58
+ if (!req.userId) {
59
+ res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
60
+ return;
61
+ }
62
+ try {
63
+ const gates = await db.getGatesForUser(req.userId);
64
+ res.json(gates);
65
+ }
66
+ catch (error) {
67
+ console.error('List gates error:', error);
68
+ res.status(500).json({ error: 'internal_error', message: 'Failed to list gates' });
69
+ }
70
+ });
71
+ // GET /name/:name - Get a single gate by name
72
+ router.get('/name/:name', async (req, res) => {
73
+ if (!req.userId) {
74
+ res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
75
+ return;
76
+ }
77
+ try {
78
+ const gate = await db.getGateByUserAndName(req.userId, req.params.name);
79
+ if (!gate) {
80
+ res.status(404).json({ error: 'not_found', message: 'Gate not found' });
81
+ return;
82
+ }
83
+ res.json(gate);
84
+ }
85
+ catch (error) {
86
+ console.error('Get gate by name error:', error);
87
+ res.status(500).json({ error: 'internal_error', message: 'Failed to get gate' });
88
+ }
89
+ });
90
+ // GET /:id - Get a single gate by ID
91
+ router.get('/:id', async (req, res) => {
92
+ if (!req.userId) {
93
+ res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
94
+ return;
95
+ }
96
+ try {
97
+ const gate = await db.getGateById(req.params.id);
98
+ if (!gate) {
99
+ res.status(404).json({ error: 'not_found', message: 'Gate not found' });
100
+ return;
101
+ }
102
+ if (gate.userId !== req.userId) {
103
+ res.status(404).json({ error: 'not_found', message: 'Gate not found' });
104
+ return;
105
+ }
106
+ res.json(gate);
107
+ }
108
+ catch (error) {
109
+ console.error('Get gate error:', error);
110
+ res.status(500).json({ error: 'internal_error', message: 'Failed to get gate' });
111
+ }
112
+ });
113
+ // PATCH /name/:name - Update a gate by name
114
+ router.patch('/name/:name', async (req, res) => {
115
+ if (!req.userId) {
116
+ res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
117
+ return;
118
+ }
119
+ try {
120
+ const { description, taskType, model, systemPrompt, allowOverrides, temperature, maxTokens, topP, tags, routingStrategy, fallbackModels, costWeight, latencyWeight, qualityWeight, reanalysisPeriod, taskAnalysis } = req.body;
121
+ const existing = await db.getGateByUserAndName(req.userId, req.params.name);
122
+ if (!existing) {
123
+ res.status(404).json({ error: 'not_found', message: 'Gate not found' });
124
+ return;
125
+ }
126
+ if (model && !MODEL_REGISTRY[model]) {
127
+ res.status(400).json({ error: 'bad_request', message: `Unsupported model: ${model}` });
128
+ return;
129
+ }
130
+ const updated = await db.updateGate(existing.id, {
131
+ description,
132
+ taskType,
133
+ model,
134
+ systemPrompt,
135
+ allowOverrides,
136
+ temperature,
137
+ maxTokens,
138
+ topP,
139
+ tags,
140
+ routingStrategy,
141
+ fallbackModels,
142
+ costWeight,
143
+ latencyWeight,
144
+ qualityWeight,
145
+ reanalysisPeriod,
146
+ taskAnalysis,
147
+ });
148
+ await cache.invalidateGate(req.userId, existing.name);
149
+ res.json(updated);
150
+ }
151
+ catch (error) {
152
+ console.error('Update gate by name error:', error);
153
+ res.status(500).json({ error: 'internal_error', message: 'Failed to update gate' });
154
+ }
155
+ });
156
+ // PATCH /:id - Update a gate by ID
157
+ router.patch('/:id', async (req, res) => {
158
+ if (!req.userId) {
159
+ res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
160
+ return;
161
+ }
162
+ try {
163
+ const { description, taskType, model, systemPrompt, allowOverrides, temperature, maxTokens, topP, tags, routingStrategy, fallbackModels, costWeight, latencyWeight, qualityWeight, reanalysisPeriod, taskAnalysis } = req.body;
164
+ const existing = await db.getGateById(req.params.id);
165
+ if (!existing) {
166
+ res.status(404).json({ error: 'not_found', message: 'Gate not found' });
167
+ return;
168
+ }
169
+ if (existing.userId !== req.userId) {
170
+ res.status(404).json({ error: 'not_found', message: 'Gate not found' });
171
+ return;
172
+ }
173
+ if (model && !MODEL_REGISTRY[model]) {
174
+ res.status(400).json({ error: 'bad_request', message: `Unsupported model: ${model}` });
175
+ return;
176
+ }
177
+ const updated = await db.updateGate(req.params.id, {
178
+ description,
179
+ taskType,
180
+ model,
181
+ systemPrompt,
182
+ allowOverrides,
183
+ temperature,
184
+ maxTokens,
185
+ topP,
186
+ tags,
187
+ routingStrategy,
188
+ fallbackModels,
189
+ costWeight,
190
+ latencyWeight,
191
+ qualityWeight,
192
+ reanalysisPeriod,
193
+ taskAnalysis,
194
+ });
195
+ await cache.invalidateGate(req.userId, existing.name);
196
+ res.json(updated);
197
+ }
198
+ catch (error) {
199
+ console.error('Update gate error:', error);
200
+ res.status(500).json({ error: 'internal_error', message: 'Failed to update gate' });
201
+ }
202
+ });
203
+ // DELETE /name/:name - Delete a gate by name
204
+ router.delete('/name/:name', async (req, res) => {
205
+ if (!req.userId) {
206
+ res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
207
+ return;
208
+ }
209
+ try {
210
+ const existing = await db.getGateByUserAndName(req.userId, req.params.name);
211
+ if (!existing) {
212
+ res.status(404).json({ error: 'not_found', message: 'Gate not found' });
213
+ return;
214
+ }
215
+ await db.deleteGate(existing.id);
216
+ await cache.invalidateGate(req.userId, existing.name);
217
+ res.status(204).send();
218
+ }
219
+ catch (error) {
220
+ console.error('Delete gate by name error:', error);
221
+ res.status(500).json({ error: 'internal_error', message: 'Failed to delete gate' });
222
+ }
223
+ });
224
+ // DELETE /:id - Delete a gate by ID
225
+ router.delete('/:id', async (req, res) => {
226
+ if (!req.userId) {
227
+ res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
228
+ return;
229
+ }
230
+ try {
231
+ const existing = await db.getGateById(req.params.id);
232
+ if (!existing) {
233
+ res.status(404).json({ error: 'not_found', message: 'Gate not found' });
234
+ return;
235
+ }
236
+ if (existing.userId !== req.userId) {
237
+ res.status(404).json({ error: 'not_found', message: 'Gate not found' });
238
+ return;
239
+ }
240
+ await db.deleteGate(req.params.id);
241
+ await cache.invalidateGate(req.userId, existing.name);
242
+ res.status(204).send();
243
+ }
244
+ catch (error) {
245
+ console.error('Delete gate error:', error);
246
+ res.status(500).json({ error: 'internal_error', message: 'Failed to delete gate' });
247
+ }
248
+ });
249
+ // POST /suggestions - Get AI-powered model suggestions for a gate
250
+ router.post('/suggestions', async (req, res) => {
251
+ if (!req.userId) {
252
+ res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
253
+ return;
254
+ }
255
+ try {
256
+ const { description, costWeight, latencyWeight, qualityWeight } = req.body;
257
+ if (!description) {
258
+ res.status(400).json({ error: 'bad_request', message: 'Gate must have a description for AI recommendations' });
259
+ return;
260
+ }
261
+ const userPreferences = {
262
+ costWeight: parseFloat(costWeight ?? '0.33'),
263
+ latencyWeight: parseFloat(latencyWeight ?? '0.33'),
264
+ qualityWeight: parseFloat(qualityWeight ?? '0.34'),
265
+ };
266
+ const { analyzeTask } = await import('../../services/task-analysis.js');
267
+ const suggestions = await analyzeTask(description, userPreferences);
268
+ res.json(suggestions);
269
+ }
270
+ catch (error) {
271
+ console.error('Get suggestions error:', error);
272
+ res.status(500).json({ error: 'internal_error', message: 'Failed to fetch suggestions' });
273
+ }
274
+ });
275
+ export default router;
@@ -0,0 +1,4 @@
1
+ import type { Router as RouterType } from 'express';
2
+ declare const router: RouterType;
3
+ export default router;
4
+ //# sourceMappingURL=keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../../src/routes/v1/keys.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAKpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AA4EpC,eAAe,MAAM,CAAC"}
@@ -0,0 +1,70 @@
1
+ import { Router } from 'express';
2
+ import crypto from 'crypto';
3
+ import { db } from '../../lib/db/postgres.js';
4
+ import { authenticate } from '../../middleware/auth.js';
5
+ const router = Router();
6
+ // All routes require sdk authentication
7
+ router.use(authenticate);
8
+ // Generate a random API key
9
+ function generateApiKey() {
10
+ const randomBytes = crypto.randomBytes(32).toString('hex');
11
+ return `layer_${randomBytes}`;
12
+ }
13
+ // Hash an API key for storage
14
+ function hashApiKey(key) {
15
+ return crypto.createHash('sha256').update(key).digest('hex');
16
+ }
17
+ // GET /api/keys - List user's API keys
18
+ router.get('/', async (req, res) => {
19
+ try {
20
+ const keys = await db.getApiKeysForUser(req.userId);
21
+ res.json(keys);
22
+ }
23
+ catch (error) {
24
+ console.error('Get keys error:', error);
25
+ res.status(500).json({ error: 'internal_error', message: 'Failed to get API keys' });
26
+ }
27
+ });
28
+ // POST /api/keys - Generate new API key
29
+ router.post('/', async (req, res) => {
30
+ try {
31
+ const { name } = req.body;
32
+ if (!name) {
33
+ res.status(400).json({ error: 'bad_request', message: 'Key name required' });
34
+ return;
35
+ }
36
+ const key = generateApiKey();
37
+ const keyHash = hashApiKey(key);
38
+ const keyPrefix = key.substring(0, 12);
39
+ const apiKey = await db.createApiKey(req.userId, keyHash, keyPrefix, name);
40
+ // Return full key only once
41
+ res.status(201).json({
42
+ id: apiKey.id,
43
+ name: apiKey.name,
44
+ key: key,
45
+ keyPrefix: apiKey.keyPrefix,
46
+ createdAt: apiKey.createdAt,
47
+ });
48
+ }
49
+ catch (error) {
50
+ console.error('Create key error:', error);
51
+ res.status(500).json({ error: 'internal_error', message: 'Failed to create API key' });
52
+ }
53
+ });
54
+ // DELETE /api/keys/:id - Delete an API key
55
+ router.delete('/:id', async (req, res) => {
56
+ try {
57
+ const { id } = req.params;
58
+ const deleted = await db.deleteApiKey(id, req.userId);
59
+ if (!deleted) {
60
+ res.status(404).json({ error: 'not_found', message: 'API key not found' });
61
+ return;
62
+ }
63
+ res.status(204).send();
64
+ }
65
+ catch (error) {
66
+ console.error('Delete key error:', error);
67
+ res.status(500).json({ error: 'internal_error', message: 'Failed to delete API key' });
68
+ }
69
+ });
70
+ export default router;
@@ -0,0 +1,4 @@
1
+ import type { Router as RouterType } from 'express';
2
+ declare const router: RouterType;
3
+ export default router;
4
+ //# sourceMappingURL=logs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../../src/routes/v1/logs.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAIpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AAsLpC,eAAe,MAAM,CAAC"}
@@ -0,0 +1,151 @@
1
+ import { Router } from 'express';
2
+ import { db } from '../../lib/db/postgres.js';
3
+ import { authenticate } from '../../middleware/auth.js';
4
+ const router = Router();
5
+ // All routes require SDK authentication
6
+ router.use(authenticate);
7
+ // GET /v1/logs - List request logs
8
+ router.get('/', async (req, res) => {
9
+ try {
10
+ const userId = req.userId;
11
+ const limit = parseInt(req.query.limit) || 50;
12
+ const offset = parseInt(req.query.offset) || 0;
13
+ const gate = req.query.gate;
14
+ let query = `
15
+ SELECT
16
+ id,
17
+ user_id,
18
+ gate_id,
19
+ gate_name,
20
+ model_requested,
21
+ model_used,
22
+ prompt_tokens,
23
+ completion_tokens,
24
+ cost_usd,
25
+ latency_ms,
26
+ success,
27
+ error_message,
28
+ created_at as logged_at
29
+ FROM requests
30
+ WHERE user_id = $1
31
+ `;
32
+ const params = [userId];
33
+ if (gate) {
34
+ query += ` AND gate_name = $2`;
35
+ params.push(gate);
36
+ }
37
+ query += ` ORDER BY created_at DESC LIMIT $${params.length + 1} OFFSET $${params.length + 2}`;
38
+ params.push(limit, offset);
39
+ const result = await db.query(query, params);
40
+ const logs = result.rows.map((row) => ({
41
+ id: row.id,
42
+ userId: row.user_id,
43
+ gateId: row.gate_id,
44
+ gateName: row.gate_name,
45
+ modelRequested: row.model_requested,
46
+ modelUsed: row.model_used,
47
+ promptTokens: row.prompt_tokens,
48
+ completionTokens: row.completion_tokens,
49
+ costUsd: parseFloat(row.cost_usd),
50
+ latencyMs: row.latency_ms,
51
+ success: row.success,
52
+ errorMessage: row.error_message,
53
+ loggedAt: row.logged_at,
54
+ }));
55
+ res.json(logs);
56
+ }
57
+ catch (error) {
58
+ console.error('Logs list error:', error);
59
+ res.status(500).json({ error: 'internal_error', message: 'Failed to fetch logs' });
60
+ }
61
+ });
62
+ // GET /v1/logs/overview - Get logs for api calls
63
+ router.get('/overview', async (req, res) => {
64
+ try {
65
+ const userId = req.userId;
66
+ const [statsResult, gatesResult, recentRequestsResult] = await Promise.all([
67
+ // Get aggregate stats
68
+ db.query(`SELECT
69
+ COUNT(*) as total_requests,
70
+ COALESCE(SUM(cost_usd), 0) as total_cost,
71
+ COALESCE(AVG(latency_ms), 0) as avg_latency
72
+ FROM requests
73
+ WHERE user_id = $1`, [userId]),
74
+ // Get gates count
75
+ db.query(`SELECT COUNT(*) as active_gates FROM gates WHERE user_id = $1`, [userId]),
76
+ // Get recent requests
77
+ db.query(`SELECT
78
+ id,
79
+ gate_name,
80
+ model_used,
81
+ prompt_tokens,
82
+ completion_tokens,
83
+ total_tokens,
84
+ cost_usd,
85
+ latency_ms,
86
+ success,
87
+ created_at
88
+ FROM requests
89
+ WHERE user_id = $1
90
+ ORDER BY created_at DESC
91
+ LIMIT 20`, [userId]),
92
+ ]);
93
+ const stats = statsResult.rows[0];
94
+ const gatesCount = gatesResult.rows[0];
95
+ const recentRequests = recentRequestsResult.rows;
96
+ res.json({
97
+ totalRequests: parseInt(stats.total_requests),
98
+ totalCost: parseFloat(stats.total_cost),
99
+ avgLatency: Math.round(parseFloat(stats.avg_latency)),
100
+ activeGates: parseInt(gatesCount.active_gates),
101
+ recentRequests: recentRequests.map((req) => ({
102
+ id: req.id,
103
+ gateName: req.gate_name,
104
+ model: req.model_used,
105
+ promptTokens: req.prompt_tokens,
106
+ completionTokens: req.completion_tokens,
107
+ totalTokens: req.total_tokens,
108
+ cost: parseFloat(req.cost_usd),
109
+ latency: req.latency_ms,
110
+ success: req.success,
111
+ createdAt: req.created_at,
112
+ })),
113
+ });
114
+ }
115
+ catch (error) {
116
+ console.error('Analytics overview error:', error);
117
+ res.status(500).json({ error: 'internal_error', message: 'Failed to fetch analytics' });
118
+ }
119
+ });
120
+ // GET /v1/logs/gate/:gateId - Get metrics for a specific gate
121
+ router.get('/gate/:gateId', async (req, res) => {
122
+ try {
123
+ const userId = req.userId;
124
+ const { gateId } = req.params;
125
+ // Verify gate ownership
126
+ const gateCheck = await db.query('SELECT id FROM gates WHERE id = $1 AND user_id = $2', [gateId, userId]);
127
+ if (gateCheck.rows.length === 0) {
128
+ return res.status(404).json({ error: 'not_found', message: 'Gate not found' });
129
+ }
130
+ // Get gate metrics
131
+ const metricsResult = await db.query(`SELECT
132
+ COUNT(*) as requests,
133
+ COALESCE(SUM(cost_usd), 0) as cost,
134
+ COALESCE(AVG(latency_ms), 0) as latency,
135
+ MAX(created_at) as last_request
136
+ FROM requests
137
+ WHERE gate_id = $1`, [gateId]);
138
+ const metrics = metricsResult.rows[0];
139
+ res.json({
140
+ requests: parseInt(metrics.requests) || 0,
141
+ cost: parseFloat(metrics.cost) || 0,
142
+ latency: Math.round(parseFloat(metrics.latency)) || 0,
143
+ lastRequest: metrics.last_request,
144
+ });
145
+ }
146
+ catch (error) {
147
+ console.error('Gate metrics error:', error);
148
+ res.status(500).json({ error: 'internal_error', message: 'Failed to fetch gate metrics' });
149
+ }
150
+ });
151
+ export default router;
@@ -85,6 +85,6 @@ export class BaseProviderAdapter {
85
85
  if (!modelInfo || !('pricing' in modelInfo) || !modelInfo.pricing?.input || !modelInfo.pricing?.output) {
86
86
  return 0;
87
87
  }
88
- return (promptTokens / 1000 * modelInfo.pricing.input) + (completionTokens / 1000 * modelInfo.pricing.output);
88
+ return (promptTokens / 1000000 * modelInfo.pricing.input) + (completionTokens / 1000000 * modelInfo.pricing.output);
89
89
  }
90
90
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@layer-ai/core",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "description": "Core API routes and services for Layer AI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",