@probeo/anymodel 0.4.0 → 0.5.1

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)
@@ -1371,7 +1394,7 @@ var MODELS = [
1371
1394
  ];
1372
1395
  function createPerplexityAdapter(apiKey) {
1373
1396
  async function makeRequest(path2, body, method = "POST") {
1374
- const res = await fetch(`${PERPLEXITY_API_BASE}${path2}`, {
1397
+ const res = await fetchWithTimeout(`${PERPLEXITY_API_BASE}${path2}`, {
1375
1398
  method,
1376
1399
  headers: {
1377
1400
  "Content-Type": "application/json",
@@ -1926,6 +1949,17 @@ var BatchStore = class {
1926
1949
  const entries = await readDirQueued(this.dir);
1927
1950
  return entries.filter((d) => d.isDirectory()).map((d) => d.name).sort();
1928
1951
  }
1952
+ /**
1953
+ * Stream requests from JSONL one line at a time (memory-efficient).
1954
+ */
1955
+ async *streamRequests(id) {
1956
+ const p = joinPath(this.batchDir(id), "requests.jsonl");
1957
+ if (!await fileExistsQueued(p)) return;
1958
+ const raw = await readFileQueued(p, "utf8");
1959
+ for (const line of raw.split("\n")) {
1960
+ if (line.trim()) yield JSON.parse(line);
1961
+ }
1962
+ }
1929
1963
  /**
1930
1964
  * Check if a batch exists.
1931
1965
  */
@@ -1990,7 +2024,7 @@ var BatchManager = class {
1990
2024
  this.processNativeBatch(id, request, native.adapter).catch(() => {
1991
2025
  });
1992
2026
  } else {
1993
- this.processConcurrentBatch(id, request).catch(() => {
2027
+ this.processConcurrentBatch(id, request.model, request.options).catch(() => {
1994
2028
  });
1995
2029
  }
1996
2030
  return batch;
@@ -2170,28 +2204,28 @@ var BatchManager = class {
2170
2204
  }
2171
2205
  /**
2172
2206
  * Process batch requests concurrently (fallback path).
2207
+ * Streams requests from disk to avoid holding them all in memory.
2173
2208
  */
2174
- async processConcurrentBatch(batchId, request) {
2209
+ async processConcurrentBatch(batchId, model, options) {
2175
2210
  const batch = await this.store.getMeta(batchId);
2176
2211
  if (!batch) return;
2177
2212
  batch.status = "processing";
2178
2213
  await this.store.updateMeta(batch);
2179
- const items = request.requests;
2180
2214
  const active = /* @__PURE__ */ new Set();
2181
2215
  const processItem = async (item) => {
2182
2216
  const current = await this.store.getMeta(batchId);
2183
2217
  if (current?.status === "cancelled") return;
2184
2218
  const chatRequest = {
2185
- model: request.model,
2219
+ model,
2186
2220
  messages: item.messages,
2187
- max_tokens: item.max_tokens ?? request.options?.max_tokens,
2188
- temperature: item.temperature ?? request.options?.temperature,
2189
- top_p: item.top_p ?? request.options?.top_p,
2190
- top_k: item.top_k ?? request.options?.top_k,
2191
- stop: item.stop ?? request.options?.stop,
2192
- response_format: item.response_format ?? request.options?.response_format,
2193
- tools: item.tools ?? request.options?.tools,
2194
- tool_choice: item.tool_choice ?? request.options?.tool_choice
2221
+ max_tokens: item.max_tokens ?? options?.max_tokens,
2222
+ temperature: item.temperature ?? options?.temperature,
2223
+ top_p: item.top_p ?? options?.top_p,
2224
+ top_k: item.top_k ?? options?.top_k,
2225
+ stop: item.stop ?? options?.stop,
2226
+ response_format: item.response_format ?? options?.response_format,
2227
+ tools: item.tools ?? options?.tools,
2228
+ tool_choice: item.tool_choice ?? options?.tool_choice
2195
2229
  };
2196
2230
  let result;
2197
2231
  try {
@@ -2222,7 +2256,7 @@ var BatchManager = class {
2222
2256
  await this.store.updateMeta(meta);
2223
2257
  }
2224
2258
  };
2225
- for (const item of items) {
2259
+ for await (const item of this.store.streamRequests(batchId)) {
2226
2260
  const current = await this.store.getMeta(batchId);
2227
2261
  if (current?.status === "cancelled") break;
2228
2262
  if (active.size >= this.concurrencyLimit) {
@@ -2243,6 +2277,51 @@ var BatchManager = class {
2243
2277
  }
2244
2278
  };
2245
2279
 
2280
+ // src/utils/token-estimate.ts
2281
+ var CHARS_PER_TOKEN2 = 4;
2282
+ var MODEL_LIMITS = [
2283
+ // OpenAI
2284
+ { pattern: "gpt-4o-mini", limit: { contextLength: 128e3, maxCompletionTokens: 16384 } },
2285
+ { pattern: "gpt-4o", limit: { contextLength: 128e3, maxCompletionTokens: 16384 } },
2286
+ { pattern: "gpt-4-turbo", limit: { contextLength: 128e3, maxCompletionTokens: 4096 } },
2287
+ { pattern: "gpt-3.5-turbo", limit: { contextLength: 16385, maxCompletionTokens: 4096 } },
2288
+ { pattern: "o1", limit: { contextLength: 2e5, maxCompletionTokens: 1e5 } },
2289
+ { pattern: "o3", limit: { contextLength: 2e5, maxCompletionTokens: 1e5 } },
2290
+ { pattern: "o4-mini", limit: { contextLength: 2e5, maxCompletionTokens: 1e5 } },
2291
+ // Anthropic
2292
+ { pattern: "claude-opus-4", limit: { contextLength: 2e5, maxCompletionTokens: 32768 } },
2293
+ { pattern: "claude-sonnet-4", limit: { contextLength: 2e5, maxCompletionTokens: 16384 } },
2294
+ { pattern: "claude-haiku-4", limit: { contextLength: 2e5, maxCompletionTokens: 8192 } },
2295
+ { pattern: "claude-3.5-sonnet", limit: { contextLength: 2e5, maxCompletionTokens: 8192 } },
2296
+ { pattern: "claude-3-opus", limit: { contextLength: 2e5, maxCompletionTokens: 4096 } },
2297
+ // Google
2298
+ { pattern: "gemini-2.5-pro", limit: { contextLength: 1048576, maxCompletionTokens: 65536 } },
2299
+ { pattern: "gemini-2.5-flash", limit: { contextLength: 1048576, maxCompletionTokens: 65536 } },
2300
+ { pattern: "gemini-2.0-flash", limit: { contextLength: 1048576, maxCompletionTokens: 65536 } },
2301
+ { pattern: "gemini-1.5-pro", limit: { contextLength: 2097152, maxCompletionTokens: 8192 } },
2302
+ { pattern: "gemini-1.5-flash", limit: { contextLength: 1048576, maxCompletionTokens: 8192 } }
2303
+ ];
2304
+ var DEFAULT_LIMIT = { contextLength: 128e3, maxCompletionTokens: 4096 };
2305
+ function getModelLimits(model) {
2306
+ const bare = model.includes("/") ? model.slice(model.indexOf("/") + 1) : model;
2307
+ for (const entry of MODEL_LIMITS) {
2308
+ if (bare.startsWith(entry.pattern) || bare.includes(entry.pattern)) {
2309
+ return entry.limit;
2310
+ }
2311
+ }
2312
+ return DEFAULT_LIMIT;
2313
+ }
2314
+ function resolveMaxTokens(model, messages, userMaxTokens) {
2315
+ if (userMaxTokens !== void 0) return userMaxTokens;
2316
+ const inputChars = JSON.stringify(messages).length;
2317
+ const estimatedInput = Math.ceil(inputChars / CHARS_PER_TOKEN2);
2318
+ const estimatedWithMargin = Math.ceil(estimatedInput * 1.05);
2319
+ const limits = getModelLimits(model);
2320
+ const available = limits.contextLength - estimatedWithMargin;
2321
+ const result = Math.min(limits.maxCompletionTokens, available);
2322
+ return Math.max(1, result);
2323
+ }
2324
+
2246
2325
  // src/providers/openai-batch.ts
2247
2326
  var OPENAI_API_BASE2 = "https://api.openai.com/v1";
2248
2327
  function createOpenAIBatchAdapter(apiKey) {
@@ -2257,7 +2336,7 @@ function createOpenAIBatchAdapter(apiKey) {
2257
2336
  headers["Content-Type"] = "application/json";
2258
2337
  fetchBody = JSON.stringify(options.body);
2259
2338
  }
2260
- const res = await fetch(`${OPENAI_API_BASE2}${path2}`, {
2339
+ const res = await fetchWithTimeout(`${OPENAI_API_BASE2}${path2}`, {
2261
2340
  method: options.method || "GET",
2262
2341
  headers,
2263
2342
  body: fetchBody
@@ -2283,7 +2362,7 @@ function createOpenAIBatchAdapter(apiKey) {
2283
2362
  model,
2284
2363
  messages: req.messages
2285
2364
  };
2286
- if (req.max_tokens !== void 0) body.max_tokens = req.max_tokens;
2365
+ body.max_tokens = req.max_tokens !== void 0 ? req.max_tokens : resolveMaxTokens(model, req.messages);
2287
2366
  if (req.temperature !== void 0) body.temperature = req.temperature;
2288
2367
  if (req.top_p !== void 0) body.top_p = req.top_p;
2289
2368
  if (req.stop !== void 0) body.stop = req.stop;
@@ -2442,7 +2521,7 @@ function createAnthropicBatchAdapter(apiKey) {
2442
2521
  "anthropic-version": ANTHROPIC_VERSION2,
2443
2522
  "Content-Type": "application/json"
2444
2523
  };
2445
- const res = await fetch(`${ANTHROPIC_API_BASE2}${path2}`, {
2524
+ const res = await fetchWithTimeout(`${ANTHROPIC_API_BASE2}${path2}`, {
2446
2525
  method: options.method || "GET",
2447
2526
  headers,
2448
2527
  body: options.body ? JSON.stringify(options.body) : void 0
@@ -2465,7 +2544,7 @@ function createAnthropicBatchAdapter(apiKey) {
2465
2544
  function translateToAnthropicParams(model, req) {
2466
2545
  const params = {
2467
2546
  model,
2468
- max_tokens: req.max_tokens || DEFAULT_MAX_TOKENS2
2547
+ max_tokens: resolveMaxTokens(model, req.messages, req.max_tokens || DEFAULT_MAX_TOKENS2)
2469
2548
  };
2470
2549
  const systemMessages = req.messages.filter((m) => m.role === "system");
2471
2550
  const nonSystemMessages = req.messages.filter((m) => m.role !== "system");
@@ -2639,6 +2718,284 @@ ${params.system}` : jsonInstruction;
2639
2718
  };
2640
2719
  }
2641
2720
 
2721
+ // src/providers/google-batch.ts
2722
+ var GEMINI_API_BASE2 = "https://generativelanguage.googleapis.com/v1beta";
2723
+ function createGoogleBatchAdapter(apiKey) {
2724
+ async function apiRequest(path2, options = {}) {
2725
+ const headers = {
2726
+ "Content-Type": "application/json",
2727
+ "x-goog-api-key": apiKey
2728
+ };
2729
+ const res = await fetchWithTimeout(`${GEMINI_API_BASE2}${path2}`, {
2730
+ method: options.method || "GET",
2731
+ headers,
2732
+ body: options.body ? JSON.stringify(options.body) : void 0
2733
+ });
2734
+ if (!res.ok) {
2735
+ let errorBody;
2736
+ try {
2737
+ errorBody = await res.json();
2738
+ } catch {
2739
+ errorBody = { message: res.statusText };
2740
+ }
2741
+ const msg = errorBody?.error?.message || errorBody?.message || res.statusText;
2742
+ throw new AnyModelError(res.status >= 500 ? 502 : res.status, msg, {
2743
+ provider_name: "google",
2744
+ raw: errorBody
2745
+ });
2746
+ }
2747
+ return res;
2748
+ }
2749
+ function translateRequestToGemini(model, req) {
2750
+ const body = {};
2751
+ const systemMessages = req.messages.filter((m) => m.role === "system");
2752
+ const nonSystemMessages = req.messages.filter((m) => m.role !== "system");
2753
+ if (systemMessages.length > 0) {
2754
+ body.systemInstruction = {
2755
+ parts: [{ text: systemMessages.map((m) => typeof m.content === "string" ? m.content : "").join("\n") }]
2756
+ };
2757
+ }
2758
+ body.contents = nonSystemMessages.map((m) => ({
2759
+ role: m.role === "assistant" ? "model" : "user",
2760
+ parts: typeof m.content === "string" ? [{ text: m.content }] : Array.isArray(m.content) ? m.content.map((p) => p.type === "text" ? { text: p.text } : { text: "" }) : [{ text: "" }]
2761
+ }));
2762
+ const generationConfig = {};
2763
+ if (req.temperature !== void 0) generationConfig.temperature = req.temperature;
2764
+ generationConfig.maxOutputTokens = req.max_tokens !== void 0 ? req.max_tokens : resolveMaxTokens(model, req.messages);
2765
+ if (req.top_p !== void 0) generationConfig.topP = req.top_p;
2766
+ if (req.top_k !== void 0) generationConfig.topK = req.top_k;
2767
+ if (req.stop !== void 0) {
2768
+ generationConfig.stopSequences = Array.isArray(req.stop) ? req.stop : [req.stop];
2769
+ }
2770
+ if (req.response_format) {
2771
+ if (req.response_format.type === "json_object") {
2772
+ generationConfig.responseMimeType = "application/json";
2773
+ } else if (req.response_format.type === "json_schema") {
2774
+ generationConfig.responseMimeType = "application/json";
2775
+ generationConfig.responseSchema = req.response_format.json_schema?.schema;
2776
+ }
2777
+ }
2778
+ if (Object.keys(generationConfig).length > 0) {
2779
+ body.generationConfig = generationConfig;
2780
+ }
2781
+ if (req.tools && req.tools.length > 0) {
2782
+ body.tools = [{
2783
+ functionDeclarations: req.tools.map((t) => ({
2784
+ name: t.function.name,
2785
+ description: t.function.description || "",
2786
+ parameters: t.function.parameters || {}
2787
+ }))
2788
+ }];
2789
+ if (req.tool_choice) {
2790
+ if (req.tool_choice === "auto") {
2791
+ body.toolConfig = { functionCallingConfig: { mode: "AUTO" } };
2792
+ } else if (req.tool_choice === "required") {
2793
+ body.toolConfig = { functionCallingConfig: { mode: "ANY" } };
2794
+ } else if (req.tool_choice === "none") {
2795
+ body.toolConfig = { functionCallingConfig: { mode: "NONE" } };
2796
+ } else if (typeof req.tool_choice === "object") {
2797
+ body.toolConfig = {
2798
+ functionCallingConfig: {
2799
+ mode: "ANY",
2800
+ allowedFunctionNames: [req.tool_choice.function.name]
2801
+ }
2802
+ };
2803
+ }
2804
+ }
2805
+ }
2806
+ return body;
2807
+ }
2808
+ function mapFinishReason(reason) {
2809
+ switch (reason) {
2810
+ case "STOP":
2811
+ return "stop";
2812
+ case "MAX_TOKENS":
2813
+ return "length";
2814
+ case "SAFETY":
2815
+ return "content_filter";
2816
+ case "RECITATION":
2817
+ return "content_filter";
2818
+ default:
2819
+ return "stop";
2820
+ }
2821
+ }
2822
+ function translateGeminiResponse(response, model) {
2823
+ const candidate = response.candidates?.[0];
2824
+ let content = "";
2825
+ const toolCalls = [];
2826
+ for (const part of candidate?.content?.parts || []) {
2827
+ if (part.text) {
2828
+ content += part.text;
2829
+ } else if (part.functionCall) {
2830
+ toolCalls.push({
2831
+ id: generateId("call"),
2832
+ type: "function",
2833
+ function: {
2834
+ name: part.functionCall.name,
2835
+ arguments: JSON.stringify(part.functionCall.args || {})
2836
+ }
2837
+ });
2838
+ }
2839
+ }
2840
+ const message = { role: "assistant", content };
2841
+ if (toolCalls.length > 0) {
2842
+ message.tool_calls = toolCalls;
2843
+ }
2844
+ const finishReason = toolCalls.length > 0 ? "tool_calls" : mapFinishReason(candidate?.finishReason || "STOP");
2845
+ return {
2846
+ id: generateId(),
2847
+ object: "chat.completion",
2848
+ created: Math.floor(Date.now() / 1e3),
2849
+ model: `google/${model}`,
2850
+ choices: [{ index: 0, message, finish_reason: finishReason }],
2851
+ usage: {
2852
+ prompt_tokens: response.usageMetadata?.promptTokenCount || 0,
2853
+ completion_tokens: response.usageMetadata?.candidatesTokenCount || 0,
2854
+ total_tokens: response.usageMetadata?.totalTokenCount || 0
2855
+ }
2856
+ };
2857
+ }
2858
+ function mapBatchState(state) {
2859
+ switch (state) {
2860
+ case "JOB_STATE_PENDING":
2861
+ return "pending";
2862
+ case "JOB_STATE_RUNNING":
2863
+ return "processing";
2864
+ case "JOB_STATE_SUCCEEDED":
2865
+ return "completed";
2866
+ case "JOB_STATE_FAILED":
2867
+ return "failed";
2868
+ case "JOB_STATE_CANCELLED":
2869
+ return "cancelled";
2870
+ case "JOB_STATE_EXPIRED":
2871
+ return "failed";
2872
+ default:
2873
+ return "pending";
2874
+ }
2875
+ }
2876
+ return {
2877
+ async createBatch(model, requests, _options) {
2878
+ const batchRequests = requests.map((req) => ({
2879
+ request: translateRequestToGemini(model, req),
2880
+ metadata: { key: req.custom_id }
2881
+ }));
2882
+ const res = await apiRequest(`/models/${model}:batchGenerateContent`, {
2883
+ method: "POST",
2884
+ body: {
2885
+ batch: {
2886
+ display_name: `anymodel-batch-${Date.now()}`,
2887
+ input_config: {
2888
+ requests: {
2889
+ requests: batchRequests
2890
+ }
2891
+ }
2892
+ }
2893
+ }
2894
+ });
2895
+ const data = await res.json();
2896
+ const batchName = data.name || data.batch?.name;
2897
+ if (!batchName) {
2898
+ throw new AnyModelError(502, "No batch name in Google response", {
2899
+ provider_name: "google",
2900
+ raw: data
2901
+ });
2902
+ }
2903
+ return {
2904
+ providerBatchId: batchName,
2905
+ metadata: {
2906
+ model,
2907
+ total_requests: requests.length
2908
+ }
2909
+ };
2910
+ },
2911
+ async pollBatch(providerBatchId) {
2912
+ const res = await apiRequest(`/${providerBatchId}`);
2913
+ const data = await res.json();
2914
+ const state = data.state || "JOB_STATE_PENDING";
2915
+ const status = mapBatchState(state);
2916
+ const totalCount = data.totalCount || data.metadata?.total_requests || 0;
2917
+ const successCount = data.succeededCount || 0;
2918
+ const failedCount = data.failedCount || 0;
2919
+ return {
2920
+ status,
2921
+ total: totalCount || successCount + failedCount,
2922
+ completed: successCount,
2923
+ failed: failedCount
2924
+ };
2925
+ },
2926
+ async getBatchResults(providerBatchId) {
2927
+ const batchRes = await apiRequest(`/${providerBatchId}`);
2928
+ const batchData = await batchRes.json();
2929
+ const results = [];
2930
+ const model = batchData.metadata?.model || "unknown";
2931
+ if (batchData.response?.inlinedResponses) {
2932
+ for (const item of batchData.response.inlinedResponses) {
2933
+ const customId = item.metadata?.key || `request-${results.length}`;
2934
+ if (item.response) {
2935
+ results.push({
2936
+ custom_id: customId,
2937
+ status: "success",
2938
+ response: translateGeminiResponse(item.response, model),
2939
+ error: null
2940
+ });
2941
+ } else if (item.error) {
2942
+ results.push({
2943
+ custom_id: customId,
2944
+ status: "error",
2945
+ response: null,
2946
+ error: {
2947
+ code: item.error.code || 500,
2948
+ message: item.error.message || "Batch item failed"
2949
+ }
2950
+ });
2951
+ }
2952
+ }
2953
+ return results;
2954
+ }
2955
+ const responsesFile = batchData.response?.responsesFileName || batchData.outputConfig?.file_name;
2956
+ if (responsesFile) {
2957
+ const downloadUrl = `${GEMINI_API_BASE2}/${responsesFile}:download?alt=media`;
2958
+ const fileRes = await fetchWithTimeout(downloadUrl, {
2959
+ headers: { "x-goog-api-key": apiKey }
2960
+ });
2961
+ if (!fileRes.ok) {
2962
+ throw new AnyModelError(502, "Failed to download batch results file", {
2963
+ provider_name: "google"
2964
+ });
2965
+ }
2966
+ const text = await fileRes.text();
2967
+ for (const line of text.trim().split("\n")) {
2968
+ if (!line) continue;
2969
+ const item = JSON.parse(line);
2970
+ const customId = item.key || item.metadata?.key || `request-${results.length}`;
2971
+ if (item.response) {
2972
+ results.push({
2973
+ custom_id: customId,
2974
+ status: "success",
2975
+ response: translateGeminiResponse(item.response, model),
2976
+ error: null
2977
+ });
2978
+ } else if (item.error) {
2979
+ results.push({
2980
+ custom_id: customId,
2981
+ status: "error",
2982
+ response: null,
2983
+ error: {
2984
+ code: item.error.code || 500,
2985
+ message: item.error.message || "Batch item failed"
2986
+ }
2987
+ });
2988
+ }
2989
+ }
2990
+ }
2991
+ return results;
2992
+ },
2993
+ async cancelBatch(providerBatchId) {
2994
+ await apiRequest(`/${providerBatchId}:cancel`, { method: "POST" });
2995
+ }
2996
+ };
2997
+ }
2998
+
2642
2999
  // src/client.ts
2643
3000
  var AnyModel = class {
2644
3001
  registry;
@@ -2654,6 +3011,7 @@ var AnyModel = class {
2654
3011
  constructor(config = {}) {
2655
3012
  this.config = resolveConfig(config);
2656
3013
  this.registry = new ProviderRegistry();
3014
+ setDefaultTimeout((this.config.defaults?.timeout ?? 120) * 1e3);
2657
3015
  if (this.config.io) {
2658
3016
  configureFsIO(this.config.io);
2659
3017
  }
@@ -2774,6 +3132,10 @@ var AnyModel = class {
2774
3132
  if (anthropicKey) {
2775
3133
  this.batchManager.registerBatchAdapter("anthropic", createAnthropicBatchAdapter(anthropicKey));
2776
3134
  }
3135
+ const googleKey = config.google?.apiKey || process.env.GOOGLE_API_KEY;
3136
+ if (googleKey) {
3137
+ this.batchManager.registerBatchAdapter("google", createGoogleBatchAdapter(googleKey));
3138
+ }
2777
3139
  }
2778
3140
  applyDefaults(request) {
2779
3141
  const defaults = this.config.defaults;