@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/cli.js CHANGED
@@ -485,6 +485,25 @@ var Router = class {
485
485
  }
486
486
  };
487
487
 
488
+ // src/utils/fetch-with-timeout.ts
489
+ var _defaultTimeout = 12e4;
490
+ var _flexTimeout = 6e5;
491
+ function setDefaultTimeout(ms) {
492
+ _defaultTimeout = ms;
493
+ }
494
+ function getFlexTimeout() {
495
+ return _flexTimeout;
496
+ }
497
+ function fetchWithTimeout(url, init, timeoutMs) {
498
+ const ms = timeoutMs ?? _defaultTimeout;
499
+ const signal = AbortSignal.timeout(ms);
500
+ if (init?.signal) {
501
+ const combined = AbortSignal.any([signal, init.signal]);
502
+ return fetch(url, { ...init, signal: combined });
503
+ }
504
+ return fetch(url, { ...init, signal });
505
+ }
506
+
488
507
  // src/providers/openai.ts
489
508
  var OPENAI_API_BASE = "https://api.openai.com/v1";
490
509
  var SUPPORTED_PARAMS = /* @__PURE__ */ new Set([
@@ -502,19 +521,20 @@ var SUPPORTED_PARAMS = /* @__PURE__ */ new Set([
502
521
  "tools",
503
522
  "tool_choice",
504
523
  "user",
505
- "logit_bias"
524
+ "logit_bias",
525
+ "service_tier"
506
526
  ]);
507
527
  function createOpenAIAdapter(apiKey, baseURL) {
508
528
  const base = baseURL || OPENAI_API_BASE;
509
- async function makeRequest(path2, body, method = "POST") {
510
- const res = await fetch(`${base}${path2}`, {
529
+ async function makeRequest(path2, body, method = "POST", timeoutMs) {
530
+ const res = await fetchWithTimeout(`${base}${path2}`, {
511
531
  method,
512
532
  headers: {
513
533
  "Content-Type": "application/json",
514
534
  "Authorization": `Bearer ${apiKey}`
515
535
  },
516
536
  body: body ? JSON.stringify(body) : void 0
517
- });
537
+ }, timeoutMs);
518
538
  if (!res.ok) {
519
539
  let errorBody;
520
540
  try {
@@ -562,6 +582,7 @@ function createOpenAIAdapter(apiKey, baseURL) {
562
582
  if (request.tools !== void 0) body.tools = request.tools;
563
583
  if (request.tool_choice !== void 0) body.tool_choice = request.tool_choice;
564
584
  if (request.user !== void 0) body.user = request.user;
585
+ if (request.service_tier !== void 0) body.service_tier = request.service_tier;
565
586
  return body;
566
587
  }
567
588
  const adapter = {
@@ -663,13 +684,15 @@ function createOpenAIAdapter(apiKey, baseURL) {
663
684
  },
664
685
  async sendRequest(request) {
665
686
  const body = buildRequestBody(request);
666
- const res = await makeRequest("/chat/completions", body);
687
+ const timeout = request.service_tier === "flex" ? getFlexTimeout() : void 0;
688
+ const res = await makeRequest("/chat/completions", body, "POST", timeout);
667
689
  const json = await res.json();
668
690
  return adapter.translateResponse(json);
669
691
  },
670
692
  async sendStreamingRequest(request) {
671
693
  const body = buildRequestBody({ ...request, stream: true });
672
- const res = await makeRequest("/chat/completions", body);
694
+ const timeout = request.service_tier === "flex" ? getFlexTimeout() : void 0;
695
+ const res = await makeRequest("/chat/completions", body, "POST", timeout);
673
696
  if (!res.body) {
674
697
  throw new AnyModelError(502, "No response body for streaming request", {
675
698
  provider_name: "openai"
@@ -716,7 +739,7 @@ var FALLBACK_MODELS = [
716
739
  ];
717
740
  function createAnthropicAdapter(apiKey) {
718
741
  async function makeRequest(path2, body, stream = false) {
719
- const res = await fetch(`${ANTHROPIC_API_BASE}${path2}`, {
742
+ const res = await fetchWithTimeout(`${ANTHROPIC_API_BASE}${path2}`, {
720
743
  method: "POST",
721
744
  headers: {
722
745
  "Content-Type": "application/json",
@@ -973,7 +996,7 @@ ${body.system}` : jsonInstruction;
973
996
  },
974
997
  async listModels() {
975
998
  try {
976
- const res = await fetch(`${ANTHROPIC_API_BASE}/models`, {
999
+ const res = await fetchWithTimeout(`${ANTHROPIC_API_BASE}/models`, {
977
1000
  method: "GET",
978
1001
  headers: {
979
1002
  "x-api-key": apiKey,
@@ -1258,7 +1281,7 @@ function createGoogleAdapter(apiKey) {
1258
1281
  },
1259
1282
  async listModels() {
1260
1283
  try {
1261
- const res = await fetch(`${GEMINI_API_BASE}/models?key=${apiKey}`);
1284
+ const res = await fetchWithTimeout(`${GEMINI_API_BASE}/models?key=${apiKey}`);
1262
1285
  if (!res.ok) return FALLBACK_MODELS2;
1263
1286
  const data = await res.json();
1264
1287
  const models = data.models || [];
@@ -1293,12 +1316,12 @@ function createGoogleAdapter(apiKey) {
1293
1316
  return SUPPORTED_PARAMS3.has(param);
1294
1317
  },
1295
1318
  supportsBatch() {
1296
- return false;
1319
+ return true;
1297
1320
  },
1298
1321
  async sendRequest(request) {
1299
1322
  const body = translateRequest(request);
1300
1323
  const url = getModelEndpoint(request.model, false);
1301
- const res = await fetch(url, {
1324
+ const res = await fetchWithTimeout(url, {
1302
1325
  method: "POST",
1303
1326
  headers: { "Content-Type": "application/json" },
1304
1327
  body: JSON.stringify(body)
@@ -1321,7 +1344,7 @@ function createGoogleAdapter(apiKey) {
1321
1344
  async sendStreamingRequest(request) {
1322
1345
  const body = translateRequest(request);
1323
1346
  const url = getModelEndpoint(request.model, true);
1324
- const res = await fetch(url, {
1347
+ const res = await fetchWithTimeout(url, {
1325
1348
  method: "POST",
1326
1349
  headers: { "Content-Type": "application/json" },
1327
1350
  body: JSON.stringify(body)
@@ -1347,6 +1370,190 @@ function createGoogleAdapter(apiKey) {
1347
1370
  return adapter;
1348
1371
  }
1349
1372
 
1373
+ // src/providers/perplexity.ts
1374
+ var PERPLEXITY_API_BASE = "https://api.perplexity.ai";
1375
+ var SUPPORTED_PARAMS4 = /* @__PURE__ */ new Set([
1376
+ "temperature",
1377
+ "max_tokens",
1378
+ "top_p",
1379
+ "frequency_penalty",
1380
+ "presence_penalty",
1381
+ "stream",
1382
+ "stop",
1383
+ "response_format",
1384
+ "tools",
1385
+ "tool_choice"
1386
+ ]);
1387
+ var MODELS = [
1388
+ { id: "sonar", name: "Sonar", context: 128e3, maxOutput: 4096, modality: "text->text", inputModalities: ["text"] },
1389
+ { id: "sonar-pro", name: "Sonar Pro", context: 2e5, maxOutput: 8192, modality: "text->text", inputModalities: ["text"] },
1390
+ { id: "sonar-reasoning", name: "Sonar Reasoning", context: 128e3, maxOutput: 8192, modality: "text->text", inputModalities: ["text"] },
1391
+ { id: "sonar-reasoning-pro", name: "Sonar Reasoning Pro", context: 128e3, maxOutput: 16384, modality: "text->text", inputModalities: ["text"] },
1392
+ { id: "sonar-deep-research", name: "Sonar Deep Research", context: 128e3, maxOutput: 16384, modality: "text->text", inputModalities: ["text"] },
1393
+ { id: "r1-1776", name: "R1 1776", context: 128e3, maxOutput: 16384, modality: "text->text", inputModalities: ["text"] }
1394
+ ];
1395
+ function createPerplexityAdapter(apiKey) {
1396
+ async function makeRequest(path2, body, method = "POST") {
1397
+ const res = await fetchWithTimeout(`${PERPLEXITY_API_BASE}${path2}`, {
1398
+ method,
1399
+ headers: {
1400
+ "Content-Type": "application/json",
1401
+ "Authorization": `Bearer ${apiKey}`
1402
+ },
1403
+ body: body ? JSON.stringify(body) : void 0
1404
+ });
1405
+ if (!res.ok) {
1406
+ let errorBody;
1407
+ try {
1408
+ errorBody = await res.json();
1409
+ } catch {
1410
+ errorBody = { message: res.statusText };
1411
+ }
1412
+ const msg = errorBody?.error?.message || errorBody?.message || res.statusText;
1413
+ throw new AnyModelError(mapErrorCode(res.status), msg, {
1414
+ provider_name: "perplexity",
1415
+ raw: errorBody
1416
+ });
1417
+ }
1418
+ return res;
1419
+ }
1420
+ function mapErrorCode(status) {
1421
+ if (status === 401 || status === 403) return 401;
1422
+ if (status === 429) return 429;
1423
+ if (status === 400 || status === 422) return 400;
1424
+ if (status >= 500) return 502;
1425
+ return status;
1426
+ }
1427
+ function rePrefixId(id) {
1428
+ if (id && id.startsWith("chatcmpl-")) {
1429
+ return `gen-${id.substring(9)}`;
1430
+ }
1431
+ return id.startsWith("gen-") ? id : `gen-${id}`;
1432
+ }
1433
+ function buildRequestBody(request) {
1434
+ const body = {
1435
+ model: request.model,
1436
+ messages: request.messages
1437
+ };
1438
+ if (request.temperature !== void 0) body.temperature = request.temperature;
1439
+ if (request.max_tokens !== void 0) body.max_tokens = request.max_tokens;
1440
+ if (request.top_p !== void 0) body.top_p = request.top_p;
1441
+ if (request.frequency_penalty !== void 0) body.frequency_penalty = request.frequency_penalty;
1442
+ if (request.presence_penalty !== void 0) body.presence_penalty = request.presence_penalty;
1443
+ if (request.stop !== void 0) body.stop = request.stop;
1444
+ if (request.stream !== void 0) body.stream = request.stream;
1445
+ if (request.response_format !== void 0) body.response_format = request.response_format;
1446
+ if (request.tools !== void 0) body.tools = request.tools;
1447
+ if (request.tool_choice !== void 0) body.tool_choice = request.tool_choice;
1448
+ return body;
1449
+ }
1450
+ const adapter = {
1451
+ name: "perplexity",
1452
+ translateRequest(request) {
1453
+ return buildRequestBody(request);
1454
+ },
1455
+ translateResponse(response) {
1456
+ const r = response;
1457
+ const result = {
1458
+ id: rePrefixId(r.id),
1459
+ object: "chat.completion",
1460
+ created: r.created,
1461
+ model: `perplexity/${r.model}`,
1462
+ choices: r.choices,
1463
+ usage: r.usage
1464
+ };
1465
+ if (r.citations && result.choices?.[0]?.message) {
1466
+ result.citations = r.citations;
1467
+ }
1468
+ return result;
1469
+ },
1470
+ async *translateStream(stream) {
1471
+ const reader = stream.getReader();
1472
+ const decoder = new TextDecoder();
1473
+ let buffer = "";
1474
+ try {
1475
+ while (true) {
1476
+ const { done, value } = await reader.read();
1477
+ if (done) break;
1478
+ buffer += decoder.decode(value, { stream: true });
1479
+ const lines = buffer.split("\n");
1480
+ buffer = lines.pop() || "";
1481
+ for (const line of lines) {
1482
+ const trimmed = line.trim();
1483
+ if (!trimmed || trimmed.startsWith(":")) continue;
1484
+ if (trimmed === "data: [DONE]") return;
1485
+ if (trimmed.startsWith("data: ")) {
1486
+ const json = JSON.parse(trimmed.substring(6));
1487
+ json.id = rePrefixId(json.id);
1488
+ json.model = `perplexity/${json.model}`;
1489
+ yield json;
1490
+ }
1491
+ }
1492
+ }
1493
+ } finally {
1494
+ reader.releaseLock();
1495
+ }
1496
+ },
1497
+ translateError(error) {
1498
+ if (error instanceof AnyModelError) {
1499
+ return { code: error.code, message: error.message, metadata: error.metadata };
1500
+ }
1501
+ const err = error;
1502
+ const status = err?.status || err?.code || 500;
1503
+ return {
1504
+ code: mapErrorCode(status),
1505
+ message: err?.message || "Unknown Perplexity error",
1506
+ metadata: { provider_name: "perplexity", raw: error }
1507
+ };
1508
+ },
1509
+ async listModels() {
1510
+ return MODELS.map((m) => ({
1511
+ id: `perplexity/${m.id}`,
1512
+ name: m.name,
1513
+ created: 0,
1514
+ description: "",
1515
+ context_length: m.context,
1516
+ pricing: { prompt: "0", completion: "0" },
1517
+ architecture: {
1518
+ modality: m.modality,
1519
+ input_modalities: m.inputModalities,
1520
+ output_modalities: ["text"],
1521
+ tokenizer: "unknown"
1522
+ },
1523
+ top_provider: {
1524
+ context_length: m.context,
1525
+ max_completion_tokens: m.maxOutput,
1526
+ is_moderated: false
1527
+ },
1528
+ supported_parameters: Array.from(SUPPORTED_PARAMS4)
1529
+ }));
1530
+ },
1531
+ supportsParameter(param) {
1532
+ return SUPPORTED_PARAMS4.has(param);
1533
+ },
1534
+ supportsBatch() {
1535
+ return false;
1536
+ },
1537
+ async sendRequest(request) {
1538
+ const body = buildRequestBody(request);
1539
+ const res = await makeRequest("/chat/completions", body);
1540
+ const json = await res.json();
1541
+ return adapter.translateResponse(json);
1542
+ },
1543
+ async sendStreamingRequest(request) {
1544
+ const body = buildRequestBody({ ...request, stream: true });
1545
+ const res = await makeRequest("/chat/completions", body);
1546
+ if (!res.body) {
1547
+ throw new AnyModelError(502, "No response body for streaming request", {
1548
+ provider_name: "perplexity"
1549
+ });
1550
+ }
1551
+ return adapter.translateStream(res.body);
1552
+ }
1553
+ };
1554
+ return adapter;
1555
+ }
1556
+
1350
1557
  // src/providers/custom.ts
1351
1558
  function createCustomAdapter(name, config) {
1352
1559
  const openaiAdapter = createOpenAIAdapter(config.apiKey || "", config.baseURL);
@@ -2059,6 +2266,51 @@ var BatchManager = class {
2059
2266
  }
2060
2267
  };
2061
2268
 
2269
+ // src/utils/token-estimate.ts
2270
+ var CHARS_PER_TOKEN2 = 4;
2271
+ var MODEL_LIMITS = [
2272
+ // OpenAI
2273
+ { pattern: "gpt-4o-mini", limit: { contextLength: 128e3, maxCompletionTokens: 16384 } },
2274
+ { pattern: "gpt-4o", limit: { contextLength: 128e3, maxCompletionTokens: 16384 } },
2275
+ { pattern: "gpt-4-turbo", limit: { contextLength: 128e3, maxCompletionTokens: 4096 } },
2276
+ { pattern: "gpt-3.5-turbo", limit: { contextLength: 16385, maxCompletionTokens: 4096 } },
2277
+ { pattern: "o1", limit: { contextLength: 2e5, maxCompletionTokens: 1e5 } },
2278
+ { pattern: "o3", limit: { contextLength: 2e5, maxCompletionTokens: 1e5 } },
2279
+ { pattern: "o4-mini", limit: { contextLength: 2e5, maxCompletionTokens: 1e5 } },
2280
+ // Anthropic
2281
+ { pattern: "claude-opus-4", limit: { contextLength: 2e5, maxCompletionTokens: 32768 } },
2282
+ { pattern: "claude-sonnet-4", limit: { contextLength: 2e5, maxCompletionTokens: 16384 } },
2283
+ { pattern: "claude-haiku-4", limit: { contextLength: 2e5, maxCompletionTokens: 8192 } },
2284
+ { pattern: "claude-3.5-sonnet", limit: { contextLength: 2e5, maxCompletionTokens: 8192 } },
2285
+ { pattern: "claude-3-opus", limit: { contextLength: 2e5, maxCompletionTokens: 4096 } },
2286
+ // Google
2287
+ { pattern: "gemini-2.5-pro", limit: { contextLength: 1048576, maxCompletionTokens: 65536 } },
2288
+ { pattern: "gemini-2.5-flash", limit: { contextLength: 1048576, maxCompletionTokens: 65536 } },
2289
+ { pattern: "gemini-2.0-flash", limit: { contextLength: 1048576, maxCompletionTokens: 65536 } },
2290
+ { pattern: "gemini-1.5-pro", limit: { contextLength: 2097152, maxCompletionTokens: 8192 } },
2291
+ { pattern: "gemini-1.5-flash", limit: { contextLength: 1048576, maxCompletionTokens: 8192 } }
2292
+ ];
2293
+ var DEFAULT_LIMIT = { contextLength: 128e3, maxCompletionTokens: 4096 };
2294
+ function getModelLimits(model) {
2295
+ const bare = model.includes("/") ? model.slice(model.indexOf("/") + 1) : model;
2296
+ for (const entry of MODEL_LIMITS) {
2297
+ if (bare.startsWith(entry.pattern) || bare.includes(entry.pattern)) {
2298
+ return entry.limit;
2299
+ }
2300
+ }
2301
+ return DEFAULT_LIMIT;
2302
+ }
2303
+ function resolveMaxTokens(model, messages, userMaxTokens) {
2304
+ if (userMaxTokens !== void 0) return userMaxTokens;
2305
+ const inputChars = JSON.stringify(messages).length;
2306
+ const estimatedInput = Math.ceil(inputChars / CHARS_PER_TOKEN2);
2307
+ const estimatedWithMargin = Math.ceil(estimatedInput * 1.05);
2308
+ const limits = getModelLimits(model);
2309
+ const available = limits.contextLength - estimatedWithMargin;
2310
+ const result = Math.min(limits.maxCompletionTokens, available);
2311
+ return Math.max(1, result);
2312
+ }
2313
+
2062
2314
  // src/providers/openai-batch.ts
2063
2315
  var OPENAI_API_BASE2 = "https://api.openai.com/v1";
2064
2316
  function createOpenAIBatchAdapter(apiKey) {
@@ -2073,7 +2325,7 @@ function createOpenAIBatchAdapter(apiKey) {
2073
2325
  headers["Content-Type"] = "application/json";
2074
2326
  fetchBody = JSON.stringify(options.body);
2075
2327
  }
2076
- const res = await fetch(`${OPENAI_API_BASE2}${path2}`, {
2328
+ const res = await fetchWithTimeout(`${OPENAI_API_BASE2}${path2}`, {
2077
2329
  method: options.method || "GET",
2078
2330
  headers,
2079
2331
  body: fetchBody
@@ -2099,7 +2351,7 @@ function createOpenAIBatchAdapter(apiKey) {
2099
2351
  model,
2100
2352
  messages: req.messages
2101
2353
  };
2102
- if (req.max_tokens !== void 0) body.max_tokens = req.max_tokens;
2354
+ body.max_tokens = req.max_tokens !== void 0 ? req.max_tokens : resolveMaxTokens(model, req.messages);
2103
2355
  if (req.temperature !== void 0) body.temperature = req.temperature;
2104
2356
  if (req.top_p !== void 0) body.top_p = req.top_p;
2105
2357
  if (req.stop !== void 0) body.stop = req.stop;
@@ -2258,7 +2510,7 @@ function createAnthropicBatchAdapter(apiKey) {
2258
2510
  "anthropic-version": ANTHROPIC_VERSION2,
2259
2511
  "Content-Type": "application/json"
2260
2512
  };
2261
- const res = await fetch(`${ANTHROPIC_API_BASE2}${path2}`, {
2513
+ const res = await fetchWithTimeout(`${ANTHROPIC_API_BASE2}${path2}`, {
2262
2514
  method: options.method || "GET",
2263
2515
  headers,
2264
2516
  body: options.body ? JSON.stringify(options.body) : void 0
@@ -2281,7 +2533,7 @@ function createAnthropicBatchAdapter(apiKey) {
2281
2533
  function translateToAnthropicParams(model, req) {
2282
2534
  const params = {
2283
2535
  model,
2284
- max_tokens: req.max_tokens || DEFAULT_MAX_TOKENS2
2536
+ max_tokens: resolveMaxTokens(model, req.messages, req.max_tokens || DEFAULT_MAX_TOKENS2)
2285
2537
  };
2286
2538
  const systemMessages = req.messages.filter((m) => m.role === "system");
2287
2539
  const nonSystemMessages = req.messages.filter((m) => m.role !== "system");
@@ -2455,6 +2707,284 @@ ${params.system}` : jsonInstruction;
2455
2707
  };
2456
2708
  }
2457
2709
 
2710
+ // src/providers/google-batch.ts
2711
+ var GEMINI_API_BASE2 = "https://generativelanguage.googleapis.com/v1beta";
2712
+ function createGoogleBatchAdapter(apiKey) {
2713
+ async function apiRequest(path2, options = {}) {
2714
+ const headers = {
2715
+ "Content-Type": "application/json",
2716
+ "x-goog-api-key": apiKey
2717
+ };
2718
+ const res = await fetchWithTimeout(`${GEMINI_API_BASE2}${path2}`, {
2719
+ method: options.method || "GET",
2720
+ headers,
2721
+ body: options.body ? JSON.stringify(options.body) : void 0
2722
+ });
2723
+ if (!res.ok) {
2724
+ let errorBody;
2725
+ try {
2726
+ errorBody = await res.json();
2727
+ } catch {
2728
+ errorBody = { message: res.statusText };
2729
+ }
2730
+ const msg = errorBody?.error?.message || errorBody?.message || res.statusText;
2731
+ throw new AnyModelError(res.status >= 500 ? 502 : res.status, msg, {
2732
+ provider_name: "google",
2733
+ raw: errorBody
2734
+ });
2735
+ }
2736
+ return res;
2737
+ }
2738
+ function translateRequestToGemini(model, req) {
2739
+ const body = {};
2740
+ const systemMessages = req.messages.filter((m) => m.role === "system");
2741
+ const nonSystemMessages = req.messages.filter((m) => m.role !== "system");
2742
+ if (systemMessages.length > 0) {
2743
+ body.systemInstruction = {
2744
+ parts: [{ text: systemMessages.map((m) => typeof m.content === "string" ? m.content : "").join("\n") }]
2745
+ };
2746
+ }
2747
+ body.contents = nonSystemMessages.map((m) => ({
2748
+ role: m.role === "assistant" ? "model" : "user",
2749
+ parts: typeof m.content === "string" ? [{ text: m.content }] : Array.isArray(m.content) ? m.content.map((p) => p.type === "text" ? { text: p.text } : { text: "" }) : [{ text: "" }]
2750
+ }));
2751
+ const generationConfig = {};
2752
+ if (req.temperature !== void 0) generationConfig.temperature = req.temperature;
2753
+ generationConfig.maxOutputTokens = req.max_tokens !== void 0 ? req.max_tokens : resolveMaxTokens(model, req.messages);
2754
+ if (req.top_p !== void 0) generationConfig.topP = req.top_p;
2755
+ if (req.top_k !== void 0) generationConfig.topK = req.top_k;
2756
+ if (req.stop !== void 0) {
2757
+ generationConfig.stopSequences = Array.isArray(req.stop) ? req.stop : [req.stop];
2758
+ }
2759
+ if (req.response_format) {
2760
+ if (req.response_format.type === "json_object") {
2761
+ generationConfig.responseMimeType = "application/json";
2762
+ } else if (req.response_format.type === "json_schema") {
2763
+ generationConfig.responseMimeType = "application/json";
2764
+ generationConfig.responseSchema = req.response_format.json_schema?.schema;
2765
+ }
2766
+ }
2767
+ if (Object.keys(generationConfig).length > 0) {
2768
+ body.generationConfig = generationConfig;
2769
+ }
2770
+ if (req.tools && req.tools.length > 0) {
2771
+ body.tools = [{
2772
+ functionDeclarations: req.tools.map((t) => ({
2773
+ name: t.function.name,
2774
+ description: t.function.description || "",
2775
+ parameters: t.function.parameters || {}
2776
+ }))
2777
+ }];
2778
+ if (req.tool_choice) {
2779
+ if (req.tool_choice === "auto") {
2780
+ body.toolConfig = { functionCallingConfig: { mode: "AUTO" } };
2781
+ } else if (req.tool_choice === "required") {
2782
+ body.toolConfig = { functionCallingConfig: { mode: "ANY" } };
2783
+ } else if (req.tool_choice === "none") {
2784
+ body.toolConfig = { functionCallingConfig: { mode: "NONE" } };
2785
+ } else if (typeof req.tool_choice === "object") {
2786
+ body.toolConfig = {
2787
+ functionCallingConfig: {
2788
+ mode: "ANY",
2789
+ allowedFunctionNames: [req.tool_choice.function.name]
2790
+ }
2791
+ };
2792
+ }
2793
+ }
2794
+ }
2795
+ return body;
2796
+ }
2797
+ function mapFinishReason(reason) {
2798
+ switch (reason) {
2799
+ case "STOP":
2800
+ return "stop";
2801
+ case "MAX_TOKENS":
2802
+ return "length";
2803
+ case "SAFETY":
2804
+ return "content_filter";
2805
+ case "RECITATION":
2806
+ return "content_filter";
2807
+ default:
2808
+ return "stop";
2809
+ }
2810
+ }
2811
+ function translateGeminiResponse(response, model) {
2812
+ const candidate = response.candidates?.[0];
2813
+ let content = "";
2814
+ const toolCalls = [];
2815
+ for (const part of candidate?.content?.parts || []) {
2816
+ if (part.text) {
2817
+ content += part.text;
2818
+ } else if (part.functionCall) {
2819
+ toolCalls.push({
2820
+ id: generateId("call"),
2821
+ type: "function",
2822
+ function: {
2823
+ name: part.functionCall.name,
2824
+ arguments: JSON.stringify(part.functionCall.args || {})
2825
+ }
2826
+ });
2827
+ }
2828
+ }
2829
+ const message = { role: "assistant", content };
2830
+ if (toolCalls.length > 0) {
2831
+ message.tool_calls = toolCalls;
2832
+ }
2833
+ const finishReason = toolCalls.length > 0 ? "tool_calls" : mapFinishReason(candidate?.finishReason || "STOP");
2834
+ return {
2835
+ id: generateId(),
2836
+ object: "chat.completion",
2837
+ created: Math.floor(Date.now() / 1e3),
2838
+ model: `google/${model}`,
2839
+ choices: [{ index: 0, message, finish_reason: finishReason }],
2840
+ usage: {
2841
+ prompt_tokens: response.usageMetadata?.promptTokenCount || 0,
2842
+ completion_tokens: response.usageMetadata?.candidatesTokenCount || 0,
2843
+ total_tokens: response.usageMetadata?.totalTokenCount || 0
2844
+ }
2845
+ };
2846
+ }
2847
+ function mapBatchState(state) {
2848
+ switch (state) {
2849
+ case "JOB_STATE_PENDING":
2850
+ return "pending";
2851
+ case "JOB_STATE_RUNNING":
2852
+ return "processing";
2853
+ case "JOB_STATE_SUCCEEDED":
2854
+ return "completed";
2855
+ case "JOB_STATE_FAILED":
2856
+ return "failed";
2857
+ case "JOB_STATE_CANCELLED":
2858
+ return "cancelled";
2859
+ case "JOB_STATE_EXPIRED":
2860
+ return "failed";
2861
+ default:
2862
+ return "pending";
2863
+ }
2864
+ }
2865
+ return {
2866
+ async createBatch(model, requests, _options) {
2867
+ const batchRequests = requests.map((req) => ({
2868
+ request: translateRequestToGemini(model, req),
2869
+ metadata: { key: req.custom_id }
2870
+ }));
2871
+ const res = await apiRequest(`/models/${model}:batchGenerateContent`, {
2872
+ method: "POST",
2873
+ body: {
2874
+ batch: {
2875
+ display_name: `anymodel-batch-${Date.now()}`,
2876
+ input_config: {
2877
+ requests: {
2878
+ requests: batchRequests
2879
+ }
2880
+ }
2881
+ }
2882
+ }
2883
+ });
2884
+ const data = await res.json();
2885
+ const batchName = data.name || data.batch?.name;
2886
+ if (!batchName) {
2887
+ throw new AnyModelError(502, "No batch name in Google response", {
2888
+ provider_name: "google",
2889
+ raw: data
2890
+ });
2891
+ }
2892
+ return {
2893
+ providerBatchId: batchName,
2894
+ metadata: {
2895
+ model,
2896
+ total_requests: requests.length
2897
+ }
2898
+ };
2899
+ },
2900
+ async pollBatch(providerBatchId) {
2901
+ const res = await apiRequest(`/${providerBatchId}`);
2902
+ const data = await res.json();
2903
+ const state = data.state || "JOB_STATE_PENDING";
2904
+ const status = mapBatchState(state);
2905
+ const totalCount = data.totalCount || data.metadata?.total_requests || 0;
2906
+ const successCount = data.succeededCount || 0;
2907
+ const failedCount = data.failedCount || 0;
2908
+ return {
2909
+ status,
2910
+ total: totalCount || successCount + failedCount,
2911
+ completed: successCount,
2912
+ failed: failedCount
2913
+ };
2914
+ },
2915
+ async getBatchResults(providerBatchId) {
2916
+ const batchRes = await apiRequest(`/${providerBatchId}`);
2917
+ const batchData = await batchRes.json();
2918
+ const results = [];
2919
+ const model = batchData.metadata?.model || "unknown";
2920
+ if (batchData.response?.inlinedResponses) {
2921
+ for (const item of batchData.response.inlinedResponses) {
2922
+ const customId = item.metadata?.key || `request-${results.length}`;
2923
+ if (item.response) {
2924
+ results.push({
2925
+ custom_id: customId,
2926
+ status: "success",
2927
+ response: translateGeminiResponse(item.response, model),
2928
+ error: null
2929
+ });
2930
+ } else if (item.error) {
2931
+ results.push({
2932
+ custom_id: customId,
2933
+ status: "error",
2934
+ response: null,
2935
+ error: {
2936
+ code: item.error.code || 500,
2937
+ message: item.error.message || "Batch item failed"
2938
+ }
2939
+ });
2940
+ }
2941
+ }
2942
+ return results;
2943
+ }
2944
+ const responsesFile = batchData.response?.responsesFileName || batchData.outputConfig?.file_name;
2945
+ if (responsesFile) {
2946
+ const downloadUrl = `${GEMINI_API_BASE2}/${responsesFile}:download?alt=media`;
2947
+ const fileRes = await fetchWithTimeout(downloadUrl, {
2948
+ headers: { "x-goog-api-key": apiKey }
2949
+ });
2950
+ if (!fileRes.ok) {
2951
+ throw new AnyModelError(502, "Failed to download batch results file", {
2952
+ provider_name: "google"
2953
+ });
2954
+ }
2955
+ const text = await fileRes.text();
2956
+ for (const line of text.trim().split("\n")) {
2957
+ if (!line) continue;
2958
+ const item = JSON.parse(line);
2959
+ const customId = item.key || item.metadata?.key || `request-${results.length}`;
2960
+ if (item.response) {
2961
+ results.push({
2962
+ custom_id: customId,
2963
+ status: "success",
2964
+ response: translateGeminiResponse(item.response, model),
2965
+ error: null
2966
+ });
2967
+ } else if (item.error) {
2968
+ results.push({
2969
+ custom_id: customId,
2970
+ status: "error",
2971
+ response: null,
2972
+ error: {
2973
+ code: item.error.code || 500,
2974
+ message: item.error.message || "Batch item failed"
2975
+ }
2976
+ });
2977
+ }
2978
+ }
2979
+ }
2980
+ return results;
2981
+ },
2982
+ async cancelBatch(providerBatchId) {
2983
+ await apiRequest(`/${providerBatchId}:cancel`, { method: "POST" });
2984
+ }
2985
+ };
2986
+ }
2987
+
2458
2988
  // src/client.ts
2459
2989
  var AnyModel = class {
2460
2990
  registry;
@@ -2470,6 +3000,7 @@ var AnyModel = class {
2470
3000
  constructor(config = {}) {
2471
3001
  this.config = resolveConfig(config);
2472
3002
  this.registry = new ProviderRegistry();
3003
+ setDefaultTimeout((this.config.defaults?.timeout ?? 120) * 1e3);
2473
3004
  if (this.config.io) {
2474
3005
  configureFsIO(this.config.io);
2475
3006
  }
@@ -2550,14 +3081,17 @@ var AnyModel = class {
2550
3081
  if (googleKey) {
2551
3082
  this.registry.register("google", createGoogleAdapter(googleKey));
2552
3083
  }
3084
+ const perplexityKey = config.perplexity?.apiKey || process.env.PERPLEXITY_API_KEY;
3085
+ if (perplexityKey) {
3086
+ this.registry.register("perplexity", createPerplexityAdapter(perplexityKey));
3087
+ }
2553
3088
  const builtinProviders = [
2554
3089
  { name: "mistral", baseURL: "https://api.mistral.ai/v1", configKey: "mistral", envVar: "MISTRAL_API_KEY" },
2555
3090
  { name: "groq", baseURL: "https://api.groq.com/openai/v1", configKey: "groq", envVar: "GROQ_API_KEY" },
2556
3091
  { name: "deepseek", baseURL: "https://api.deepseek.com", configKey: "deepseek", envVar: "DEEPSEEK_API_KEY" },
2557
3092
  { name: "xai", baseURL: "https://api.x.ai/v1", configKey: "xai", envVar: "XAI_API_KEY" },
2558
3093
  { name: "together", baseURL: "https://api.together.xyz/v1", configKey: "together", envVar: "TOGETHER_API_KEY" },
2559
- { name: "fireworks", baseURL: "https://api.fireworks.ai/inference/v1", configKey: "fireworks", envVar: "FIREWORKS_API_KEY" },
2560
- { name: "perplexity", baseURL: "https://api.perplexity.ai", configKey: "perplexity", envVar: "PERPLEXITY_API_KEY" }
3094
+ { name: "fireworks", baseURL: "https://api.fireworks.ai/inference/v1", configKey: "fireworks", envVar: "FIREWORKS_API_KEY" }
2561
3095
  ];
2562
3096
  for (const { name, baseURL, configKey, envVar } of builtinProviders) {
2563
3097
  const providerConfig = config[configKey];
@@ -2587,6 +3121,10 @@ var AnyModel = class {
2587
3121
  if (anthropicKey) {
2588
3122
  this.batchManager.registerBatchAdapter("anthropic", createAnthropicBatchAdapter(anthropicKey));
2589
3123
  }
3124
+ const googleKey = config.google?.apiKey || process.env.GOOGLE_API_KEY;
3125
+ if (googleKey) {
3126
+ this.batchManager.registerBatchAdapter("google", createGoogleBatchAdapter(googleKey));
3127
+ }
2590
3128
  }
2591
3129
  applyDefaults(request) {
2592
3130
  const defaults = this.config.defaults;