@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.cjs CHANGED
@@ -508,6 +508,25 @@ var Router = class {
508
508
  }
509
509
  };
510
510
 
511
+ // src/utils/fetch-with-timeout.ts
512
+ var _defaultTimeout = 12e4;
513
+ var _flexTimeout = 6e5;
514
+ function setDefaultTimeout(ms) {
515
+ _defaultTimeout = ms;
516
+ }
517
+ function getFlexTimeout() {
518
+ return _flexTimeout;
519
+ }
520
+ function fetchWithTimeout(url, init, timeoutMs) {
521
+ const ms = timeoutMs ?? _defaultTimeout;
522
+ const signal = AbortSignal.timeout(ms);
523
+ if (init?.signal) {
524
+ const combined = AbortSignal.any([signal, init.signal]);
525
+ return fetch(url, { ...init, signal: combined });
526
+ }
527
+ return fetch(url, { ...init, signal });
528
+ }
529
+
511
530
  // src/providers/openai.ts
512
531
  var OPENAI_API_BASE = "https://api.openai.com/v1";
513
532
  var SUPPORTED_PARAMS = /* @__PURE__ */ new Set([
@@ -525,19 +544,20 @@ var SUPPORTED_PARAMS = /* @__PURE__ */ new Set([
525
544
  "tools",
526
545
  "tool_choice",
527
546
  "user",
528
- "logit_bias"
547
+ "logit_bias",
548
+ "service_tier"
529
549
  ]);
530
550
  function createOpenAIAdapter(apiKey, baseURL) {
531
551
  const base = baseURL || OPENAI_API_BASE;
532
- async function makeRequest(path2, body, method = "POST") {
533
- const res = await fetch(`${base}${path2}`, {
552
+ async function makeRequest(path2, body, method = "POST", timeoutMs) {
553
+ const res = await fetchWithTimeout(`${base}${path2}`, {
534
554
  method,
535
555
  headers: {
536
556
  "Content-Type": "application/json",
537
557
  "Authorization": `Bearer ${apiKey}`
538
558
  },
539
559
  body: body ? JSON.stringify(body) : void 0
540
- });
560
+ }, timeoutMs);
541
561
  if (!res.ok) {
542
562
  let errorBody;
543
563
  try {
@@ -585,6 +605,7 @@ function createOpenAIAdapter(apiKey, baseURL) {
585
605
  if (request.tools !== void 0) body.tools = request.tools;
586
606
  if (request.tool_choice !== void 0) body.tool_choice = request.tool_choice;
587
607
  if (request.user !== void 0) body.user = request.user;
608
+ if (request.service_tier !== void 0) body.service_tier = request.service_tier;
588
609
  return body;
589
610
  }
590
611
  const adapter = {
@@ -686,13 +707,15 @@ function createOpenAIAdapter(apiKey, baseURL) {
686
707
  },
687
708
  async sendRequest(request) {
688
709
  const body = buildRequestBody(request);
689
- const res = await makeRequest("/chat/completions", body);
710
+ const timeout = request.service_tier === "flex" ? getFlexTimeout() : void 0;
711
+ const res = await makeRequest("/chat/completions", body, "POST", timeout);
690
712
  const json = await res.json();
691
713
  return adapter.translateResponse(json);
692
714
  },
693
715
  async sendStreamingRequest(request) {
694
716
  const body = buildRequestBody({ ...request, stream: true });
695
- const res = await makeRequest("/chat/completions", body);
717
+ const timeout = request.service_tier === "flex" ? getFlexTimeout() : void 0;
718
+ const res = await makeRequest("/chat/completions", body, "POST", timeout);
696
719
  if (!res.body) {
697
720
  throw new AnyModelError(502, "No response body for streaming request", {
698
721
  provider_name: "openai"
@@ -739,7 +762,7 @@ var FALLBACK_MODELS = [
739
762
  ];
740
763
  function createAnthropicAdapter(apiKey) {
741
764
  async function makeRequest(path2, body, stream = false) {
742
- const res = await fetch(`${ANTHROPIC_API_BASE}${path2}`, {
765
+ const res = await fetchWithTimeout(`${ANTHROPIC_API_BASE}${path2}`, {
743
766
  method: "POST",
744
767
  headers: {
745
768
  "Content-Type": "application/json",
@@ -996,7 +1019,7 @@ ${body.system}` : jsonInstruction;
996
1019
  },
997
1020
  async listModels() {
998
1021
  try {
999
- const res = await fetch(`${ANTHROPIC_API_BASE}/models`, {
1022
+ const res = await fetchWithTimeout(`${ANTHROPIC_API_BASE}/models`, {
1000
1023
  method: "GET",
1001
1024
  headers: {
1002
1025
  "x-api-key": apiKey,
@@ -1281,7 +1304,7 @@ function createGoogleAdapter(apiKey) {
1281
1304
  },
1282
1305
  async listModels() {
1283
1306
  try {
1284
- const res = await fetch(`${GEMINI_API_BASE}/models?key=${apiKey}`);
1307
+ const res = await fetchWithTimeout(`${GEMINI_API_BASE}/models?key=${apiKey}`);
1285
1308
  if (!res.ok) return FALLBACK_MODELS2;
1286
1309
  const data = await res.json();
1287
1310
  const models = data.models || [];
@@ -1316,12 +1339,12 @@ function createGoogleAdapter(apiKey) {
1316
1339
  return SUPPORTED_PARAMS3.has(param);
1317
1340
  },
1318
1341
  supportsBatch() {
1319
- return false;
1342
+ return true;
1320
1343
  },
1321
1344
  async sendRequest(request) {
1322
1345
  const body = translateRequest(request);
1323
1346
  const url = getModelEndpoint(request.model, false);
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)
@@ -1344,7 +1367,7 @@ function createGoogleAdapter(apiKey) {
1344
1367
  async sendStreamingRequest(request) {
1345
1368
  const body = translateRequest(request);
1346
1369
  const url = getModelEndpoint(request.model, true);
1347
- const res = await fetch(url, {
1370
+ const res = await fetchWithTimeout(url, {
1348
1371
  method: "POST",
1349
1372
  headers: { "Content-Type": "application/json" },
1350
1373
  body: JSON.stringify(body)
@@ -1370,6 +1393,190 @@ function createGoogleAdapter(apiKey) {
1370
1393
  return adapter;
1371
1394
  }
1372
1395
 
1396
+ // src/providers/perplexity.ts
1397
+ var PERPLEXITY_API_BASE = "https://api.perplexity.ai";
1398
+ var SUPPORTED_PARAMS4 = /* @__PURE__ */ new Set([
1399
+ "temperature",
1400
+ "max_tokens",
1401
+ "top_p",
1402
+ "frequency_penalty",
1403
+ "presence_penalty",
1404
+ "stream",
1405
+ "stop",
1406
+ "response_format",
1407
+ "tools",
1408
+ "tool_choice"
1409
+ ]);
1410
+ var MODELS = [
1411
+ { id: "sonar", name: "Sonar", context: 128e3, maxOutput: 4096, modality: "text->text", inputModalities: ["text"] },
1412
+ { id: "sonar-pro", name: "Sonar Pro", context: 2e5, maxOutput: 8192, modality: "text->text", inputModalities: ["text"] },
1413
+ { id: "sonar-reasoning", name: "Sonar Reasoning", context: 128e3, maxOutput: 8192, modality: "text->text", inputModalities: ["text"] },
1414
+ { id: "sonar-reasoning-pro", name: "Sonar Reasoning Pro", context: 128e3, maxOutput: 16384, modality: "text->text", inputModalities: ["text"] },
1415
+ { id: "sonar-deep-research", name: "Sonar Deep Research", context: 128e3, maxOutput: 16384, modality: "text->text", inputModalities: ["text"] },
1416
+ { id: "r1-1776", name: "R1 1776", context: 128e3, maxOutput: 16384, modality: "text->text", inputModalities: ["text"] }
1417
+ ];
1418
+ function createPerplexityAdapter(apiKey) {
1419
+ async function makeRequest(path2, body, method = "POST") {
1420
+ const res = await fetchWithTimeout(`${PERPLEXITY_API_BASE}${path2}`, {
1421
+ method,
1422
+ headers: {
1423
+ "Content-Type": "application/json",
1424
+ "Authorization": `Bearer ${apiKey}`
1425
+ },
1426
+ body: body ? JSON.stringify(body) : void 0
1427
+ });
1428
+ if (!res.ok) {
1429
+ let errorBody;
1430
+ try {
1431
+ errorBody = await res.json();
1432
+ } catch {
1433
+ errorBody = { message: res.statusText };
1434
+ }
1435
+ const msg = errorBody?.error?.message || errorBody?.message || res.statusText;
1436
+ throw new AnyModelError(mapErrorCode(res.status), msg, {
1437
+ provider_name: "perplexity",
1438
+ raw: errorBody
1439
+ });
1440
+ }
1441
+ return res;
1442
+ }
1443
+ function mapErrorCode(status) {
1444
+ if (status === 401 || status === 403) return 401;
1445
+ if (status === 429) return 429;
1446
+ if (status === 400 || status === 422) return 400;
1447
+ if (status >= 500) return 502;
1448
+ return status;
1449
+ }
1450
+ function rePrefixId(id) {
1451
+ if (id && id.startsWith("chatcmpl-")) {
1452
+ return `gen-${id.substring(9)}`;
1453
+ }
1454
+ return id.startsWith("gen-") ? id : `gen-${id}`;
1455
+ }
1456
+ function buildRequestBody(request) {
1457
+ const body = {
1458
+ model: request.model,
1459
+ messages: request.messages
1460
+ };
1461
+ if (request.temperature !== void 0) body.temperature = request.temperature;
1462
+ if (request.max_tokens !== void 0) body.max_tokens = request.max_tokens;
1463
+ if (request.top_p !== void 0) body.top_p = request.top_p;
1464
+ if (request.frequency_penalty !== void 0) body.frequency_penalty = request.frequency_penalty;
1465
+ if (request.presence_penalty !== void 0) body.presence_penalty = request.presence_penalty;
1466
+ if (request.stop !== void 0) body.stop = request.stop;
1467
+ if (request.stream !== void 0) body.stream = request.stream;
1468
+ if (request.response_format !== void 0) body.response_format = request.response_format;
1469
+ if (request.tools !== void 0) body.tools = request.tools;
1470
+ if (request.tool_choice !== void 0) body.tool_choice = request.tool_choice;
1471
+ return body;
1472
+ }
1473
+ const adapter = {
1474
+ name: "perplexity",
1475
+ translateRequest(request) {
1476
+ return buildRequestBody(request);
1477
+ },
1478
+ translateResponse(response) {
1479
+ const r = response;
1480
+ const result = {
1481
+ id: rePrefixId(r.id),
1482
+ object: "chat.completion",
1483
+ created: r.created,
1484
+ model: `perplexity/${r.model}`,
1485
+ choices: r.choices,
1486
+ usage: r.usage
1487
+ };
1488
+ if (r.citations && result.choices?.[0]?.message) {
1489
+ result.citations = r.citations;
1490
+ }
1491
+ return result;
1492
+ },
1493
+ async *translateStream(stream) {
1494
+ const reader = stream.getReader();
1495
+ const decoder = new TextDecoder();
1496
+ let buffer = "";
1497
+ try {
1498
+ while (true) {
1499
+ const { done, value } = await reader.read();
1500
+ if (done) break;
1501
+ buffer += decoder.decode(value, { stream: true });
1502
+ const lines = buffer.split("\n");
1503
+ buffer = lines.pop() || "";
1504
+ for (const line of lines) {
1505
+ const trimmed = line.trim();
1506
+ if (!trimmed || trimmed.startsWith(":")) continue;
1507
+ if (trimmed === "data: [DONE]") return;
1508
+ if (trimmed.startsWith("data: ")) {
1509
+ const json = JSON.parse(trimmed.substring(6));
1510
+ json.id = rePrefixId(json.id);
1511
+ json.model = `perplexity/${json.model}`;
1512
+ yield json;
1513
+ }
1514
+ }
1515
+ }
1516
+ } finally {
1517
+ reader.releaseLock();
1518
+ }
1519
+ },
1520
+ translateError(error) {
1521
+ if (error instanceof AnyModelError) {
1522
+ return { code: error.code, message: error.message, metadata: error.metadata };
1523
+ }
1524
+ const err = error;
1525
+ const status = err?.status || err?.code || 500;
1526
+ return {
1527
+ code: mapErrorCode(status),
1528
+ message: err?.message || "Unknown Perplexity error",
1529
+ metadata: { provider_name: "perplexity", raw: error }
1530
+ };
1531
+ },
1532
+ async listModels() {
1533
+ return MODELS.map((m) => ({
1534
+ id: `perplexity/${m.id}`,
1535
+ name: m.name,
1536
+ created: 0,
1537
+ description: "",
1538
+ context_length: m.context,
1539
+ pricing: { prompt: "0", completion: "0" },
1540
+ architecture: {
1541
+ modality: m.modality,
1542
+ input_modalities: m.inputModalities,
1543
+ output_modalities: ["text"],
1544
+ tokenizer: "unknown"
1545
+ },
1546
+ top_provider: {
1547
+ context_length: m.context,
1548
+ max_completion_tokens: m.maxOutput,
1549
+ is_moderated: false
1550
+ },
1551
+ supported_parameters: Array.from(SUPPORTED_PARAMS4)
1552
+ }));
1553
+ },
1554
+ supportsParameter(param) {
1555
+ return SUPPORTED_PARAMS4.has(param);
1556
+ },
1557
+ supportsBatch() {
1558
+ return false;
1559
+ },
1560
+ async sendRequest(request) {
1561
+ const body = buildRequestBody(request);
1562
+ const res = await makeRequest("/chat/completions", body);
1563
+ const json = await res.json();
1564
+ return adapter.translateResponse(json);
1565
+ },
1566
+ async sendStreamingRequest(request) {
1567
+ const body = buildRequestBody({ ...request, stream: true });
1568
+ const res = await makeRequest("/chat/completions", body);
1569
+ if (!res.body) {
1570
+ throw new AnyModelError(502, "No response body for streaming request", {
1571
+ provider_name: "perplexity"
1572
+ });
1573
+ }
1574
+ return adapter.translateStream(res.body);
1575
+ }
1576
+ };
1577
+ return adapter;
1578
+ }
1579
+
1373
1580
  // src/providers/custom.ts
1374
1581
  function createCustomAdapter(name, config) {
1375
1582
  const openaiAdapter = createOpenAIAdapter(config.apiKey || "", config.baseURL);
@@ -2082,6 +2289,51 @@ var BatchManager = class {
2082
2289
  }
2083
2290
  };
2084
2291
 
2292
+ // src/utils/token-estimate.ts
2293
+ var CHARS_PER_TOKEN2 = 4;
2294
+ var MODEL_LIMITS = [
2295
+ // OpenAI
2296
+ { pattern: "gpt-4o-mini", limit: { contextLength: 128e3, maxCompletionTokens: 16384 } },
2297
+ { pattern: "gpt-4o", limit: { contextLength: 128e3, maxCompletionTokens: 16384 } },
2298
+ { pattern: "gpt-4-turbo", limit: { contextLength: 128e3, maxCompletionTokens: 4096 } },
2299
+ { pattern: "gpt-3.5-turbo", limit: { contextLength: 16385, maxCompletionTokens: 4096 } },
2300
+ { pattern: "o1", limit: { contextLength: 2e5, maxCompletionTokens: 1e5 } },
2301
+ { pattern: "o3", limit: { contextLength: 2e5, maxCompletionTokens: 1e5 } },
2302
+ { pattern: "o4-mini", limit: { contextLength: 2e5, maxCompletionTokens: 1e5 } },
2303
+ // Anthropic
2304
+ { pattern: "claude-opus-4", limit: { contextLength: 2e5, maxCompletionTokens: 32768 } },
2305
+ { pattern: "claude-sonnet-4", limit: { contextLength: 2e5, maxCompletionTokens: 16384 } },
2306
+ { pattern: "claude-haiku-4", limit: { contextLength: 2e5, maxCompletionTokens: 8192 } },
2307
+ { pattern: "claude-3.5-sonnet", limit: { contextLength: 2e5, maxCompletionTokens: 8192 } },
2308
+ { pattern: "claude-3-opus", limit: { contextLength: 2e5, maxCompletionTokens: 4096 } },
2309
+ // Google
2310
+ { pattern: "gemini-2.5-pro", limit: { contextLength: 1048576, maxCompletionTokens: 65536 } },
2311
+ { pattern: "gemini-2.5-flash", limit: { contextLength: 1048576, maxCompletionTokens: 65536 } },
2312
+ { pattern: "gemini-2.0-flash", limit: { contextLength: 1048576, maxCompletionTokens: 65536 } },
2313
+ { pattern: "gemini-1.5-pro", limit: { contextLength: 2097152, maxCompletionTokens: 8192 } },
2314
+ { pattern: "gemini-1.5-flash", limit: { contextLength: 1048576, maxCompletionTokens: 8192 } }
2315
+ ];
2316
+ var DEFAULT_LIMIT = { contextLength: 128e3, maxCompletionTokens: 4096 };
2317
+ function getModelLimits(model) {
2318
+ const bare = model.includes("/") ? model.slice(model.indexOf("/") + 1) : model;
2319
+ for (const entry of MODEL_LIMITS) {
2320
+ if (bare.startsWith(entry.pattern) || bare.includes(entry.pattern)) {
2321
+ return entry.limit;
2322
+ }
2323
+ }
2324
+ return DEFAULT_LIMIT;
2325
+ }
2326
+ function resolveMaxTokens(model, messages, userMaxTokens) {
2327
+ if (userMaxTokens !== void 0) return userMaxTokens;
2328
+ const inputChars = JSON.stringify(messages).length;
2329
+ const estimatedInput = Math.ceil(inputChars / CHARS_PER_TOKEN2);
2330
+ const estimatedWithMargin = Math.ceil(estimatedInput * 1.05);
2331
+ const limits = getModelLimits(model);
2332
+ const available = limits.contextLength - estimatedWithMargin;
2333
+ const result = Math.min(limits.maxCompletionTokens, available);
2334
+ return Math.max(1, result);
2335
+ }
2336
+
2085
2337
  // src/providers/openai-batch.ts
2086
2338
  var OPENAI_API_BASE2 = "https://api.openai.com/v1";
2087
2339
  function createOpenAIBatchAdapter(apiKey) {
@@ -2096,7 +2348,7 @@ function createOpenAIBatchAdapter(apiKey) {
2096
2348
  headers["Content-Type"] = "application/json";
2097
2349
  fetchBody = JSON.stringify(options.body);
2098
2350
  }
2099
- const res = await fetch(`${OPENAI_API_BASE2}${path2}`, {
2351
+ const res = await fetchWithTimeout(`${OPENAI_API_BASE2}${path2}`, {
2100
2352
  method: options.method || "GET",
2101
2353
  headers,
2102
2354
  body: fetchBody
@@ -2122,7 +2374,7 @@ function createOpenAIBatchAdapter(apiKey) {
2122
2374
  model,
2123
2375
  messages: req.messages
2124
2376
  };
2125
- if (req.max_tokens !== void 0) body.max_tokens = req.max_tokens;
2377
+ body.max_tokens = req.max_tokens !== void 0 ? req.max_tokens : resolveMaxTokens(model, req.messages);
2126
2378
  if (req.temperature !== void 0) body.temperature = req.temperature;
2127
2379
  if (req.top_p !== void 0) body.top_p = req.top_p;
2128
2380
  if (req.stop !== void 0) body.stop = req.stop;
@@ -2281,7 +2533,7 @@ function createAnthropicBatchAdapter(apiKey) {
2281
2533
  "anthropic-version": ANTHROPIC_VERSION2,
2282
2534
  "Content-Type": "application/json"
2283
2535
  };
2284
- const res = await fetch(`${ANTHROPIC_API_BASE2}${path2}`, {
2536
+ const res = await fetchWithTimeout(`${ANTHROPIC_API_BASE2}${path2}`, {
2285
2537
  method: options.method || "GET",
2286
2538
  headers,
2287
2539
  body: options.body ? JSON.stringify(options.body) : void 0
@@ -2304,7 +2556,7 @@ function createAnthropicBatchAdapter(apiKey) {
2304
2556
  function translateToAnthropicParams(model, req) {
2305
2557
  const params = {
2306
2558
  model,
2307
- max_tokens: req.max_tokens || DEFAULT_MAX_TOKENS2
2559
+ max_tokens: resolveMaxTokens(model, req.messages, req.max_tokens || DEFAULT_MAX_TOKENS2)
2308
2560
  };
2309
2561
  const systemMessages = req.messages.filter((m) => m.role === "system");
2310
2562
  const nonSystemMessages = req.messages.filter((m) => m.role !== "system");
@@ -2478,6 +2730,284 @@ ${params.system}` : jsonInstruction;
2478
2730
  };
2479
2731
  }
2480
2732
 
2733
+ // src/providers/google-batch.ts
2734
+ var GEMINI_API_BASE2 = "https://generativelanguage.googleapis.com/v1beta";
2735
+ function createGoogleBatchAdapter(apiKey) {
2736
+ async function apiRequest(path2, options = {}) {
2737
+ const headers = {
2738
+ "Content-Type": "application/json",
2739
+ "x-goog-api-key": apiKey
2740
+ };
2741
+ const res = await fetchWithTimeout(`${GEMINI_API_BASE2}${path2}`, {
2742
+ method: options.method || "GET",
2743
+ headers,
2744
+ body: options.body ? JSON.stringify(options.body) : void 0
2745
+ });
2746
+ if (!res.ok) {
2747
+ let errorBody;
2748
+ try {
2749
+ errorBody = await res.json();
2750
+ } catch {
2751
+ errorBody = { message: res.statusText };
2752
+ }
2753
+ const msg = errorBody?.error?.message || errorBody?.message || res.statusText;
2754
+ throw new AnyModelError(res.status >= 500 ? 502 : res.status, msg, {
2755
+ provider_name: "google",
2756
+ raw: errorBody
2757
+ });
2758
+ }
2759
+ return res;
2760
+ }
2761
+ function translateRequestToGemini(model, req) {
2762
+ const body = {};
2763
+ const systemMessages = req.messages.filter((m) => m.role === "system");
2764
+ const nonSystemMessages = req.messages.filter((m) => m.role !== "system");
2765
+ if (systemMessages.length > 0) {
2766
+ body.systemInstruction = {
2767
+ parts: [{ text: systemMessages.map((m) => typeof m.content === "string" ? m.content : "").join("\n") }]
2768
+ };
2769
+ }
2770
+ body.contents = nonSystemMessages.map((m) => ({
2771
+ role: m.role === "assistant" ? "model" : "user",
2772
+ parts: typeof m.content === "string" ? [{ text: m.content }] : Array.isArray(m.content) ? m.content.map((p) => p.type === "text" ? { text: p.text } : { text: "" }) : [{ text: "" }]
2773
+ }));
2774
+ const generationConfig = {};
2775
+ if (req.temperature !== void 0) generationConfig.temperature = req.temperature;
2776
+ generationConfig.maxOutputTokens = req.max_tokens !== void 0 ? req.max_tokens : resolveMaxTokens(model, req.messages);
2777
+ if (req.top_p !== void 0) generationConfig.topP = req.top_p;
2778
+ if (req.top_k !== void 0) generationConfig.topK = req.top_k;
2779
+ if (req.stop !== void 0) {
2780
+ generationConfig.stopSequences = Array.isArray(req.stop) ? req.stop : [req.stop];
2781
+ }
2782
+ if (req.response_format) {
2783
+ if (req.response_format.type === "json_object") {
2784
+ generationConfig.responseMimeType = "application/json";
2785
+ } else if (req.response_format.type === "json_schema") {
2786
+ generationConfig.responseMimeType = "application/json";
2787
+ generationConfig.responseSchema = req.response_format.json_schema?.schema;
2788
+ }
2789
+ }
2790
+ if (Object.keys(generationConfig).length > 0) {
2791
+ body.generationConfig = generationConfig;
2792
+ }
2793
+ if (req.tools && req.tools.length > 0) {
2794
+ body.tools = [{
2795
+ functionDeclarations: req.tools.map((t) => ({
2796
+ name: t.function.name,
2797
+ description: t.function.description || "",
2798
+ parameters: t.function.parameters || {}
2799
+ }))
2800
+ }];
2801
+ if (req.tool_choice) {
2802
+ if (req.tool_choice === "auto") {
2803
+ body.toolConfig = { functionCallingConfig: { mode: "AUTO" } };
2804
+ } else if (req.tool_choice === "required") {
2805
+ body.toolConfig = { functionCallingConfig: { mode: "ANY" } };
2806
+ } else if (req.tool_choice === "none") {
2807
+ body.toolConfig = { functionCallingConfig: { mode: "NONE" } };
2808
+ } else if (typeof req.tool_choice === "object") {
2809
+ body.toolConfig = {
2810
+ functionCallingConfig: {
2811
+ mode: "ANY",
2812
+ allowedFunctionNames: [req.tool_choice.function.name]
2813
+ }
2814
+ };
2815
+ }
2816
+ }
2817
+ }
2818
+ return body;
2819
+ }
2820
+ function mapFinishReason(reason) {
2821
+ switch (reason) {
2822
+ case "STOP":
2823
+ return "stop";
2824
+ case "MAX_TOKENS":
2825
+ return "length";
2826
+ case "SAFETY":
2827
+ return "content_filter";
2828
+ case "RECITATION":
2829
+ return "content_filter";
2830
+ default:
2831
+ return "stop";
2832
+ }
2833
+ }
2834
+ function translateGeminiResponse(response, model) {
2835
+ const candidate = response.candidates?.[0];
2836
+ let content = "";
2837
+ const toolCalls = [];
2838
+ for (const part of candidate?.content?.parts || []) {
2839
+ if (part.text) {
2840
+ content += part.text;
2841
+ } else if (part.functionCall) {
2842
+ toolCalls.push({
2843
+ id: generateId("call"),
2844
+ type: "function",
2845
+ function: {
2846
+ name: part.functionCall.name,
2847
+ arguments: JSON.stringify(part.functionCall.args || {})
2848
+ }
2849
+ });
2850
+ }
2851
+ }
2852
+ const message = { role: "assistant", content };
2853
+ if (toolCalls.length > 0) {
2854
+ message.tool_calls = toolCalls;
2855
+ }
2856
+ const finishReason = toolCalls.length > 0 ? "tool_calls" : mapFinishReason(candidate?.finishReason || "STOP");
2857
+ return {
2858
+ id: generateId(),
2859
+ object: "chat.completion",
2860
+ created: Math.floor(Date.now() / 1e3),
2861
+ model: `google/${model}`,
2862
+ choices: [{ index: 0, message, finish_reason: finishReason }],
2863
+ usage: {
2864
+ prompt_tokens: response.usageMetadata?.promptTokenCount || 0,
2865
+ completion_tokens: response.usageMetadata?.candidatesTokenCount || 0,
2866
+ total_tokens: response.usageMetadata?.totalTokenCount || 0
2867
+ }
2868
+ };
2869
+ }
2870
+ function mapBatchState(state) {
2871
+ switch (state) {
2872
+ case "JOB_STATE_PENDING":
2873
+ return "pending";
2874
+ case "JOB_STATE_RUNNING":
2875
+ return "processing";
2876
+ case "JOB_STATE_SUCCEEDED":
2877
+ return "completed";
2878
+ case "JOB_STATE_FAILED":
2879
+ return "failed";
2880
+ case "JOB_STATE_CANCELLED":
2881
+ return "cancelled";
2882
+ case "JOB_STATE_EXPIRED":
2883
+ return "failed";
2884
+ default:
2885
+ return "pending";
2886
+ }
2887
+ }
2888
+ return {
2889
+ async createBatch(model, requests, _options) {
2890
+ const batchRequests = requests.map((req) => ({
2891
+ request: translateRequestToGemini(model, req),
2892
+ metadata: { key: req.custom_id }
2893
+ }));
2894
+ const res = await apiRequest(`/models/${model}:batchGenerateContent`, {
2895
+ method: "POST",
2896
+ body: {
2897
+ batch: {
2898
+ display_name: `anymodel-batch-${Date.now()}`,
2899
+ input_config: {
2900
+ requests: {
2901
+ requests: batchRequests
2902
+ }
2903
+ }
2904
+ }
2905
+ }
2906
+ });
2907
+ const data = await res.json();
2908
+ const batchName = data.name || data.batch?.name;
2909
+ if (!batchName) {
2910
+ throw new AnyModelError(502, "No batch name in Google response", {
2911
+ provider_name: "google",
2912
+ raw: data
2913
+ });
2914
+ }
2915
+ return {
2916
+ providerBatchId: batchName,
2917
+ metadata: {
2918
+ model,
2919
+ total_requests: requests.length
2920
+ }
2921
+ };
2922
+ },
2923
+ async pollBatch(providerBatchId) {
2924
+ const res = await apiRequest(`/${providerBatchId}`);
2925
+ const data = await res.json();
2926
+ const state = data.state || "JOB_STATE_PENDING";
2927
+ const status = mapBatchState(state);
2928
+ const totalCount = data.totalCount || data.metadata?.total_requests || 0;
2929
+ const successCount = data.succeededCount || 0;
2930
+ const failedCount = data.failedCount || 0;
2931
+ return {
2932
+ status,
2933
+ total: totalCount || successCount + failedCount,
2934
+ completed: successCount,
2935
+ failed: failedCount
2936
+ };
2937
+ },
2938
+ async getBatchResults(providerBatchId) {
2939
+ const batchRes = await apiRequest(`/${providerBatchId}`);
2940
+ const batchData = await batchRes.json();
2941
+ const results = [];
2942
+ const model = batchData.metadata?.model || "unknown";
2943
+ if (batchData.response?.inlinedResponses) {
2944
+ for (const item of batchData.response.inlinedResponses) {
2945
+ const customId = item.metadata?.key || `request-${results.length}`;
2946
+ if (item.response) {
2947
+ results.push({
2948
+ custom_id: customId,
2949
+ status: "success",
2950
+ response: translateGeminiResponse(item.response, model),
2951
+ error: null
2952
+ });
2953
+ } else if (item.error) {
2954
+ results.push({
2955
+ custom_id: customId,
2956
+ status: "error",
2957
+ response: null,
2958
+ error: {
2959
+ code: item.error.code || 500,
2960
+ message: item.error.message || "Batch item failed"
2961
+ }
2962
+ });
2963
+ }
2964
+ }
2965
+ return results;
2966
+ }
2967
+ const responsesFile = batchData.response?.responsesFileName || batchData.outputConfig?.file_name;
2968
+ if (responsesFile) {
2969
+ const downloadUrl = `${GEMINI_API_BASE2}/${responsesFile}:download?alt=media`;
2970
+ const fileRes = await fetchWithTimeout(downloadUrl, {
2971
+ headers: { "x-goog-api-key": apiKey }
2972
+ });
2973
+ if (!fileRes.ok) {
2974
+ throw new AnyModelError(502, "Failed to download batch results file", {
2975
+ provider_name: "google"
2976
+ });
2977
+ }
2978
+ const text = await fileRes.text();
2979
+ for (const line of text.trim().split("\n")) {
2980
+ if (!line) continue;
2981
+ const item = JSON.parse(line);
2982
+ const customId = item.key || item.metadata?.key || `request-${results.length}`;
2983
+ if (item.response) {
2984
+ results.push({
2985
+ custom_id: customId,
2986
+ status: "success",
2987
+ response: translateGeminiResponse(item.response, model),
2988
+ error: null
2989
+ });
2990
+ } else if (item.error) {
2991
+ results.push({
2992
+ custom_id: customId,
2993
+ status: "error",
2994
+ response: null,
2995
+ error: {
2996
+ code: item.error.code || 500,
2997
+ message: item.error.message || "Batch item failed"
2998
+ }
2999
+ });
3000
+ }
3001
+ }
3002
+ }
3003
+ return results;
3004
+ },
3005
+ async cancelBatch(providerBatchId) {
3006
+ await apiRequest(`/${providerBatchId}:cancel`, { method: "POST" });
3007
+ }
3008
+ };
3009
+ }
3010
+
2481
3011
  // src/client.ts
2482
3012
  var AnyModel = class {
2483
3013
  registry;
@@ -2493,6 +3023,7 @@ var AnyModel = class {
2493
3023
  constructor(config = {}) {
2494
3024
  this.config = resolveConfig(config);
2495
3025
  this.registry = new ProviderRegistry();
3026
+ setDefaultTimeout((this.config.defaults?.timeout ?? 120) * 1e3);
2496
3027
  if (this.config.io) {
2497
3028
  configureFsIO(this.config.io);
2498
3029
  }
@@ -2573,14 +3104,17 @@ var AnyModel = class {
2573
3104
  if (googleKey) {
2574
3105
  this.registry.register("google", createGoogleAdapter(googleKey));
2575
3106
  }
3107
+ const perplexityKey = config.perplexity?.apiKey || process.env.PERPLEXITY_API_KEY;
3108
+ if (perplexityKey) {
3109
+ this.registry.register("perplexity", createPerplexityAdapter(perplexityKey));
3110
+ }
2576
3111
  const builtinProviders = [
2577
3112
  { name: "mistral", baseURL: "https://api.mistral.ai/v1", configKey: "mistral", envVar: "MISTRAL_API_KEY" },
2578
3113
  { name: "groq", baseURL: "https://api.groq.com/openai/v1", configKey: "groq", envVar: "GROQ_API_KEY" },
2579
3114
  { name: "deepseek", baseURL: "https://api.deepseek.com", configKey: "deepseek", envVar: "DEEPSEEK_API_KEY" },
2580
3115
  { name: "xai", baseURL: "https://api.x.ai/v1", configKey: "xai", envVar: "XAI_API_KEY" },
2581
3116
  { name: "together", baseURL: "https://api.together.xyz/v1", configKey: "together", envVar: "TOGETHER_API_KEY" },
2582
- { name: "fireworks", baseURL: "https://api.fireworks.ai/inference/v1", configKey: "fireworks", envVar: "FIREWORKS_API_KEY" },
2583
- { name: "perplexity", baseURL: "https://api.perplexity.ai", configKey: "perplexity", envVar: "PERPLEXITY_API_KEY" }
3117
+ { name: "fireworks", baseURL: "https://api.fireworks.ai/inference/v1", configKey: "fireworks", envVar: "FIREWORKS_API_KEY" }
2584
3118
  ];
2585
3119
  for (const { name, baseURL, configKey, envVar } of builtinProviders) {
2586
3120
  const providerConfig = config[configKey];
@@ -2610,6 +3144,10 @@ var AnyModel = class {
2610
3144
  if (anthropicKey) {
2611
3145
  this.batchManager.registerBatchAdapter("anthropic", createAnthropicBatchAdapter(anthropicKey));
2612
3146
  }
3147
+ const googleKey = config.google?.apiKey || process.env.GOOGLE_API_KEY;
3148
+ if (googleKey) {
3149
+ this.batchManager.registerBatchAdapter("google", createGoogleBatchAdapter(googleKey));
3150
+ }
2613
3151
  }
2614
3152
  applyDefaults(request) {
2615
3153
  const defaults = this.config.defaults;