@ank1015/providers 0.0.1
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/README.md +453 -0
- package/biome.json +43 -0
- package/dist/agent/agent-loop.d.ts +5 -0
- package/dist/agent/agent-loop.d.ts.map +1 -0
- package/dist/agent/agent-loop.js +219 -0
- package/dist/agent/agent-loop.js.map +1 -0
- package/dist/agent/types.d.ts +67 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +3 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/models.d.ts +3 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.generated.d.ts +247 -0
- package/dist/models.generated.d.ts.map +1 -0
- package/dist/models.generated.js +315 -0
- package/dist/models.generated.js.map +1 -0
- package/dist/models.js +41 -0
- package/dist/models.js.map +1 -0
- package/dist/providers/convert.d.ts +6 -0
- package/dist/providers/convert.d.ts.map +1 -0
- package/dist/providers/convert.js +207 -0
- package/dist/providers/convert.js.map +1 -0
- package/dist/providers/google.d.ts +26 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/google.js +434 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/providers/openai.d.ts +17 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +396 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/stream.d.ts +4 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/stream.js +40 -0
- package/dist/stream.js.map +1 -0
- package/dist/test-google-agent-loop.d.ts +2 -0
- package/dist/test-google-agent-loop.d.ts.map +1 -0
- package/dist/test-google-agent-loop.js +186 -0
- package/dist/test-google-agent-loop.js.map +1 -0
- package/dist/test-google.d.ts +2 -0
- package/dist/test-google.d.ts.map +1 -0
- package/dist/test-google.js +41 -0
- package/dist/test-google.js.map +1 -0
- package/dist/types.d.ts +187 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/event-stream.d.ts +16 -0
- package/dist/utils/event-stream.d.ts.map +1 -0
- package/dist/utils/event-stream.js +61 -0
- package/dist/utils/event-stream.js.map +1 -0
- package/dist/utils/json-parse.d.ts +9 -0
- package/dist/utils/json-parse.d.ts.map +1 -0
- package/dist/utils/json-parse.js +32 -0
- package/dist/utils/json-parse.js.map +1 -0
- package/dist/utils/sanitize-unicode.d.ts +22 -0
- package/dist/utils/sanitize-unicode.d.ts.map +1 -0
- package/dist/utils/sanitize-unicode.js +29 -0
- package/dist/utils/sanitize-unicode.js.map +1 -0
- package/dist/utils/validation.d.ts +11 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +61 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +33 -0
- package/src/agent/agent-loop.ts +275 -0
- package/src/agent/types.ts +80 -0
- package/src/index.ts +72 -0
- package/src/models.generated.ts +314 -0
- package/src/models.ts +45 -0
- package/src/providers/convert.ts +222 -0
- package/src/providers/google.ts +496 -0
- package/src/providers/openai.ts +437 -0
- package/src/stream.ts +60 -0
- package/src/types.ts +198 -0
- package/src/utils/event-stream.ts +60 -0
- package/src/utils/json-parse.ts +28 -0
- package/src/utils/sanitize-unicode.ts +25 -0
- package/src/utils/validation.ts +69 -0
- package/test/core/agent-loop.test.ts +958 -0
- package/test/core/stream.test.ts +409 -0
- package/test/data/red-circle.png +0 -0
- package/test/data/superintelligentwill.pdf +0 -0
- package/test/edge-cases/general.test.ts +565 -0
- package/test/integration/e2e.test.ts +530 -0
- package/test/models/cost.test.ts +499 -0
- package/test/models/registry.test.ts +298 -0
- package/test/providers/convert.test.ts +846 -0
- package/test/providers/google-schema.test.ts +666 -0
- package/test/providers/google-stream.test.ts +369 -0
- package/test/providers/openai-stream.test.ts +251 -0
- package/test/utils/event-stream.test.ts +289 -0
- package/test/utils/json-parse.test.ts +344 -0
- package/test/utils/sanitize-unicode.test.ts +329 -0
- package/test/utils/validation.test.ts +614 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { MODELS } from '../../src/models.generated';
|
|
3
|
+
import type { Model, Api } from '../../src/types';
|
|
4
|
+
|
|
5
|
+
// Helper to get all models from the nested structure
|
|
6
|
+
const getAllModels = (): Model<Api>[] => {
|
|
7
|
+
return Object.values(MODELS).flatMap(provider => Object.values(provider)) as Model<Api>[];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
describe('MODELS Registry', () => {
|
|
11
|
+
describe('Registry structure', () => {
|
|
12
|
+
it('should contain models', () => {
|
|
13
|
+
expect(MODELS).toBeDefined();
|
|
14
|
+
expect(Object.keys(MODELS).length).toBeGreaterThan(0);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should have OpenAI models', () => {
|
|
18
|
+
const openaiModels = getAllModels().filter(m => m.api === 'openai');
|
|
19
|
+
expect(openaiModels.length).toBeGreaterThan(0);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should have Google models', () => {
|
|
23
|
+
const googleModels = getAllModels().filter(m => m.api === 'google');
|
|
24
|
+
expect(googleModels.length).toBeGreaterThan(0);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('Model field validation', () => {
|
|
29
|
+
it('should have all required fields for each model', () => {
|
|
30
|
+
for (const model of getAllModels()) {
|
|
31
|
+
expect(model).toHaveProperty('id');
|
|
32
|
+
expect(model).toHaveProperty('name');
|
|
33
|
+
expect(model).toHaveProperty('api');
|
|
34
|
+
expect(model).toHaveProperty('baseUrl');
|
|
35
|
+
expect(model).toHaveProperty('reasoning');
|
|
36
|
+
expect(model).toHaveProperty('input');
|
|
37
|
+
expect(model).toHaveProperty('cost');
|
|
38
|
+
expect(model).toHaveProperty('contextWindow');
|
|
39
|
+
expect(model).toHaveProperty('maxTokens');
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should have valid id for each model', () => {
|
|
44
|
+
for (const model of getAllModels()) {
|
|
45
|
+
expect(model.id).toBeTruthy();
|
|
46
|
+
expect(typeof model.id).toBe('string');
|
|
47
|
+
expect(model.id.length).toBeGreaterThan(0);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should have valid name for each model', () => {
|
|
52
|
+
for (const model of getAllModels()) {
|
|
53
|
+
expect(model.name).toBeTruthy();
|
|
54
|
+
expect(typeof model.name).toBe('string');
|
|
55
|
+
expect(model.name.length).toBeGreaterThan(0);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should have valid api field', () => {
|
|
60
|
+
for (const model of getAllModels()) {
|
|
61
|
+
expect(['openai', 'google']).toContain(model.api);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should have valid baseUrl', () => {
|
|
66
|
+
for (const model of getAllModels()) {
|
|
67
|
+
expect(model.baseUrl).toBeTruthy();
|
|
68
|
+
expect(typeof model.baseUrl).toBe('string');
|
|
69
|
+
expect(model.baseUrl.startsWith('http')).toBe(true);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should have boolean reasoning field', () => {
|
|
74
|
+
for (const model of getAllModels()) {
|
|
75
|
+
expect(typeof model.reasoning).toBe('boolean');
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('Input types validation', () => {
|
|
81
|
+
it('should have non-empty input types array', () => {
|
|
82
|
+
for (const model of getAllModels()) {
|
|
83
|
+
expect(Array.isArray(model.input)).toBe(true);
|
|
84
|
+
expect(model.input.length).toBeGreaterThan(0);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should have valid input type values', () => {
|
|
89
|
+
const validInputTypes = ['text', 'image', 'file'];
|
|
90
|
+
for (const model of getAllModels()) {
|
|
91
|
+
for (const inputType of model.input) {
|
|
92
|
+
expect(validInputTypes).toContain(inputType);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should include text input for all models', () => {
|
|
98
|
+
for (const model of getAllModels()) {
|
|
99
|
+
expect(model.input).toContain('text');
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('Cost structure validation', () => {
|
|
105
|
+
it('should have all cost fields', () => {
|
|
106
|
+
for (const model of getAllModels()) {
|
|
107
|
+
expect(model.cost).toHaveProperty('input');
|
|
108
|
+
expect(model.cost).toHaveProperty('output');
|
|
109
|
+
expect(model.cost).toHaveProperty('cacheRead');
|
|
110
|
+
expect(model.cost).toHaveProperty('cacheWrite');
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should have non-negative cost values', () => {
|
|
115
|
+
for (const model of getAllModels()) {
|
|
116
|
+
expect(model.cost.input).toBeGreaterThanOrEqual(0);
|
|
117
|
+
expect(model.cost.output).toBeGreaterThanOrEqual(0);
|
|
118
|
+
expect(model.cost.cacheRead).toBeGreaterThanOrEqual(0);
|
|
119
|
+
expect(model.cost.cacheWrite).toBeGreaterThanOrEqual(0);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should have numeric cost values', () => {
|
|
124
|
+
for (const model of getAllModels()) {
|
|
125
|
+
expect(typeof model.cost.input).toBe('number');
|
|
126
|
+
expect(typeof model.cost.output).toBe('number');
|
|
127
|
+
expect(typeof model.cost.cacheRead).toBe('number');
|
|
128
|
+
expect(typeof model.cost.cacheWrite).toBe('number');
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should have output cost >= input cost (typical pricing)', () => {
|
|
133
|
+
for (const model of getAllModels()) {
|
|
134
|
+
if (model.cost.input > 0 && model.cost.output > 0) {
|
|
135
|
+
expect(model.cost.output).toBeGreaterThanOrEqual(model.cost.input);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should have cache read cost <= input cost', () => {
|
|
141
|
+
for (const model of getAllModels()) {
|
|
142
|
+
if (model.cost.cacheRead > 0 && model.cost.input > 0) {
|
|
143
|
+
expect(model.cost.cacheRead).toBeLessThanOrEqual(model.cost.input);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('Context window and tokens validation', () => {
|
|
150
|
+
it('should have positive context window', () => {
|
|
151
|
+
for (const model of getAllModels()) {
|
|
152
|
+
expect(model.contextWindow).toBeGreaterThan(0);
|
|
153
|
+
expect(typeof model.contextWindow).toBe('number');
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should have positive max tokens', () => {
|
|
158
|
+
for (const model of getAllModels()) {
|
|
159
|
+
expect(model.maxTokens).toBeGreaterThan(0);
|
|
160
|
+
expect(typeof model.maxTokens).toBe('number');
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should have maxTokens <= contextWindow', () => {
|
|
165
|
+
for (const model of getAllModels()) {
|
|
166
|
+
expect(model.maxTokens).toBeLessThanOrEqual(model.contextWindow);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should have realistic context windows (at least 1k tokens)', () => {
|
|
171
|
+
for (const model of getAllModels()) {
|
|
172
|
+
expect(model.contextWindow).toBeGreaterThanOrEqual(1000);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('Model ID uniqueness', () => {
|
|
178
|
+
it('should have unique model IDs', () => {
|
|
179
|
+
const ids = getAllModels().map(m => m.id);
|
|
180
|
+
const uniqueIds = new Set(ids);
|
|
181
|
+
expect(uniqueIds.size).toBe(ids.length);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should have unique model names', () => {
|
|
185
|
+
const names = getAllModels().map(m => m.name);
|
|
186
|
+
const uniqueNames = new Set(names);
|
|
187
|
+
expect(uniqueNames.size).toBe(names.length);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe('Headers validation', () => {
|
|
192
|
+
it('should have headers as object if present', () => {
|
|
193
|
+
for (const model of getAllModels()) {
|
|
194
|
+
if (model.headers) {
|
|
195
|
+
expect(typeof model.headers).toBe('object');
|
|
196
|
+
expect(Array.isArray(model.headers)).toBe(false);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should have string values in headers if present', () => {
|
|
202
|
+
for (const model of getAllModels()) {
|
|
203
|
+
if (model.headers) {
|
|
204
|
+
for (const value of Object.values(model.headers)) {
|
|
205
|
+
expect(typeof value).toBe('string');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('Provider-specific validations', () => {
|
|
213
|
+
describe('OpenAI models', () => {
|
|
214
|
+
it('should have correct baseUrl for OpenAI', () => {
|
|
215
|
+
const openaiModels = getAllModels().filter(m => m.api === 'openai');
|
|
216
|
+
for (const model of openaiModels) {
|
|
217
|
+
expect(model.baseUrl).toContain('openai.com');
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should have sensible costs for OpenAI', () => {
|
|
222
|
+
const openaiModels = getAllModels().filter(m => m.api === 'openai');
|
|
223
|
+
for (const model of openaiModels) {
|
|
224
|
+
// OpenAI costs are typically in range of $0.01 to $50 per million tokens
|
|
225
|
+
expect(model.cost.input).toBeLessThan(100);
|
|
226
|
+
expect(model.cost.output).toBeLessThan(200);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe('Google models', () => {
|
|
232
|
+
it('should have correct baseUrl for Google', () => {
|
|
233
|
+
const googleModels = getAllModels().filter(m => m.api === 'google');
|
|
234
|
+
for (const model of googleModels) {
|
|
235
|
+
expect(model.baseUrl).toContain('googleapis.com');
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should have sensible costs for Google', () => {
|
|
240
|
+
const googleModels = getAllModels().filter(m => m.api === 'google');
|
|
241
|
+
for (const model of googleModels) {
|
|
242
|
+
// Google costs are typically in similar range
|
|
243
|
+
expect(model.cost.input).toBeLessThan(100);
|
|
244
|
+
expect(model.cost.output).toBeLessThan(200);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('Reasoning capabilities', () => {
|
|
251
|
+
it('should have some models with reasoning enabled', () => {
|
|
252
|
+
const reasoningModels = getAllModels().filter(m => m.reasoning);
|
|
253
|
+
expect(reasoningModels.length).toBeGreaterThan(0);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe('Model availability', () => {
|
|
258
|
+
it('should have at least one model per provider', () => {
|
|
259
|
+
const apis = new Set(getAllModels().map(m => m.api));
|
|
260
|
+
expect(apis.has('openai')).toBe(true);
|
|
261
|
+
expect(apis.has('google')).toBe(true);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should have models with different capabilities', () => {
|
|
265
|
+
const capabilities = new Set();
|
|
266
|
+
for (const model of getAllModels()) {
|
|
267
|
+
capabilities.add(model.input.sort().join(','));
|
|
268
|
+
}
|
|
269
|
+
// Should have at least some variety in capabilities
|
|
270
|
+
expect(capabilities.size).toBeGreaterThan(0);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe('Multimodal support', () => {
|
|
275
|
+
it('should have some models with image support', () => {
|
|
276
|
+
const imageModels = getAllModels().filter(m => m.input.includes('image'));
|
|
277
|
+
expect(imageModels.length).toBeGreaterThan(0);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should have some models with file support', () => {
|
|
281
|
+
const fileModels = getAllModels().filter(m => m.input.includes('file'));
|
|
282
|
+
expect(fileModels.length).toBeGreaterThan(0);
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
describe('Cost effectiveness comparison', () => {
|
|
287
|
+
it('should have models with varying price points', () => {
|
|
288
|
+
const inputCosts = getAllModels().map(m => m.cost.input);
|
|
289
|
+
const minCost = Math.min(...inputCosts);
|
|
290
|
+
const maxCost = Math.max(...inputCosts);
|
|
291
|
+
|
|
292
|
+
// Should have variety in pricing
|
|
293
|
+
if (minCost > 0 && maxCost > 0) {
|
|
294
|
+
expect(maxCost).toBeGreaterThan(minCost);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
});
|