@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.cjs CHANGED
@@ -39,12 +39,15 @@ __export(src_exports, {
39
39
  configureFsIO: () => configureFsIO,
40
40
  createAnthropicBatchAdapter: () => createAnthropicBatchAdapter,
41
41
  createAnyModelServer: () => createAnyModelServer,
42
+ createGoogleBatchAdapter: () => createGoogleBatchAdapter,
42
43
  createOpenAIBatchAdapter: () => createOpenAIBatchAdapter,
43
44
  ensureDir: () => ensureDir,
45
+ estimateTokenCount: () => estimateTokenCount,
44
46
  getFsQueueStatus: () => getFsQueueStatus,
45
47
  joinPath: () => joinPath,
46
48
  readFileQueued: () => readFileQueued,
47
49
  resolveConfig: () => resolveConfig,
50
+ resolveMaxTokens: () => resolveMaxTokens,
48
51
  startServer: () => startServer,
49
52
  waitForFsQueuesIdle: () => waitForFsQueuesIdle,
50
53
  writeFileFlushedQueued: () => writeFileFlushedQueued,
@@ -534,6 +537,25 @@ var Router = class {
534
537
  }
535
538
  };
536
539
 
540
+ // src/utils/fetch-with-timeout.ts
541
+ var _defaultTimeout = 12e4;
542
+ var _flexTimeout = 6e5;
543
+ function setDefaultTimeout(ms) {
544
+ _defaultTimeout = ms;
545
+ }
546
+ function getFlexTimeout() {
547
+ return _flexTimeout;
548
+ }
549
+ function fetchWithTimeout(url, init, timeoutMs) {
550
+ const ms = timeoutMs ?? _defaultTimeout;
551
+ const signal = AbortSignal.timeout(ms);
552
+ if (init?.signal) {
553
+ const combined = AbortSignal.any([signal, init.signal]);
554
+ return fetch(url, { ...init, signal: combined });
555
+ }
556
+ return fetch(url, { ...init, signal });
557
+ }
558
+
537
559
  // src/providers/openai.ts
538
560
  var OPENAI_API_BASE = "https://api.openai.com/v1";
539
561
  var SUPPORTED_PARAMS = /* @__PURE__ */ new Set([
@@ -551,19 +573,20 @@ var SUPPORTED_PARAMS = /* @__PURE__ */ new Set([
551
573
  "tools",
552
574
  "tool_choice",
553
575
  "user",
554
- "logit_bias"
576
+ "logit_bias",
577
+ "service_tier"
555
578
  ]);
556
579
  function createOpenAIAdapter(apiKey, baseURL) {
557
580
  const base = baseURL || OPENAI_API_BASE;
558
- async function makeRequest(path2, body, method = "POST") {
559
- const res = await fetch(`${base}${path2}`, {
581
+ async function makeRequest(path2, body, method = "POST", timeoutMs) {
582
+ const res = await fetchWithTimeout(`${base}${path2}`, {
560
583
  method,
561
584
  headers: {
562
585
  "Content-Type": "application/json",
563
586
  "Authorization": `Bearer ${apiKey}`
564
587
  },
565
588
  body: body ? JSON.stringify(body) : void 0
566
- });
589
+ }, timeoutMs);
567
590
  if (!res.ok) {
568
591
  let errorBody;
569
592
  try {
@@ -611,6 +634,7 @@ function createOpenAIAdapter(apiKey, baseURL) {
611
634
  if (request.tools !== void 0) body.tools = request.tools;
612
635
  if (request.tool_choice !== void 0) body.tool_choice = request.tool_choice;
613
636
  if (request.user !== void 0) body.user = request.user;
637
+ if (request.service_tier !== void 0) body.service_tier = request.service_tier;
614
638
  return body;
615
639
  }
616
640
  const adapter = {
@@ -712,13 +736,15 @@ function createOpenAIAdapter(apiKey, baseURL) {
712
736
  },
713
737
  async sendRequest(request) {
714
738
  const body = buildRequestBody(request);
715
- const res = await makeRequest("/chat/completions", body);
739
+ const timeout = request.service_tier === "flex" ? getFlexTimeout() : void 0;
740
+ const res = await makeRequest("/chat/completions", body, "POST", timeout);
716
741
  const json = await res.json();
717
742
  return adapter.translateResponse(json);
718
743
  },
719
744
  async sendStreamingRequest(request) {
720
745
  const body = buildRequestBody({ ...request, stream: true });
721
- const res = await makeRequest("/chat/completions", body);
746
+ const timeout = request.service_tier === "flex" ? getFlexTimeout() : void 0;
747
+ const res = await makeRequest("/chat/completions", body, "POST", timeout);
722
748
  if (!res.body) {
723
749
  throw new AnyModelError(502, "No response body for streaming request", {
724
750
  provider_name: "openai"
@@ -765,7 +791,7 @@ var FALLBACK_MODELS = [
765
791
  ];
766
792
  function createAnthropicAdapter(apiKey) {
767
793
  async function makeRequest(path2, body, stream = false) {
768
- const res = await fetch(`${ANTHROPIC_API_BASE}${path2}`, {
794
+ const res = await fetchWithTimeout(`${ANTHROPIC_API_BASE}${path2}`, {
769
795
  method: "POST",
770
796
  headers: {
771
797
  "Content-Type": "application/json",
@@ -1022,7 +1048,7 @@ ${body.system}` : jsonInstruction;
1022
1048
  },
1023
1049
  async listModels() {
1024
1050
  try {
1025
- const res = await fetch(`${ANTHROPIC_API_BASE}/models`, {
1051
+ const res = await fetchWithTimeout(`${ANTHROPIC_API_BASE}/models`, {
1026
1052
  method: "GET",
1027
1053
  headers: {
1028
1054
  "x-api-key": apiKey,
@@ -1307,7 +1333,7 @@ function createGoogleAdapter(apiKey) {
1307
1333
  },
1308
1334
  async listModels() {
1309
1335
  try {
1310
- const res = await fetch(`${GEMINI_API_BASE}/models?key=${apiKey}`);
1336
+ const res = await fetchWithTimeout(`${GEMINI_API_BASE}/models?key=${apiKey}`);
1311
1337
  if (!res.ok) return FALLBACK_MODELS2;
1312
1338
  const data = await res.json();
1313
1339
  const models = data.models || [];
@@ -1342,12 +1368,12 @@ function createGoogleAdapter(apiKey) {
1342
1368
  return SUPPORTED_PARAMS3.has(param);
1343
1369
  },
1344
1370
  supportsBatch() {
1345
- return false;
1371
+ return true;
1346
1372
  },
1347
1373
  async sendRequest(request) {
1348
1374
  const body = translateRequest(request);
1349
1375
  const url = getModelEndpoint(request.model, false);
1350
- const res = await fetch(url, {
1376
+ const res = await fetchWithTimeout(url, {
1351
1377
  method: "POST",
1352
1378
  headers: { "Content-Type": "application/json" },
1353
1379
  body: JSON.stringify(body)
@@ -1370,7 +1396,7 @@ function createGoogleAdapter(apiKey) {
1370
1396
  async sendStreamingRequest(request) {
1371
1397
  const body = translateRequest(request);
1372
1398
  const url = getModelEndpoint(request.model, true);
1373
- const res = await fetch(url, {
1399
+ const res = await fetchWithTimeout(url, {
1374
1400
  method: "POST",
1375
1401
  headers: { "Content-Type": "application/json" },
1376
1402
  body: JSON.stringify(body)
@@ -1420,7 +1446,7 @@ var MODELS = [
1420
1446
  ];
1421
1447
  function createPerplexityAdapter(apiKey) {
1422
1448
  async function makeRequest(path2, body, method = "POST") {
1423
- const res = await fetch(`${PERPLEXITY_API_BASE}${path2}`, {
1449
+ const res = await fetchWithTimeout(`${PERPLEXITY_API_BASE}${path2}`, {
1424
1450
  method,
1425
1451
  headers: {
1426
1452
  "Content-Type": "application/json",
@@ -2301,6 +2327,54 @@ var BatchManager = class {
2301
2327
  }
2302
2328
  };
2303
2329
 
2330
+ // src/utils/token-estimate.ts
2331
+ var CHARS_PER_TOKEN2 = 4;
2332
+ function estimateTokenCount(text) {
2333
+ return Math.ceil(text.length / CHARS_PER_TOKEN2);
2334
+ }
2335
+ var MODEL_LIMITS = [
2336
+ // OpenAI
2337
+ { pattern: "gpt-4o-mini", limit: { contextLength: 128e3, maxCompletionTokens: 16384 } },
2338
+ { pattern: "gpt-4o", limit: { contextLength: 128e3, maxCompletionTokens: 16384 } },
2339
+ { pattern: "gpt-4-turbo", limit: { contextLength: 128e3, maxCompletionTokens: 4096 } },
2340
+ { pattern: "gpt-3.5-turbo", limit: { contextLength: 16385, maxCompletionTokens: 4096 } },
2341
+ { pattern: "o1", limit: { contextLength: 2e5, maxCompletionTokens: 1e5 } },
2342
+ { pattern: "o3", limit: { contextLength: 2e5, maxCompletionTokens: 1e5 } },
2343
+ { pattern: "o4-mini", limit: { contextLength: 2e5, maxCompletionTokens: 1e5 } },
2344
+ // Anthropic
2345
+ { pattern: "claude-opus-4", limit: { contextLength: 2e5, maxCompletionTokens: 32768 } },
2346
+ { pattern: "claude-sonnet-4", limit: { contextLength: 2e5, maxCompletionTokens: 16384 } },
2347
+ { pattern: "claude-haiku-4", limit: { contextLength: 2e5, maxCompletionTokens: 8192 } },
2348
+ { pattern: "claude-3.5-sonnet", limit: { contextLength: 2e5, maxCompletionTokens: 8192 } },
2349
+ { pattern: "claude-3-opus", limit: { contextLength: 2e5, maxCompletionTokens: 4096 } },
2350
+ // Google
2351
+ { pattern: "gemini-2.5-pro", limit: { contextLength: 1048576, maxCompletionTokens: 65536 } },
2352
+ { pattern: "gemini-2.5-flash", limit: { contextLength: 1048576, maxCompletionTokens: 65536 } },
2353
+ { pattern: "gemini-2.0-flash", limit: { contextLength: 1048576, maxCompletionTokens: 65536 } },
2354
+ { pattern: "gemini-1.5-pro", limit: { contextLength: 2097152, maxCompletionTokens: 8192 } },
2355
+ { pattern: "gemini-1.5-flash", limit: { contextLength: 1048576, maxCompletionTokens: 8192 } }
2356
+ ];
2357
+ var DEFAULT_LIMIT = { contextLength: 128e3, maxCompletionTokens: 4096 };
2358
+ function getModelLimits(model) {
2359
+ const bare = model.includes("/") ? model.slice(model.indexOf("/") + 1) : model;
2360
+ for (const entry of MODEL_LIMITS) {
2361
+ if (bare.startsWith(entry.pattern) || bare.includes(entry.pattern)) {
2362
+ return entry.limit;
2363
+ }
2364
+ }
2365
+ return DEFAULT_LIMIT;
2366
+ }
2367
+ function resolveMaxTokens(model, messages, userMaxTokens) {
2368
+ if (userMaxTokens !== void 0) return userMaxTokens;
2369
+ const inputChars = JSON.stringify(messages).length;
2370
+ const estimatedInput = Math.ceil(inputChars / CHARS_PER_TOKEN2);
2371
+ const estimatedWithMargin = Math.ceil(estimatedInput * 1.05);
2372
+ const limits = getModelLimits(model);
2373
+ const available = limits.contextLength - estimatedWithMargin;
2374
+ const result = Math.min(limits.maxCompletionTokens, available);
2375
+ return Math.max(1, result);
2376
+ }
2377
+
2304
2378
  // src/providers/openai-batch.ts
2305
2379
  var OPENAI_API_BASE2 = "https://api.openai.com/v1";
2306
2380
  function createOpenAIBatchAdapter(apiKey) {
@@ -2315,7 +2389,7 @@ function createOpenAIBatchAdapter(apiKey) {
2315
2389
  headers["Content-Type"] = "application/json";
2316
2390
  fetchBody = JSON.stringify(options.body);
2317
2391
  }
2318
- const res = await fetch(`${OPENAI_API_BASE2}${path2}`, {
2392
+ const res = await fetchWithTimeout(`${OPENAI_API_BASE2}${path2}`, {
2319
2393
  method: options.method || "GET",
2320
2394
  headers,
2321
2395
  body: fetchBody
@@ -2341,7 +2415,7 @@ function createOpenAIBatchAdapter(apiKey) {
2341
2415
  model,
2342
2416
  messages: req.messages
2343
2417
  };
2344
- if (req.max_tokens !== void 0) body.max_tokens = req.max_tokens;
2418
+ body.max_tokens = req.max_tokens !== void 0 ? req.max_tokens : resolveMaxTokens(model, req.messages);
2345
2419
  if (req.temperature !== void 0) body.temperature = req.temperature;
2346
2420
  if (req.top_p !== void 0) body.top_p = req.top_p;
2347
2421
  if (req.stop !== void 0) body.stop = req.stop;
@@ -2500,7 +2574,7 @@ function createAnthropicBatchAdapter(apiKey) {
2500
2574
  "anthropic-version": ANTHROPIC_VERSION2,
2501
2575
  "Content-Type": "application/json"
2502
2576
  };
2503
- const res = await fetch(`${ANTHROPIC_API_BASE2}${path2}`, {
2577
+ const res = await fetchWithTimeout(`${ANTHROPIC_API_BASE2}${path2}`, {
2504
2578
  method: options.method || "GET",
2505
2579
  headers,
2506
2580
  body: options.body ? JSON.stringify(options.body) : void 0
@@ -2523,7 +2597,7 @@ function createAnthropicBatchAdapter(apiKey) {
2523
2597
  function translateToAnthropicParams(model, req) {
2524
2598
  const params = {
2525
2599
  model,
2526
- max_tokens: req.max_tokens || DEFAULT_MAX_TOKENS2
2600
+ max_tokens: resolveMaxTokens(model, req.messages, req.max_tokens || DEFAULT_MAX_TOKENS2)
2527
2601
  };
2528
2602
  const systemMessages = req.messages.filter((m) => m.role === "system");
2529
2603
  const nonSystemMessages = req.messages.filter((m) => m.role !== "system");
@@ -2697,6 +2771,284 @@ ${params.system}` : jsonInstruction;
2697
2771
  };
2698
2772
  }
2699
2773
 
2774
+ // src/providers/google-batch.ts
2775
+ var GEMINI_API_BASE2 = "https://generativelanguage.googleapis.com/v1beta";
2776
+ function createGoogleBatchAdapter(apiKey) {
2777
+ async function apiRequest(path2, options = {}) {
2778
+ const headers = {
2779
+ "Content-Type": "application/json",
2780
+ "x-goog-api-key": apiKey
2781
+ };
2782
+ const res = await fetchWithTimeout(`${GEMINI_API_BASE2}${path2}`, {
2783
+ method: options.method || "GET",
2784
+ headers,
2785
+ body: options.body ? JSON.stringify(options.body) : void 0
2786
+ });
2787
+ if (!res.ok) {
2788
+ let errorBody;
2789
+ try {
2790
+ errorBody = await res.json();
2791
+ } catch {
2792
+ errorBody = { message: res.statusText };
2793
+ }
2794
+ const msg = errorBody?.error?.message || errorBody?.message || res.statusText;
2795
+ throw new AnyModelError(res.status >= 500 ? 502 : res.status, msg, {
2796
+ provider_name: "google",
2797
+ raw: errorBody
2798
+ });
2799
+ }
2800
+ return res;
2801
+ }
2802
+ function translateRequestToGemini(model, req) {
2803
+ const body = {};
2804
+ const systemMessages = req.messages.filter((m) => m.role === "system");
2805
+ const nonSystemMessages = req.messages.filter((m) => m.role !== "system");
2806
+ if (systemMessages.length > 0) {
2807
+ body.systemInstruction = {
2808
+ parts: [{ text: systemMessages.map((m) => typeof m.content === "string" ? m.content : "").join("\n") }]
2809
+ };
2810
+ }
2811
+ body.contents = nonSystemMessages.map((m) => ({
2812
+ role: m.role === "assistant" ? "model" : "user",
2813
+ parts: typeof m.content === "string" ? [{ text: m.content }] : Array.isArray(m.content) ? m.content.map((p) => p.type === "text" ? { text: p.text } : { text: "" }) : [{ text: "" }]
2814
+ }));
2815
+ const generationConfig = {};
2816
+ if (req.temperature !== void 0) generationConfig.temperature = req.temperature;
2817
+ generationConfig.maxOutputTokens = req.max_tokens !== void 0 ? req.max_tokens : resolveMaxTokens(model, req.messages);
2818
+ if (req.top_p !== void 0) generationConfig.topP = req.top_p;
2819
+ if (req.top_k !== void 0) generationConfig.topK = req.top_k;
2820
+ if (req.stop !== void 0) {
2821
+ generationConfig.stopSequences = Array.isArray(req.stop) ? req.stop : [req.stop];
2822
+ }
2823
+ if (req.response_format) {
2824
+ if (req.response_format.type === "json_object") {
2825
+ generationConfig.responseMimeType = "application/json";
2826
+ } else if (req.response_format.type === "json_schema") {
2827
+ generationConfig.responseMimeType = "application/json";
2828
+ generationConfig.responseSchema = req.response_format.json_schema?.schema;
2829
+ }
2830
+ }
2831
+ if (Object.keys(generationConfig).length > 0) {
2832
+ body.generationConfig = generationConfig;
2833
+ }
2834
+ if (req.tools && req.tools.length > 0) {
2835
+ body.tools = [{
2836
+ functionDeclarations: req.tools.map((t) => ({
2837
+ name: t.function.name,
2838
+ description: t.function.description || "",
2839
+ parameters: t.function.parameters || {}
2840
+ }))
2841
+ }];
2842
+ if (req.tool_choice) {
2843
+ if (req.tool_choice === "auto") {
2844
+ body.toolConfig = { functionCallingConfig: { mode: "AUTO" } };
2845
+ } else if (req.tool_choice === "required") {
2846
+ body.toolConfig = { functionCallingConfig: { mode: "ANY" } };
2847
+ } else if (req.tool_choice === "none") {
2848
+ body.toolConfig = { functionCallingConfig: { mode: "NONE" } };
2849
+ } else if (typeof req.tool_choice === "object") {
2850
+ body.toolConfig = {
2851
+ functionCallingConfig: {
2852
+ mode: "ANY",
2853
+ allowedFunctionNames: [req.tool_choice.function.name]
2854
+ }
2855
+ };
2856
+ }
2857
+ }
2858
+ }
2859
+ return body;
2860
+ }
2861
+ function mapFinishReason(reason) {
2862
+ switch (reason) {
2863
+ case "STOP":
2864
+ return "stop";
2865
+ case "MAX_TOKENS":
2866
+ return "length";
2867
+ case "SAFETY":
2868
+ return "content_filter";
2869
+ case "RECITATION":
2870
+ return "content_filter";
2871
+ default:
2872
+ return "stop";
2873
+ }
2874
+ }
2875
+ function translateGeminiResponse(response, model) {
2876
+ const candidate = response.candidates?.[0];
2877
+ let content = "";
2878
+ const toolCalls = [];
2879
+ for (const part of candidate?.content?.parts || []) {
2880
+ if (part.text) {
2881
+ content += part.text;
2882
+ } else if (part.functionCall) {
2883
+ toolCalls.push({
2884
+ id: generateId("call"),
2885
+ type: "function",
2886
+ function: {
2887
+ name: part.functionCall.name,
2888
+ arguments: JSON.stringify(part.functionCall.args || {})
2889
+ }
2890
+ });
2891
+ }
2892
+ }
2893
+ const message = { role: "assistant", content };
2894
+ if (toolCalls.length > 0) {
2895
+ message.tool_calls = toolCalls;
2896
+ }
2897
+ const finishReason = toolCalls.length > 0 ? "tool_calls" : mapFinishReason(candidate?.finishReason || "STOP");
2898
+ return {
2899
+ id: generateId(),
2900
+ object: "chat.completion",
2901
+ created: Math.floor(Date.now() / 1e3),
2902
+ model: `google/${model}`,
2903
+ choices: [{ index: 0, message, finish_reason: finishReason }],
2904
+ usage: {
2905
+ prompt_tokens: response.usageMetadata?.promptTokenCount || 0,
2906
+ completion_tokens: response.usageMetadata?.candidatesTokenCount || 0,
2907
+ total_tokens: response.usageMetadata?.totalTokenCount || 0
2908
+ }
2909
+ };
2910
+ }
2911
+ function mapBatchState(state) {
2912
+ switch (state) {
2913
+ case "JOB_STATE_PENDING":
2914
+ return "pending";
2915
+ case "JOB_STATE_RUNNING":
2916
+ return "processing";
2917
+ case "JOB_STATE_SUCCEEDED":
2918
+ return "completed";
2919
+ case "JOB_STATE_FAILED":
2920
+ return "failed";
2921
+ case "JOB_STATE_CANCELLED":
2922
+ return "cancelled";
2923
+ case "JOB_STATE_EXPIRED":
2924
+ return "failed";
2925
+ default:
2926
+ return "pending";
2927
+ }
2928
+ }
2929
+ return {
2930
+ async createBatch(model, requests, _options) {
2931
+ const batchRequests = requests.map((req) => ({
2932
+ request: translateRequestToGemini(model, req),
2933
+ metadata: { key: req.custom_id }
2934
+ }));
2935
+ const res = await apiRequest(`/models/${model}:batchGenerateContent`, {
2936
+ method: "POST",
2937
+ body: {
2938
+ batch: {
2939
+ display_name: `anymodel-batch-${Date.now()}`,
2940
+ input_config: {
2941
+ requests: {
2942
+ requests: batchRequests
2943
+ }
2944
+ }
2945
+ }
2946
+ }
2947
+ });
2948
+ const data = await res.json();
2949
+ const batchName = data.name || data.batch?.name;
2950
+ if (!batchName) {
2951
+ throw new AnyModelError(502, "No batch name in Google response", {
2952
+ provider_name: "google",
2953
+ raw: data
2954
+ });
2955
+ }
2956
+ return {
2957
+ providerBatchId: batchName,
2958
+ metadata: {
2959
+ model,
2960
+ total_requests: requests.length
2961
+ }
2962
+ };
2963
+ },
2964
+ async pollBatch(providerBatchId) {
2965
+ const res = await apiRequest(`/${providerBatchId}`);
2966
+ const data = await res.json();
2967
+ const state = data.state || "JOB_STATE_PENDING";
2968
+ const status = mapBatchState(state);
2969
+ const totalCount = data.totalCount || data.metadata?.total_requests || 0;
2970
+ const successCount = data.succeededCount || 0;
2971
+ const failedCount = data.failedCount || 0;
2972
+ return {
2973
+ status,
2974
+ total: totalCount || successCount + failedCount,
2975
+ completed: successCount,
2976
+ failed: failedCount
2977
+ };
2978
+ },
2979
+ async getBatchResults(providerBatchId) {
2980
+ const batchRes = await apiRequest(`/${providerBatchId}`);
2981
+ const batchData = await batchRes.json();
2982
+ const results = [];
2983
+ const model = batchData.metadata?.model || "unknown";
2984
+ if (batchData.response?.inlinedResponses) {
2985
+ for (const item of batchData.response.inlinedResponses) {
2986
+ const customId = item.metadata?.key || `request-${results.length}`;
2987
+ if (item.response) {
2988
+ results.push({
2989
+ custom_id: customId,
2990
+ status: "success",
2991
+ response: translateGeminiResponse(item.response, model),
2992
+ error: null
2993
+ });
2994
+ } else if (item.error) {
2995
+ results.push({
2996
+ custom_id: customId,
2997
+ status: "error",
2998
+ response: null,
2999
+ error: {
3000
+ code: item.error.code || 500,
3001
+ message: item.error.message || "Batch item failed"
3002
+ }
3003
+ });
3004
+ }
3005
+ }
3006
+ return results;
3007
+ }
3008
+ const responsesFile = batchData.response?.responsesFileName || batchData.outputConfig?.file_name;
3009
+ if (responsesFile) {
3010
+ const downloadUrl = `${GEMINI_API_BASE2}/${responsesFile}:download?alt=media`;
3011
+ const fileRes = await fetchWithTimeout(downloadUrl, {
3012
+ headers: { "x-goog-api-key": apiKey }
3013
+ });
3014
+ if (!fileRes.ok) {
3015
+ throw new AnyModelError(502, "Failed to download batch results file", {
3016
+ provider_name: "google"
3017
+ });
3018
+ }
3019
+ const text = await fileRes.text();
3020
+ for (const line of text.trim().split("\n")) {
3021
+ if (!line) continue;
3022
+ const item = JSON.parse(line);
3023
+ const customId = item.key || item.metadata?.key || `request-${results.length}`;
3024
+ if (item.response) {
3025
+ results.push({
3026
+ custom_id: customId,
3027
+ status: "success",
3028
+ response: translateGeminiResponse(item.response, model),
3029
+ error: null
3030
+ });
3031
+ } else if (item.error) {
3032
+ results.push({
3033
+ custom_id: customId,
3034
+ status: "error",
3035
+ response: null,
3036
+ error: {
3037
+ code: item.error.code || 500,
3038
+ message: item.error.message || "Batch item failed"
3039
+ }
3040
+ });
3041
+ }
3042
+ }
3043
+ }
3044
+ return results;
3045
+ },
3046
+ async cancelBatch(providerBatchId) {
3047
+ await apiRequest(`/${providerBatchId}:cancel`, { method: "POST" });
3048
+ }
3049
+ };
3050
+ }
3051
+
2700
3052
  // src/client.ts
2701
3053
  var AnyModel = class {
2702
3054
  registry;
@@ -2712,6 +3064,7 @@ var AnyModel = class {
2712
3064
  constructor(config = {}) {
2713
3065
  this.config = resolveConfig(config);
2714
3066
  this.registry = new ProviderRegistry();
3067
+ setDefaultTimeout((this.config.defaults?.timeout ?? 120) * 1e3);
2715
3068
  if (this.config.io) {
2716
3069
  configureFsIO(this.config.io);
2717
3070
  }
@@ -2832,6 +3185,10 @@ var AnyModel = class {
2832
3185
  if (anthropicKey) {
2833
3186
  this.batchManager.registerBatchAdapter("anthropic", createAnthropicBatchAdapter(anthropicKey));
2834
3187
  }
3188
+ const googleKey = config.google?.apiKey || process.env.GOOGLE_API_KEY;
3189
+ if (googleKey) {
3190
+ this.batchManager.registerBatchAdapter("google", createGoogleBatchAdapter(googleKey));
3191
+ }
2835
3192
  }
2836
3193
  applyDefaults(request) {
2837
3194
  const defaults = this.config.defaults;
@@ -3011,12 +3368,15 @@ function startServer(options = {}) {
3011
3368
  configureFsIO,
3012
3369
  createAnthropicBatchAdapter,
3013
3370
  createAnyModelServer,
3371
+ createGoogleBatchAdapter,
3014
3372
  createOpenAIBatchAdapter,
3015
3373
  ensureDir,
3374
+ estimateTokenCount,
3016
3375
  getFsQueueStatus,
3017
3376
  joinPath,
3018
3377
  readFileQueued,
3019
3378
  resolveConfig,
3379
+ resolveMaxTokens,
3020
3380
  startServer,
3021
3381
  waitForFsQueuesIdle,
3022
3382
  writeFileFlushedQueued,