@probeo/anymodel 0.2.0 → 0.3.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 (114) hide show
  1. package/README.md +92 -6
  2. package/dist/batch/index.d.ts +4 -0
  3. package/dist/batch/index.d.ts.map +1 -0
  4. package/dist/batch/index.js +3 -0
  5. package/dist/batch/index.js.map +1 -0
  6. package/dist/batch/manager.d.ts +72 -0
  7. package/dist/batch/manager.d.ts.map +1 -0
  8. package/dist/batch/manager.js +328 -0
  9. package/dist/batch/manager.js.map +1 -0
  10. package/dist/batch/store.d.ts +54 -0
  11. package/dist/batch/store.d.ts.map +1 -0
  12. package/dist/batch/store.js +109 -0
  13. package/dist/batch/store.js.map +1 -0
  14. package/dist/cli.cjs +4 -28
  15. package/dist/cli.cjs.map +1 -1
  16. package/dist/cli.d.ts +2 -0
  17. package/dist/cli.d.ts.map +1 -0
  18. package/dist/cli.js +28 -2110
  19. package/dist/cli.js.map +1 -1
  20. package/dist/client.d.ts +42 -0
  21. package/dist/client.d.ts.map +1 -0
  22. package/dist/client.js +181 -0
  23. package/dist/client.js.map +1 -0
  24. package/dist/config.d.ts +6 -0
  25. package/dist/config.d.ts.map +1 -0
  26. package/dist/config.js +120 -0
  27. package/dist/config.js.map +1 -0
  28. package/dist/index.cjs +4 -28
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.ts +14 -548
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +15 -2089
  33. package/dist/index.js.map +1 -1
  34. package/dist/providers/adapter.d.ts +33 -0
  35. package/dist/providers/adapter.d.ts.map +1 -0
  36. package/dist/providers/adapter.js +2 -0
  37. package/dist/providers/adapter.js.map +1 -0
  38. package/dist/providers/anthropic-batch.d.ts +3 -0
  39. package/dist/providers/anthropic-batch.d.ts.map +1 -0
  40. package/dist/providers/anthropic-batch.js +228 -0
  41. package/dist/providers/anthropic-batch.js.map +1 -0
  42. package/dist/providers/anthropic.d.ts +3 -0
  43. package/dist/providers/anthropic.d.ts.map +1 -0
  44. package/dist/providers/anthropic.js +358 -0
  45. package/dist/providers/anthropic.js.map +1 -0
  46. package/dist/providers/custom.d.ts +8 -0
  47. package/dist/providers/custom.d.ts.map +1 -0
  48. package/dist/providers/custom.js +61 -0
  49. package/dist/providers/custom.js.map +1 -0
  50. package/dist/providers/google.d.ts +3 -0
  51. package/dist/providers/google.d.ts.map +1 -0
  52. package/dist/providers/google.js +331 -0
  53. package/dist/providers/google.js.map +1 -0
  54. package/dist/providers/index.d.ts +6 -0
  55. package/dist/providers/index.d.ts.map +1 -0
  56. package/dist/providers/index.js +5 -0
  57. package/dist/providers/index.js.map +1 -0
  58. package/dist/providers/openai-batch.d.ts +3 -0
  59. package/dist/providers/openai-batch.d.ts.map +1 -0
  60. package/dist/providers/openai-batch.js +208 -0
  61. package/dist/providers/openai-batch.js.map +1 -0
  62. package/dist/providers/openai.d.ts +3 -0
  63. package/dist/providers/openai.d.ts.map +1 -0
  64. package/dist/providers/openai.js +221 -0
  65. package/dist/providers/openai.js.map +1 -0
  66. package/dist/providers/registry.d.ts +10 -0
  67. package/dist/providers/registry.d.ts.map +1 -0
  68. package/dist/providers/registry.js +27 -0
  69. package/dist/providers/registry.js.map +1 -0
  70. package/dist/router.d.ts +29 -0
  71. package/dist/router.d.ts.map +1 -0
  72. package/dist/router.js +212 -0
  73. package/dist/router.js.map +1 -0
  74. package/dist/server.d.ts +10 -0
  75. package/dist/server.d.ts.map +1 -0
  76. package/dist/server.js +149 -0
  77. package/dist/server.js.map +1 -0
  78. package/dist/types.d.ts +283 -0
  79. package/dist/types.d.ts.map +1 -0
  80. package/dist/types.js +21 -0
  81. package/dist/types.js.map +1 -0
  82. package/dist/utils/fs-io.d.ts +40 -0
  83. package/dist/utils/fs-io.d.ts.map +1 -0
  84. package/dist/utils/fs-io.js +203 -0
  85. package/dist/utils/fs-io.js.map +1 -0
  86. package/dist/utils/generation-stats.d.ts +25 -0
  87. package/dist/utils/generation-stats.d.ts.map +1 -0
  88. package/dist/utils/generation-stats.js +46 -0
  89. package/dist/utils/generation-stats.js.map +1 -0
  90. package/dist/utils/id.d.ts +2 -0
  91. package/dist/utils/id.d.ts.map +1 -0
  92. package/dist/utils/id.js +6 -0
  93. package/dist/utils/id.js.map +1 -0
  94. package/dist/utils/model-parser.d.ts +6 -0
  95. package/dist/utils/model-parser.d.ts.map +1 -0
  96. package/dist/utils/model-parser.js +21 -0
  97. package/dist/utils/model-parser.js.map +1 -0
  98. package/dist/utils/rate-limiter.d.ts +36 -0
  99. package/dist/utils/rate-limiter.d.ts.map +1 -0
  100. package/dist/utils/rate-limiter.js +80 -0
  101. package/dist/utils/rate-limiter.js.map +1 -0
  102. package/dist/utils/retry.d.ts +7 -0
  103. package/dist/utils/retry.d.ts.map +1 -0
  104. package/dist/utils/retry.js +54 -0
  105. package/dist/utils/retry.js.map +1 -0
  106. package/dist/utils/transforms.d.ts +7 -0
  107. package/dist/utils/transforms.d.ts.map +1 -0
  108. package/dist/utils/transforms.js +66 -0
  109. package/dist/utils/transforms.js.map +1 -0
  110. package/dist/utils/validate.d.ts +3 -0
  111. package/dist/utils/validate.d.ts.map +1 -0
  112. package/dist/utils/validate.js +31 -0
  113. package/dist/utils/validate.js.map +1 -0
  114. package/package.json +4 -1
