Insomniac_360

TEST-GEMINI

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