@probeo/anymodel 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -480,6 +480,25 @@ var Router = class {
480
480
  }
481
481
  };
482
482
 
483
+ // src/utils/fetch-with-timeout.ts
484
+ var _defaultTimeout = 12e4;
485
+ var _flexTimeout = 6e5;
486
+ function setDefaultTimeout(ms) {
487
+ _defaultTimeout = ms;
488
+ }
489
+ function getFlexTimeout() {
490
+ return _flexTimeout;
491
+ }
492
+ function fetchWithTimeout(url, init, timeoutMs) {
493
+ const ms = timeoutMs ?? _defaultTimeout;
494
+ const signal = AbortSignal.timeout(ms);
495
+ if (init?.signal) {
496
+ const combined = AbortSignal.any([signal, init.signal]);
497
+ return fetch(url, { ...init, signal: combined });
498
+ }
499
+ return fetch(url, { ...init, signal });
500
+ }
501
+
483
502
  // src/providers/openai.ts
484
503
  var OPENAI_API_BASE = "https://api.openai.com/v1";
485
504
  var SUPPORTED_PARAMS = /* @__PURE__ */ new Set([
@@ -497,19 +516,20 @@ var SUPPORTED_PARAMS = /* @__PURE__ */ new Set([
497
516
  "tools",
498
517
  "tool_choice",
499
518
  "user",
500
- "logit_bias"
519
+ "logit_bias",
520
+ "service_tier"
501
521
  ]);
502
522
  function createOpenAIAdapter(apiKey, baseURL) {
503
523
  const base = baseURL || OPENAI_API_BASE;
504
- async function makeRequest(path2, body, method = "POST") {
505
- const res = await fetch(`${base}${path2}`, {
524
+ async function makeRequest(path2, body, method = "POST", timeoutMs) {
525
+ const res = await fetchWithTimeout(`${base}${path2}`, {
506
526
  method,
507
527
  headers: {
508
528
  "Content-Type": "application/json",
509
529
  "Authorization": `Bearer ${apiKey}`
510
530
  },
511
531
  body: body ? JSON.stringify(body) : void 0
512
- });
532
+ }, timeoutMs);
513
533
  if (!res.ok) {
514
534
  let errorBody;
515
535
  try {
@@ -557,6 +577,7 @@ function createOpenAIAdapter(apiKey, baseURL) {
557
577
  if (request.tools !== void 0) body.tools = request.tools;
558
578
  if (request.tool_choice !== void 0) body.tool_choice = request.tool_choice;
559
579
  if (request.user !== void 0) body.user = request.user;
580
+ if (request.service_tier !== void 0) body.service_tier = request.service_tier;
560
581
  return body;
561
582
  }
562
583
  const adapter = {
@@ -658,13 +679,15 @@ function createOpenAIAdapter(apiKey, baseURL) {
658
679
  },
659
680
  async sendRequest(request) {
660
681
  const body = buildRequestBody(request);
661
- const res = await makeRequest("/chat/completions", body);
682
+ const timeout = request.service_tier === "flex" ? getFlexTimeout() : void 0;
683
+ const res = await makeRequest("/chat/completions", body, "POST", timeout);
662
684
  const json = await res.json();
663
685
  return adapter.translateResponse(json);
664
686
  },
665
687
  async sendStreamingRequest(request) {
666
688
  const body = buildRequestBody({ ...request, stream: true });
667
- const res = await makeRequest("/chat/completions", body);
689
+ const timeout = request.service_tier === "flex" ? getFlexTimeout() : void 0;
690
+ const res = await makeRequest("/chat/completions", body, "POST", timeout);
668
691
  if (!res.body) {
669
692
  throw new AnyModelError(502, "No response body for streaming request", {
670
693
  provider_name: "openai"
@@ -711,7 +734,7 @@ var FALLBACK_MODELS = [
711
734
  ];
712
735
  function createAnthropicAdapter(apiKey) {
713
736
  async function makeRequest(path2, body, stream = false) {
714
- const res = await fetch(`${ANTHROPIC_API_BASE}${path2}`, {
737
+ const res = await fetchWithTimeout(`${ANTHROPIC_API_BASE}${path2}`, {
715
738
  method: "POST",
716
739
  headers: {
717
740
  "Content-Type": "application/json",
@@ -968,7 +991,7 @@ ${body.system}` : jsonInstruction;
968
991
  },
969
992
  async listModels() {
970
993
  try {
971
- const res = await fetch(`${ANTHROPIC_API_BASE}/models`, {
994
+ const res = await fetchWithTimeout(`${ANTHROPIC_API_BASE}/models`, {
972
995
  method: "GET",
973
996
  headers: {
974
997
  "x-api-key": apiKey,
@@ -1253,7 +1276,7 @@ function createGoogleAdapter(apiKey) {
1253
1276
  },
1254
1277
  async listModels() {
1255
1278
  try {
1256
- const res = await fetch(`${GEMINI_API_BASE}/models?key=${apiKey}`);
1279
+ const res = await fetchWithTimeout(`${GEMINI_API_BASE}/models?key=${apiKey}`);
1257
1280
  if (!res.ok) return FALLBACK_MODELS2;
1258
1281
  const data = await res.json();
1259
1282
  const models = data.models || [];
@@ -1288,12 +1311,12 @@ function createGoogleAdapter(apiKey) {
1288
1311
  return SUPPORTED_PARAMS3.has(param);
1289
1312
  },
1290
1313
  supportsBatch() {
1291
- return false;
1314
+ return true;
1292
1315
  },
1293
1316
  async sendRequest(request) {
1294
1317
  const body = translateRequest(request);
1295
1318
  const url = getModelEndpoint(request.model, false);
1296
- const res = await fetch(url, {
1319
+ const res = await fetchWithTimeout(url, {
1297
1320
  method: "POST",
1298
1321
  headers: { "Content-Type": "application/json" },
1299
1322
  body: JSON.stringify(body)
@@ -1316,7 +1339,7 @@ function createGoogleAdapter(apiKey) {
1316
1339
  async sendStreamingRequest(request) {
1317
1340
  const body = translateRequest(request);
1318
1341
  const url = getModelEndpoint(request.model, true);
1319
- const res = await fetch(url, {
1342
+ const res = await fetchWithTimeout(url, {
1320
1343
  method: "POST",
1321
1344
  headers: { "Content-Type": "application/json" },
1322
1345
  body: JSON.stringify(body)
@@ -1342,6 +1365,190 @@ function createGoogleAdapter(apiKey) {
1342
1365
  return adapter;
1343
1366
  }
1344
1367
 
1368
+ // src/providers/perplexity.ts
1369
+ var PERPLEXITY_API_BASE = "https://api.perplexity.ai";
1370
+ var SUPPORTED_PARAMS4 = /* @__PURE__ */ new Set([
1371
+ "temperature",
1372
+ "max_tokens",
1373
+ "top_p",
1374
+ "frequency_penalty",
1375
+ "presence_penalty",
1376
+ "stream",
1377
+ "stop",
1378
+ "response_format",
1379
+ "tools",
1380
+ "tool_choice"
1381
+ ]);
1382
+ var MODELS = [
1383
+ { id: "sonar", name: "Sonar", context: 128e3, maxOutput: 4096, modality: "text->text", inputModalities: ["text"] },
1384
+ { id: "sonar-pro", name: "Sonar Pro", context: 2e5, maxOutput: 8192, modality: "text->text", inputModalities: ["text"] },
1385
+ { id: "sonar-reasoning", name: "Sonar Reasoning", context: 128e3, maxOutput: 8192, modality: "text->text", inputModalities: ["text"] },
1386
+ { id: "sonar-reasoning-pro", name: "Sonar Reasoning Pro", context: 128e3, maxOutput: 16384, modality: "text->text", inputModalities: ["text"] },
1387
+ { id: "sonar-deep-research", name: "Sonar Deep Research", context: 128e3, maxOutput: 16384, modality: "text->text", inputModalities: ["text"] },
1388
+ { id: "r1-1776", name: "R1 1776", context: 128e3, maxOutput: 16384, modality: "text->text", inputModalities: ["text"] }
1389
+ ];
1390
+ function createPerplexityAdapter(apiKey) {
1391
+ async function makeRequest(path2, body, method = "POST") {
1392
+ const res = await fetchWithTimeout(`${PERPLEXITY_API_BASE}${path2}`, {
1393
+ method,
1394
+ headers: {
1395
+ "Content-Type": "application/json",
1396
+ "Authorization": `Bearer ${apiKey}`
1397
+ },
1398
+ body: body ? JSON.stringify(body) : void 0
1399
+ });
1400
+ if (!res.ok) {
1401
+ let errorBody;
1402
+ try {
1403
+ errorBody = await res.json();
1404
+ } catch {
1405
+ errorBody = { message: res.statusText };
1406
+ }
1407
+ const msg = errorBody?.error?.message || errorBody?.message || res.statusText;
1408
+ throw new AnyModelError(mapErrorCode(res.status), msg, {
1409
+ provider_name: "perplexity",
1410
+ raw: errorBody
1411
+ });
1412
+ }
1413
+ return res;
1414
+ }
1415
+ function mapErrorCode(status) {
1416
+ if (status === 401 || status === 403) return 401;
1417
+ if (status === 429) return 429;
1418
+ if (status === 400 || status === 422) return 400;
1419
+ if (status >= 500) return 502;
1420
+ return status;
1421
+ }
1422
+ function rePrefixId(id) {
1423
+ if (id && id.startsWith("chatcmpl-")) {
1424
+ return `gen-${id.substring(9)}`;
1425
+ }
1426
+ return id.startsWith("gen-") ? id : `gen-${id}`;
1427
+ }
1428
+ function buildRequestBody(request) {
1429
+ const body = {
1430
+ model: request.model,
1431
+ messages: request.messages
1432
+ };
1433
+ if (request.temperature !== void 0) body.temperature = request.temperature;
1434
+ if (request.max_tokens !== void 0) body.max_tokens = request.max_tokens;
1435
+ if (request.top_p !== void 0) body.top_p = request.top_p;
1436
+ if (request.frequency_penalty !== void 0) body.frequency_penalty = request.frequency_penalty;
1437
+ if (request.presence_penalty !== void 0) body.presence_penalty = request.presence_penalty;
1438
+ if (request.stop !== void 0) body.stop = request.stop;
1439
+ if (request.stream !== void 0) body.stream = request.stream;
1440
+ if (request.response_format !== void 0) body.response_format = request.response_format;
1441
+ if (request.tools !== void 0) body.tools = request.tools;
1442
+ if (request.tool_choice !== void 0) body.tool_choice = request.tool_choice;
1443
+ return body;
1444
+ }
1445
+ const adapter = {
1446
+ name: "perplexity",
1447
+ translateRequest(request) {
1448
+ return buildRequestBody(request);
1449
+ },
1450
+ translateResponse(response) {
1451
+ const r = response;
1452
+ const result = {
1453
+ id: rePrefixId(r.id),
1454
+ object: "chat.completion",
1455
+ created: r.created,
1456
+ model: `perplexity/${r.model}`,
1457
+ choices: r.choices,
1458
+ usage: r.usage
1459
+ };
1460
+ if (r.citations && result.choices?.[0]?.message) {
1461
+ result.citations = r.citations;
1462
+ }
1463
+ return result;
1464
+ },
1465
+ async *translateStream(stream) {
1466
+ const reader = stream.getReader();
1467
+ const decoder = new TextDecoder();
1468
+ let buffer = "";
1469
+ try {
1470
+ while (true) {
1471
+ const { done, value } = await reader.read();
1472
+ if (done) break;
1473
+ buffer += decoder.decode(value, { stream: true });
1474
+ const lines = buffer.split("\n");
1475
+ buffer = lines.pop() || "";
1476
+ for (const line of lines) {
1477
+ const trimmed = line.trim();
1478
+ if (!trimmed || trimmed.startsWith(":")) continue;
1479
+ if (trimmed === "data: [DONE]") return;
1480
+ if (trimmed.startsWith("data: ")) {
1481
+ const json = JSON.parse(trimmed.substring(6));
1482
+ json.id = rePrefixId(json.id);
1483
+ json.model = `perplexity/${json.model}`;
1484
+ yield json;
1485
+ }
1486
+ }
1487
+ }
1488
+ } finally {
1489
+ reader.releaseLock();
1490
+ }
1491
+ },
1492
+ translateError(error) {
1493
+ if (error instanceof AnyModelError) {
1494
+ return { code: error.code, message: error.message, metadata: error.metadata };
1495
+ }
1496
+ const err = error;
1497
+ const status = err?.status || err?.code || 500;
1498
+ return {
1499
+ code: mapErrorCode(status),
1500
+ message: err?.message || "Unknown Perplexity error",
1501
+ metadata: { provider_name: "perplexity", raw: error }
1502
+ };
1503
+ },
1504
+ async listModels() {
1505
+ return MODELS.map((m) => ({
1506
+ id: `perplexity/${m.id}`,
1507
+ name: m.name,
1508
+ created: 0,
1509
+ description: "",
1510
+ context_length: m.context,
1511
+ pricing: { prompt: "0", completion: "0" },
1512
+ architecture: {
1513
+ modality: m.modality,
1514
+ input_modalities: m.inputModalities,
1515
+ output_modalities: ["text"],
1516
+ tokenizer: "unknown"
1517
+ },
1518
+ top_provider: {
1519
+ context_length: m.context,
1520
+ max_completion_tokens: m.maxOutput,
1521
+ is_moderated: false
1522
+ },
1523
+ supported_parameters: Array.from(SUPPORTED_PARAMS4)
1524
+ }));
1525
+ },
1526
+ supportsParameter(param) {
1527
+ return SUPPORTED_PARAMS4.has(param);
1528
+ },
1529
+ supportsBatch() {
1530
+ return false;
1531
+ },
1532
+ async sendRequest(request) {
1533
+ const body = buildRequestBody(request);
1534
+ const res = await makeRequest("/chat/completions", body);
1535
+ const json = await res.json();
1536
+ return adapter.translateResponse(json);
1537
+ },
1538
+ async sendStreamingRequest(request) {
1539
+ const body = buildRequestBody({ ...request, stream: true });
1540
+ const res = await makeRequest("/chat/completions", body);
1541
+ if (!res.body) {
1542
+ throw new AnyModelError(502, "No response body for streaming request", {
1543
+ provider_name: "perplexity"
1544
+ });
1545
+ }
1546
+ return adapter.translateStream(res.body);
1547
+ }
1548
+ };
1549
+ return adapter;
1550
+ }
1551
+
1345
1552
  // src/providers/custom.ts
1346
1553
  function createCustomAdapter(name, config) {
1347
1554
  const openaiAdapter = createOpenAIAdapter(config.apiKey || "", config.baseURL);
@@ -2063,6 +2270,54 @@ var BatchManager = class {
2063
2270
  }
2064
2271
  };
2065
2272
 
2273
+ // src/utils/token-estimate.ts
2274
+ var CHARS_PER_TOKEN2 = 4;
2275
+ function estimateTokenCount(text) {
2276
+ return Math.ceil(text.length / CHARS_PER_TOKEN2);
2277
+ }
2278
+ var MODEL_LIMITS = [
2279
+ // OpenAI
2280
+ { pattern: "gpt-4o-mini", limit: { contextLength: 128e3, maxCompletionTokens: 16384 } },
2281
+ { pattern: "gpt-4o", limit: { contextLength: 128e3, maxCompletionTokens: 16384 } },
2282
+ { pattern: "gpt-4-turbo", limit: { contextLength: 128e3, maxCompletionTokens: 4096 } },
2283
+ { pattern: "gpt-3.5-turbo", limit: { contextLength: 16385, maxCompletionTokens: 4096 } },
2284
+ { pattern: "o1", limit: { contextLength: 2e5, maxCompletionTokens: 1e5 } },
2285
+ { pattern: "o3", limit: { contextLength: 2e5, maxCompletionTokens: 1e5 } },
2286
+ { pattern: "o4-mini", limit: { contextLength: 2e5, maxCompletionTokens: 1e5 } },
2287
+ // Anthropic
2288
+ { pattern: "claude-opus-4", limit: { contextLength: 2e5, maxCompletionTokens: 32768 } },
2289
+ { pattern: "claude-sonnet-4", limit: { contextLength: 2e5, maxCompletionTokens: 16384 } },
2290
+ { pattern: "claude-haiku-4", limit: { contextLength: 2e5, maxCompletionTokens: 8192 } },
2291
+ { pattern: "claude-3.5-sonnet", limit: { contextLength: 2e5, maxCompletionTokens: 8192 } },
2292
+ { pattern: "claude-3-opus", limit: { contextLength: 2e5, maxCompletionTokens: 4096 } },
2293
+ // Google
2294
+ { pattern: "gemini-2.5-pro", limit: { contextLength: 1048576, maxCompletionTokens: 65536 } },
2295
+ { pattern: "gemini-2.5-flash", limit: { contextLength: 1048576, maxCompletionTokens: 65536 } },
2296
+ { pattern: "gemini-2.0-flash", limit: { contextLength: 1048576, maxCompletionTokens: 65536 } },
2297
+ { pattern: "gemini-1.5-pro", limit: { contextLength: 2097152, maxCompletionTokens: 8192 } },
2298
+ { pattern: "gemini-1.5-flash", limit: { contextLength: 1048576, maxCompletionTokens: 8192 } }
2299
+ ];
2300
+ var DEFAULT_LIMIT = { contextLength: 128e3, maxCompletionTokens: 4096 };
2301
+ function getModelLimits(model) {
2302
+ const bare = model.includes("/") ? model.slice(model.indexOf("/") + 1) : model;
2303
+ for (const entry of MODEL_LIMITS) {
2304
+ if (bare.startsWith(entry.pattern) || bare.includes(entry.pattern)) {
2305
+ return entry.limit;
2306
+ }
2307
+ }
2308
+ return DEFAULT_LIMIT;
2309
+ }
2310
+ function resolveMaxTokens(model, messages, userMaxTokens) {
2311
+ if (userMaxTokens !== void 0) return userMaxTokens;
2312
+ const inputChars = JSON.stringify(messages).length;
2313
+ const estimatedInput = Math.ceil(inputChars / CHARS_PER_TOKEN2);
2314
+ const estimatedWithMargin = Math.ceil(estimatedInput * 1.05);
2315
+ const limits = getModelLimits(model);
2316
+ const available = limits.contextLength - estimatedWithMargin;
2317
+ const result = Math.min(limits.maxCompletionTokens, available);
2318
+ return Math.max(1, result);
2319
+ }
2320
+
2066
2321
  // src/providers/openai-batch.ts
2067
2322
  var OPENAI_API_BASE2 = "https://api.openai.com/v1";
2068
2323
  function createOpenAIBatchAdapter(apiKey) {
@@ -2077,7 +2332,7 @@ function createOpenAIBatchAdapter(apiKey) {
2077
2332
  headers["Content-Type"] = "application/json";
2078
2333
  fetchBody = JSON.stringify(options.body);
2079
2334
  }
2080
- const res = await fetch(`${OPENAI_API_BASE2}${path2}`, {
2335
+ const res = await fetchWithTimeout(`${OPENAI_API_BASE2}${path2}`, {
2081
2336
  method: options.method || "GET",
2082
2337
  headers,
2083
2338
  body: fetchBody
@@ -2103,7 +2358,7 @@ function createOpenAIBatchAdapter(apiKey) {
2103
2358
  model,
2104
2359
  messages: req.messages
2105
2360
  };
2106
- if (req.max_tokens !== void 0) body.max_tokens = req.max_tokens;
2361
+ body.max_tokens = req.max_tokens !== void 0 ? req.max_tokens : resolveMaxTokens(model, req.messages);
2107
2362
  if (req.temperature !== void 0) body.temperature = req.temperature;
2108
2363
  if (req.top_p !== void 0) body.top_p = req.top_p;
2109
2364
  if (req.stop !== void 0) body.stop = req.stop;
@@ -2262,7 +2517,7 @@ function createAnthropicBatchAdapter(apiKey) {
2262
2517
  "anthropic-version": ANTHROPIC_VERSION2,
2263
2518
  "Content-Type": "application/json"
2264
2519
  };
2265
- const res = await fetch(`${ANTHROPIC_API_BASE2}${path2}`, {
2520
+ const res = await fetchWithTimeout(`${ANTHROPIC_API_BASE2}${path2}`, {
2266
2521
  method: options.method || "GET",
2267
2522
  headers,
2268
2523
  body: options.body ? JSON.stringify(options.body) : void 0
@@ -2285,7 +2540,7 @@ function createAnthropicBatchAdapter(apiKey) {
2285
2540
  function translateToAnthropicParams(model, req) {
2286
2541
  const params = {
2287
2542
  model,
2288
- max_tokens: req.max_tokens || DEFAULT_MAX_TOKENS2
2543
+ max_tokens: resolveMaxTokens(model, req.messages, req.max_tokens || DEFAULT_MAX_TOKENS2)
2289
2544
  };
2290
2545
  const systemMessages = req.messages.filter((m) => m.role === "system");
2291
2546
  const nonSystemMessages = req.messages.filter((m) => m.role !== "system");
@@ -2459,6 +2714,284 @@ ${params.system}` : jsonInstruction;
2459
2714
  };
2460
2715
  }
2461
2716
 
2717
+ // src/providers/google-batch.ts
2718
+ var GEMINI_API_BASE2 = "https://generativelanguage.googleapis.com/v1beta";
2719
+ function createGoogleBatchAdapter(apiKey) {
2720
+ async function apiRequest(path2, options = {}) {
2721
+ const headers = {
2722
+ "Content-Type": "application/json",
2723
+ "x-goog-api-key": apiKey
2724
+ };
2725
+ const res = await fetchWithTimeout(`${GEMINI_API_BASE2}${path2}`, {
2726
+ method: options.method || "GET",
2727
+ headers,
2728
+ body: options.body ? JSON.stringify(options.body) : void 0
2729
+ });
2730
+ if (!res.ok) {
2731
+ let errorBody;
2732
+ try {
2733
+ errorBody = await res.json();
2734
+ } catch {
2735
+ errorBody = { message: res.statusText };
2736
+ }
2737
+ const msg = errorBody?.error?.message || errorBody?.message || res.statusText;
2738
+ throw new AnyModelError(res.status >= 500 ? 502 : res.status, msg, {
2739
+ provider_name: "google",
2740
+ raw: errorBody
2741
+ });
2742
+ }
2743
+ return res;
2744
+ }
2745
+ function translateRequestToGemini(model, req) {
2746
+ const body = {};
2747
+ const systemMessages = req.messages.filter((m) => m.role === "system");
2748
+ const nonSystemMessages = req.messages.filter((m) => m.role !== "system");
2749
+ if (systemMessages.length > 0) {
2750
+ body.systemInstruction = {
2751
+ parts: [{ text: systemMessages.map((m) => typeof m.content === "string" ? m.content : "").join("\n") }]
2752
+ };
2753
+ }
2754
+ body.contents = nonSystemMessages.map((m) => ({
2755
+ role: m.role === "assistant" ? "model" : "user",
2756
+ parts: typeof m.content === "string" ? [{ text: m.content }] : Array.isArray(m.content) ? m.content.map((p) => p.type === "text" ? { text: p.text } : { text: "" }) : [{ text: "" }]
2757
+ }));
2758
+ const generationConfig = {};
2759
+ if (req.temperature !== void 0) generationConfig.temperature = req.temperature;
2760
+ generationConfig.maxOutputTokens = req.max_tokens !== void 0 ? req.max_tokens : resolveMaxTokens(model, req.messages);
2761
+ if (req.top_p !== void 0) generationConfig.topP = req.top_p;
2762
+ if (req.top_k !== void 0) generationConfig.topK = req.top_k;
2763
+ if (req.stop !== void 0) {
2764
+ generationConfig.stopSequences = Array.isArray(req.stop) ? req.stop : [req.stop];
2765
+ }
2766
+ if (req.response_format) {
2767
+ if (req.response_format.type === "json_object") {
2768
+ generationConfig.responseMimeType = "application/json";
2769
+ } else if (req.response_format.type === "json_schema") {
2770
+ generationConfig.responseMimeType = "application/json";
2771
+ generationConfig.responseSchema = req.response_format.json_schema?.schema;
2772
+ }
2773
+ }
2774
+ if (Object.keys(generationConfig).length > 0) {
2775
+ body.generationConfig = generationConfig;
2776
+ }
2777
+ if (req.tools && req.tools.length > 0) {
2778
+ body.tools = [{
2779
+ functionDeclarations: req.tools.map((t) => ({
2780
+ name: t.function.name,
2781
+ description: t.function.description || "",
2782
+ parameters: t.function.parameters || {}
2783
+ }))
2784
+ }];
2785
+ if (req.tool_choice) {
2786
+ if (req.tool_choice === "auto") {
2787
+ body.toolConfig = { functionCallingConfig: { mode: "AUTO" } };
2788
+ } else if (req.tool_choice === "required") {
2789
+ body.toolConfig = { functionCallingConfig: { mode: "ANY" } };
2790
+ } else if (req.tool_choice === "none") {
2791
+ body.toolConfig = { functionCallingConfig: { mode: "NONE" } };
2792
+ } else if (typeof req.tool_choice === "object") {
2793
+ body.toolConfig = {
2794
+ functionCallingConfig: {
2795
+ mode: "ANY",
2796
+ allowedFunctionNames: [req.tool_choice.function.name]
2797
+ }
2798
+ };
2799
+ }
2800
+ }
2801
+ }
2802
+ return body;
2803
+ }
2804
+ function mapFinishReason(reason) {
2805
+ switch (reason) {
2806
+ case "STOP":
2807
+ return "stop";
2808
+ case "MAX_TOKENS":
2809
+ return "length";
2810
+ case "SAFETY":
2811
+ return "content_filter";
2812
+ case "RECITATION":
2813
+ return "content_filter";
2814
+ default:
2815
+ return "stop";
2816
+ }
2817
+ }
2818
+ function translateGeminiResponse(response, model) {
2819
+ const candidate = response.candidates?.[0];
2820
+ let content = "";
2821
+ const toolCalls = [];
2822
+ for (const part of candidate?.content?.parts || []) {
2823
+ if (part.text) {
2824
+ content += part.text;
2825
+ } else if (part.functionCall) {
2826
+ toolCalls.push({
2827
+ id: generateId("call"),
2828
+ type: "function",
2829
+ function: {
2830
+ name: part.functionCall.name,
2831
+ arguments: JSON.stringify(part.functionCall.args || {})
2832
+ }
2833
+ });
2834
+ }
2835
+ }
2836
+ const message = { role: "assistant", content };
2837
+ if (toolCalls.length > 0) {
2838
+ message.tool_calls = toolCalls;
2839
+ }
2840
+ const finishReason = toolCalls.length > 0 ? "tool_calls" : mapFinishReason(candidate?.finishReason || "STOP");
2841
+ return {
2842
+ id: generateId(),
2843
+ object: "chat.completion",
2844
+ created: Math.floor(Date.now() / 1e3),
2845
+ model: `google/${model}`,
2846
+ choices: [{ index: 0, message, finish_reason: finishReason }],
2847
+ usage: {
2848
+ prompt_tokens: response.usageMetadata?.promptTokenCount || 0,
2849
+ completion_tokens: response.usageMetadata?.candidatesTokenCount || 0,
2850
+ total_tokens: response.usageMetadata?.totalTokenCount || 0
2851
+ }
2852
+ };
2853
+ }
2854
+ function mapBatchState(state) {
2855
+ switch (state) {
2856
+ case "JOB_STATE_PENDING":
2857
+ return "pending";
2858
+ case "JOB_STATE_RUNNING":
2859
+ return "processing";
2860
+ case "JOB_STATE_SUCCEEDED":
2861
+ return "completed";
2862
+ case "JOB_STATE_FAILED":
2863
+ return "failed";
2864
+ case "JOB_STATE_CANCELLED":
2865
+ return "cancelled";
2866
+ case "JOB_STATE_EXPIRED":
2867
+ return "failed";
2868
+ default:
2869
+ return "pending";
2870
+ }
2871
+ }
2872
+ return {
2873
+ async createBatch(model, requests, _options) {
2874
+ const batchRequests = requests.map((req) => ({
2875
+ request: translateRequestToGemini(model, req),
2876
+ metadata: { key: req.custom_id }
2877
+ }));
2878
+ const res = await apiRequest(`/models/${model}:batchGenerateContent`, {
2879
+ method: "POST",
2880
+ body: {
2881
+ batch: {
2882
+ display_name: `anymodel-batch-${Date.now()}`,
2883
+ input_config: {
2884
+ requests: {
2885
+ requests: batchRequests
2886
+ }
2887
+ }
2888
+ }
2889
+ }
2890
+ });
2891
+ const data = await res.json();
2892
+ const batchName = data.name || data.batch?.name;
2893
+ if (!batchName) {
2894
+ throw new AnyModelError(502, "No batch name in Google response", {
2895
+ provider_name: "google",
2896
+ raw: data
2897
+ });
2898
+ }
2899
+ return {
2900
+ providerBatchId: batchName,
2901
+ metadata: {
2902
+ model,
2903
+ total_requests: requests.length
2904
+ }
2905
+ };
2906
+ },
2907
+ async pollBatch(providerBatchId) {
2908
+ const res = await apiRequest(`/${providerBatchId}`);
2909
+ const data = await res.json();
2910
+ const state = data.state || "JOB_STATE_PENDING";
2911
+ const status = mapBatchState(state);
2912
+ const totalCount = data.totalCount || data.metadata?.total_requests || 0;
2913
+ const successCount = data.succeededCount || 0;
2914
+ const failedCount = data.failedCount || 0;
2915
+ return {
2916
+ status,
2917
+ total: totalCount || successCount + failedCount,
2918
+ completed: successCount,
2919
+ failed: failedCount
2920
+ };
2921
+ },
2922
+ async getBatchResults(providerBatchId) {
2923
+ const batchRes = await apiRequest(`/${providerBatchId}`);
2924
+ const batchData = await batchRes.json();
2925
+ const results = [];
2926
+ const model = batchData.metadata?.model || "unknown";
2927
+ if (batchData.response?.inlinedResponses) {
2928
+ for (const item of batchData.response.inlinedResponses) {
2929
+ const customId = item.metadata?.key || `request-${results.length}`;
2930
+ if (item.response) {
2931
+ results.push({
2932
+ custom_id: customId,
2933
+ status: "success",
2934
+ response: translateGeminiResponse(item.response, model),
2935
+ error: null
2936
+ });
2937
+ } else if (item.error) {
2938
+ results.push({
2939
+ custom_id: customId,
2940
+ status: "error",
2941
+ response: null,
2942
+ error: {
2943
+ code: item.error.code || 500,
2944
+ message: item.error.message || "Batch item failed"
2945
+ }
2946
+ });
2947
+ }
2948
+ }
2949
+ return results;
2950
+ }
2951
+ const responsesFile = batchData.response?.responsesFileName || batchData.outputConfig?.file_name;
2952
+ if (responsesFile) {
2953
+ const downloadUrl = `${GEMINI_API_BASE2}/${responsesFile}:download?alt=media`;
2954
+ const fileRes = await fetchWithTimeout(downloadUrl, {
2955
+ headers: { "x-goog-api-key": apiKey }
2956
+ });
2957
+ if (!fileRes.ok) {
2958
+ throw new AnyModelError(502, "Failed to download batch results file", {
2959
+ provider_name: "google"
2960
+ });
2961
+ }
2962
+ const text = await fileRes.text();
2963
+ for (const line of text.trim().split("\n")) {
2964
+ if (!line) continue;
2965
+ const item = JSON.parse(line);
2966
+ const customId = item.key || item.metadata?.key || `request-${results.length}`;
2967
+ if (item.response) {
2968
+ results.push({
2969
+ custom_id: customId,
2970
+ status: "success",
2971
+ response: translateGeminiResponse(item.response, model),
2972
+ error: null
2973
+ });
2974
+ } else if (item.error) {
2975
+ results.push({
2976
+ custom_id: customId,
2977
+ status: "error",
2978
+ response: null,
2979
+ error: {
2980
+ code: item.error.code || 500,
2981
+ message: item.error.message || "Batch item failed"
2982
+ }
2983
+ });
2984
+ }
2985
+ }
2986
+ }
2987
+ return results;
2988
+ },
2989
+ async cancelBatch(providerBatchId) {
2990
+ await apiRequest(`/${providerBatchId}:cancel`, { method: "POST" });
2991
+ }
2992
+ };
2993
+ }
2994
+
2462
2995
  // src/client.ts
2463
2996
  var AnyModel = class {
2464
2997
  registry;
@@ -2474,6 +3007,7 @@ var AnyModel = class {
2474
3007
  constructor(config = {}) {
2475
3008
  this.config = resolveConfig(config);
2476
3009
  this.registry = new ProviderRegistry();
3010
+ setDefaultTimeout((this.config.defaults?.timeout ?? 120) * 1e3);
2477
3011
  if (this.config.io) {
2478
3012
  configureFsIO(this.config.io);
2479
3013
  }
@@ -2554,14 +3088,17 @@ var AnyModel = class {
2554
3088
  if (googleKey) {
2555
3089
  this.registry.register("google", createGoogleAdapter(googleKey));
2556
3090
  }
3091
+ const perplexityKey = config.perplexity?.apiKey || process.env.PERPLEXITY_API_KEY;
3092
+ if (perplexityKey) {
3093
+ this.registry.register("perplexity", createPerplexityAdapter(perplexityKey));
3094
+ }
2557
3095
  const builtinProviders = [
2558
3096
  { name: "mistral", baseURL: "https://api.mistral.ai/v1", configKey: "mistral", envVar: "MISTRAL_API_KEY" },
2559
3097
  { name: "groq", baseURL: "https://api.groq.com/openai/v1", configKey: "groq", envVar: "GROQ_API_KEY" },
2560
3098
  { name: "deepseek", baseURL: "https://api.deepseek.com", configKey: "deepseek", envVar: "DEEPSEEK_API_KEY" },
2561
3099
  { name: "xai", baseURL: "https://api.x.ai/v1", configKey: "xai", envVar: "XAI_API_KEY" },
2562
3100
  { name: "together", baseURL: "https://api.together.xyz/v1", configKey: "together", envVar: "TOGETHER_API_KEY" },
2563
- { name: "fireworks", baseURL: "https://api.fireworks.ai/inference/v1", configKey: "fireworks", envVar: "FIREWORKS_API_KEY" },
2564
- { name: "perplexity", baseURL: "https://api.perplexity.ai", configKey: "perplexity", envVar: "PERPLEXITY_API_KEY" }
3101
+ { name: "fireworks", baseURL: "https://api.fireworks.ai/inference/v1", configKey: "fireworks", envVar: "FIREWORKS_API_KEY" }
2565
3102
  ];
2566
3103
  for (const { name, baseURL, configKey, envVar } of builtinProviders) {
2567
3104
  const providerConfig = config[configKey];
@@ -2591,6 +3128,10 @@ var AnyModel = class {
2591
3128
  if (anthropicKey) {
2592
3129
  this.batchManager.registerBatchAdapter("anthropic", createAnthropicBatchAdapter(anthropicKey));
2593
3130
  }
3131
+ const googleKey = config.google?.apiKey || process.env.GOOGLE_API_KEY;
3132
+ if (googleKey) {
3133
+ this.batchManager.registerBatchAdapter("google", createGoogleBatchAdapter(googleKey));
3134
+ }
2594
3135
  }
2595
3136
  applyDefaults(request) {
2596
3137
  const defaults = this.config.defaults;
@@ -2769,12 +3310,15 @@ export {
2769
3310
  configureFsIO,
2770
3311
  createAnthropicBatchAdapter,
2771
3312
  createAnyModelServer,
3313
+ createGoogleBatchAdapter,
2772
3314
  createOpenAIBatchAdapter,
2773
3315
  ensureDir,
3316
+ estimateTokenCount,
2774
3317
  getFsQueueStatus,
2775
3318
  joinPath,
2776
3319
  readFileQueued,
2777
3320
  resolveConfig,
3321
+ resolveMaxTokens,
2778
3322
  startServer,
2779
3323
  waitForFsQueuesIdle,
2780
3324
  writeFileFlushedQueued,