Insomniac_360

GEMINI PROXY BY INSOMNIAC

Sep 6th, 2026
519
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. var DEFAULT_CONFIG = {
  2.   retryAttempts: 3,
  3.   retryDelaySec: 2,
  4.   requestTimeoutSec: 28,
  5.   geminiBl: 'boq_assistant-bard-web-server_20260831.15_p2',
  6.   authUser: null,
  7.   xsrfToken: null,
  8.   defaultModel: 'gemini-3.8-flash',
  9.   apiKeys: ['sk-gemini'],
  10.   cookieString: null,
  11.   sapisid: null,
  12.   logRequests: true,
  13.   rateLimit: {
  14.     enabled: true,
  15.     maxRequests: 3000,
  16.     windowSec: 60,
  17.   },
  18.   fingerprintJitterMs: 1500,
  19. };
  20.  
  21. var USER_AGENTS = [
  22.   'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36',
  23.   'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
  24.   'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15',
  25.   'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36',
  26.   'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36',
  27.   'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0',
  28.   'Mozilla/5.0 (Macintosh; Intel Mac OS X 14.5; rv:128.0) Gecko/20100101 Firefox/128.0',
  29.   'Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0',
  30. ];
  31.  
  32. var UA_WEIGHTS = [35, 30, 8, 10, 7, 5, 3, 2];
  33.  
  34. function getRandomUserAgent() {
  35.   var totalWeight = 0;
  36.   for (var i = 0; i < UA_WEIGHTS.length; i++) {
  37.     totalWeight += UA_WEIGHTS[i];
  38.   }
  39.  
  40.   var random = Math.random() * totalWeight;
  41.  
  42.   var cumulative = 0;
  43.   for (var j = 0; j < USER_AGENTS.length; j++) {
  44.     cumulative += UA_WEIGHTS[j];
  45.  
  46.     if (random < cumulative) {
  47.       return USER_AGENTS[j];
  48.     }
  49.   }
  50.  
  51.   return USER_AGENTS[0];
  52. }
  53.  
  54. var ACCEPT_LANGUAGES = [
  55.   'en-US,en;q=0.9',
  56.   'en-US,en;q=0.9,zh-CN;q=0.8',
  57.   'en-GB,en;q=0.9',
  58.   'en-US,en;q=0.9,ja;q=0.8',
  59.   'en-US,en;q=0.9,ko;q=0.8',
  60.   'en-US,en;q=0.9,es;q=0.8',
  61. ];
  62.  
  63. function getRandomAcceptLanguage() {
  64.   var idx = Math.floor(Math.random() * ACCEPT_LANGUAGES.length);
  65.   return ACCEPT_LANGUAGES[idx];
  66. }
  67.  
  68. var SEC_CH_UA_POOLS = [
  69.   '"Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"',
  70.   '"Not)A;Brand";v="99", "Google Chrome";v="126", "Chromium";v="126"',
  71.   '"Not)A;Brand";v="99", "Google Chrome";v="125", "Chromium";v="125"',
  72. ];
  73.  
  74. var SEC_CH_UA_PLATFORMS = [
  75.   '"Windows"',
  76.   '"macOS"',
  77.   '"Linux"',
  78. ];
  79.  
  80. function getRandomSecChUa() {
  81.   var idx = Math.floor(Math.random() * SEC_CH_UA_POOLS.length);
  82.   return SEC_CH_UA_POOLS[idx];
  83. }
  84.  
  85. function getRandomSecChUaPlatform() {
  86.   var idx = Math.floor(Math.random() * SEC_CH_UA_PLATFORMS.length);
  87.   return SEC_CH_UA_PLATFORMS[idx];
  88. }
  89.  
  90. var MODELS = {
  91.   'gemini-3.8-flash': {
  92.     mode: 1,
  93.     think: 4,
  94.     desc: 'Latest all-around model (Gemini 3.8 Flash)',
  95.   },
  96.   'gemini-3.8-flash-thinking': {
  97.     mode: 2,
  98.     think: 0,
  99.     desc: 'Gemini 3.8 Flash with deep thinking mode (~20k chars)',
  100.   },
  101.   'gemini-3.7-flash': {
  102.     mode: 1,
  103.     think: 4,
  104.     desc: 'Gemini 3.7 Flash',
  105.   },
  106.   'gemini-3.6-flash': {
  107.     mode: 1,
  108.     think: 4,
  109.     desc: 'All-around model (Gemini 3.6 Flash)',
  110.   },
  111.   'gemini-3.5-flash': {
  112.     mode: 1,
  113.     think: 4,
  114.     desc: 'Alias for gemini-3.6-flash (backend upgraded)',
  115.   },
  116.   'gemini-3.5-flash-thinking': {
  117.     mode: 2,
  118.     think: 0,
  119.     desc: 'Deep thinking mode, longest output (~20k chars)',
  120.   },
  121.   'gemini-3.1-pro': {
  122.     mode: 3,
  123.     think: 4,
  124.     desc: 'Pro model (requires cookie for real routing)',
  125.   },
  126.   'gemini-auto': {
  127.     mode: 4,
  128.     think: 4,
  129.     desc: 'Auto model selection',
  130.   },
  131.   'gemini-3.5-flash-thinking-lite': {
  132.     mode: 5,
  133.     think: 0,
  134.     desc: 'Dynamic thinking with adaptive depth',
  135.   },
  136.   'gemini-flash-lite': {
  137.     mode: 6,
  138.     think: 4,
  139.     desc: 'Lightweight fast model',
  140.   },
  141. };
  142.  
  143. function getRequestConfig(env) {
  144.   var config = {
  145.     retryAttempts: DEFAULT_CONFIG.retryAttempts,
  146.     retryDelaySec: DEFAULT_CONFIG.retryDelaySec,
  147.     requestTimeoutSec: DEFAULT_CONFIG.requestTimeoutSec,
  148.     geminiBl: DEFAULT_CONFIG.geminiBl,
  149.     authUser: DEFAULT_CONFIG.authUser,
  150.     xsrfToken: DEFAULT_CONFIG.xsrfToken,
  151.     defaultModel: DEFAULT_CONFIG.defaultModel,
  152.     apiKeys: DEFAULT_CONFIG.apiKeys,
  153.     cookieString: DEFAULT_CONFIG.cookieString,
  154.     sapisid: DEFAULT_CONFIG.sapisid,
  155.     logRequests: DEFAULT_CONFIG.logRequests,
  156.     fingerprintJitterMs: DEFAULT_CONFIG.fingerprintJitterMs,
  157.     rateLimit: {
  158.       enabled: DEFAULT_CONFIG.rateLimit.enabled,
  159.       maxRequests: DEFAULT_CONFIG.rateLimit.maxRequests,
  160.       windowSec: DEFAULT_CONFIG.rateLimit.windowSec,
  161.     },
  162.   };
  163.  
  164.   if (env.GEMINI_BL) {
  165.     config.geminiBl = env.GEMINI_BL;
  166.   }
  167.   if (env.DEFAULT_MODEL) {
  168.     config.defaultModel = env.DEFAULT_MODEL;
  169.   }
  170.  
  171.   config.authUser = env.AUTH_USER || null;
  172.   config.xsrfToken = env.XSRF_TOKEN || null;
  173.  
  174.   var cookieStrings = (env.COOKIE_STRING || '').split('|').filter(function (c) {
  175.     return c.trim();
  176.   });
  177.   var sapisids = (env.SAPISID || '').split('|').filter(function (s) {
  178.     return s.trim();
  179.   });
  180.  
  181.   if (cookieStrings.length > 0) {
  182.     var cookieIdx = Math.floor(Math.random() * cookieStrings.length);
  183.     config.cookieString = cookieStrings[cookieIdx].trim();
  184.  
  185.     if (sapisids.length === cookieStrings.length) {
  186.       config.sapisid = sapisids[cookieIdx].trim();
  187.     } else if (sapisids.length > 0) {
  188.       var sapisidIdx = Math.floor(Math.random() * sapisids.length);
  189.       config.sapisid = sapisids[sapisidIdx].trim();
  190.     }
  191.   }
  192.  
  193.   else if (sapisids.length > 0) {
  194.     var sapisidIdx = Math.floor(Math.random() * sapisids.length);
  195.     config.sapisid = sapisids[sapisidIdx].trim();
  196.   }
  197.  
  198.   if (!config.sapisid && config.cookieString) {
  199.     var match = config.cookieString.match(/SAPISID=([^;]+)/);
  200.     if (match) {
  201.       config.sapisid = match[1].trim();
  202.     }
  203.   }
  204.  
  205.   if (env.API_KEYS) {
  206.     try {
  207.       config.apiKeys = JSON.parse(env.API_KEYS);
  208.     } catch (e) {
  209.       console.error('[ERROR] Failed to parse API_KEYS: ' + e.message + ', using default value');
  210.     }
  211.   }
  212.  
  213.   if (env.RETRY_ATTEMPTS) {
  214.     var ra = parseInt(env.RETRY_ATTEMPTS, 10);
  215.     if (!isNaN(ra)) config.retryAttempts = ra;
  216.   }
  217.   if (env.RETRY_DELAY_SEC) {
  218.     var rd = parseInt(env.RETRY_DELAY_SEC, 10);
  219.     if (!isNaN(rd)) config.retryDelaySec = rd;
  220.   }
  221.   if (env.REQUEST_TIMEOUT_SEC) {
  222.     var rt = parseInt(env.REQUEST_TIMEOUT_SEC, 10);
  223.     if (!isNaN(rt)) config.requestTimeoutSec = rt;
  224.   }
  225.  
  226.   if (env.FINGERPRINT_JITTER_MS) {
  227.     var fj = parseInt(env.FINGERPRINT_JITTER_MS, 10);
  228.     if (!isNaN(fj)) config.fingerprintJitterMs = fj;
  229.   }
  230.  
  231.   if (env.RATE_LIMIT_MAX) {
  232.     var rlmax = parseInt(env.RATE_LIMIT_MAX, 10);
  233.     if (!isNaN(rlmax)) config.rateLimit.maxRequests = rlmax;
  234.   }
  235.   if (env.RATE_LIMIT_WINDOW) {
  236.     var rlwin = parseInt(env.RATE_LIMIT_WINDOW, 10);
  237.     if (!isNaN(rlwin)) config.rateLimit.windowSec = rlwin;
  238.   }
  239.  
  240.   return config;
  241. }
  242.  
  243. function log(msg, level, config) {
  244.   level = level || 'INFO';
  245.  
  246.   var shouldLog = config ? config.logRequests : DEFAULT_CONFIG.logRequests;
  247.   if (shouldLog) {
  248.     var ts = new Date().toISOString().split('T')[1].split('.')[0];
  249.     console.log('[' + ts + '] [' + level + '] ' + msg);
  250.   }
  251. }
  252.  
  253. function generateUUID() {
  254.   if (typeof crypto !== 'undefined' && crypto.randomUUID) {
  255.     return crypto.randomUUID();
  256.   }
  257.  
  258.   return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
  259.     var r = Math.random() * 16 | 0;
  260.  
  261.     var v = c === 'x' ? r : (r & 0x3 | 0x8);
  262.     return v.toString(16);
  263.   });
  264. }
  265.  
  266. function generateShortId(length) {
  267.   var len = length || 12;
  268.  
  269.   return generateUUID().replace(/-/g, '').substring(0, len);
  270. }
  271.  
  272. function timestamp() {
  273.   return Math.floor(Date.now() / 1000);
  274. }
  275.  
  276. function estimateTokens(text) {
  277.   if (!text) return 0;
  278.  
  279.   return Math.max(1, Math.ceil(text.length / 4));
  280. }
  281.  
  282. async function makeSapisidHash(sapisid) {
  283.   var ts = timestamp();
  284.  
  285.   var input = ts + ' ' + sapisid + ' https://gemini.google.com';
  286.  
  287.   var encoder = new TextEncoder();
  288.   var data = encoder.encode(input);
  289.  
  290.   var hashBuffer = await crypto.subtle.digest('SHA-1', data);
  291.  
  292.   var hashArray = Array.from(new Uint8Array(hashBuffer));
  293.   var hashHex = hashArray.map(function (b) {
  294.     return b.toString(16).padStart(2, '0');
  295.   }).join('');
  296.  
  297.   return 'SAPISIDHASH ' + ts + '_' + hashHex;
  298. }
  299.  
  300. function getAccountPrefix(config) {
  301.   var authUser = config.authUser;
  302.  
  303.   if (authUser === null || authUser === undefined || authUser === '') {
  304.     return '';
  305.   }
  306.  
  307.   return '/u/' + authUser;
  308. }
  309.  
  310. function buildPayload(prompt, modelId, thinkMode, config) {
  311.   var inner = new Array(80).fill(null);
  312.  
  313.   inner[0] = [prompt, 0, null, null, null, null, 0];
  314.  
  315.   inner[1] = ['en'];
  316.  
  317.   inner[2] = ['', '', '', null, null, null, null, null, null, ''];
  318.  
  319.   inner[6] = [0];
  320.  
  321.   inner[7] = 1;
  322.   inner[10] = 1;
  323.  
  324.   inner[11] = 0;
  325.  
  326.   inner[17] = [[thinkMode]];
  327.  
  328.   inner[18] = 0;
  329.  
  330.   inner[27] = 1;
  331.   inner[30] = [4];
  332.   inner[41] = [2];
  333.   inner[53] = 0;
  334.  
  335.   inner[59] = generateUUID();
  336.  
  337.   inner[61] = [];
  338.  
  339.   inner[68] = 1;
  340.  
  341.   inner[79] = modelId;
  342.  
  343.   var outer = [null, JSON.stringify(inner)];
  344.  
  345.   var params = new URLSearchParams();
  346.  
  347.   params.append('f.req', JSON.stringify(outer));
  348.  
  349.   if (config.xsrfToken) {
  350.     params.append('at', config.xsrfToken);
  351.   }
  352.  
  353.   return params.toString();
  354. }
  355.  
  356. function buildUrl(config) {
  357.   var prefix = getAccountPrefix(config);
  358.  
  359.   var reqid = timestamp() % 1000000;
  360.  
  361.   return 'https://gemini.google.com' + prefix +
  362.     '/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate' +
  363.     '?bl=' + config.geminiBl +
  364.     '&hl=en' +
  365.     '&_reqid=' + reqid +
  366.     '&rt=c';
  367. }
  368.  
  369. async function buildHeaders(config) {
  370.   var prefix = getAccountPrefix(config);
  371.  
  372.   var selectedUA = getRandomUserAgent();
  373.   var selectedLanguage = getRandomAcceptLanguage();
  374.  
  375.   var headers = {
  376.     'Content-Type': 'application/x-www-form-urlencoded',
  377.     'Origin': 'https://gemini.google.com',
  378.     'Referer': 'https://gemini.google.com' + prefix + '/app',
  379.     'X-Same-Domain': '1',
  380.     'User-Agent': selectedUA,
  381.     'Accept': '*/*',
  382.     'Accept-Language': selectedLanguage,
  383.     'Sec-Fetch-Dest': 'empty',
  384.     'Sec-Fetch-Mode': 'cors',
  385.     'Sec-Fetch-Site': 'same-origin',
  386.   };
  387.  
  388.   if (selectedUA.indexOf('Chrome') !== -1) {
  389.     headers['Sec-Ch-Ua'] = getRandomSecChUa();
  390.     headers['Sec-Ch-Ua-Mobile'] = '?0';
  391.     headers['Sec-Ch-Ua-Platform'] = getRandomSecChUaPlatform();
  392.   }
  393.  
  394.   if (prefix) {
  395.     headers['X-Goog-AuthUser'] = String(config.authUser);
  396.   }
  397.  
  398.   if (config.cookieString) {
  399.     headers['Cookie'] = config.cookieString;
  400.   }
  401.  
  402.   if (config.sapisid) {
  403.     headers['Authorization'] = await makeSapisidHash(config.sapisid);
  404.   }
  405.  
  406.   return headers;
  407. }
  408.  
  409. async function geminiStreamGenerate(prompt, modelId, thinkMode, config) {
  410.   if (config.fingerprintJitterMs > 0) {
  411.     var jitter = Math.random() * config.fingerprintJitterMs;
  412.     await new Promise(function (resolve) { setTimeout(resolve, jitter); });
  413.   }
  414.  
  415.   var body = buildPayload(prompt, modelId, thinkMode, config);
  416.   var headers = await buildHeaders(config);
  417.   var url = buildUrl(config);
  418.  
  419.   var lastError;
  420.  
  421.   for (var attempt = 0; attempt < config.retryAttempts; attempt++) {
  422.     try {
  423.       if (attempt > 0) {
  424.         headers = await buildHeaders(config);
  425.  
  426.         if (config.fingerprintJitterMs > 0) {
  427.           var retryJitter = Math.random() * config.fingerprintJitterMs;
  428.           await new Promise(function (resolve) { setTimeout(resolve, retryJitter); });
  429.         }
  430.       }
  431.  
  432.       var controller = new AbortController();
  433.       var timeout = setTimeout(function () {
  434.         controller.abort();
  435.       }, config.requestTimeoutSec * 1000);
  436.  
  437.       var response = await fetch(url, {
  438.         method: 'POST',
  439.         headers: headers,
  440.         body: body,
  441.         signal: controller.signal,
  442.       });
  443.  
  444.       clearTimeout(timeout);
  445.  
  446.       if (response.status === 405) {
  447.         throw new Error('HTTP 405: Method Not Allowed - BL version may be outdated, please update geminiBl');
  448.       }
  449.  
  450.       if (response.status === 429) {
  451.         var retryAfter = parseInt(response.headers.get('Retry-After') || '5', 10);
  452.         log('Received 429 rate limit, waiting ' + retryAfter + 's before retrying...', 'WARN', config);
  453.         if (attempt < config.retryAttempts - 1) {
  454.           await new Promise(function (resolve) { setTimeout(resolve, retryAfter * 1000); });
  455.           continue;
  456.         }
  457.         throw new Error('HTTP 429: Too Many Requests - please add a valid cookie or reduce request frequency');
  458.       }
  459.  
  460.       if (response.status === 403) {
  461.         throw new Error('HTTP 403: Forbidden - valid cookie authentication may be required');
  462.       }
  463.  
  464.       if (!response.ok) {
  465.         var errorText = '';
  466.         try {
  467.           errorText = await response.text();
  468.         } catch (e) {
  469.           errorText = 'Unable to read error message';
  470.         }
  471.         throw new Error('HTTP ' + response.status + ': ' + errorText.substring(0, 200));
  472.       }
  473.  
  474.       return await response.text();
  475.     } catch (error) {
  476.       lastError = error;
  477.  
  478.       if (attempt < config.retryAttempts - 1) {
  479.         log('Retry ' + (attempt + 1) + '/' + config.retryAttempts + ': ' + error.message, 'WARN', config);
  480.  
  481.         var delay = config.retryDelaySec * Math.pow(2, attempt) * 1000;
  482.         await new Promise(function (resolve) { setTimeout(resolve, delay); });
  483.       }
  484.     }
  485.   }
  486.  
  487.   throw lastError;
  488. }
  489.  
  490. function cleanGeminiText(text, strip) {
  491.   if (strip === undefined) strip = true;
  492.  
  493.   text = text.replace(
  494.     /```(?:python|javascript|text)\?code_(?:reference|stdout)&code_event_index=\d+\n[\s\S]*?```\n?/g,
  495.     ''
  496.   );
  497.  
  498.   return strip ? text.trim() : text;
  499. }
  500.  
  501. function extractResponseText(raw) {
  502.   var bardErr = raw.match(/BardErrorInfo\s*\[(\d+)\]/);
  503.   if (bardErr) {
  504.     throw new Error('Gemini upstream rejected request: BardErrorInfo [' + bardErr[1] + ']');
  505.   }
  506.  
  507.   var texts = [];
  508.  
  509.   var lines = raw.split('\n');
  510.   for (var i = 0; i < lines.length; i++) {
  511.     var line = lines[i];
  512.  
  513.     if (line.indexOf('"wrb.fr"') === -1 || line.length < 200) continue;
  514.  
  515.     try {
  516.       var arr = JSON.parse(line);
  517.  
  518.       var innerStr = arr[0][2];
  519.  
  520.       if (!innerStr || innerStr.length < 50) continue;
  521.  
  522.       var inner = JSON.parse(innerStr);
  523.  
  524.       if (Array.isArray(inner) && inner.length > 4 && inner[4]) {
  525.         var parts = inner[4];
  526.  
  527.         for (var j = 0; j < parts.length; j++) {
  528.           var part = parts[j];
  529.  
  530.           if (Array.isArray(part) && part.length > 1 && part[1]) {
  531.             if (Array.isArray(part[1])) {
  532.               var textItems = part[1];
  533.  
  534.               for (var k = 0; k < textItems.length; k++) {
  535.                 var t = textItems[k];
  536.  
  537.                 if (typeof t === 'string' && t.length > 0) {
  538.                   texts.push(t);
  539.                 }
  540.               }
  541.             }
  542.           }
  543.         }
  544.       }
  545.     } catch (e) {
  546.     }
  547.   }
  548.  
  549.   var text = '';
  550.   for (var m = texts.length - 1; m >= 0; m--) {
  551.     if (texts[m].trim()) {
  552.       text = texts[m];
  553.       break;
  554.     }
  555.   }
  556.  
  557.   return cleanGeminiText(text);
  558. }
  559.  
  560. function messagesToPrompt(messages, tools) {
  561.   var parts = [];
  562.  
  563.   if (tools && tools.length > 0) {
  564.     var toolDefs = [];
  565.     for (var ti = 0; ti < tools.length; ti++) {
  566.       var tool = tools[ti];
  567.       var fn = (tool.type === 'function') ? (tool.function || tool) : tool;
  568.       toolDefs.push({
  569.         name: fn.name || tool.name || '',
  570.         description: fn.description || tool.description || '',
  571.         parameters: fn.parameters || tool.parameters || {},
  572.       });
  573.     }
  574.  
  575.     parts.push(
  576.       '[System instruction]: You have access to tools. ' +
  577.       'To call a tool, respond with:\n' +
  578.       '```tool_call\n{"name": "func_name", "arguments": {...}}\n```\n' +
  579.       'Only use tool_call blocks when needed.\n\n' +
  580.       'Available tools:\n' + JSON.stringify(toolDefs, null, 2)
  581.     );
  582.   }
  583.  
  584.   for (var mi = 0; mi < messages.length; mi++) {
  585.     var msg = messages[mi];
  586.     var role = msg.role || 'user';
  587.     var content = msg.content || '';
  588.  
  589.     if (Array.isArray(content)) {
  590.       var textParts = [];
  591.       for (var ci = 0; ci < content.length; ci++) {
  592.         var c = content[ci];
  593.         if (c.type === 'text' || c.type === 'input_text') {
  594.           textParts.push(c.text || '');
  595.         }
  596.       }
  597.       content = textParts.join(' ');
  598.     }
  599.  
  600.     if (role === 'system') {
  601.       parts.push('[System instruction]: ' + content);
  602.     } else if (role === 'assistant') {
  603.       if (msg.tool_calls && msg.tool_calls.length > 0) {
  604.         var tcStrs = [];
  605.         for (var tci = 0; tci < msg.tool_calls.length; tci++) {
  606.           var tc = msg.tool_calls[tci];
  607.           var fn = tc.function || {};
  608.           tcStrs.push(
  609.             '```tool_call\n' +
  610.             '{"name": "' + fn.name + '", "arguments": ' + (fn.arguments || '{}') + '}\n' +
  611.             '```'
  612.           );
  613.         }
  614.         parts.push('[Assistant]: ' + (content || '') + '\n' + tcStrs.join('\n'));
  615.       } else {
  616.         parts.push('[Assistant]: ' + content);
  617.       }
  618.     } else if (role === 'tool') {
  619.       parts.push('[Tool result for ' + (msg.name || 'unknown') + ']: ' + content);
  620.     } else {
  621.       parts.push(content || '');
  622.     }
  623.   }
  624.  
  625.   return parts.filter(function (p) { return p; }).join('\n\n');
  626. }
  627.  
  628. function parseToolCalls(text) {
  629.   var toolCalls = [];
  630.  
  631.   var pattern = /```tool_call\s*\n(.*?)\n```/gs;
  632.   var match;
  633.  
  634.   while ((match = pattern.exec(text)) !== null) {
  635.     try {
  636.       var data = JSON.parse(match[1].trim());
  637.  
  638.       toolCalls.push({
  639.         id: 'call_' + generateShortId(8),
  640.         type: 'function',
  641.         function: {
  642.           name: data.name,
  643.           arguments: JSON.stringify(data.arguments || {}),
  644.         },
  645.       });
  646.     } catch (e) {
  647.     }
  648.   }
  649.  
  650.   var cleanText = text.replace(pattern, '').trim();
  651.  
  652.   return {
  653.     cleanText: cleanText,
  654.     toolCalls: toolCalls
  655.   };
  656. }
  657.  
  658. function googleContentsToPrompt(req) {
  659.   var parts = [];
  660.  
  661.   var sysInst = req.systemInstruction;
  662.   if (sysInst && sysInst.parts) {
  663.     var sysTextParts = [];
  664.     for (var si = 0; si < sysInst.parts.length; si++) {
  665.       var sp = sysInst.parts[si];
  666.       if (sp.text) sysTextParts.push(sp.text);
  667.     }
  668.     var sysText = sysTextParts.join(' ');
  669.     if (sysText) {
  670.       parts.push('[System instruction]: ' + sysText);
  671.     }
  672.   }
  673.  
  674.   var contents = req.contents || [];
  675.   for (var ci = 0; ci < contents.length; ci++) {
  676.     var content = contents[ci];
  677.     var role = content.role || 'user';
  678.     var textParts = [];
  679.     var partsArr = content.parts || [];
  680.     for (var pi = 0; pi < partsArr.length; pi++) {
  681.       if (partsArr[pi].text) textParts.push(partsArr[pi].text);
  682.     }
  683.     var text = textParts.join(' ');
  684.  
  685.     if (role === 'model') {
  686.       parts.push('[Assistant]: ' + text);
  687.     } else {
  688.       parts.push(text);
  689.     }
  690.   }
  691.  
  692.   return parts.filter(function (p) { return p; }).join('\n\n');
  693. }
  694.  
  695. var rateLimitStore = new Map();
  696.  
  697. function checkRateLimit(clientIP, config) {
  698.   if (!config.rateLimit || !config.rateLimit.enabled) return true;
  699.  
  700.   var now = Date.now();
  701.  
  702.   var windowMs = config.rateLimit.windowSec * 1000;
  703.  
  704.   var key = 'rl:' + clientIP;
  705.  
  706.   var timestamps = (rateLimitStore.get(key) || []).filter(function (t) {
  707.     return now - t < windowMs;
  708.   });
  709.  
  710.   if (timestamps.length >= config.rateLimit.maxRequests) {
  711.     return false;
  712.   }
  713.  
  714.   timestamps.push(now);
  715.   rateLimitStore.set(key, timestamps);
  716.  
  717.   if (Math.random() < 0.05) {
  718.     rateLimitStore.forEach(function (v, k) {
  719.       var valid = v.filter(function (t) {
  720.         return now - t < windowMs;
  721.       });
  722.       if (valid.length === 0) {
  723.         rateLimitStore.delete(k);
  724.       } else {
  725.         rateLimitStore.set(k, valid);
  726.       }
  727.     });
  728.   }
  729.  
  730.   return true;
  731. }
  732.  
  733. function checkApiKey(request, config) {
  734.   var keys = config.apiKeys || [];
  735.  
  736.   if (keys.length === 0) return true;
  737.  
  738.   var auth = request.headers.get('Authorization') || '';
  739.  
  740.   if (auth.indexOf('Bearer ') === 0) {
  741.     var token = auth.slice(7);
  742.  
  743.     if (keys.indexOf(token) !== -1) return true;
  744.   }
  745.  
  746.   var headerNames = ['x-api-key', 'x-goog-api-key'];
  747.   for (var i = 0; i < headerNames.length; i++) {
  748.     var value = request.headers.get(headerNames[i]) || '';
  749.     if (keys.indexOf(value) !== -1) return true;
  750.   }
  751.  
  752.   var url = new URL(request.url);
  753.   var keyParam = url.searchParams.get('key');
  754.   if (keyParam && keys.indexOf(keyParam) !== -1) return true;
  755.  
  756.   return false;
  757. }
  758.  
  759. function sendJSON(data, status) {
  760.   if (status === undefined) status = 200;
  761.  
  762.   var body = JSON.stringify(data);
  763.  
  764.   return new Response(body, {
  765.     status: status,
  766.     headers: {
  767.       'Content-Type': 'application/json; charset=utf-8',
  768.       'Access-Control-Allow-Origin': '*',
  769.       'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
  770.       'Access-Control-Allow-Headers': '*',
  771.     },
  772.   });
  773. }
  774.  
  775. function sendSSE(stream) {
  776.   return new Response(stream, {
  777.     headers: {
  778.       'Content-Type': 'text/event-stream; charset=utf-8',
  779.       'Cache-Control': 'no-cache',
  780.       'Connection': 'keep-alive',
  781.       'X-Accel-Buffering': 'no',
  782.       'Access-Control-Allow-Origin': '*',
  783.       'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
  784.       'Access-Control-Allow-Headers': '*',
  785.     },
  786.   });
  787. }
  788.  
  789. function resolveModel(modelName) {
  790.   var thinkOverride = null;
  791.   var actualModelName = modelName;
  792.  
  793.   if (modelName.indexOf('@think=') !== -1) {
  794.     var parts = modelName.split('@think=');
  795.     actualModelName = parts[0];
  796.     thinkOverride = parseInt(parts[1], 10);
  797.     if (isNaN(thinkOverride)) {
  798.       return { error: 'Invalid think parameter: ' + parts[1] };
  799.     }
  800.   }
  801.  
  802.   var cfg = MODELS[actualModelName];
  803.   if (!cfg) {
  804.     return { error: 'Unknown model: ' + actualModelName };
  805.   }
  806.  
  807.   return {
  808.     modelName: actualModelName,
  809.     modelId: cfg.mode,
  810.     thinkMode: thinkOverride !== null ? thinkOverride : cfg.think,
  811.     error: null,
  812.   };
  813. }
  814.  
  815. async function handleChatCompletions(request, body, config) {
  816.   var resolved = resolveModel(body.model || config.defaultModel);
  817.   if (resolved.error) {
  818.     return sendJSON({ error: { message: resolved.error } }, 400);
  819.   }
  820.  
  821.   var modelName = resolved.modelName;
  822.   var modelId = resolved.modelId;
  823.   var thinkMode = resolved.thinkMode;
  824.   var tools = body.tools || null;
  825.  
  826.   var prompt = messagesToPrompt(body.messages || [], tools);
  827.   if (!prompt.trim()) {
  828.     return sendJSON({ error: { message: 'empty prompt' } }, 400);
  829.   }
  830.  
  831.   var stream = body.stream === true;
  832.   var chatId = 'chatcmpl-' + generateShortId(12);
  833.  
  834.   log('Chat: model=' + modelName + ', stream=' + stream + ', tokens≈' + estimateTokens(prompt), 'INFO', config);
  835.  
  836.   if (!stream || tools) {
  837.     try {
  838.       var raw = await geminiStreamGenerate(prompt, modelId, thinkMode, config);
  839.  
  840.       var text = extractResponseText(raw);
  841.       var toolCalls = null;
  842.  
  843.       if (tools && text) {
  844.         var parsed = parseToolCalls(text);
  845.         text = parsed.cleanText;
  846.         toolCalls = parsed.toolCalls.length > 0 ? parsed.toolCalls : null;
  847.       }
  848.  
  849.       var msg = { role: 'assistant', content: text || null };
  850.       if (toolCalls) {
  851.         msg.tool_calls = toolCalls;
  852.       }
  853.  
  854.       var finishReason = toolCalls ? 'tool_calls' : 'stop';
  855.  
  856.       if (stream) {
  857.         var encoder = new TextEncoder();
  858.         var nonStreamSSE = new ReadableStream({
  859.           start: function (controller) {
  860.             var chunk = {
  861.               id: chatId,
  862.               object: 'chat.completion.chunk',
  863.               created: timestamp(),
  864.               model: modelName,
  865.               choices: [{ index: 0, delta: msg, finish_reason: finishReason }],
  866.             };
  867.             controller.enqueue(encoder.encode('data: ' + JSON.stringify(chunk) + '\n\n'));
  868.             controller.enqueue(encoder.encode('data: [DONE]\n\n'));
  869.             controller.close();
  870.           },
  871.         });
  872.         return sendSSE(nonStreamSSE);
  873.       }
  874.  
  875.       return sendJSON({
  876.         id: chatId,
  877.         object: 'chat.completion',
  878.         created: timestamp(),
  879.         model: modelName,
  880.         choices: [{ index: 0, message: msg, finish_reason: finishReason }],
  881.         usage: {
  882.           prompt_tokens: estimateTokens(prompt),
  883.           completion_tokens: estimateTokens(text),
  884.           total_tokens: estimateTokens(prompt + text),
  885.         },
  886.       });
  887.     } catch (error) {
  888.       log('Upstream error: ' + error.message, 'ERROR', config);
  889.       return sendJSON({ error: { message: 'upstream error: ' + error.message } }, 502);
  890.     }
  891.   }
  892.  
  893.   var streamEncoder = new TextEncoder();
  894.  
  895.   var streamBody = new ReadableStream({
  896.     start: function (controller) {
  897.       var heartbeatTimer = null;
  898.       var isFinished = false;
  899.  
  900.       var clearHeartbeat = function () {
  901.         if (heartbeatTimer) {
  902.           clearInterval(heartbeatTimer);
  903.           heartbeatTimer = null;
  904.         }
  905.       };
  906.  
  907.       var finishStream = function (reason) {
  908.         if (isFinished) return;
  909.         clearHeartbeat();
  910.         isFinished = true;
  911.         try {
  912.           controller.enqueue(streamEncoder.encode('data: ' + JSON.stringify({
  913.             id: chatId,
  914.             object: 'chat.completion.chunk',
  915.             created: timestamp(),
  916.             model: modelName,
  917.             choices: [{
  918.               index: 0,
  919.               delta: { content: "" },
  920.               finish_reason: reason || 'stop'
  921.             }],
  922.           }) + '\n\n'));
  923.  
  924.           controller.enqueue(streamEncoder.encode('data: [DONE]\n\n'));
  925.           controller.close();
  926.         } catch (e) {
  927.           log('Failed to finish stream: ' + e.message, 'ERROR', config);
  928.         }
  929.       };
  930.  
  931.       (async function () {
  932.         try {
  933.           controller.enqueue(streamEncoder.encode('data: ' + JSON.stringify({
  934.             id: chatId,
  935.             object: 'chat.completion.chunk',
  936.             created: timestamp(),
  937.             model: modelName,
  938.             choices: [{
  939.               index: 0,
  940.               delta: { role: 'assistant' },
  941.               finish_reason: null
  942.             }],
  943.           }) + '\n\n'));
  944.  
  945.           heartbeatTimer = setInterval(function () {
  946.             if (!isFinished) {
  947.               try {
  948.                 controller.enqueue(streamEncoder.encode(': heartbeat\n\n'));
  949.               } catch (e) {
  950.                 clearHeartbeat();
  951.               }
  952.             } else {
  953.               clearHeartbeat();
  954.             }
  955.           }, 2000);
  956.  
  957.           var reqBody = buildPayload(prompt, modelId, thinkMode, config);
  958.           var headers = await buildHeaders(config);
  959.           var url = buildUrl(config);
  960.  
  961.           var fetchController = new AbortController();
  962.           var fetchTimeout = setTimeout(function () {
  963.             fetchController.abort();
  964.           }, (config.requestTimeoutSec - 2) * 1000);
  965.  
  966.           try {
  967.             var response = await fetch(url, {
  968.               method: 'POST',
  969.               headers: headers,
  970.               body: reqBody,
  971.               signal: fetchController.signal,
  972.             });
  973.             clearTimeout(fetchTimeout);
  974.  
  975.             if (!response.ok) {
  976.               var errorText = '';
  977.               try {
  978.                 errorText = await response.text();
  979.               } catch (e) {
  980.                 errorText = 'Unable to read error message';
  981.               }
  982.               throw new Error('HTTP ' + response.status + ': ' + errorText.substring(0, 200));
  983.             }
  984.  
  985.             var reader = response.body.getReader();
  986.             var decoder = new TextDecoder();
  987.             var buffer = '';
  988.             var prevText = '';
  989.  
  990.             while (true) {
  991.               var readResult = await reader.read();
  992.               if (readResult.done) break;
  993.  
  994.               buffer += decoder.decode(readResult.value, { stream: true });
  995.  
  996.               if (buffer.indexOf('BardErrorInfo') !== -1) {
  997.                 var match = buffer.match(/BardErrorInfo\s*\[(\d+)\]/);
  998.                 if (match) {
  999.                   throw new Error('Gemini upstream rejected request: BardErrorInfo [' + match[1] + ']');
  1000.                 }
  1001.               }
  1002.  
  1003.               var lines = buffer.split('\n');
  1004.  
  1005.               buffer = lines.pop() || '';
  1006.  
  1007.               for (var li = 0; li < lines.length; li++) {
  1008.                 var line = lines[li];
  1009.  
  1010.                 if (line.indexOf('"wrb.fr"') === -1 || line.length < 200) continue;
  1011.  
  1012.                 try {
  1013.                   var arr = JSON.parse(line);
  1014.                   var innerStr = arr[0][2];
  1015.                   if (!innerStr || innerStr.length < 50) continue;
  1016.  
  1017.                   var inner2 = JSON.parse(innerStr);
  1018.  
  1019.                   if (Array.isArray(inner2) && inner2.length > 4 && inner2[4]) {
  1020.                     var parts = inner2[4];
  1021.                     for (var pi = 0; pi < parts.length; pi++) {
  1022.                       var part = parts[pi];
  1023.                       if (Array.isArray(part) && part.length > 1 && part[1] && Array.isArray(part[1])) {
  1024.                         var textItems = part[1];
  1025.                         for (var ti = 0; ti < textItems.length; ti++) {
  1026.                           var t = textItems[ti];
  1027.  
  1028.                           if (typeof t === 'string' && t.length > prevText.length) {
  1029.                             var delta = t.slice(prevText.length);
  1030.  
  1031.                             var cleaned = cleanGeminiText(delta, false);
  1032.                             if (cleaned) {
  1033.                               controller.enqueue(streamEncoder.encode('data: ' + JSON.stringify({
  1034.                                 id: chatId,
  1035.                                 object: 'chat.completion.chunk',
  1036.                                 created: timestamp(),
  1037.                                 model: modelName,
  1038.                                 choices: [{
  1039.                                   index: 0,
  1040.                                   delta: { content: cleaned },
  1041.                                   finish_reason: null
  1042.                                 }],
  1043.                               }) + '\n\n'));
  1044.                             }
  1045.  
  1046.                             prevText = t;
  1047.                           }
  1048.                         }
  1049.                       }
  1050.                     }
  1051.                   }
  1052.                 } catch (e) {
  1053.                 }
  1054.               }
  1055.             }
  1056.           } finally {
  1057.             clearTimeout(fetchTimeout);
  1058.           }
  1059.  
  1060.           finishStream('stop');
  1061.         } catch (error) {
  1062.           log('Stream error: ' + error.message, 'ERROR', config);
  1063.           try {
  1064.             if (!isFinished) {
  1065.               controller.enqueue(streamEncoder.encode('data: ' + JSON.stringify({
  1066.                 error: { message: error.message, type: 'upstream_error' }
  1067.               }) + '\n\n'));
  1068.             }
  1069.           } catch (e) {
  1070.           }
  1071.           finishStream('error');
  1072.         }
  1073.       })();
  1074.     },
  1075.     cancel: function () {
  1076.       log('Client disconnected from stream', 'INFO', config);
  1077.     },
  1078.   });
  1079.  
  1080.   return sendSSE(streamBody);
  1081. }
  1082.  
  1083. async function handleResponses(request, body, config) {
  1084.   var resolved = resolveModel(body.model || config.defaultModel);
  1085.   if (resolved.error) {
  1086.     return sendJSON({ error: { message: resolved.error } }, 400);
  1087.   }
  1088.  
  1089.   var modelName = resolved.modelName;
  1090.   var modelId = resolved.modelId;
  1091.   var thinkMode = resolved.thinkMode;
  1092.   var messages = [];
  1093.  
  1094.   if (body.instructions) {
  1095.     messages.push({ role: 'system', content: body.instructions });
  1096.   }
  1097.  
  1098.   var inputs = body.input || [];
  1099.  
  1100.   if (typeof inputs === 'string') {
  1101.     inputs = [inputs];
  1102.   }
  1103.   for (var i = 0; i < inputs.length; i++) {
  1104.     var item = inputs[i];
  1105.     if (typeof item === 'string') {
  1106.       messages.push({ role: 'user', content: item });
  1107.     } else if (item.type === 'function_call_output') {
  1108.       messages.push({
  1109.         role: 'tool',
  1110.         tool_call_id: item.call_id,
  1111.         name: item.name,
  1112.         content: item.output,
  1113.       });
  1114.     } else {
  1115.       var content = item.content;
  1116.       if (Array.isArray(content)) {
  1117.         var textParts = [];
  1118.         for (var j = 0; j < content.length; j++) {
  1119.           var c = content[j];
  1120.           if (c.type === 'output_text') textParts.push(c.text || '');
  1121.         }
  1122.         content = textParts.join(' ');
  1123.       }
  1124.       messages.push({ role: item.role || 'user', content: content });
  1125.     }
  1126.   }
  1127.  
  1128.   var tools = body.tools;
  1129.   if (tools) {
  1130.     var normalizedTools = [];
  1131.     for (var ti = 0; ti < tools.length; ti++) {
  1132.       var t = tools[ti];
  1133.       if (t.type === 'function' && !t.function) {
  1134.         normalizedTools.push({
  1135.           type: 'function',
  1136.           function: { name: t.name, description: t.description || '', parameters: t.parameters || {} },
  1137.         });
  1138.       } else {
  1139.         normalizedTools.push(t);
  1140.       }
  1141.     }
  1142.     tools = normalizedTools;
  1143.   }
  1144.  
  1145.   var prompt = messagesToPrompt(messages, tools);
  1146.   if (!prompt.trim()) {
  1147.     return sendJSON({ error: { message: 'empty input' } }, 400);
  1148.   }
  1149.  
  1150.   try {
  1151.     var raw = await geminiStreamGenerate(prompt, modelId, thinkMode, config);
  1152.     var text = extractResponseText(raw);
  1153.     var toolCalls = null;
  1154.  
  1155.     if (tools && text) {
  1156.       var parsed = parseToolCalls(text);
  1157.       text = parsed.cleanText;
  1158.       toolCalls = parsed.toolCalls.length > 0 ? parsed.toolCalls : null;
  1159.     }
  1160.  
  1161.     var responseId = 'resp_' + generateShortId(16);
  1162.     var messageId = 'msg_' + generateShortId(12);
  1163.     var output = [];
  1164.  
  1165.     if (toolCalls) {
  1166.       for (var tci = 0; tci < toolCalls.length; tci++) {
  1167.         var tc = toolCalls[tci];
  1168.         output.push({
  1169.           type: 'function_call',
  1170.           id: tc.id,
  1171.           call_id: tc.id,
  1172.           name: tc.function.name,
  1173.           arguments: tc.function.arguments,
  1174.           status: 'completed',
  1175.         });
  1176.       }
  1177.     }
  1178.  
  1179.     if (text || !toolCalls) {
  1180.       output.push({
  1181.         type: 'message',
  1182.         id: messageId,
  1183.         role: 'assistant',
  1184.         status: 'completed',
  1185.         content: [{ type: 'output_text', text: text || '', annotations: [] }],
  1186.       });
  1187.     }
  1188.  
  1189.     return sendJSON({
  1190.       id: responseId,
  1191.       object: 'response',
  1192.       created_at: timestamp(),
  1193.       status: 'completed',
  1194.       model: modelName,
  1195.       output: output,
  1196.       usage: {
  1197.         input_tokens: estimateTokens(prompt),
  1198.         output_tokens: estimateTokens(text),
  1199.         total_tokens: estimateTokens(prompt + text),
  1200.       },
  1201.     });
  1202.   } catch (error) {
  1203.     return sendJSON({ error: { message: 'upstream error: ' + error.message } }, 502);
  1204.   }
  1205. }
  1206.  
  1207. async function handleGoogleAPI(request, body, stream, config) {
  1208.   var requestUrl = new URL(request.url);
  1209.   var match = requestUrl.pathname.match(/\/v1beta\/models\/([^:]+)/);
  1210.   var modelName = match ? match[1] : null;
  1211.  
  1212.   if (!modelName) {
  1213.     return sendJSON({ error: { message: 'model not specified in path' } }, 400);
  1214.   }
  1215.  
  1216.   var resolved = resolveModel(modelName);
  1217.   if (resolved.error) {
  1218.     return sendJSON({ error: { message: resolved.error } }, 400);
  1219.   }
  1220.  
  1221.   var modelId = resolved.modelId;
  1222.   var thinkMode = resolved.thinkMode;
  1223.  
  1224.   var prompt = googleContentsToPrompt(body);
  1225.   if (!prompt.trim()) {
  1226.     return sendJSON({ error: { message: 'empty content' } }, 400);
  1227.   }
  1228.  
  1229.   try {
  1230.     var raw = await geminiStreamGenerate(prompt, modelId, thinkMode, config);
  1231.     var text = extractResponseText(raw);
  1232.  
  1233.     var response = {
  1234.       candidates: [{
  1235.         content: { parts: [{ text: text || '' }], role: 'model' },
  1236.         finishReason: 'STOP',
  1237.         index: 0,
  1238.       }],
  1239.       usageMetadata: {
  1240.         promptTokenCount: estimateTokens(prompt),
  1241.         candidatesTokenCount: estimateTokens(text),
  1242.         totalTokenCount: estimateTokens(prompt + text),
  1243.       },
  1244.       modelVersion: modelName,
  1245.     };
  1246.  
  1247.     if (stream) {
  1248.       return new Response('data: ' + JSON.stringify(response) + '\n\n', {
  1249.         headers: {
  1250.           'Content-Type': 'text/event-stream',
  1251.           'Cache-Control': 'no-cache',
  1252.           'Access-Control-Allow-Origin': '*',
  1253.         },
  1254.       });
  1255.     }
  1256.  
  1257.     return sendJSON(response);
  1258.   } catch (error) {
  1259.     return sendJSON({ error: { message: 'upstream error: ' + error.message } }, 502);
  1260.   }
  1261. }
  1262.  
  1263. export default {
  1264.   async fetch(request, env, ctx) {
  1265.     if (request.method === 'OPTIONS') {
  1266.       return new Response(null, {
  1267.         status: 204,
  1268.         headers: {
  1269.           'Access-Control-Allow-Origin': '*',
  1270.           'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
  1271.           'Access-Control-Allow-Headers': '*',
  1272.           'Access-Control-Max-Age': '86400',
  1273.         },
  1274.       });
  1275.     }
  1276.  
  1277.     var config = getRequestConfig(env);
  1278.  
  1279.     var requestUrl = new URL(request.url);
  1280.     var path = requestUrl.pathname;
  1281.     var method = request.method;
  1282.  
  1283.     var clientIP = request.headers.get('CF-Connecting-IP') || '0.0.0.0';
  1284.     if (!checkRateLimit(clientIP, config)) {
  1285.       log('Rate limit exceeded: ' + clientIP, 'WARN', config);
  1286.       return sendJSON({
  1287.         error: {
  1288.           message: 'Too many requests, please try again later',
  1289.           type: 'rate_limit_exceeded',
  1290.         },
  1291.       }, 429);
  1292.     }
  1293.  
  1294.     if (path.indexOf('/v1') === 0 && !checkApiKey(request, config)) {
  1295.       return sendJSON({
  1296.         error: { message: 'invalid api key' },
  1297.       }, 401);
  1298.     }
  1299.  
  1300.     if (method === 'GET') {
  1301.       if (path === '/' || path === '/health') {
  1302.         return sendJSON({
  1303.           status: 'ok',
  1304.           version: '1.5.0-cf-multifingerprint',
  1305.           platform: 'Cloudflare Workers',
  1306.           models: Object.keys(MODELS),
  1307.           defaultModel: config.defaultModel,
  1308.         });
  1309.       }
  1310.  
  1311.       if (path === '/v1/models') {
  1312.         var modelList = [];
  1313.         var modelKeys = Object.keys(MODELS);
  1314.         for (var i = 0; i < modelKeys.length; i++) {
  1315.           var id = modelKeys[i];
  1316.           var cfg = MODELS[id];
  1317.           modelList.push({
  1318.             id: id,
  1319.             object: 'model',
  1320.             created: 1700000000,
  1321.             owned_by: 'google',
  1322.             description: cfg.desc,
  1323.           });
  1324.         }
  1325.         return sendJSON({ object: 'list', data: modelList });
  1326.       }
  1327.  
  1328.       if (path === '/v1beta/models') {
  1329.         var googleModels = [];
  1330.         var gKeys = Object.keys(MODELS);
  1331.         for (var j = 0; j < gKeys.length; j++) {
  1332.           var name = gKeys[j];
  1333.           var gCfg = MODELS[name];
  1334.           googleModels.push({
  1335.             name: 'models/' + name,
  1336.             displayName: name,
  1337.             description: gCfg.desc,
  1338.             supportedGenerationMethods: ['generateContent', 'streamGenerateContent'],
  1339.           });
  1340.         }
  1341.         return sendJSON({ models: googleModels });
  1342.       }
  1343.  
  1344.       return sendJSON({ error: { message: 'not found' } }, 404);
  1345.     }
  1346.  
  1347.     if (method === 'POST') {
  1348.       var body;
  1349.       try {
  1350.         body = await request.json();
  1351.       } catch (e) {
  1352.         return sendJSON({ error: { message: 'invalid JSON' } }, 400);
  1353.       }
  1354.  
  1355.       if (path === '/v1/chat/completions') {
  1356.         return handleChatCompletions(request, body, config);
  1357.       }
  1358.  
  1359.       if (path === '/v1/responses') {
  1360.         return handleResponses(request, body, config);
  1361.       }
  1362.  
  1363.       if (path.indexOf(':streamGenerateContent') !== -1) {
  1364.         return handleGoogleAPI(request, body, true, config);
  1365.       }
  1366.  
  1367.       if (path.indexOf(':generateContent') !== -1) {
  1368.         return handleGoogleAPI(request, body, false, config);
  1369.       }
  1370.  
  1371.       if (path.indexOf('/v1/') === 0) {
  1372.         return handleChatCompletions(request, body, config);
  1373.       }
  1374.  
  1375.       return sendJSON({ error: { message: 'not found' } }, 404);
  1376.     }
  1377.  
  1378.     return sendJSON({ error: { message: 'method not allowed' } }, 405);
  1379.   },
  1380. };
  1381.  
  1382.  
Tags: janitorai
Advertisement
Add Comment
Please, Sign In to add comment