package/dist/index.js CHANGED
@@ -1,2090 +1,16 @@
1
- // src/types.ts
2
- var AnyModelError = class extends Error {
3
- code;
4
- metadata;
5
- constructor(code, message, metadata = {}) {
6
- super(message);
7
- this.name = "AnyModelError";
8
- this.code = code;
9
- this.metadata = metadata;
10
- }
11
- toJSON() {
12
- return {
13
- error: {
14
- code: this.code,
15
- message: this.message,
16
- metadata: this.metadata
17
- }
18
- };
19
- }
20
- };
21
-
22
- // src/providers/registry.ts
23
- var ProviderRegistry = class {
24
- adapters = /* @__PURE__ */ new Map();
25
- register(slug, adapter) {
26
- if (this.adapters.has(slug)) {
27
- throw new AnyModelError(500, `Provider '${slug}' is already registered`);
28
- }
29
- this.adapters.set(slug, adapter);
30
- }
31
- get(slug) {
32
- const adapter = this.adapters.get(slug);
33
- if (!adapter) {
34
- throw new AnyModelError(400, `Provider '${slug}' not configured`);
35
- }
36
- return adapter;
37
- }
38
- has(slug) {
39
- return this.adapters.has(slug);
40
- }
41
- list() {
42
- return Array.from(this.adapters.keys());
43
- }
44
- all() {
45
- return Array.from(this.adapters.values());
46
- }
47
- };
48
-
49
- // src/utils/model-parser.ts
50
- function parseModelString(model, aliases) {
51
- if (aliases && model in aliases) {
52
- model = aliases[model];
53
- }
54
- const slashIndex = model.indexOf("/");
55
- if (slashIndex === -1) {
56
- throw new AnyModelError(
57
- 400,
58
- `Model must be in provider/model format or be a valid alias. Got: '${model}'`
59
- );
60
- }
61
- const provider = model.substring(0, slashIndex);
62
- const modelId = model.substring(slashIndex + 1);
63
- if (!provider) {
64
- throw new AnyModelError(400, `Invalid model string: missing provider in '${model}'`);
65
- }
66
- if (!modelId) {
67
- throw new AnyModelError(400, `Invalid model string: missing model ID in '${model}'`);
68
- }
69
- return { provider, model: modelId };
70
- }
71
-
72
- // src/utils/validate.ts
73
- function validateRequest(request) {
74
- if (!request.model && !request.models?.length) {
75
- throw new AnyModelError(400, "Missing required field: model");
76
- }
77
- if (!request.messages || !Array.isArray(request.messages) || request.messages.length === 0) {
78
- throw new AnyModelError(400, "Missing or empty required field: messages");
79
- }
80
- if (request.temperature !== void 0 && (request.temperature < 0 || request.temperature > 2)) {
81
- throw new AnyModelError(400, "temperature must be between 0.0 and 2.0");
82
- }
83
- if (request.top_p !== void 0 && (request.top_p < 0 || request.top_p > 1)) {
84
- throw new AnyModelError(400, "top_p must be between 0.0 and 1.0");
85
- }
86
- if (request.top_logprobs !== void 0 && !request.logprobs) {
87
- throw new AnyModelError(400, "top_logprobs requires logprobs: true");
88
- }
89
- if (request.top_logprobs !== void 0 && (request.top_logprobs < 0 || request.top_logprobs > 20)) {
90
- throw new AnyModelError(400, "top_logprobs must be between 0 and 20");
91
- }
92
- if (request.stop !== void 0) {
93
- const stops = Array.isArray(request.stop) ? request.stop : [request.stop];
94
- if (stops.length > 4) {
95
- throw new AnyModelError(400, "stop may have at most 4 sequences");
96
- }
97
- }
98
- if (request.models && request.models.length > 0 && request.route && request.route !== "fallback") {
99
- throw new AnyModelError(400, `Invalid route: '${request.route}'. Only 'fallback' is supported.`);
100
- }
101
- }
102
-
103
- // src/utils/retry.ts
104
- var DEFAULT_RETRY = {
105
- maxRetries: 2,
106
- baseDelay: 500,
107
- maxDelay: 1e4
108
- };
109
- var RETRYABLE_CODES = /* @__PURE__ */ new Set([429, 502, 503, 529]);
110
- function isRetryable(error) {
111
- if (error instanceof AnyModelError) {
112
- return RETRYABLE_CODES.has(error.code);
113
- }
114
- return false;
115
- }
116
- function getRetryAfter(error) {
117
- if (error instanceof AnyModelError && error.metadata.raw) {
118
- const raw = error.metadata.raw;
119
- if (raw?.retry_after) return Number(raw.retry_after) * 1e3;
120
- if (raw?.headers?.["retry-after"]) return Number(raw.headers["retry-after"]) * 1e3;
121
- }
122
- return null;
123
- }
124
- function computeDelay(attempt, options, error) {
125
- const retryAfter = getRetryAfter(error);
126
- if (retryAfter && retryAfter > 0) {
127
- return Math.min(retryAfter, options.maxDelay);
128
- }
129
- const exponential = options.baseDelay * Math.pow(2, attempt);
130
- const jitter = exponential * 0.2 * Math.random();
131
- return Math.min(exponential + jitter, options.maxDelay);
132
- }
133
- async function withRetry(fn, options = {}) {
134
- const opts = { ...DEFAULT_RETRY, ...options };
135
- let lastError;
136
- for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
137
- try {
138
- return await fn();
139
- } catch (error) {
140
- lastError = error;
141
- if (attempt >= opts.maxRetries || !isRetryable(error)) {
142
- throw error;
143
- }
144
- const delay = computeDelay(attempt, opts, error);
145
- await new Promise((resolve3) => setTimeout(resolve3, delay));
146
- }
147
- }
148
- throw lastError;
149
- }
150
-
151
- // src/utils/rate-limiter.ts
152
- var RateLimitTracker = class {
153
- state = /* @__PURE__ */ new Map();
154
- /**
155
- * Update rate limit state from response headers.
156
- */
157
- updateFromHeaders(provider, headers) {
158
- const state = this.getOrCreate(provider);
159
- const remaining = headers["x-ratelimit-remaining"] || headers["x-ratelimit-remaining-requests"];
160
- if (remaining !== void 0) {
161
- state.remaining = parseInt(remaining, 10);
162
- }
163
- const reset = headers["x-ratelimit-reset"] || headers["x-ratelimit-reset-requests"];
164
- if (reset !== void 0) {
165
- const parsed = Number(reset);
166
- state.resetAt = parsed > 1e12 ? parsed : parsed * 1e3;
167
- }
168
- const retryAfter = headers["retry-after"];
169
- if (retryAfter !== void 0) {
170
- state.retryAfter = Number(retryAfter) * 1e3;
171
- }
172
- state.lastUpdated = Date.now();
173
- }
174
- /**
175
- * Record a 429 for a provider.
176
- */
177
- recordRateLimit(provider, retryAfterMs) {
178
- const state = this.getOrCreate(provider);
179
- state.remaining = 0;
180
- if (retryAfterMs) {
181
- state.retryAfter = retryAfterMs;
182
- state.resetAt = Date.now() + retryAfterMs;
183
- }
184
- state.lastUpdated = Date.now();
185
- }
186
- /**
187
- * Check if a provider is currently rate-limited.
188
- */
189
- isRateLimited(provider) {
190
- const state = this.state.get(provider);
191
- if (!state) return false;
192
- if (state.remaining === 0 && state.resetAt) {
193
- return Date.now() < state.resetAt;
194
- }
195
- return false;
196
- }
197
- /**
198
- * Get ms until rate limit resets for a provider.
199
- */
200
- getWaitTime(provider) {
201
- const state = this.state.get(provider);
202
- if (!state?.resetAt) return 0;
203
- const wait = state.resetAt - Date.now();
204
- return Math.max(0, wait);
205
- }
206
- /**
207
- * Get state for a provider.
208
- */
209
- getState(provider) {
210
- return this.state.get(provider);
211
- }
212
- getOrCreate(provider) {
213
- let state = this.state.get(provider);
214
- if (!state) {
215
- state = {
216
- provider,
217
- remaining: null,
218
- resetAt: null,
219
- retryAfter: null,
220
- lastUpdated: Date.now()
221
- };
222
- this.state.set(provider, state);
223
- }
224
- return state;
225
- }
226
- };
227
-
228
- // src/utils/transforms.ts
229
- var CHARS_PER_TOKEN = 4;
230
- function middleOut(messages, maxTokens) {
231
- if (messages.length <= 2) return messages;
232
- const maxChars = maxTokens * CHARS_PER_TOKEN;
233
- function messageLength(msg) {
234
- const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
235
- return content.length + 20;
236
- }
237
- const totalChars = messages.reduce((sum, m) => sum + messageLength(m), 0);
238
- if (totalChars <= maxChars) return messages;
239
- const systemMessages = messages.filter((m) => m.role === "system");
240
- const conversationMessages = messages.filter((m) => m.role !== "system");
241
- if (conversationMessages.length <= 2) return messages;
242
- const systemChars = systemMessages.reduce((sum, m) => sum + messageLength(m), 0);
243
- const budgetForConversation = maxChars - systemChars;
244
- if (budgetForConversation <= 0) return [...systemMessages, conversationMessages[conversationMessages.length - 1]];
245
- const kept = [];
246
- let usedChars = 0;
247
- const tail = [];
248
- let tailChars = 0;
249
- for (let i = conversationMessages.length - 1; i >= 0; i--) {
250
- const len = messageLength(conversationMessages[i]);
251
- if (tailChars + len > budgetForConversation * 0.7) break;
252
- tail.unshift(conversationMessages[i]);
253
- tailChars += len;
254
- }
255
- const headBudget = budgetForConversation - tailChars;
256
- const headEnd = conversationMessages.length - tail.length;
257
- for (let i = 0; i < headEnd; i++) {
258
- const len = messageLength(conversationMessages[i]);
259
- if (usedChars + len > headBudget) break;
260
- kept.push(conversationMessages[i]);
261
- usedChars += len;
262
- }
263
- return [...systemMessages, ...kept, ...tail];
264
- }
265
- function applyTransform(name, messages, contextLength) {
266
- if (name === "middle-out") {
267
- return middleOut(messages, contextLength);
268
- }
269
- return messages;
270
- }
271
-
272
- // src/router.ts
273
- var STRIPPABLE_PARAMS = [
274
- "temperature",
275
- "max_tokens",
276
- "top_p",
277
- "top_k",
278
- "frequency_penalty",
279
- "presence_penalty",
280
- "repetition_penalty",
281
- "seed",
282
- "stop",
283
- "logprobs",
284
- "top_logprobs",
285
- "response_format",
286
- "tools",
287
- "tool_choice",
288
- "user"
289
- ];
290
- var Router = class {
291
- constructor(registry, aliases, config) {
292
- this.registry = registry;
293
- this.aliases = aliases;
294
- this.config = config;
295
- }
296
- rateLimiter = new RateLimitTracker();
297
- /**
298
- * Strip parameters that the target provider doesn't support.
299
- */
300
- stripUnsupported(request, adapter) {
301
- const cleaned = { ...request };
302
- for (const param of STRIPPABLE_PARAMS) {
303
- if (cleaned[param] !== void 0 && !adapter.supportsParameter(param)) {
304
- delete cleaned[param];
305
- }
306
- }
307
- return cleaned;
308
- }
309
- /**
310
- * Apply transforms (e.g., middle-out) to the request messages.
311
- */
312
- applyTransforms(request) {
313
- const transforms = request.transforms || this.config?.defaults?.transforms;
314
- if (!transforms || transforms.length === 0) return request;
315
- let messages = [...request.messages];
316
- const contextLength = 128e3;
317
- for (const transform of transforms) {
318
- messages = applyTransform(transform, messages, contextLength);
319
- }
320
- return { ...request, messages };
321
- }
322
- /**
323
- * Order models based on provider preferences.
324
- */
325
- applyProviderPreferences(models, prefs) {
326
- if (!prefs) return models;
327
- let filtered = [...models];
328
- if (prefs.only && prefs.only.length > 0) {
329
- const onlySet = new Set(prefs.only);
330
- filtered = filtered.filter((m) => {
331
- const { provider } = parseModelString(m, this.aliases);
332
- return onlySet.has(provider);
333
- });
334
- }
335
- if (prefs.ignore && prefs.ignore.length > 0) {
336
- const ignoreSet = new Set(prefs.ignore);
337
- filtered = filtered.filter((m) => {
338
- const { provider } = parseModelString(m, this.aliases);
339
- return !ignoreSet.has(provider);
340
- });
341
- }
342
- if (prefs.order && prefs.order.length > 0) {
343
- const orderMap = new Map(prefs.order.map((p, i) => [p, i]));
344
- filtered.sort((a, b) => {
345
- const aProvider = parseModelString(a, this.aliases).provider;
346
- const bProvider = parseModelString(b, this.aliases).provider;
347
- const aOrder = orderMap.get(aProvider) ?? Infinity;
348
- const bOrder = orderMap.get(bProvider) ?? Infinity;
349
- return aOrder - bOrder;
350
- });
351
- }
352
- if (prefs.require_parameters) {
353
- filtered = filtered.filter((m) => {
354
- try {
355
- const { provider } = parseModelString(m, this.aliases);
356
- const adapter = this.registry.get(provider);
357
- return adapter !== void 0;
358
- } catch {
359
- return false;
360
- }
361
- });
362
- }
363
- filtered = filtered.filter((m) => {
364
- const { provider } = parseModelString(m, this.aliases);
365
- return !this.rateLimiter.isRateLimited(provider);
366
- });
367
- return filtered;
368
- }
369
- getRetryOptions() {
370
- return {
371
- maxRetries: this.config?.defaults?.retries ?? 2
372
- };
373
- }
374
- async complete(request) {
375
- validateRequest(request);
376
- const transformed = this.applyTransforms(request);
377
- if (transformed.models && transformed.models.length > 0 && transformed.route === "fallback") {
378
- return this.completeWithFallback(transformed);
379
- }
380
- const { provider, model } = parseModelString(transformed.model, this.aliases);
381
- const adapter = this.registry.get(provider);
382
- const resolvedRequest = this.stripUnsupported({ ...transformed, model }, adapter);
383
- return withRetry(
384
- () => adapter.sendRequest(resolvedRequest),
385
- this.getRetryOptions()
386
- );
387
- }
388
- async stream(request) {
389
- validateRequest(request);
390
- const transformed = this.applyTransforms(request);
391
- if (transformed.models && transformed.models.length > 0 && transformed.route === "fallback") {
392
- return this.streamWithFallback(transformed);
393
- }
394
- const { provider, model } = parseModelString(transformed.model, this.aliases);
395
- const adapter = this.registry.get(provider);
396
- const resolvedRequest = this.stripUnsupported({ ...transformed, model, stream: true }, adapter);
397
- return withRetry(
398
- () => adapter.sendStreamingRequest(resolvedRequest),
399
- this.getRetryOptions()
400
- );
401
- }
402
- async completeWithFallback(request) {
403
- let models = request.models;
404
- const errors = [];
405
- models = this.applyProviderPreferences(models, request.provider);
406
- for (const modelStr of models) {
407
- try {
408
- const { provider, model } = parseModelString(modelStr, this.aliases);
409
- const adapter = this.registry.get(provider);
410
- const resolvedRequest = this.stripUnsupported(
411
- { ...request, model, models: void 0, route: void 0 },
412
- adapter
413
- );
414
- const response = await withRetry(
415
- () => adapter.sendRequest(resolvedRequest),
416
- this.getRetryOptions()
417
- );
418
- response.model = modelStr;
419
- return response;
420
- } catch (err) {
421
- const error = err instanceof AnyModelError ? err : new AnyModelError(500, String(err));
422
- if (error.code === 429) {
423
- const { provider } = parseModelString(modelStr, this.aliases);
424
- this.rateLimiter.recordRateLimit(provider);
425
- }
426
- errors.push({ model: modelStr, error });
427
- }
428
- }
429
- const lastError = errors[errors.length - 1];
430
- throw new AnyModelError(lastError.error.code, lastError.error.message, {
431
- ...lastError.error.metadata,
432
- raw: {
433
- attempts: errors.map((e) => ({
434
- model: e.model,
435
- code: e.error.code,
436
- message: e.error.message
437
- }))
438
- }
439
- });
440
- }
441
- async streamWithFallback(request) {
442
- let models = request.models;
443
- const errors = [];
444
- models = this.applyProviderPreferences(models, request.provider);
445
- for (const modelStr of models) {
446
- try {
447
- const { provider, model } = parseModelString(modelStr, this.aliases);
448
- const adapter = this.registry.get(provider);
449
- const resolvedRequest = this.stripUnsupported(
450
- { ...request, model, models: void 0, route: void 0, stream: true },
451
- adapter
452
- );
453
- return await withRetry(
454
- () => adapter.sendStreamingRequest(resolvedRequest),
455
- this.getRetryOptions()
456
- );
457
- } catch (err) {
458
- const error = err instanceof AnyModelError ? err : new AnyModelError(500, String(err));
459
- if (error.code === 429) {
460
- const { provider } = parseModelString(modelStr, this.aliases);
461
- this.rateLimiter.recordRateLimit(provider);
462
- }
463
- errors.push({ model: modelStr, error });
464
- }
465
- }
466
- const lastError = errors[errors.length - 1];
467
- throw new AnyModelError(lastError.error.code, lastError.error.message, {
468
- ...lastError.error.metadata,
469
- raw: {
470
- attempts: errors.map((e) => ({
471
- model: e.model,
472
- code: e.error.code,
473
- message: e.error.message
474
- }))
475
- }
476
- });
477
- }
478
- getRateLimiter() {
479
- return this.rateLimiter;
480
- }
481
- };
482
-
483
- // src/providers/openai.ts
484
- var OPENAI_API_BASE = "https://api.openai.com/v1";
485
- var SUPPORTED_PARAMS = /* @__PURE__ */ new Set([
486
- "temperature",
487
- "max_tokens",
488
- "top_p",
489
- "frequency_penalty",
490
- "presence_penalty",
491
- "seed",
492
- "stop",
493
- "stream",
494
- "logprobs",
495
- "top_logprobs",
496
- "response_format",
497
- "tools",
498
- "tool_choice",
499
- "user",
500
- "logit_bias"
501
- ]);
502
- function createOpenAIAdapter(apiKey, baseURL) {
503
- const base = baseURL || OPENAI_API_BASE;
504
- async function makeRequest(path, body, method = "POST") {
505
- const res = await fetch(`${base}${path}`, {
506
- method,
507
- headers: {
508
- "Content-Type": "application/json",
509
- "Authorization": `Bearer ${apiKey}`
510
- },
511
- body: body ? JSON.stringify(body) : void 0
512
- });
513
- if (!res.ok) {
514
- let errorBody;
515
- try {
516
- errorBody = await res.json();
517
- } catch {
518
- errorBody = { message: res.statusText };
519
- }
520
- const msg = errorBody?.error?.message || errorBody?.message || res.statusText;
521
- throw new AnyModelError(mapErrorCode(res.status), msg, {
522
- provider_name: "openai",
523
- raw: errorBody
524
- });
525
- }
526
- return res;
527
- }
528
- function mapErrorCode(status) {
529
- if (status === 401 || status === 403) return 401;
530
- if (status === 429) return 429;
531
- if (status === 400 || status === 422) return 400;
532
- if (status >= 500) return 502;
533
- return status;
534
- }
535
- function rePrefixId(id) {
536
- if (id && id.startsWith("chatcmpl-")) {
537
- return `gen-${id.substring(9)}`;
538
- }
539
- return id.startsWith("gen-") ? id : `gen-${id}`;
540
- }
541
- function buildRequestBody(request) {
542
- const body = {
543
- model: request.model,
544
- messages: request.messages
545
- };
546
- if (request.temperature !== void 0) body.temperature = request.temperature;
547
- if (request.max_tokens !== void 0) body.max_tokens = request.max_tokens;
548
- if (request.top_p !== void 0) body.top_p = request.top_p;
549
- if (request.frequency_penalty !== void 0) body.frequency_penalty = request.frequency_penalty;
550
- if (request.presence_penalty !== void 0) body.presence_penalty = request.presence_penalty;
551
- if (request.seed !== void 0) body.seed = request.seed;
552
- if (request.stop !== void 0) body.stop = request.stop;
553
- if (request.stream !== void 0) body.stream = request.stream;
554
- if (request.logprobs !== void 0) body.logprobs = request.logprobs;
555
- if (request.top_logprobs !== void 0) body.top_logprobs = request.top_logprobs;
556
- if (request.response_format !== void 0) body.response_format = request.response_format;
557
- if (request.tools !== void 0) body.tools = request.tools;
558
- if (request.tool_choice !== void 0) body.tool_choice = request.tool_choice;
559
- if (request.user !== void 0) body.user = request.user;
560
- return body;
561
- }
562
- const adapter = {
563
- name: "openai",
564
- translateRequest(request) {
565
- return buildRequestBody(request);
566
- },
567
- translateResponse(response) {
568
- const r = response;
569
- return {
570
- id: rePrefixId(r.id),
571
- object: "chat.completion",
572
- created: r.created,
573
- model: `openai/${r.model}`,
574
- choices: r.choices,
575
- usage: r.usage
576
- };
577
- },
578
- async *translateStream(stream) {
579
- const reader = stream.getReader();
580
- const decoder = new TextDecoder();
581
- let buffer = "";
582
- try {
583
- while (true) {
584
- const { done, value } = await reader.read();
585
- if (done) break;
586
- buffer += decoder.decode(value, { stream: true });
587
- const lines = buffer.split("\n");
588
- buffer = lines.pop() || "";
589
- for (const line of lines) {
590
- const trimmed = line.trim();
591
- if (!trimmed || trimmed.startsWith(":")) continue;
592
- if (trimmed === "data: [DONE]") return;
593
- if (trimmed.startsWith("data: ")) {
594
- const json = JSON.parse(trimmed.substring(6));
595
- json.id = rePrefixId(json.id);
596
- json.model = `openai/${json.model}`;
597
- yield json;
598
- }
599
- }
600
- }
601
- } finally {
602
- reader.releaseLock();
603
- }
604
- },
605
- translateError(error) {
606
- if (error instanceof AnyModelError) {
607
- return { code: error.code, message: error.message, metadata: error.metadata };
608
- }
609
- const err = error;
610
- const status = err?.status || err?.code || 500;
611
- return {
612
- code: mapErrorCode(status),
613
- message: err?.message || "Unknown OpenAI error",
614
- metadata: { provider_name: "openai", raw: error }
615
- };
616
- },
617
- async listModels() {
618
- const res = await makeRequest("/models", void 0, "GET");
619
- const data = await res.json();
620
- return (data.data || []).filter((m) => {
621
- const id = m.id;
622
- if (id.includes("embedding")) return false;
623
- if (id.includes("whisper")) return false;
624
- if (id.includes("tts")) return false;
625
- if (id.includes("dall-e")) return false;
626
- if (id.includes("davinci")) return false;
627
- if (id.includes("babbage")) return false;
628
- if (id.includes("moderation")) return false;
629
- if (id.includes("realtime")) return false;
630
- if (id.startsWith("ft:")) return false;
631
- return id.startsWith("gpt-") || id.startsWith("o1") || id.startsWith("o3") || id.startsWith("o4") || id.startsWith("chatgpt-");
632
- }).map((m) => ({
633
- id: `openai/${m.id}`,
634
- name: m.id,
635
- created: m.created,
636
- description: "",
637
- context_length: 128e3,
638
- pricing: { prompt: "0", completion: "0" },
639
- architecture: {
640
- modality: "text+image->text",
641
- input_modalities: ["text", "image"],
642
- output_modalities: ["text"],
643
- tokenizer: "o200k_base"
644
- },
645
- top_provider: {
646
- context_length: 128e3,
647
- max_completion_tokens: 16384,
648
- is_moderated: true
649
- },
650
- supported_parameters: Array.from(SUPPORTED_PARAMS)
651
- }));
652
- },
653
- supportsParameter(param) {
654
- return SUPPORTED_PARAMS.has(param);
655
- },
656
- async sendRequest(request) {
657
- const body = buildRequestBody(request);
658
- const res = await makeRequest("/chat/completions", body);
659
- const json = await res.json();
660
- return adapter.translateResponse(json);
661
- },
662
- async sendStreamingRequest(request) {
663
- const body = buildRequestBody({ ...request, stream: true });
664
- const res = await makeRequest("/chat/completions", body);
665
- if (!res.body) {
666
- throw new AnyModelError(502, "No response body for streaming request", {
667
- provider_name: "openai"
668
- });
669
- }
670
- return adapter.translateStream(res.body);
671
- }
672
- };
673
- return adapter;
674
- }
675
-
676
- // src/utils/id.ts
677
- import { randomBytes } from "crypto";
678
- function generateId(prefix = "gen") {
679
- const random = randomBytes(12).toString("base64url");
680
- return `${prefix}-${random}`;
681
- }
682
-
683
- // src/providers/anthropic.ts
684
- var ANTHROPIC_API_BASE = "https://api.anthropic.com/v1";
685
- var ANTHROPIC_VERSION = "2023-06-01";
686
- var DEFAULT_MAX_TOKENS = 4096;
687
- var SUPPORTED_PARAMS2 = /* @__PURE__ */ new Set([
688
- "temperature",
689
- "max_tokens",
690
- "top_p",
691
- "top_k",
692
- "stop",
693
- "stream",
694
- "tools",
695
- "tool_choice",
696
- "response_format"
697
- ]);
698
- var FALLBACK_MODELS = [
699
- // Claude 4.6
700
- { id: "anthropic/claude-opus-4-6", name: "Claude Opus 4.6", created: 0, description: "Most capable model", context_length: 2e5, pricing: { prompt: "0.000015", completion: "0.000075" }, architecture: { modality: "text+image->text", input_modalities: ["text", "image"], output_modalities: ["text"], tokenizer: "claude" }, top_provider: { context_length: 2e5, max_completion_tokens: 32768, is_moderated: false }, supported_parameters: Array.from(SUPPORTED_PARAMS2) },
701
- { id: "anthropic/claude-sonnet-4-6", name: "Claude Sonnet 4.6", created: 0, description: "Best balance of speed and capability", context_length: 2e5, pricing: { prompt: "0.000003", completion: "0.000015" }, architecture: { modality: "text+image->text", input_modalities: ["text", "image"], output_modalities: ["text"], tokenizer: "claude" }, top_provider: { context_length: 2e5, max_completion_tokens: 16384, is_moderated: false }, supported_parameters: Array.from(SUPPORTED_PARAMS2) },
702
- // Claude 4.5
703
- { id: "anthropic/claude-sonnet-4-5-20251022", name: "Claude Sonnet 4.5", created: 0, description: "Previous generation balanced model", context_length: 2e5, pricing: { prompt: "0.000003", completion: "0.000015" }, architecture: { modality: "text+image->text", input_modalities: ["text", "image"], output_modalities: ["text"], tokenizer: "claude" }, top_provider: { context_length: 2e5, max_completion_tokens: 16384, is_moderated: false }, supported_parameters: Array.from(SUPPORTED_PARAMS2) },
704
- { id: "anthropic/claude-haiku-4-5", name: "Claude Haiku 4.5", created: 0, description: "Fast and compact", context_length: 2e5, pricing: { prompt: "0.000001", completion: "0.000005" }, architecture: { modality: "text+image->text", input_modalities: ["text", "image"], output_modalities: ["text"], tokenizer: "claude" }, top_provider: { context_length: 2e5, max_completion_tokens: 8192, is_moderated: false }, supported_parameters: Array.from(SUPPORTED_PARAMS2) },
705
- // Claude 3.5
706
- { id: "anthropic/claude-3-5-sonnet-20241022", name: "Claude 3.5 Sonnet", created: 0, description: "Legacy balanced model", context_length: 2e5, pricing: { prompt: "0.000003", completion: "0.000015" }, architecture: { modality: "text+image->text", input_modalities: ["text", "image"], output_modalities: ["text"], tokenizer: "claude" }, top_provider: { context_length: 2e5, max_completion_tokens: 8192, is_moderated: false }, supported_parameters: Array.from(SUPPORTED_PARAMS2) },
707
- { id: "anthropic/claude-3-5-haiku-20241022", name: "Claude 3.5 Haiku", created: 0, description: "Legacy fast model", context_length: 2e5, pricing: { prompt: "0.0000008", completion: "0.000004" }, architecture: { modality: "text+image->text", input_modalities: ["text", "image"], output_modalities: ["text"], tokenizer: "claude" }, top_provider: { context_length: 2e5, max_completion_tokens: 8192, is_moderated: false }, supported_parameters: Array.from(SUPPORTED_PARAMS2) }
708
- ];
709
- function createAnthropicAdapter(apiKey) {
710
- async function makeRequest(path, body, stream = false) {
711
- const res = await fetch(`${ANTHROPIC_API_BASE}${path}`, {
712
- method: "POST",
713
- headers: {
714
- "Content-Type": "application/json",
715
- "x-api-key": apiKey,
716
- "anthropic-version": ANTHROPIC_VERSION
717
- },
718
- body: JSON.stringify(body)
719
- });
720
- if (!res.ok) {
721
- let errorBody;
722
- try {
723
- errorBody = await res.json();
724
- } catch {
725
- errorBody = { message: res.statusText };
726
- }
727
- const msg = errorBody?.error?.message || errorBody?.message || res.statusText;
728
- throw new AnyModelError(mapErrorCode(res.status), msg, {
729
- provider_name: "anthropic",
730
- raw: errorBody
731
- });
732
- }
733
- return res;
734
- }
735
- function mapErrorCode(status) {
736
- if (status === 401 || status === 403) return 401;
737
- if (status === 429) return 429;
738
- if (status === 400 || status === 422) return 400;
739
- if (status === 529) return 502;
740
- if (status >= 500) return 502;
741
- return status;
742
- }
743
- function translateRequest(request) {
744
- const body = {
745
- model: request.model,
746
- max_tokens: request.max_tokens || DEFAULT_MAX_TOKENS
747
- };
748
- const systemMessages = request.messages.filter((m) => m.role === "system");
749
- const nonSystemMessages = request.messages.filter((m) => m.role !== "system");
750
- if (systemMessages.length > 0) {
751
- body.system = systemMessages.map((m) => typeof m.content === "string" ? m.content : "").join("\n");
752
- }
753
- body.messages = nonSystemMessages.map((m) => ({
754
- role: m.role === "tool" ? "user" : m.role,
755
- content: m.tool_call_id ? [{ type: "tool_result", tool_use_id: m.tool_call_id, content: typeof m.content === "string" ? m.content : "" }] : m.content
756
- }));
757
- if (request.temperature !== void 0) body.temperature = request.temperature;
758
- if (request.top_p !== void 0) body.top_p = request.top_p;
759
- if (request.top_k !== void 0) body.top_k = request.top_k;
760
- if (request.stop !== void 0) body.stop_sequences = Array.isArray(request.stop) ? request.stop : [request.stop];
761
- if (request.stream) body.stream = true;
762
- if (request.tools && request.tools.length > 0) {
763
- body.tools = request.tools.map((t) => ({
764
- name: t.function.name,
765
- description: t.function.description || "",
766
- input_schema: t.function.parameters || { type: "object", properties: {} }
767
- }));
768
- if (request.tool_choice) {
769
- if (request.tool_choice === "auto") {
770
- body.tool_choice = { type: "auto" };
771
- } else if (request.tool_choice === "required") {
772
- body.tool_choice = { type: "any" };
773
- } else if (request.tool_choice === "none") {
774
- delete body.tools;
775
- } else if (typeof request.tool_choice === "object") {
776
- body.tool_choice = { type: "tool", name: request.tool_choice.function.name };
777
- }
778
- }
779
- }
780
- if (request.response_format) {
781
- if (request.response_format.type === "json_object" || request.response_format.type === "json_schema") {
782
- const jsonInstruction = "Respond with valid JSON only. Do not include any text outside the JSON object.";
783
- body.system = body.system ? `${jsonInstruction}
784
-
785
- ${body.system}` : jsonInstruction;
786
- }
787
- }
788
- return body;
789
- }
790
- function mapStopReason(reason) {
791
- switch (reason) {
792
- case "end_turn":
793
- return "stop";
794
- case "max_tokens":
795
- return "length";
796
- case "tool_use":
797
- return "tool_calls";
798
- case "stop_sequence":
799
- return "stop";
800
- default:
801
- return "stop";
802
- }
803
- }
804
- function translateResponse(response) {
805
- const r = response;
806
- let content = "";
807
- const toolCalls = [];
808
- for (const block of r.content || []) {
809
- if (block.type === "text") {
810
- content += block.text;
811
- } else if (block.type === "tool_use") {
812
- toolCalls.push({
813
- id: block.id,
814
- type: "function",
815
- function: {
816
- name: block.name,
817
- arguments: JSON.stringify(block.input)
818
- }
819
- });
820
- }
821
- }
822
- const message = {
823
- role: "assistant",
824
- content
825
- };
826
- if (toolCalls.length > 0) {
827
- message.tool_calls = toolCalls;
828
- }
829
- return {
830
- id: generateId(),
831
- object: "chat.completion",
832
- created: Math.floor(Date.now() / 1e3),
833
- model: `anthropic/${r.model}`,
834
- choices: [{
835
- index: 0,
836
- message,
837
- finish_reason: mapStopReason(r.stop_reason)
838
- }],
839
- usage: {
840
- prompt_tokens: r.usage?.input_tokens || 0,
841
- completion_tokens: r.usage?.output_tokens || 0,
842
- total_tokens: (r.usage?.input_tokens || 0) + (r.usage?.output_tokens || 0)
843
- }
844
- };
845
- }
846
- const adapter = {
847
- name: "anthropic",
848
- translateRequest,
849
- translateResponse,
850
- async *translateStream(stream) {
851
- const reader = stream.getReader();
852
- const decoder = new TextDecoder();
853
- let buffer = "";
854
- const id = generateId();
855
- const created = Math.floor(Date.now() / 1e3);
856
- let model = "";
857
- let usage = null;
858
- try {
859
- while (true) {
860
- const { done, value } = await reader.read();
861
- if (done) break;
862
- buffer += decoder.decode(value, { stream: true });
863
- const lines = buffer.split("\n");
864
- buffer = lines.pop() || "";
865
- for (const line of lines) {
866
- const trimmed = line.trim();
867
- if (!trimmed || trimmed.startsWith(":")) continue;
868
- if (trimmed.startsWith("data: ")) {
869
- const data = JSON.parse(trimmed.substring(6));
870
- if (data.type === "message_start") {
871
- model = `anthropic/${data.message.model}`;
872
- usage = data.message.usage;
873
- yield {
874
- id,
875
- object: "chat.completion.chunk",
876
- created,
877
- model,
878
- choices: [{ index: 0, delta: { role: "assistant" }, finish_reason: null }]
879
- };
880
- } else if (data.type === "content_block_delta") {
881
- if (data.delta?.type === "text_delta") {
882
- yield {
883
- id,
884
- object: "chat.completion.chunk",
885
- created,
886
- model,
887
- choices: [{ index: 0, delta: { content: data.delta.text }, finish_reason: null }]
888
- };
889
- } else if (data.delta?.type === "input_json_delta") {
890
- yield {
891
- id,
892
- object: "chat.completion.chunk",
893
- created,
894
- model,
895
- choices: [{
896
- index: 0,
897
- delta: {
898
- tool_calls: [{
899
- id: data.content_block?.id || "",
900
- type: "function",
901
- function: { name: "", arguments: data.delta.partial_json }
902
- }]
903
- },
904
- finish_reason: null
905
- }]
906
- };
907
- }
908
- } else if (data.type === "content_block_start") {
909
- if (data.content_block?.type === "tool_use") {
910
- yield {
911
- id,
912
- object: "chat.completion.chunk",
913
- created,
914
- model,
915
- choices: [{
916
- index: 0,
917
- delta: {
918
- tool_calls: [{
919
- id: data.content_block.id,
920
- type: "function",
921
- function: { name: data.content_block.name, arguments: "" }
922
- }]
923
- },
924
- finish_reason: null
925
- }]
926
- };
927
- }
928
- } else if (data.type === "message_delta") {
929
- const finalUsage = usage ? {
930
- prompt_tokens: usage.input_tokens || 0,
931
- completion_tokens: data.usage?.output_tokens || 0,
932
- total_tokens: (usage.input_tokens || 0) + (data.usage?.output_tokens || 0)
933
- } : void 0;
934
- yield {
935
- id,
936
- object: "chat.completion.chunk",
937
- created,
938
- model,
939
- choices: [{
940
- index: 0,
941
- delta: {},
942
- finish_reason: mapStopReason(data.delta?.stop_reason || "end_turn")
943
- }],
944
- usage: finalUsage
945
- };
946
- }
947
- }
948
- }
949
- }
950
- } finally {
951
- reader.releaseLock();
952
- }
953
- },
954
- translateError(error) {
955
- if (error instanceof AnyModelError) {
956
- return { code: error.code, message: error.message, metadata: error.metadata };
957
- }
958
- const err = error;
959
- const status = err?.status || err?.code || 500;
960
- return {
961
- code: mapErrorCode(status),
962
- message: err?.message || "Unknown Anthropic error",
963
- metadata: { provider_name: "anthropic", raw: error }
964
- };
965
- },
966
- async listModels() {
967
- try {
968
- const res = await fetch(`${ANTHROPIC_API_BASE}/models`, {
969
- method: "GET",
970
- headers: {
971
- "x-api-key": apiKey,
972
- "anthropic-version": ANTHROPIC_VERSION
973
- }
974
- });
975
- if (!res.ok) return FALLBACK_MODELS;
976
- const data = await res.json();
977
- const models = data.data || [];
978
- return models.filter((m) => m.type === "model").map((m) => ({
979
- id: `anthropic/${m.id}`,
980
- name: m.display_name || m.id,
981
- created: m.created_at ? new Date(m.created_at).getTime() / 1e3 : 0,
982
- description: m.display_name || "",
983
- context_length: 2e5,
984
- pricing: { prompt: "0", completion: "0" },
985
- architecture: {
986
- modality: "text+image->text",
987
- input_modalities: ["text", "image"],
988
- output_modalities: ["text"],
989
- tokenizer: "claude"
990
- },
991
- top_provider: {
992
- context_length: 2e5,
993
- max_completion_tokens: 16384,
994
- is_moderated: false
995
- },
996
- supported_parameters: Array.from(SUPPORTED_PARAMS2)
997
- }));
998
- } catch {
999
- return FALLBACK_MODELS;
1000
- }
1001
- },
1002
- supportsParameter(param) {
1003
- return SUPPORTED_PARAMS2.has(param);
1004
- },
1005
- async sendRequest(request) {
1006
- const body = translateRequest(request);
1007
- const res = await makeRequest("/messages", body);
1008
- const json = await res.json();
1009
- return translateResponse(json);
1010
- },
1011
- async sendStreamingRequest(request) {
1012
- const body = translateRequest({ ...request, stream: true });
1013
- const res = await makeRequest("/messages", body, true);
1014
- if (!res.body) {
1015
- throw new AnyModelError(502, "No response body for streaming request", {
1016
- provider_name: "anthropic"
1017
- });
1018
- }
1019
- return adapter.translateStream(res.body);
1020
- }
1021
- };
1022
- return adapter;
1023
- }
1024
-
1025
- // src/providers/google.ts
1026
- var GEMINI_API_BASE = "https://generativelanguage.googleapis.com/v1beta";
1027
- var SUPPORTED_PARAMS3 = /* @__PURE__ */ new Set([
1028
- "temperature",
1029
- "max_tokens",
1030
- "top_p",
1031
- "top_k",
1032
- "stop",
1033
- "stream",
1034
- "tools",
1035
- "tool_choice",
1036
- "response_format"
1037
- ]);
1038
- var FALLBACK_MODELS2 = [
1039
- // Gemini 2.5
1040
- { id: "google/gemini-2.5-pro", name: "Gemini 2.5 Pro", created: 0, description: "Most capable Gemini model", context_length: 1048576, pricing: { prompt: "0.00000125", completion: "0.000005" }, architecture: { modality: "text+image->text", input_modalities: ["text", "image", "video", "audio"], output_modalities: ["text"], tokenizer: "gemini" }, top_provider: { context_length: 1048576, max_completion_tokens: 65536, is_moderated: false }, supported_parameters: Array.from(SUPPORTED_PARAMS3) },
1041
- { id: "google/gemini-2.5-flash", name: "Gemini 2.5 Flash", created: 0, description: "Fast and efficient", context_length: 1048576, pricing: { prompt: "0.00000015", completion: "0.0000006" }, architecture: { modality: "text+image->text", input_modalities: ["text", "image", "video", "audio"], output_modalities: ["text"], tokenizer: "gemini" }, top_provider: { context_length: 1048576, max_completion_tokens: 65536, is_moderated: false }, supported_parameters: Array.from(SUPPORTED_PARAMS3) },
1042
- // Gemini 2.0
1043
- { id: "google/gemini-2.0-flash", name: "Gemini 2.0 Flash", created: 0, description: "Fast multimodal model", context_length: 1048576, pricing: { prompt: "0.0000001", completion: "0.0000004" }, architecture: { modality: "text+image->text", input_modalities: ["text", "image", "video", "audio"], output_modalities: ["text"], tokenizer: "gemini" }, top_provider: { context_length: 1048576, max_completion_tokens: 65536, is_moderated: false }, supported_parameters: Array.from(SUPPORTED_PARAMS3) },
1044
- { id: "google/gemini-2.0-flash-lite", name: "Gemini 2.0 Flash Lite", created: 0, description: "Lightweight and fast", context_length: 1048576, pricing: { prompt: "0.00000005", completion: "0.0000002" }, architecture: { modality: "text+image->text", input_modalities: ["text", "image", "video", "audio"], output_modalities: ["text"], tokenizer: "gemini" }, top_provider: { context_length: 1048576, max_completion_tokens: 65536, is_moderated: false }, supported_parameters: Array.from(SUPPORTED_PARAMS3) },
1045
- // Gemini 1.5
1046
- { id: "google/gemini-1.5-pro", name: "Gemini 1.5 Pro", created: 0, description: "Previous generation pro model", context_length: 2097152, pricing: { prompt: "0.00000125", completion: "0.000005" }, architecture: { modality: "text+image->text", input_modalities: ["text", "image", "video", "audio"], output_modalities: ["text"], tokenizer: "gemini" }, top_provider: { context_length: 2097152, max_completion_tokens: 8192, is_moderated: false }, supported_parameters: Array.from(SUPPORTED_PARAMS3) },
1047
- { id: "google/gemini-1.5-flash", name: "Gemini 1.5 Flash", created: 0, description: "Previous generation flash model", context_length: 1048576, pricing: { prompt: "0.000000075", completion: "0.0000003" }, architecture: { modality: "text+image->text", input_modalities: ["text", "image", "video", "audio"], output_modalities: ["text"], tokenizer: "gemini" }, top_provider: { context_length: 1048576, max_completion_tokens: 8192, is_moderated: false }, supported_parameters: Array.from(SUPPORTED_PARAMS3) }
1048
- ];
1049
- function createGoogleAdapter(apiKey) {
1050
- function getModelEndpoint(model, stream) {
1051
- const action = stream ? "streamGenerateContent" : "generateContent";
1052
- return `${GEMINI_API_BASE}/models/${model}:${action}?key=${apiKey}${stream ? "&alt=sse" : ""}`;
1053
- }
1054
- function mapErrorCode(status) {
1055
- if (status === 401 || status === 403) return 401;
1056
- if (status === 429) return 429;
1057
- if (status === 400) return 400;
1058
- if (status >= 500) return 502;
1059
- return status;
1060
- }
1061
- function translateRequest(request) {
1062
- const body = {};
1063
- const systemMessages = request.messages.filter((m) => m.role === "system");
1064
- const nonSystemMessages = request.messages.filter((m) => m.role !== "system");
1065
- if (systemMessages.length > 0) {
1066
- body.systemInstruction = {
1067
- parts: [{ text: systemMessages.map((m) => typeof m.content === "string" ? m.content : "").join("\n") }]
1068
- };
1069
- }
1070
- body.contents = nonSystemMessages.map((m) => ({
1071
- role: m.role === "assistant" ? "model" : "user",
1072
- parts: typeof m.content === "string" ? [{ text: m.content }] : Array.isArray(m.content) ? m.content.map((p) => p.type === "text" ? { text: p.text } : { text: "" }) : [{ text: "" }]
1073
- }));
1074
- const generationConfig = {};
1075
- if (request.temperature !== void 0) generationConfig.temperature = request.temperature;
1076
- if (request.max_tokens !== void 0) generationConfig.maxOutputTokens = request.max_tokens;
1077
- if (request.top_p !== void 0) generationConfig.topP = request.top_p;
1078
- if (request.top_k !== void 0) generationConfig.topK = request.top_k;
1079
- if (request.stop !== void 0) {
1080
- generationConfig.stopSequences = Array.isArray(request.stop) ? request.stop : [request.stop];
1081
- }
1082
- if (request.response_format) {
1083
- if (request.response_format.type === "json_object") {
1084
- generationConfig.responseMimeType = "application/json";
1085
- } else if (request.response_format.type === "json_schema") {
1086
- generationConfig.responseMimeType = "application/json";
1087
- generationConfig.responseSchema = request.response_format.json_schema.schema;
1088
- }
1089
- }
1090
- if (Object.keys(generationConfig).length > 0) {
1091
- body.generationConfig = generationConfig;
1092
- }
1093
- if (request.tools && request.tools.length > 0) {
1094
- body.tools = [{
1095
- functionDeclarations: request.tools.map((t) => ({
1096
- name: t.function.name,
1097
- description: t.function.description || "",
1098
- parameters: t.function.parameters || {}
1099
- }))
1100
- }];
1101
- if (request.tool_choice) {
1102
- if (request.tool_choice === "auto") {
1103
- body.toolConfig = { functionCallingConfig: { mode: "AUTO" } };
1104
- } else if (request.tool_choice === "required") {
1105
- body.toolConfig = { functionCallingConfig: { mode: "ANY" } };
1106
- } else if (request.tool_choice === "none") {
1107
- body.toolConfig = { functionCallingConfig: { mode: "NONE" } };
1108
- } else if (typeof request.tool_choice === "object") {
1109
- body.toolConfig = {
1110
- functionCallingConfig: {
1111
- mode: "ANY",
1112
- allowedFunctionNames: [request.tool_choice.function.name]
1113
- }
1114
- };
1115
- }
1116
- }
1117
- }
1118
- return body;
1119
- }
1120
- function mapFinishReason(reason) {
1121
- switch (reason) {
1122
- case "STOP":
1123
- return "stop";
1124
- case "MAX_TOKENS":
1125
- return "length";
1126
- case "SAFETY":
1127
- return "content_filter";
1128
- case "RECITATION":
1129
- return "content_filter";
1130
- default:
1131
- return "stop";
1132
- }
1133
- }
1134
- function translateResponse(response) {
1135
- const r = response;
1136
- const candidate = r.candidates?.[0];
1137
- let content = "";
1138
- const toolCalls = [];
1139
- for (const part of candidate?.content?.parts || []) {
1140
- if (part.text) {
1141
- content += part.text;
1142
- } else if (part.functionCall) {
1143
- toolCalls.push({
1144
- id: generateId("call"),
1145
- type: "function",
1146
- function: {
1147
- name: part.functionCall.name,
1148
- arguments: JSON.stringify(part.functionCall.args || {})
1149
- }
1150
- });
1151
- }
1152
- }
1153
- const message = { role: "assistant", content };
1154
- if (toolCalls.length > 0) {
1155
- message.tool_calls = toolCalls;
1156
- }
1157
- const finishReason = toolCalls.length > 0 ? "tool_calls" : mapFinishReason(candidate?.finishReason || "STOP");
1158
- return {
1159
- id: generateId(),
1160
- object: "chat.completion",
1161
- created: Math.floor(Date.now() / 1e3),
1162
- model: `google/${r.modelVersion || "unknown"}`,
1163
- choices: [{ index: 0, message, finish_reason: finishReason }],
1164
- usage: {
1165
- prompt_tokens: r.usageMetadata?.promptTokenCount || 0,
1166
- completion_tokens: r.usageMetadata?.candidatesTokenCount || 0,
1167
- total_tokens: r.usageMetadata?.totalTokenCount || 0
1168
- }
1169
- };
1170
- }
1171
- const adapter = {
1172
- name: "google",
1173
- translateRequest,
1174
- translateResponse,
1175
- async *translateStream(stream) {
1176
- const reader = stream.getReader();
1177
- const decoder = new TextDecoder();
1178
- let buffer = "";
1179
- const id = generateId();
1180
- const created = Math.floor(Date.now() / 1e3);
1181
- let emittedRole = false;
1182
- try {
1183
- while (true) {
1184
- const { done, value } = await reader.read();
1185
- if (done) break;
1186
- buffer += decoder.decode(value, { stream: true });
1187
- const lines = buffer.split("\n");
1188
- buffer = lines.pop() || "";
1189
- for (const line of lines) {
1190
- const trimmed = line.trim();
1191
- if (!trimmed || trimmed.startsWith(":")) continue;
1192
- if (trimmed === "data: [DONE]") return;
1193
- if (trimmed.startsWith("data: ")) {
1194
- const data = JSON.parse(trimmed.substring(6));
1195
- const candidate = data.candidates?.[0];
1196
- if (!candidate) continue;
1197
- const parts = candidate.content?.parts || [];
1198
- for (const part of parts) {
1199
- if (part.text !== void 0) {
1200
- const chunk = {
1201
- id,
1202
- object: "chat.completion.chunk",
1203
- created,
1204
- model: `google/${data.modelVersion || "unknown"}`,
1205
- choices: [{
1206
- index: 0,
1207
- delta: emittedRole ? { content: part.text } : { role: "assistant", content: part.text },
1208
- finish_reason: null
1209
- }]
1210
- };
1211
- emittedRole = true;
1212
- yield chunk;
1213
- }
1214
- }
1215
- if (candidate.finishReason) {
1216
- yield {
1217
- id,
1218
- object: "chat.completion.chunk",
1219
- created,
1220
- model: `google/${data.modelVersion || "unknown"}`,
1221
- choices: [{ index: 0, delta: {}, finish_reason: mapFinishReason(candidate.finishReason) }],
1222
- usage: data.usageMetadata ? {
1223
- prompt_tokens: data.usageMetadata.promptTokenCount || 0,
1224
- completion_tokens: data.usageMetadata.candidatesTokenCount || 0,
1225
- total_tokens: data.usageMetadata.totalTokenCount || 0
1226
- } : void 0
1227
- };
1228
- }
1229
- }
1230
- }
1231
- }
1232
- } finally {
1233
- reader.releaseLock();
1234
- }
1235
- },
1236
- translateError(error) {
1237
- if (error instanceof AnyModelError) {
1238
- return { code: error.code, message: error.message, metadata: error.metadata };
1239
- }
1240
- const err = error;
1241
- const status = err?.status || err?.code || 500;
1242
- return {
1243
- code: mapErrorCode(status),
1244
- message: err?.message || "Unknown Google error",
1245
- metadata: { provider_name: "google", raw: error }
1246
- };
1247
- },
1248
- async listModels() {
1249
- try {
1250
- const res = await fetch(`${GEMINI_API_BASE}/models?key=${apiKey}`);
1251
- if (!res.ok) return FALLBACK_MODELS2;
1252
- const data = await res.json();
1253
- const models = data.models || [];
1254
- return models.filter((m) => m.name?.startsWith("models/gemini-") && m.supportedGenerationMethods?.includes("generateContent")).map((m) => {
1255
- const modelId = m.name.replace("models/", "");
1256
- return {
1257
- id: `google/${modelId}`,
1258
- name: m.displayName || modelId,
1259
- created: 0,
1260
- description: m.description || "",
1261
- context_length: m.inputTokenLimit || 1048576,
1262
- pricing: { prompt: "0", completion: "0" },
1263
- architecture: {
1264
- modality: "text+image->text",
1265
- input_modalities: ["text", "image", "video", "audio"],
1266
- output_modalities: ["text"],
1267
- tokenizer: "gemini"
1268
- },
1269
- top_provider: {
1270
- context_length: m.inputTokenLimit || 1048576,
1271
- max_completion_tokens: m.outputTokenLimit || 65536,
1272
- is_moderated: false
1273
- },
1274
- supported_parameters: Array.from(SUPPORTED_PARAMS3)
1275
- };
1276
- });
1277
- } catch {
1278
- return FALLBACK_MODELS2;
1279
- }
1280
- },
1281
- supportsParameter(param) {
1282
- return SUPPORTED_PARAMS3.has(param);
1283
- },
1284
- async sendRequest(request) {
1285
- const body = translateRequest(request);
1286
- const url = getModelEndpoint(request.model, false);
1287
- const res = await fetch(url, {
1288
- method: "POST",
1289
- headers: { "Content-Type": "application/json" },
1290
- body: JSON.stringify(body)
1291
- });
1292
- if (!res.ok) {
1293
- let errorBody;
1294
- try {
1295
- errorBody = await res.json();
1296
- } catch {
1297
- errorBody = { message: res.statusText };
1298
- }
1299
- throw new AnyModelError(mapErrorCode(res.status), errorBody?.error?.message || res.statusText, {
1300
- provider_name: "google",
1301
- raw: errorBody
1302
- });
1303
- }
1304
- const json = await res.json();
1305
- return translateResponse(json);
1306
- },
1307
- async sendStreamingRequest(request) {
1308
- const body = translateRequest(request);
1309
- const url = getModelEndpoint(request.model, true);
1310
- const res = await fetch(url, {
1311
- method: "POST",
1312
- headers: { "Content-Type": "application/json" },
1313
- body: JSON.stringify(body)
1314
- });
1315
- if (!res.ok) {
1316
- let errorBody;
1317
- try {
1318
- errorBody = await res.json();
1319
- } catch {
1320
- errorBody = { message: res.statusText };
1321
- }
1322
- throw new AnyModelError(mapErrorCode(res.status), errorBody?.error?.message || res.statusText, {
1323
- provider_name: "google",
1324
- raw: errorBody
1325
- });
1326
- }
1327
- if (!res.body) {
1328
- throw new AnyModelError(502, "No response body for streaming request", { provider_name: "google" });
1329
- }
1330
- return adapter.translateStream(res.body);
1331
- }
1332
- };
1333
- return adapter;
1334
- }
1335
-
1336
- // src/providers/custom.ts
1337
- function createCustomAdapter(name, config) {
1338
- const openaiAdapter = createOpenAIAdapter(config.apiKey || "", config.baseURL);
1339
- return {
1340
- ...openaiAdapter,
1341
- name,
1342
- async listModels() {
1343
- if (config.models && config.models.length > 0) {
1344
- return config.models.map((modelId) => ({
1345
- id: `${name}/${modelId}`,
1346
- name: modelId,
1347
- created: 0,
1348
- description: `Custom model via ${name}`,
1349
- context_length: 128e3,
1350
- pricing: { prompt: "0", completion: "0" },
1351
- architecture: {
1352
- modality: "text->text",
1353
- input_modalities: ["text"],
1354
- output_modalities: ["text"],
1355
- tokenizer: "unknown"
1356
- },
1357
- top_provider: {
1358
- context_length: 128e3,
1359
- max_completion_tokens: 16384,
1360
- is_moderated: false
1361
- },
1362
- supported_parameters: ["temperature", "max_tokens", "top_p", "stop", "stream", "tools", "tool_choice"]
1363
- }));
1364
- }
1365
- try {
1366
- const models = await openaiAdapter.listModels();
1367
- return models.map((m) => ({
1368
- ...m,
1369
- id: `${name}/${m.name}`
1370
- }));
1371
- } catch {
1372
- return [];
1373
- }
1374
- },
1375
- translateResponse(response) {
1376
- const translated = openaiAdapter.translateResponse(response);
1377
- if (translated.model.startsWith("openai/")) {
1378
- translated.model = `${name}/${translated.model.substring(7)}`;
1379
- }
1380
- return translated;
1381
- }
1382
- };
1383
- }
1384
-
1385
- // src/config.ts
1386
- import { readFileSync, existsSync } from "fs";
1387
- import { resolve, join } from "path";
1388
- import { homedir } from "os";
1389
- var LOCAL_CONFIG_NAMES = ["anymodel.config.json"];
1390
- var GLOBAL_CONFIG_DIR = join(homedir(), ".anymodel");
1391
- var GLOBAL_CONFIG_FILE = join(GLOBAL_CONFIG_DIR, "config.json");
1392
- function interpolateEnvVars(value) {
1393
- return value.replace(/\$\{([^}]+)\}/g, (_, name) => process.env[name] || "");
1394
- }
1395
- function interpolateDeep(obj) {
1396
- if (typeof obj === "string") return interpolateEnvVars(obj);
1397
- if (Array.isArray(obj)) return obj.map(interpolateDeep);
1398
- if (obj !== null && typeof obj === "object") {
1399
- const result = {};
1400
- for (const [key, val] of Object.entries(obj)) {
1401
- result[key] = interpolateDeep(val);
1402
- }
1403
- return result;
1404
- }
1405
- return obj;
1406
- }
1407
- function loadJsonFile(path) {
1408
- if (!existsSync(path)) return null;
1409
- try {
1410
- const raw = readFileSync(path, "utf-8");
1411
- const parsed = JSON.parse(raw);
1412
- return interpolateDeep(parsed);
1413
- } catch {
1414
- return null;
1415
- }
1416
- }
1417
- function findLocalConfig(cwd) {
1418
- const dir = cwd || process.cwd();
1419
- for (const name of LOCAL_CONFIG_NAMES) {
1420
- const config = loadJsonFile(resolve(dir, name));
1421
- if (config) return config;
1422
- }
1423
- return null;
1424
- }
1425
- function findGlobalConfig() {
1426
- return loadJsonFile(GLOBAL_CONFIG_FILE);
1427
- }
1428
- function deepMerge(target, source) {
1429
- const result = { ...target };
1430
- for (const [key, value] of Object.entries(source)) {
1431
- if (value === void 0) continue;
1432
- const existing = target[key];
1433
- if (existing !== null && typeof existing === "object" && !Array.isArray(existing) && value !== null && typeof value === "object" && !Array.isArray(value)) {
1434
- result[key] = deepMerge(existing, value);
1435
- } else {
1436
- result[key] = value;
1437
- }
1438
- }
1439
- return result;
1440
- }
1441
- function envConfig() {
1442
- const config = {};
1443
- const envMap = [
1444
- ["openai", "OPENAI_API_KEY"],
1445
- ["anthropic", "ANTHROPIC_API_KEY"],
1446
- ["google", "GOOGLE_API_KEY"],
1447
- ["mistral", "MISTRAL_API_KEY"],
1448
- ["groq", "GROQ_API_KEY"],
1449
- ["deepseek", "DEEPSEEK_API_KEY"],
1450
- ["xai", "XAI_API_KEY"],
1451
- ["together", "TOGETHER_API_KEY"],
1452
- ["fireworks", "FIREWORKS_API_KEY"],
1453
- ["perplexity", "PERPLEXITY_API_KEY"]
1454
- ];
1455
- for (const [key, envVar] of envMap) {
1456
- if (process.env[envVar]) {
1457
- config[key] = { apiKey: process.env[envVar] };
1458
- }
1459
- }
1460
- return config;
1461
- }
1462
- function resolveConfig(programmatic = {}, cwd) {
1463
- const env = envConfig();
1464
- const global = findGlobalConfig() || {};
1465
- const local = findLocalConfig(cwd) || {};
1466
- let config = deepMerge(env, global);
1467
- config = deepMerge(config, local);
1468
- config = deepMerge(config, programmatic);
1469
- return config;
1470
- }
1471
-
1472
- // src/utils/generation-stats.ts
1473
- var GenerationStatsStore = class {
1474
- records = /* @__PURE__ */ new Map();
1475
- maxRecords;
1476
- constructor(maxRecords = 1e3) {
1477
- this.maxRecords = maxRecords;
1478
- }
1479
- record(entry) {
1480
- if (this.records.size >= this.maxRecords) {
1481
- const oldest = this.records.keys().next().value;
1482
- if (oldest) this.records.delete(oldest);
1483
- }
1484
- this.records.set(entry.id, entry);
1485
- }
1486
- get(id) {
1487
- const rec = this.records.get(id);
1488
- if (!rec) return void 0;
1489
- const latency = rec.endTime - rec.startTime;
1490
- return {
1491
- id: rec.id,
1492
- model: rec.model,
1493
- provider_name: rec.providerName,
1494
- total_cost: 0,
1495
- // Cost calculation requires pricing data
1496
- tokens_prompt: rec.promptTokens,
1497
- tokens_completion: rec.completionTokens,
1498
- latency,
1499
- generation_time: latency,
1500
- created_at: new Date(rec.startTime).toISOString(),
1501
- finish_reason: rec.finishReason,
1502
- streamed: rec.streamed
1503
- };
1504
- }
1505
- list(limit = 50) {
1506
- const entries = Array.from(this.records.values()).slice(-limit).reverse();
1507
- return entries.map((rec) => this.get(rec.id));
1508
- }
1509
- };
1510
-
1511
- // src/batch/store.ts
1512
- import { mkdirSync, writeFileSync, readFileSync as readFileSync2, existsSync as existsSync2, readdirSync, appendFileSync } from "fs";
1513
- import { join as join2, resolve as resolve2 } from "path";
1514
- import { homedir as homedir2 } from "os";
1515
- var DEFAULT_BATCH_DIR = join2(homedir2(), ".anymodel", "batches");
1516
- var BatchStore = class {
1517
- dir;
1518
- constructor(dir) {
1519
- this.dir = resolve2(dir || DEFAULT_BATCH_DIR);
1520
- mkdirSync(this.dir, { recursive: true });
1521
- }
1522
- batchDir(id) {
1523
- return join2(this.dir, id);
1524
- }
1525
- /**
1526
- * Create a new batch directory and save initial metadata.
1527
- */
1528
- create(batch) {
1529
- const dir = this.batchDir(batch.id);
1530
- mkdirSync(dir, { recursive: true });
1531
- writeFileSync(join2(dir, "meta.json"), JSON.stringify(batch, null, 2));
1532
- }
1533
- /**
1534
- * Update batch metadata.
1535
- */
1536
- updateMeta(batch) {
1537
- const dir = this.batchDir(batch.id);
1538
- writeFileSync(join2(dir, "meta.json"), JSON.stringify(batch, null, 2));
1539
- }
1540
- /**
1541
- * Save requests as JSONL.
1542
- */
1543
- saveRequests(id, requests) {
1544
- const dir = this.batchDir(id);
1545
- const lines = requests.map((r) => JSON.stringify(r)).join("\n") + "\n";
1546
- writeFileSync(join2(dir, "requests.jsonl"), lines);
1547
- }
1548
- /**
1549
- * Append a result to results.jsonl.
1550
- */
1551
- appendResult(id, result) {
1552
- const dir = this.batchDir(id);
1553
- appendFileSync(join2(dir, "results.jsonl"), JSON.stringify(result) + "\n");
1554
- }
1555
- /**
1556
- * Save provider-specific state (e.g., provider batch ID).
1557
- */
1558
- saveProviderState(id, state) {
1559
- const dir = this.batchDir(id);
1560
- writeFileSync(join2(dir, "provider.json"), JSON.stringify(state, null, 2));
1561
- }
1562
- /**
1563
- * Load provider state.
1564
- */
1565
- loadProviderState(id) {
1566
- const path = join2(this.batchDir(id), "provider.json");
1567
- if (!existsSync2(path)) return null;
1568
- return JSON.parse(readFileSync2(path, "utf-8"));
1569
- }
1570
- /**
1571
- * Get batch metadata.
1572
- */
1573
- getMeta(id) {
1574
- const path = join2(this.batchDir(id), "meta.json");
1575
- if (!existsSync2(path)) return null;
1576
- return JSON.parse(readFileSync2(path, "utf-8"));
1577
- }
1578
- /**
1579
- * Get all results for a batch.
1580
- */
1581
- getResults(id) {
1582
- const path = join2(this.batchDir(id), "results.jsonl");
1583
- if (!existsSync2(path)) return [];
1584
- return readFileSync2(path, "utf-8").trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
1585
- }
1586
- /**
1587
- * List all batch IDs.
1588
- */
1589
- listBatches() {
1590
- if (!existsSync2(this.dir)) return [];
1591
- return readdirSync(this.dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
1592
- }
1593
- /**
1594
- * Check if a batch exists.
1595
- */
1596
- exists(id) {
1597
- return existsSync2(join2(this.batchDir(id), "meta.json"));
1598
- }
1599
- };
1600
-
1601
- // src/batch/manager.ts
1602
- var BatchManager = class {
1603
- store;
1604
- router;
1605
- concurrencyLimit;
1606
- constructor(router, options) {
1607
- this.store = new BatchStore(options?.dir);
1608
- this.router = router;
1609
- this.concurrencyLimit = options?.concurrency ?? 5;
1610
- }
1611
- /**
1612
- * Create a batch and return immediately (no polling).
1613
- */
1614
- async create(request) {
1615
- const id = generateId("batch");
1616
- const now = (/* @__PURE__ */ new Date()).toISOString();
1617
- const batch = {
1618
- id,
1619
- object: "batch",
1620
- status: "pending",
1621
- model: request.model,
1622
- provider_name: request.model.split("/")[0] || "unknown",
1623
- batch_mode: "concurrent",
1624
- total: request.requests.length,
1625
- completed: 0,
1626
- failed: 0,
1627
- created_at: now,
1628
- completed_at: null,
1629
- expires_at: null
1630
- };
1631
- this.store.create(batch);
1632
- this.store.saveRequests(id, request.requests);
1633
- this.processBatch(id, request).catch(() => {
1634
- });
1635
- return batch;
1636
- }
1637
- /**
1638
- * Create a batch and poll until completion.
1639
- */
1640
- async createAndPoll(request, options = {}) {
1641
- const batch = await this.create(request);
1642
- return this.poll(batch.id, options);
1643
- }
1644
- /**
1645
- * Poll an existing batch until completion.
1646
- */
1647
- async poll(id, options = {}) {
1648
- const interval = options.interval ?? 5e3;
1649
- const timeout = options.timeout ?? 0;
1650
- const startTime = Date.now();
1651
- while (true) {
1652
- const batch = this.store.getMeta(id);
1653
- if (!batch) {
1654
- throw new AnyModelError(404, `Batch ${id} not found`);
1655
- }
1656
- if (options.onProgress) {
1657
- options.onProgress(batch);
1658
- }
1659
- if (batch.status === "completed" || batch.status === "failed" || batch.status === "cancelled") {
1660
- return this.getResults(id);
1661
- }
1662
- if (timeout > 0 && Date.now() - startTime > timeout) {
1663
- throw new AnyModelError(408, `Batch ${id} timed out after ${timeout}ms`);
1664
- }
1665
- await new Promise((resolve3) => setTimeout(resolve3, interval));
1666
- }
1667
- }
1668
- /**
1669
- * Get the current status of a batch.
1670
- */
1671
- get(id) {
1672
- return this.store.getMeta(id);
1673
- }
1674
- /**
1675
- * Get results for a completed batch.
1676
- */
1677
- getResults(id) {
1678
- const batch = this.store.getMeta(id);
1679
- if (!batch) {
1680
- throw new AnyModelError(404, `Batch ${id} not found`);
1681
- }
1682
- const results = this.store.getResults(id);
1683
- const usage = {
1684
- total_prompt_tokens: 0,
1685
- total_completion_tokens: 0,
1686
- estimated_cost: 0
1687
- };
1688
- for (const result of results) {
1689
- if (result.response) {
1690
- usage.total_prompt_tokens += result.response.usage.prompt_tokens;
1691
- usage.total_completion_tokens += result.response.usage.completion_tokens;
1692
- }
1693
- }
1694
- return {
1695
- id: batch.id,
1696
- status: batch.status,
1697
- results,
1698
- usage_summary: usage
1699
- };
1700
- }
1701
- /**
1702
- * List all batches.
1703
- */
1704
- list() {
1705
- return this.store.listBatches().map((id) => this.store.getMeta(id)).filter((b) => b !== null);
1706
- }
1707
- /**
1708
- * Cancel a batch.
1709
- */
1710
- cancel(id) {
1711
- const batch = this.store.getMeta(id);
1712
- if (!batch) {
1713
- throw new AnyModelError(404, `Batch ${id} not found`);
1714
- }
1715
- if (batch.status === "completed" || batch.status === "cancelled") {
1716
- return batch;
1717
- }
1718
- batch.status = "cancelled";
1719
- batch.completed_at = (/* @__PURE__ */ new Date()).toISOString();
1720
- this.store.updateMeta(batch);
1721
- return batch;
1722
- }
1723
- /**
1724
- * Process batch requests concurrently.
1725
- */
1726
- async processBatch(batchId, request) {
1727
- const batch = this.store.getMeta(batchId);
1728
- batch.status = "processing";
1729
- this.store.updateMeta(batch);
1730
- const items = request.requests;
1731
- const queue = [...items];
1732
- const active = /* @__PURE__ */ new Set();
1733
- const processItem = async (item) => {
1734
- const current = this.store.getMeta(batchId);
1735
- if (current?.status === "cancelled") return;
1736
- const chatRequest = {
1737
- model: request.model,
1738
- messages: item.messages,
1739
- max_tokens: item.max_tokens ?? request.options?.max_tokens,
1740
- temperature: item.temperature ?? request.options?.temperature,
1741
- top_p: item.top_p ?? request.options?.top_p,
1742
- top_k: item.top_k ?? request.options?.top_k,
1743
- stop: item.stop ?? request.options?.stop,
1744
- response_format: item.response_format ?? request.options?.response_format,
1745
- tools: item.tools ?? request.options?.tools,
1746
- tool_choice: item.tool_choice ?? request.options?.tool_choice
1747
- };
1748
- let result;
1749
- try {
1750
- const response = await this.router.complete(chatRequest);
1751
- result = {
1752
- custom_id: item.custom_id,
1753
- status: "success",
1754
- response,
1755
- error: null
1756
- };
1757
- } catch (err) {
1758
- const error = err instanceof AnyModelError ? err : new AnyModelError(500, String(err));
1759
- result = {
1760
- custom_id: item.custom_id,
1761
- status: "error",
1762
- response: null,
1763
- error: { code: error.code, message: error.message }
1764
- };
1765
- }
1766
- this.store.appendResult(batchId, result);
1767
- const meta = this.store.getMeta(batchId);
1768
- if (result.status === "success") {
1769
- meta.completed++;
1770
- } else {
1771
- meta.failed++;
1772
- }
1773
- this.store.updateMeta(meta);
1774
- };
1775
- for (const item of queue) {
1776
- const current = this.store.getMeta(batchId);
1777
- if (current?.status === "cancelled") break;
1778
- if (active.size >= this.concurrencyLimit) {
1779
- await Promise.race(active);
1780
- }
1781
- const promise = processItem(item).then(() => {
1782
- active.delete(promise);
1783
- });
1784
- active.add(promise);
1785
- }
1786
- await Promise.all(active);
1787
- const finalMeta = this.store.getMeta(batchId);
1788
- if (finalMeta.status !== "cancelled") {
1789
- finalMeta.status = finalMeta.failed === finalMeta.total ? "failed" : "completed";
1790
- finalMeta.completed_at = (/* @__PURE__ */ new Date()).toISOString();
1791
- this.store.updateMeta(finalMeta);
1792
- }
1793
- }
1794
- };
1795
-
1796
- // src/client.ts
1797
- var AnyModel = class {
1798
- registry;
1799
- router;
1800
- config;
1801
- modelCache = null;
1802
- statsStore = new GenerationStatsStore();
1803
- batchManager;
1804
- chat;
1805
- models;
1806
- generation;
1807
- batches;
1808
- constructor(config = {}) {
1809
- this.config = resolveConfig(config);
1810
- this.registry = new ProviderRegistry();
1811
- this.registerProviders();
1812
- this.router = new Router(this.registry, this.config.aliases, this.config);
1813
- this.chat = {
1814
- completions: {
1815
- create: async (request) => {
1816
- const merged = this.applyDefaults(request);
1817
- if (merged.stream) {
1818
- return this.router.stream(merged);
1819
- }
1820
- const startTime = Date.now();
1821
- const response = await this.router.complete(merged);
1822
- const endTime = Date.now();
1823
- const providerName = response.model.includes("/") ? response.model.split("/")[0] : "unknown";
1824
- this.statsStore.record({
1825
- id: response.id,
1826
- model: response.model,
1827
- providerName,
1828
- promptTokens: response.usage.prompt_tokens,
1829
- completionTokens: response.usage.completion_tokens,
1830
- startTime,
1831
- endTime,
1832
- finishReason: response.choices[0]?.finish_reason || "stop",
1833
- streamed: false
1834
- });
1835
- return response;
1836
- }
1837
- }
1838
- };
1839
- this.models = {
1840
- list: async (opts) => {
1841
- if (!this.modelCache) {
1842
- this.modelCache = await this.fetchModels();
1843
- }
1844
- if (opts?.provider) {
1845
- return this.modelCache.filter((m) => m.id.startsWith(`${opts.provider}/`));
1846
- }
1847
- return this.modelCache;
1848
- },
1849
- refresh: async () => {
1850
- this.modelCache = null;
1851
- return this.models.list();
1852
- }
1853
- };
1854
- this.generation = {
1855
- get: (id) => this.statsStore.get(id),
1856
- list: (limit) => this.statsStore.list(limit)
1857
- };
1858
- this.batchManager = new BatchManager(this.router, {
1859
- dir: this.config.batch?.dir,
1860
- concurrency: this.config.batch?.concurrencyFallback
1861
- });
1862
- this.batches = {
1863
- create: (request) => this.batchManager.create(request),
1864
- createAndPoll: (request, options) => this.batchManager.createAndPoll(request, options),
1865
- poll: (id, options) => this.batchManager.poll(id, options),
1866
- get: (id) => this.batchManager.get(id),
1867
- list: () => this.batchManager.list(),
1868
- cancel: (id) => this.batchManager.cancel(id),
1869
- results: (id) => this.batchManager.getResults(id)
1870
- };
1871
- }
1872
- registerProviders() {
1873
- const config = this.config;
1874
- const openaiKey = config.openai?.apiKey || process.env.OPENAI_API_KEY;
1875
- if (openaiKey) {
1876
- this.registry.register("openai", createOpenAIAdapter(openaiKey));
1877
- }
1878
- const anthropicKey = config.anthropic?.apiKey || process.env.ANTHROPIC_API_KEY;
1879
- if (anthropicKey) {
1880
- this.registry.register("anthropic", createAnthropicAdapter(anthropicKey));
1881
- }
1882
- const googleKey = config.google?.apiKey || process.env.GOOGLE_API_KEY;
1883
- if (googleKey) {
1884
- this.registry.register("google", createGoogleAdapter(googleKey));
1885
- }
1886
- const builtinProviders = [
1887
- { name: "mistral", baseURL: "https://api.mistral.ai/v1", configKey: "mistral", envVar: "MISTRAL_API_KEY" },
1888
- { name: "groq", baseURL: "https://api.groq.com/openai/v1", configKey: "groq", envVar: "GROQ_API_KEY" },
1889
- { name: "deepseek", baseURL: "https://api.deepseek.com", configKey: "deepseek", envVar: "DEEPSEEK_API_KEY" },
1890
- { name: "xai", baseURL: "https://api.x.ai/v1", configKey: "xai", envVar: "XAI_API_KEY" },
1891
- { name: "together", baseURL: "https://api.together.xyz/v1", configKey: "together", envVar: "TOGETHER_API_KEY" },
1892
- { name: "fireworks", baseURL: "https://api.fireworks.ai/inference/v1", configKey: "fireworks", envVar: "FIREWORKS_API_KEY" },
1893
- { name: "perplexity", baseURL: "https://api.perplexity.ai", configKey: "perplexity", envVar: "PERPLEXITY_API_KEY" }
1894
- ];
1895
- for (const { name, baseURL, configKey, envVar } of builtinProviders) {
1896
- const providerConfig = config[configKey];
1897
- const key = providerConfig?.apiKey || process.env[envVar];
1898
- if (key) {
1899
- this.registry.register(name, createCustomAdapter(name, { baseURL, apiKey: key }));
1900
- }
1901
- }
1902
- const ollamaConfig = config.ollama;
1903
- const ollamaURL = ollamaConfig?.baseURL || process.env.OLLAMA_BASE_URL || "http://localhost:11434/v1";
1904
- if (ollamaConfig || process.env.OLLAMA_BASE_URL) {
1905
- this.registry.register("ollama", createCustomAdapter("ollama", { baseURL: ollamaURL }));
1906
- }
1907
- if (config.custom) {
1908
- for (const [name, customConfig] of Object.entries(config.custom)) {
1909
- this.registry.register(name, createCustomAdapter(name, customConfig));
1910
- }
1911
- }
1912
- }
1913
- applyDefaults(request) {
1914
- const defaults = this.config.defaults;
1915
- if (!defaults) return request;
1916
- return {
1917
- ...request,
1918
- temperature: request.temperature ?? defaults.temperature,
1919
- max_tokens: request.max_tokens ?? defaults.max_tokens
1920
- };
1921
- }
1922
- async fetchModels() {
1923
- const all = [];
1924
- for (const adapter of this.registry.all()) {
1925
- try {
1926
- const models = await adapter.listModels();
1927
- all.push(...models);
1928
- } catch {
1929
- }
1930
- }
1931
- return all.sort((a, b) => a.id.localeCompare(b.id));
1932
- }
1933
- getRegistry() {
1934
- return this.registry;
1935
- }
1936
- };
1937
-
1938
- // src/server.ts
1939
- import { createServer } from "http";
1940
- function parseBody(req) {
1941
- return new Promise((resolve3, reject) => {
1942
- const chunks = [];
1943
- req.on("data", (chunk) => chunks.push(chunk));
1944
- req.on("end", () => resolve3(Buffer.concat(chunks).toString()));
1945
- req.on("error", reject);
1946
- });
1947
- }
1948
- function sendJSON(res, status, body) {
1949
- res.writeHead(status, { "Content-Type": "application/json" });
1950
- res.end(JSON.stringify(body));
1951
- }
1952
- function sendError(res, status, message) {
1953
- sendJSON(res, status, { error: { code: status, message, metadata: {} } });
1954
- }
1955
- async function sendSSE(res, stream) {
1956
- res.writeHead(200, {
1957
- "Content-Type": "text/event-stream",
1958
- "Cache-Control": "no-cache",
1959
- "Connection": "keep-alive"
1960
- });
1961
- for await (const chunk of stream) {
1962
- res.write(`data: ${JSON.stringify(chunk)}
1963
-
1964
- `);
1965
- }
1966
- res.write("data: [DONE]\n\n");
1967
- res.end();
1968
- }
1969
- function createAnyModelServer(options = {}) {
1970
- const client = new AnyModel(options.config);
1971
- const basePath = "/api/v1";
1972
- const server = createServer(async (req, res) => {
1973
- const url = new URL(req.url || "/", `http://${req.headers.host}`);
1974
- const path = url.pathname;
1975
- res.setHeader("Access-Control-Allow-Origin", "*");
1976
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
1977
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
1978
- if (req.method === "OPTIONS") {
1979
- res.writeHead(204);
1980
- res.end();
1981
- return;
1982
- }
1983
- try {
1984
- if (path === "/health" && req.method === "GET") {
1985
- sendJSON(res, 200, { status: "ok" });
1986
- return;
1987
- }
1988
- if (path === `${basePath}/chat/completions` && req.method === "POST") {
1989
- const body = JSON.parse(await parseBody(req));
1990
- if (body.stream) {
1991
- const stream = await client.chat.completions.create(body);
1992
- await sendSSE(res, stream);
1993
- } else {
1994
- const response = await client.chat.completions.create(body);
1995
- sendJSON(res, 200, response);
1996
- }
1997
- return;
1998
- }
1999
- if (path === `${basePath}/models` && req.method === "GET") {
2000
- const provider = url.searchParams.get("provider") || void 0;
2001
- const models = await client.models.list({ provider });
2002
- sendJSON(res, 200, { object: "list", data: models });
2003
- return;
2004
- }
2005
- if (path.startsWith(`${basePath}/generation/`) && req.method === "GET") {
2006
- const id = path.substring(`${basePath}/generation/`.length);
2007
- const stats = client.generation.get(id);
2008
- if (!stats) {
2009
- sendError(res, 404, `Generation ${id} not found`);
2010
- return;
2011
- }
2012
- sendJSON(res, 200, stats);
2013
- return;
2014
- }
2015
- if (path === `${basePath}/batches` && req.method === "POST") {
2016
- const body = JSON.parse(await parseBody(req));
2017
- const batch = await client.batches.create(body);
2018
- sendJSON(res, 201, batch);
2019
- return;
2020
- }
2021
- if (path === `${basePath}/batches` && req.method === "GET") {
2022
- const batches = client.batches.list();
2023
- sendJSON(res, 200, { object: "list", data: batches });
2024
- return;
2025
- }
2026
- if (path.startsWith(`${basePath}/batches/`) && req.method === "GET") {
2027
- const parts = path.substring(`${basePath}/batches/`.length).split("/");
2028
- const id = parts[0];
2029
- if (parts[1] === "results") {
2030
- const results = client.batches.results(id);
2031
- sendJSON(res, 200, results);
2032
- return;
2033
- }
2034
- const batch = client.batches.get(id);
2035
- if (!batch) {
2036
- sendError(res, 404, `Batch ${id} not found`);
2037
- return;
2038
- }
2039
- sendJSON(res, 200, batch);
2040
- return;
2041
- }
2042
- if (path.startsWith(`${basePath}/batches/`) && req.method === "POST") {
2043
- const parts = path.substring(`${basePath}/batches/`.length).split("/");
2044
- const id = parts[0];
2045
- if (parts[1] === "cancel") {
2046
- const batch = client.batches.cancel(id);
2047
- sendJSON(res, 200, batch);
2048
- return;
2049
- }
2050
- }
2051
- sendError(res, 404, `Not found: ${path}`);
2052
- } catch (err) {
2053
- const code = err?.code || 500;
2054
- const message = err?.message || "Internal server error";
2055
- sendError(res, code, message);
2056
- }
2057
- });
2058
- return server;
2059
- }
2060
- function startServer(options = {}) {
2061
- const port = options.port ?? 4141;
2062
- const host = options.host ?? "0.0.0.0";
2063
- const server = createAnyModelServer(options);
2064
- server.listen(port, host, () => {
2065
- console.log(`@probeo/anymodel server running at http://${host}:${port}`);
2066
- console.log(`API base: http://${host}:${port}/api/v1`);
2067
- console.log("");
2068
- console.log("Endpoints:");
2069
- console.log(" POST /api/v1/chat/completions");
2070
- console.log(" GET /api/v1/models");
2071
- console.log(" GET /api/v1/generation/:id");
2072
- console.log(" POST /api/v1/batches");
2073
- console.log(" GET /api/v1/batches");
2074
- console.log(" GET /api/v1/batches/:id");
2075
- console.log(" GET /api/v1/batches/:id/results");
2076
- console.log(" POST /api/v1/batches/:id/cancel");
2077
- console.log(" GET /health");
2078
- });
2079
- }
2080
- export {
2081
- AnyModel,
2082
- AnyModelError,
2083
- BatchManager,
2084
- BatchStore,
2085
- GenerationStatsStore,
2086
- createAnyModelServer,
2087
- resolveConfig,
2088
- startServer
2089
- };
1
+ // Client
2
+ export { AnyModel } from './client.js';
3
+ export { AnyModelError } from './types.js';
4
+ // Config
5
+ export { resolveConfig } from './config.js';
6
+ // Generation stats
7
+ export { GenerationStatsStore } from './utils/generation-stats.js';
8
+ // Batch
9
+ export { BatchManager, BatchStore } from './batch/index.js';
10
+ // Server
11
+ export { createAnyModelServer, startServer } from './server.js';
12
+ // Filesystem IO (queued, high-volume)
13
+ export { readFileQueued, writeFileQueued, writeFileFlushedQueued, appendFileQueued, ensureDir, joinPath, getFsQueueStatus, waitForFsQueuesIdle, } from './utils/fs-io.js';
14
+ export { createOpenAIBatchAdapter } from './providers/openai-batch.js';
15
+ export { createAnthropicBatchAdapter } from './providers/anthropic-batch.js';
2090
16
  //# sourceMappingURL=index.js.map