@theihtisham/budget-llm 1.0.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.
- package/.env.example +21 -0
- package/LICENSE +21 -0
- package/README.md +293 -0
- package/dist/config.d.ts +77 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +246 -0
- package/dist/config.js.map +1 -0
- package/dist/database.d.ts +24 -0
- package/dist/database.d.ts.map +1 -0
- package/dist/database.js +414 -0
- package/dist/database.js.map +1 -0
- package/dist/providers.d.ts +20 -0
- package/dist/providers.d.ts.map +1 -0
- package/dist/providers.js +208 -0
- package/dist/providers.js.map +1 -0
- package/dist/proxy.d.ts +7 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +181 -0
- package/dist/proxy.js.map +1 -0
- package/dist/rate-limiter.d.ts +8 -0
- package/dist/rate-limiter.d.ts.map +1 -0
- package/dist/rate-limiter.js +72 -0
- package/dist/rate-limiter.js.map +1 -0
- package/dist/router.d.ts +33 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +186 -0
- package/dist/router.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +705 -0
- package/dist/server.js.map +1 -0
- package/dist/task-classifier.d.ts +4 -0
- package/dist/task-classifier.d.ts.map +1 -0
- package/dist/task-classifier.js +123 -0
- package/dist/task-classifier.js.map +1 -0
- package/dist/types.d.ts +205 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +46 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/encryption.d.ts +4 -0
- package/dist/utils/encryption.d.ts.map +1 -0
- package/dist/utils/encryption.js +40 -0
- package/dist/utils/encryption.js.map +1 -0
- package/package.json +63 -0
- package/src/config.ts +254 -0
- package/src/database.ts +496 -0
- package/src/providers.ts +315 -0
- package/src/proxy.ts +226 -0
- package/src/rate-limiter.ts +81 -0
- package/src/router.ts +228 -0
- package/src/server.ts +754 -0
- package/src/task-classifier.ts +134 -0
- package/src/types/sql.js.d.ts +27 -0
- package/src/types.ts +258 -0
- package/src/utils/encryption.ts +36 -0
- package/tests/config.test.ts +85 -0
- package/tests/database.test.ts +194 -0
- package/tests/encryption.test.ts +57 -0
- package/tests/rate-limiter.test.ts +83 -0
- package/tests/router.test.ts +182 -0
- package/tests/server.test.ts +253 -0
- package/tests/setup.ts +15 -0
- package/tests/task-classifier.test.ts +117 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
2
|
+
import './setup';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
|
|
5
|
+
describe('Server API Endpoints', () => {
|
|
6
|
+
let app: express.Express;
|
|
7
|
+
|
|
8
|
+
beforeAll(async () => {
|
|
9
|
+
// Create a test app with the same routes
|
|
10
|
+
app = express();
|
|
11
|
+
app.use(express.json());
|
|
12
|
+
|
|
13
|
+
// Import after env is set
|
|
14
|
+
const database = await import('../src/database');
|
|
15
|
+
const { MODEL_CATALOG } = await import('../src/config');
|
|
16
|
+
const { checkRateLimit } = await import('../src/rate-limiter');
|
|
17
|
+
|
|
18
|
+
// Initialize DB first
|
|
19
|
+
await database.initDb();
|
|
20
|
+
|
|
21
|
+
// Health
|
|
22
|
+
app.get('/health', (_req, res) => {
|
|
23
|
+
res.json({ status: 'ok', version: '1.0.0', timestamp: new Date().toISOString() });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Models
|
|
27
|
+
app.get('/v1/models', (_req, res) => {
|
|
28
|
+
res.json({
|
|
29
|
+
object: 'list',
|
|
30
|
+
data: MODEL_CATALOG.map((m: { id: string; displayName: string; provider: string; capabilities: string[]; contextWindow: number }) => ({
|
|
31
|
+
id: m.id,
|
|
32
|
+
object: 'model',
|
|
33
|
+
owned_by: m.provider,
|
|
34
|
+
display_name: m.displayName,
|
|
35
|
+
})),
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Dashboard
|
|
40
|
+
app.get('/api/dashboard', (_req, res) => {
|
|
41
|
+
res.json(database.getDashboardData());
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Costs
|
|
45
|
+
app.get('/api/costs', (req, res) => {
|
|
46
|
+
const days = parseInt(req.query.days as string) || 30;
|
|
47
|
+
res.json(database.getCostSummary(Math.min(days, 365)));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Budget
|
|
51
|
+
app.get('/api/budget', (_req, res) => {
|
|
52
|
+
const config = database.getBudgetConfig();
|
|
53
|
+
const status = database.getBudgetStatus(config);
|
|
54
|
+
res.json({ config, status });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
app.put('/api/budget', (req, res) => {
|
|
58
|
+
const { dailyBudget, monthlyBudget, perRequestCap } = req.body;
|
|
59
|
+
const updates: Record<string, number> = {};
|
|
60
|
+
if (dailyBudget !== undefined) updates.dailyBudget = dailyBudget;
|
|
61
|
+
if (monthlyBudget !== undefined) updates.monthlyBudget = monthlyBudget;
|
|
62
|
+
if (perRequestCap !== undefined) updates.perRequestCap = perRequestCap;
|
|
63
|
+
database.setBudgetConfig(updates);
|
|
64
|
+
const config = database.getBudgetConfig();
|
|
65
|
+
const status = database.getBudgetStatus(config);
|
|
66
|
+
res.json({ config, status });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Cache clear
|
|
70
|
+
app.delete('/api/cache', (_req, res) => {
|
|
71
|
+
const cleared = database.clearCache();
|
|
72
|
+
res.json({ cleared });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Rate limit
|
|
76
|
+
app.get('/api/rate-limit', (req, res) => {
|
|
77
|
+
const ip = req.ip || '127.0.0.1';
|
|
78
|
+
res.json({ ip, ...checkRateLimit(ip) });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Chat completions with validation
|
|
82
|
+
app.post('/v1/chat/completions', (req, res) => {
|
|
83
|
+
if (!req.body.messages || !Array.isArray(req.body.messages)) {
|
|
84
|
+
return res.status(400).json({
|
|
85
|
+
error: { message: 'messages is required and must be an array', type: 'invalid_request_error' },
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// In test mode, we don't actually call providers
|
|
89
|
+
res.json({
|
|
90
|
+
id: 'test-response',
|
|
91
|
+
object: 'chat.completion',
|
|
92
|
+
created: Math.floor(Date.now() / 1000),
|
|
93
|
+
model: req.body.model || 'gpt-4o-mini',
|
|
94
|
+
choices: [{
|
|
95
|
+
index: 0,
|
|
96
|
+
message: { role: 'assistant', content: 'Test response' },
|
|
97
|
+
finish_reason: 'stop',
|
|
98
|
+
}],
|
|
99
|
+
usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
|
|
100
|
+
cost: { inputCost: 0, outputCost: 0, totalCost: 0, currency: 'USD', model: 'test', provider: 'openai', savingsVsGpt4: 0 },
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('GET /health', () => {
|
|
106
|
+
it('should return health status', async () => {
|
|
107
|
+
const { default: supertest } = await import('supertest');
|
|
108
|
+
const res = await supertest(app).get('/health');
|
|
109
|
+
expect(res.status).toBe(200);
|
|
110
|
+
expect(res.body.status).toBe('ok');
|
|
111
|
+
expect(res.body.version).toBe('1.0.0');
|
|
112
|
+
expect(res.body.timestamp).toBeTruthy();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('GET /v1/models', () => {
|
|
117
|
+
it('should return list of models', async () => {
|
|
118
|
+
const { default: supertest } = await import('supertest');
|
|
119
|
+
const res = await supertest(app).get('/v1/models');
|
|
120
|
+
expect(res.status).toBe(200);
|
|
121
|
+
expect(res.body.object).toBe('list');
|
|
122
|
+
expect(Array.isArray(res.body.data)).toBe(true);
|
|
123
|
+
expect(res.body.data.length).toBeGreaterThan(0);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should include model details', async () => {
|
|
127
|
+
const { default: supertest } = await import('supertest');
|
|
128
|
+
const res = await supertest(app).get('/v1/models');
|
|
129
|
+
const model = res.body.data[0];
|
|
130
|
+
expect(model.id).toBeTruthy();
|
|
131
|
+
expect(model.object).toBe('model');
|
|
132
|
+
expect(model.owned_by).toBeTruthy();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('POST /v1/chat/completions', () => {
|
|
137
|
+
it('should reject requests without messages', async () => {
|
|
138
|
+
const { default: supertest } = await import('supertest');
|
|
139
|
+
const res = await supertest(app)
|
|
140
|
+
.post('/v1/chat/completions')
|
|
141
|
+
.send({});
|
|
142
|
+
expect(res.status).toBe(400);
|
|
143
|
+
expect(res.body.error.message).toContain('messages');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should reject requests with non-array messages', async () => {
|
|
147
|
+
const { default: supertest } = await import('supertest');
|
|
148
|
+
const res = await supertest(app)
|
|
149
|
+
.post('/v1/chat/completions')
|
|
150
|
+
.send({ messages: 'not an array' });
|
|
151
|
+
expect(res.status).toBe(400);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should accept valid requests', async () => {
|
|
155
|
+
const { default: supertest } = await import('supertest');
|
|
156
|
+
const res = await supertest(app)
|
|
157
|
+
.post('/v1/chat/completions')
|
|
158
|
+
.send({
|
|
159
|
+
messages: [{ role: 'user', content: 'Hello' }],
|
|
160
|
+
});
|
|
161
|
+
expect(res.status).toBe(200);
|
|
162
|
+
expect(res.body.object).toBe('chat.completion');
|
|
163
|
+
expect(res.body.choices[0].message.content).toBeTruthy();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should include OpenAI-compatible response fields', async () => {
|
|
167
|
+
const { default: supertest } = await import('supertest');
|
|
168
|
+
const res = await supertest(app)
|
|
169
|
+
.post('/v1/chat/completions')
|
|
170
|
+
.send({
|
|
171
|
+
messages: [{ role: 'user', content: 'Hello' }],
|
|
172
|
+
});
|
|
173
|
+
expect(res.body.id).toBeTruthy();
|
|
174
|
+
expect(res.body.created).toBeTruthy();
|
|
175
|
+
expect(res.body.model).toBeTruthy();
|
|
176
|
+
expect(res.body.usage).toBeTruthy();
|
|
177
|
+
expect(res.body.usage.prompt_tokens).toBeGreaterThanOrEqual(0);
|
|
178
|
+
expect(res.body.usage.completion_tokens).toBeGreaterThanOrEqual(0);
|
|
179
|
+
expect(res.body.usage.total_tokens).toBeGreaterThanOrEqual(0);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('GET /api/dashboard', () => {
|
|
184
|
+
it('should return dashboard data', async () => {
|
|
185
|
+
const { default: supertest } = await import('supertest');
|
|
186
|
+
const res = await supertest(app).get('/api/dashboard');
|
|
187
|
+
expect(res.status).toBe(200);
|
|
188
|
+
expect(res.body.overview).toBeTruthy();
|
|
189
|
+
expect(res.body.budget).toBeTruthy();
|
|
190
|
+
expect(Array.isArray(res.body.recentRequests)).toBe(true);
|
|
191
|
+
expect(Array.isArray(res.body.costByDay)).toBe(true);
|
|
192
|
+
expect(Array.isArray(res.body.modelDistribution)).toBe(true);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe('GET /api/costs', () => {
|
|
197
|
+
it('should return cost summary', async () => {
|
|
198
|
+
const { default: supertest } = await import('supertest');
|
|
199
|
+
const res = await supertest(app).get('/api/costs');
|
|
200
|
+
expect(res.status).toBe(200);
|
|
201
|
+
expect(res.body.totalSpent).toBeGreaterThanOrEqual(0);
|
|
202
|
+
expect(res.body.totalRequests).toBeGreaterThanOrEqual(0);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should accept days parameter', async () => {
|
|
206
|
+
const { default: supertest } = await import('supertest');
|
|
207
|
+
const res = await supertest(app).get('/api/costs?days=7');
|
|
208
|
+
expect(res.status).toBe(200);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('GET /api/budget', () => {
|
|
213
|
+
it('should return budget config and status', async () => {
|
|
214
|
+
const { default: supertest } = await import('supertest');
|
|
215
|
+
const res = await supertest(app).get('/api/budget');
|
|
216
|
+
expect(res.status).toBe(200);
|
|
217
|
+
expect(res.body.config).toBeTruthy();
|
|
218
|
+
expect(res.body.status).toBeTruthy();
|
|
219
|
+
expect(res.body.status.daily).toBeTruthy();
|
|
220
|
+
expect(res.body.status.monthly).toBeTruthy();
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('PUT /api/budget', () => {
|
|
225
|
+
it('should update budget configuration', async () => {
|
|
226
|
+
const { default: supertest } = await import('supertest');
|
|
227
|
+
const res = await supertest(app)
|
|
228
|
+
.put('/api/budget')
|
|
229
|
+
.send({ dailyBudget: 15 });
|
|
230
|
+
expect(res.status).toBe(200);
|
|
231
|
+
expect(res.body.config.dailyBudget).toBe(15);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe('DELETE /api/cache', () => {
|
|
236
|
+
it('should clear cache', async () => {
|
|
237
|
+
const { default: supertest } = await import('supertest');
|
|
238
|
+
const res = await supertest(app).delete('/api/cache');
|
|
239
|
+
expect(res.status).toBe(200);
|
|
240
|
+
expect(typeof res.body.cleared).toBe('number');
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe('GET /api/rate-limit', () => {
|
|
245
|
+
it('should return rate limit status', async () => {
|
|
246
|
+
const { default: supertest } = await import('supertest');
|
|
247
|
+
const res = await supertest(app).get('/api/rate-limit');
|
|
248
|
+
expect(res.status).toBe(200);
|
|
249
|
+
expect(typeof res.body.allowed).toBe('boolean');
|
|
250
|
+
expect(typeof res.body.remaining).toBe('number');
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
});
|
package/tests/setup.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
// Set test environment variables before importing any modules
|
|
4
|
+
process.env.NODE_ENV = 'test';
|
|
5
|
+
process.env.PORT = '3211';
|
|
6
|
+
process.env.ENCRYPTION_KEY = 'test_key_for_testing_exactly_32_chars!!';
|
|
7
|
+
process.env.OPENAI_API_KEY = 'test-openai-key';
|
|
8
|
+
process.env.ANTHROPIC_API_KEY = 'test-anthropic-key';
|
|
9
|
+
process.env.GOOGLE_API_KEY = 'test-google-key';
|
|
10
|
+
process.env.DEEPSEEK_API_KEY = 'test-deepseek-key';
|
|
11
|
+
process.env.DEFAULT_DAILY_BUDGET = '10';
|
|
12
|
+
process.env.DEFAULT_MONTHLY_BUDGET = '200';
|
|
13
|
+
process.env.DEFAULT_PER_REQUEST_CAP = '1';
|
|
14
|
+
process.env.CACHE_TTL = '3600';
|
|
15
|
+
process.env.RATE_LIMIT_MAX_REQUESTS = '100';
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import './setup';
|
|
3
|
+
import { classifyTask, estimateTokens } from '../src/task-classifier';
|
|
4
|
+
import type { ChatMessage } from '../src/types';
|
|
5
|
+
|
|
6
|
+
describe('classifyTask', () => {
|
|
7
|
+
it('should classify code tasks', () => {
|
|
8
|
+
const messages: ChatMessage[] = [
|
|
9
|
+
{ role: 'user', content: 'Write a function that sorts an array using quicksort' },
|
|
10
|
+
];
|
|
11
|
+
expect(classifyTask(messages)).toBe('code');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should classify code tasks with system instructions', () => {
|
|
15
|
+
const messages: ChatMessage[] = [
|
|
16
|
+
{ role: 'system', content: 'You are a helpful coding assistant' },
|
|
17
|
+
{ role: 'user', content: 'Implement a binary search algorithm in TypeScript' },
|
|
18
|
+
];
|
|
19
|
+
expect(classifyTask(messages)).toBe('code');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should classify creative tasks', () => {
|
|
23
|
+
const messages: ChatMessage[] = [
|
|
24
|
+
{ role: 'user', content: 'Write a poem about the ocean at sunset' },
|
|
25
|
+
];
|
|
26
|
+
expect(classifyTask(messages)).toBe('creative');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should classify translation tasks', () => {
|
|
30
|
+
const messages: ChatMessage[] = [
|
|
31
|
+
{ role: 'user', content: 'Translate this sentence to French' },
|
|
32
|
+
];
|
|
33
|
+
expect(classifyTask(messages)).toBe('translation');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should classify summarization tasks', () => {
|
|
37
|
+
const messages: ChatMessage[] = [
|
|
38
|
+
{ role: 'user', content: 'Summarize this article in a few bullet points' },
|
|
39
|
+
];
|
|
40
|
+
expect(classifyTask(messages)).toBe('summarization');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should classify reasoning tasks', () => {
|
|
44
|
+
const messages: ChatMessage[] = [
|
|
45
|
+
{ role: 'user', content: 'Analyze the pros and cons of microservices vs monolithic architecture' },
|
|
46
|
+
];
|
|
47
|
+
expect(classifyTask(messages)).toBe('reasoning');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should classify math tasks', () => {
|
|
51
|
+
const messages: ChatMessage[] = [
|
|
52
|
+
{ role: 'user', content: 'Calculate the derivative of x^3 + 2x^2 - 5x + 3' },
|
|
53
|
+
];
|
|
54
|
+
expect(classifyTask(messages)).toBe('math');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should default to chat for simple greetings', () => {
|
|
58
|
+
const messages: ChatMessage[] = [
|
|
59
|
+
{ role: 'user', content: 'Hello, how are you today?' },
|
|
60
|
+
];
|
|
61
|
+
expect(classifyTask(messages)).toBe('chat');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should classify analysis tasks', () => {
|
|
65
|
+
const messages: ChatMessage[] = [
|
|
66
|
+
{ role: 'user', content: 'Analyze the trends in this data and provide insights' },
|
|
67
|
+
];
|
|
68
|
+
expect(classifyTask(messages)).toBe('analysis');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should handle multiple messages', () => {
|
|
72
|
+
const messages: ChatMessage[] = [
|
|
73
|
+
{ role: 'system', content: 'You help with code' },
|
|
74
|
+
{ role: 'user', content: 'Can you fix the bug in my Python script?' },
|
|
75
|
+
{ role: 'assistant', content: 'Sure, show me the code' },
|
|
76
|
+
{ role: 'user', content: 'Here is the code, the debug says error on line 5' },
|
|
77
|
+
];
|
|
78
|
+
// Multiple code-related keywords should make this 'code'
|
|
79
|
+
const result = classifyTask(messages);
|
|
80
|
+
expect(['code', 'chat']).toContain(result);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should return a valid TaskType for empty messages', () => {
|
|
84
|
+
const messages: ChatMessage[] = [
|
|
85
|
+
{ role: 'user', content: 'xyzabc123' },
|
|
86
|
+
];
|
|
87
|
+
const result = classifyTask(messages);
|
|
88
|
+
expect(result).toBeTruthy();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('estimateTokens', () => {
|
|
93
|
+
it('should estimate tokens based on character count', () => {
|
|
94
|
+
const messages: ChatMessage[] = [
|
|
95
|
+
{ role: 'user', content: 'Hello world' }, // 11 chars
|
|
96
|
+
];
|
|
97
|
+
const tokens = estimateTokens(messages);
|
|
98
|
+
// ~4 chars per token => ~3 tokens for 11 chars
|
|
99
|
+
expect(tokens).toBeGreaterThan(0);
|
|
100
|
+
expect(tokens).toBeLessThan(100);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should increase with longer messages', () => {
|
|
104
|
+
const short = estimateTokens([{ role: 'user', content: 'Hi' }]);
|
|
105
|
+
const long = estimateTokens([{ role: 'user', content: 'This is a much longer message with many more words and characters in it' }]);
|
|
106
|
+
expect(long).toBeGreaterThan(short);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should account for multiple messages', () => {
|
|
110
|
+
const one = estimateTokens([{ role: 'user', content: 'Hello' }]);
|
|
111
|
+
const two = estimateTokens([
|
|
112
|
+
{ role: 'user', content: 'Hello' },
|
|
113
|
+
{ role: 'assistant', content: 'World' },
|
|
114
|
+
]);
|
|
115
|
+
expect(two).toBeGreaterThan(one);
|
|
116
|
+
});
|
|
117
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2022"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true,
|
|
16
|
+
"noUnusedLocals": true,
|
|
17
|
+
"noUnusedParameters": true,
|
|
18
|
+
"noImplicitReturns": true,
|
|
19
|
+
"noFallthroughCasesInSwitch": true,
|
|
20
|
+
"exactOptionalPropertyTypes": false,
|
|
21
|
+
"noUncheckedIndexedAccess": true
|
|
22
|
+
},
|
|
23
|
+
"include": ["src/**/*"],
|
|
24
|
+
"exclude": ["node_modules", "dist", "tests"]
|
|
25
|
+
}
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
globals: true,
|
|
6
|
+
environment: 'node',
|
|
7
|
+
include: ['tests/**/*.test.ts'],
|
|
8
|
+
coverage: {
|
|
9
|
+
provider: 'v8',
|
|
10
|
+
include: ['src/**/*.ts'],
|
|
11
|
+
exclude: ['src/server.ts'],
|
|
12
|
+
},
|
|
13
|
+
testTimeout: 10000,
|
|
14
|
+
},
|
|
15
|
+
});
|