@probeo/anymodel 0.4.0 → 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.d.cts CHANGED
@@ -77,6 +77,7 @@ interface ChatCompletionRequest {
77
77
  tools?: Tool[];
78
78
  tool_choice?: ToolChoice;
79
79
  user?: string;
80
+ service_tier?: 'auto' | 'flex';
80
81
  models?: string[];
81
82
  route?: 'fallback';
82
83
  transforms?: string[];
@@ -629,4 +630,21 @@ declare function createOpenAIBatchAdapter(apiKey: string): BatchAdapter;
629
630
 
630
631
  declare function createAnthropicBatchAdapter(apiKey: string): BatchAdapter;
631
632
 
632
- export { AnyModel, type AnyModelConfig, AnyModelError, type AnyModelErrorMetadata, type BatchAdapter, type BatchCreateRequest, BatchManager, type BatchMode, type BatchObject, type BatchPollOptions, type BatchRequestItem, type BatchResultItem, type BatchResults, type BatchStatus, BatchStore, type BatchUsageSummary, type ChatCompletion, type ChatCompletionChoice, type ChatCompletionChunk, type ChatCompletionRequest, type ChunkChoice, type ChunkDelta, type ContentPart, type CustomProviderConfig, type FinishReason, type GenerationStats, GenerationStatsStore, type Message, type ModelArchitecture, type ModelInfo, type ModelPricing, type ModelTopProvider, type NativeBatchStatus, type ProviderAdapter, type ProviderConfig, type ProviderPreferences, type ResponseFormat, type Role, type ServerOptions, type Tool, type ToolCall, type ToolChoice, type Usage, appendFileQueued, configureFsIO, createAnthropicBatchAdapter, createAnyModelServer, createOpenAIBatchAdapter, ensureDir, getFsQueueStatus, joinPath, readFileQueued, resolveConfig, startServer, waitForFsQueuesIdle, writeFileFlushedQueued, writeFileQueued };
633
+ declare function createGoogleBatchAdapter(apiKey: string): BatchAdapter;
634
+
635
+ /**
636
+ * Rough token estimation and model-limit lookups for automatic max_tokens calculation.
637
+ */
638
+ /** Estimate the number of tokens in a string (~4 chars per token). */
639
+ declare function estimateTokenCount(text: string): number;
640
+ /**
641
+ * Determine the best max_tokens value for a request.
642
+ *
643
+ * - If the caller already supplied a value, return it unchanged.
644
+ * - Otherwise estimate input tokens, apply a 5 % safety margin, then
645
+ * return min(maxCompletionTokens, contextLength - estimatedInput).
646
+ * - The result is clamped to at least 1.
647
+ */
648
+ declare function resolveMaxTokens(model: string, messages: unknown[], userMaxTokens?: number): number;
649
+
650
+ export { AnyModel, type AnyModelConfig, AnyModelError, type AnyModelErrorMetadata, type BatchAdapter, type BatchCreateRequest, BatchManager, type BatchMode, type BatchObject, type BatchPollOptions, type BatchRequestItem, type BatchResultItem, type BatchResults, type BatchStatus, BatchStore, type BatchUsageSummary, type ChatCompletion, type ChatCompletionChoice, type ChatCompletionChunk, type ChatCompletionRequest, type ChunkChoice, type ChunkDelta, type ContentPart, type CustomProviderConfig, type FinishReason, type GenerationStats, GenerationStatsStore, type Message, type ModelArchitecture, type ModelInfo, type ModelPricing, type ModelTopProvider, type NativeBatchStatus, type ProviderAdapter, type ProviderConfig, type ProviderPreferences, type ResponseFormat, type Role, type ServerOptions, type Tool, type ToolCall, type ToolChoice, type Usage, appendFileQueued, configureFsIO, createAnthropicBatchAdapter, createAnyModelServer, createGoogleBatchAdapter, createOpenAIBatchAdapter, ensureDir, estimateTokenCount, getFsQueueStatus, joinPath, readFileQueued, resolveConfig, resolveMaxTokens, startServer, waitForFsQueuesIdle, writeFileFlushedQueued, writeFileQueued };
package/dist/index.d.ts CHANGED
@@ -77,6 +77,7 @@ interface ChatCompletionRequest {
77
77
  tools?: Tool[];
78
78
  tool_choice?: ToolChoice;
79
79
  user?: string;
80
+ service_tier?: 'auto' | 'flex';
80
81
  models?: string[];
81
82
  route?: 'fallback';
82
83
  transforms?: string[];
@@ -629,4 +630,21 @@ declare function createOpenAIBatchAdapter(apiKey: string): BatchAdapter;
629
630
 
630
631
  declare function createAnthropicBatchAdapter(apiKey: string): BatchAdapter;
631
632
 
632
- export { AnyModel, type AnyModelConfig, AnyModelError, type AnyModelErrorMetadata, type BatchAdapter, type BatchCreateRequest, BatchManager, type BatchMode, type BatchObject, type BatchPollOptions, type BatchRequestItem, type BatchResultItem, type BatchResults, type BatchStatus, BatchStore, type BatchUsageSummary, type ChatCompletion, type ChatCompletionChoice, type ChatCompletionChunk, type ChatCompletionRequest, type ChunkChoice, type ChunkDelta, type ContentPart, type CustomProviderConfig, type FinishReason, type GenerationStats, GenerationStatsStore, type Message, type ModelArchitecture, type ModelInfo, type ModelPricing, type ModelTopProvider, type NativeBatchStatus, type ProviderAdapter, type ProviderConfig, type ProviderPreferences, type ResponseFormat, type Role, type ServerOptions, type Tool, type ToolCall, type ToolChoice, type Usage, appendFileQueued, configureFsIO, createAnthropicBatchAdapter, createAnyModelServer, createOpenAIBatchAdapter, ensureDir, getFsQueueStatus, joinPath, readFileQueued, resolveConfig, startServer, waitForFsQueuesIdle, writeFileFlushedQueued, writeFileQueued };
633
+ declare function createGoogleBatchAdapter(apiKey: string): BatchAdapter;
634
+
635
+ /**
636
+ * Rough token estimation and model-limit lookups for automatic max_tokens calculation.
637
+ */
638
+ /** Estimate the number of tokens in a string (~4 chars per token). */
639
+ declare function estimateTokenCount(text: string): number;
640
+ /**
641
+ * Determine the best max_tokens value for a request.
642
+ *
643
+ * - If the caller already supplied a value, return it unchanged.
644
+ * - Otherwise estimate input tokens, apply a 5 % safety margin, then
645
+ * return min(maxCompletionTokens, contextLength - estimatedInput).
646
+ * - The result is clamped to at least 1.
647
+ */
648
+ declare function resolveMaxTokens(model: string, messages: unknown[], userMaxTokens?: number): number;
649
+
650
+ export { AnyModel, type AnyModelConfig, AnyModelError, type AnyModelErrorMetadata, type BatchAdapter, type BatchCreateRequest, BatchManager, type BatchMode, type BatchObject, type BatchPollOptions, type BatchRequestItem, type BatchResultItem, type BatchResults, type BatchStatus, BatchStore, type BatchUsageSummary, type ChatCompletion, type ChatCompletionChoice, type ChatCompletionChunk, type ChatCompletionRequest, type ChunkChoice, type ChunkDelta, type ContentPart, type CustomProviderConfig, type FinishReason, type GenerationStats, GenerationStatsStore, type Message, type ModelArchitecture, type ModelInfo, type ModelPricing, type ModelTopProvider, type NativeBatchStatus, type ProviderAdapter, type ProviderConfig, type ProviderPreferences, type ResponseFormat, type Role, type ServerOptions, type Tool, type ToolCall, type ToolChoice, type Usage, appendFileQueued, configureFsIO, createAnthropicBatchAdapter, createAnyModelServer, createGoogleBatchAdapter, createOpenAIBatchAdapter, ensureDir, estimateTokenCount, getFsQueueStatus, joinPath, readFileQueued, resolveConfig, resolveMaxTokens, startServer, waitForFsQueuesIdle, writeFileFlushedQueued, writeFileQueued };
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)
@@ -1366,7 +1389,7 @@ var MODELS = [
1366
1389
  ];
1367
1390
  function createPerplexityAdapter(apiKey) {
1368
1391
  async function makeRequest(path2, body, method = "POST") {
1369
- const res = await fetch(`${PERPLEXITY_API_BASE}${path2}`, {
1392
+ const res = await fetchWithTimeout(`${PERPLEXITY_API_BASE}${path2}`, {
1370
1393
  method,
1371
1394
  headers: {
1372
1395
  "Content-Type": "application/json",
@@ -2247,6 +2270,54 @@ var BatchManager = class {
2247
2270
  }
2248
2271
  };
2249
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
+
2250
2321
  // src/providers/openai-batch.ts
2251
2322
  var OPENAI_API_BASE2 = "https://api.openai.com/v1";
2252
2323
  function createOpenAIBatchAdapter(apiKey) {
@@ -2261,7 +2332,7 @@ function createOpenAIBatchAdapter(apiKey) {
2261
2332
  headers["Content-Type"] = "application/json";
2262
2333
  fetchBody = JSON.stringify(options.body);
2263
2334
  }
2264
- const res = await fetch(`${OPENAI_API_BASE2}${path2}`, {
2335
+ const res = await fetchWithTimeout(`${OPENAI_API_BASE2}${path2}`, {
2265
2336
  method: options.method || "GET",
2266
2337
  headers,
2267
2338
  body: fetchBody
@@ -2287,7 +2358,7 @@ function createOpenAIBatchAdapter(apiKey) {
2287
2358
  model,
2288
2359
  messages: req.messages
2289
2360
  };
2290
- 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);
2291
2362
  if (req.temperature !== void 0) body.temperature = req.temperature;
2292
2363
  if (req.top_p !== void 0) body.top_p = req.top_p;
2293
2364
  if (req.stop !== void 0) body.stop = req.stop;
@@ -2446,7 +2517,7 @@ function createAnthropicBatchAdapter(apiKey) {
2446
2517
  "anthropic-version": ANTHROPIC_VERSION2,
2447
2518
  "Content-Type": "application/json"
2448
2519
  };
2449
- const res = await fetch(`${ANTHROPIC_API_BASE2}${path2}`, {
2520
+ const res = await fetchWithTimeout(`${ANTHROPIC_API_BASE2}${path2}`, {
2450
2521
  method: options.method || "GET",
2451
2522
  headers,
2452
2523
  body: options.body ? JSON.stringify(options.body) : void 0
@@ -2469,7 +2540,7 @@ function createAnthropicBatchAdapter(apiKey) {
2469
2540
  function translateToAnthropicParams(model, req) {
2470
2541
  const params = {
2471
2542
  model,
2472
- max_tokens: req.max_tokens || DEFAULT_MAX_TOKENS2
2543
+ max_tokens: resolveMaxTokens(model, req.messages, req.max_tokens || DEFAULT_MAX_TOKENS2)
2473
2544
  };
2474
2545
  const systemMessages = req.messages.filter((m) => m.role === "system");
2475
2546
  const nonSystemMessages = req.messages.filter((m) => m.role !== "system");
@@ -2643,6 +2714,284 @@ ${params.system}` : jsonInstruction;
2643
2714
  };
2644
2715
  }
2645
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
+
2646
2995
  // src/client.ts
2647
2996
  var AnyModel = class {
2648
2997
  registry;
@@ -2658,6 +3007,7 @@ var AnyModel = class {
2658
3007
  constructor(config = {}) {
2659
3008
  this.config = resolveConfig(config);
2660
3009
  this.registry = new ProviderRegistry();
3010
+ setDefaultTimeout((this.config.defaults?.timeout ?? 120) * 1e3);
2661
3011
  if (this.config.io) {
2662
3012
  configureFsIO(this.config.io);
2663
3013
  }
@@ -2778,6 +3128,10 @@ var AnyModel = class {
2778
3128
  if (anthropicKey) {
2779
3129
  this.batchManager.registerBatchAdapter("anthropic", createAnthropicBatchAdapter(anthropicKey));
2780
3130
  }
3131
+ const googleKey = config.google?.apiKey || process.env.GOOGLE_API_KEY;
3132
+ if (googleKey) {
3133
+ this.batchManager.registerBatchAdapter("google", createGoogleBatchAdapter(googleKey));
3134
+ }
2781
3135
  }
2782
3136
  applyDefaults(request) {
2783
3137
  const defaults = this.config.defaults;
@@ -2956,12 +3310,15 @@ export {
2956
3310
  configureFsIO,
2957
3311
  createAnthropicBatchAdapter,
2958
3312
  createAnyModelServer,
3313
+ createGoogleBatchAdapter,
2959
3314
  createOpenAIBatchAdapter,
2960
3315
  ensureDir,
3316
+ estimateTokenCount,
2961
3317
  getFsQueueStatus,
2962
3318
  joinPath,
2963
3319
  readFileQueued,
2964
3320
  resolveConfig,
3321
+ resolveMaxTokens,
2965
3322
  startServer,
2966
3323
  waitForFsQueuesIdle,
2967
3324
  writeFileFlushedQueued,