Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- var DEFAULT_CONFIG = {
- retryAttempts: 3,
- retryDelaySec: 2,
- requestTimeoutSec: 28,
- geminiBl: 'boq_assistant-bard-web-server_20260831.15_p2',
- authUser: null,
- xsrfToken: null,
- defaultModel: 'gemini-3.8-flash',
- apiKeys: ['sk-gemini'],
- cookieString: null,
- sapisid: null,
- logRequests: true,
- rateLimit: {
- enabled: true,
- maxRequests: 3000,
- windowSec: 60,
- },
- fingerprintJitterMs: 1500,
- };
- var USER_AGENTS = [
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36',
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
- 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15',
- 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36',
- 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36',
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0',
- 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14.5; rv:128.0) Gecko/20100101 Firefox/128.0',
- 'Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0',
- ];
- var UA_WEIGHTS = [35, 30, 8, 10, 7, 5, 3, 2];
- function getRandomUserAgent() {
- var totalWeight = 0;
- for (var i = 0; i < UA_WEIGHTS.length; i++) {
- totalWeight += UA_WEIGHTS[i];
- }
- var random = Math.random() * totalWeight;
- var cumulative = 0;
- for (var j = 0; j < USER_AGENTS.length; j++) {
- cumulative += UA_WEIGHTS[j];
- if (random < cumulative) {
- return USER_AGENTS[j];
- }
- }
- return USER_AGENTS[0];
- }
- var ACCEPT_LANGUAGES = [
- 'en-US,en;q=0.9',
- 'en-US,en;q=0.9,zh-CN;q=0.8',
- 'en-GB,en;q=0.9',
- 'en-US,en;q=0.9,ja;q=0.8',
- 'en-US,en;q=0.9,ko;q=0.8',
- 'en-US,en;q=0.9,es;q=0.8',
- ];
- function getRandomAcceptLanguage() {
- var idx = Math.floor(Math.random() * ACCEPT_LANGUAGES.length);
- return ACCEPT_LANGUAGES[idx];
- }
- var SEC_CH_UA_POOLS = [
- '"Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"',
- '"Not)A;Brand";v="99", "Google Chrome";v="126", "Chromium";v="126"',
- '"Not)A;Brand";v="99", "Google Chrome";v="125", "Chromium";v="125"',
- ];
- var SEC_CH_UA_PLATFORMS = [
- '"Windows"',
- '"macOS"',
- '"Linux"',
- ];
- function getRandomSecChUa() {
- var idx = Math.floor(Math.random() * SEC_CH_UA_POOLS.length);
- return SEC_CH_UA_POOLS[idx];
- }
- function getRandomSecChUaPlatform() {
- var idx = Math.floor(Math.random() * SEC_CH_UA_PLATFORMS.length);
- return SEC_CH_UA_PLATFORMS[idx];
- }
- var MODELS = {
- 'gemini-3.8-flash': {
- mode: 1,
- think: 4,
- desc: 'Latest all-around model (Gemini 3.8 Flash)',
- },
- 'gemini-3.8-flash-thinking': {
- mode: 2,
- think: 0,
- desc: 'Gemini 3.8 Flash with deep thinking mode (~20k chars)',
- },
- 'gemini-3.7-flash': {
- mode: 1,
- think: 4,
- desc: 'Gemini 3.7 Flash',
- },
- 'gemini-3.6-flash': {
- mode: 1,
- think: 4,
- desc: 'All-around model (Gemini 3.6 Flash)',
- },
- 'gemini-3.5-flash': {
- mode: 1,
- think: 4,
- desc: 'Alias for gemini-3.6-flash (backend upgraded)',
- },
- 'gemini-3.5-flash-thinking': {
- mode: 2,
- think: 0,
- desc: 'Deep thinking mode, longest output (~20k chars)',
- },
- 'gemini-3.1-pro': {
- mode: 3,
- think: 4,
- desc: 'Pro model (requires cookie for real routing)',
- },
- 'gemini-auto': {
- mode: 4,
- think: 4,
- desc: 'Auto model selection',
- },
- 'gemini-3.5-flash-thinking-lite': {
- mode: 5,
- think: 0,
- desc: 'Dynamic thinking with adaptive depth',
- },
- 'gemini-flash-lite': {
- mode: 6,
- think: 4,
- desc: 'Lightweight fast model',
- },
- };
- function getRequestConfig(env) {
- var config = {
- retryAttempts: DEFAULT_CONFIG.retryAttempts,
- retryDelaySec: DEFAULT_CONFIG.retryDelaySec,
- requestTimeoutSec: DEFAULT_CONFIG.requestTimeoutSec,
- geminiBl: DEFAULT_CONFIG.geminiBl,
- authUser: DEFAULT_CONFIG.authUser,
- xsrfToken: DEFAULT_CONFIG.xsrfToken,
- defaultModel: DEFAULT_CONFIG.defaultModel,
- apiKeys: DEFAULT_CONFIG.apiKeys,
- cookieString: DEFAULT_CONFIG.cookieString,
- sapisid: DEFAULT_CONFIG.sapisid,
- logRequests: DEFAULT_CONFIG.logRequests,
- fingerprintJitterMs: DEFAULT_CONFIG.fingerprintJitterMs,
- rateLimit: {
- enabled: DEFAULT_CONFIG.rateLimit.enabled,
- maxRequests: DEFAULT_CONFIG.rateLimit.maxRequests,
- windowSec: DEFAULT_CONFIG.rateLimit.windowSec,
- },
- };
- if (env.GEMINI_BL) {
- config.geminiBl = env.GEMINI_BL;
- }
- if (env.DEFAULT_MODEL) {
- config.defaultModel = env.DEFAULT_MODEL;
- }
- config.authUser = env.AUTH_USER || null;
- config.xsrfToken = env.XSRF_TOKEN || null;
- var cookieStrings = (env.COOKIE_STRING || '').split('|').filter(function (c) {
- return c.trim();
- });
- var sapisids = (env.SAPISID || '').split('|').filter(function (s) {
- return s.trim();
- });
- if (cookieStrings.length > 0) {
- var cookieIdx = Math.floor(Math.random() * cookieStrings.length);
- config.cookieString = cookieStrings[cookieIdx].trim();
- if (sapisids.length === cookieStrings.length) {
- config.sapisid = sapisids[cookieIdx].trim();
- } else if (sapisids.length > 0) {
- var sapisidIdx = Math.floor(Math.random() * sapisids.length);
- config.sapisid = sapisids[sapisidIdx].trim();
- }
- }
- else if (sapisids.length > 0) {
- var sapisidIdx = Math.floor(Math.random() * sapisids.length);
- config.sapisid = sapisids[sapisidIdx].trim();
- }
- if (!config.sapisid && config.cookieString) {
- var match = config.cookieString.match(/SAPISID=([^;]+)/);
- if (match) {
- config.sapisid = match[1].trim();
- }
- }
- if (env.API_KEYS) {
- try {
- config.apiKeys = JSON.parse(env.API_KEYS);
- } catch (e) {
- console.error('[ERROR] Failed to parse API_KEYS: ' + e.message + ', using default value');
- }
- }
- if (env.RETRY_ATTEMPTS) {
- var ra = parseInt(env.RETRY_ATTEMPTS, 10);
- if (!isNaN(ra)) config.retryAttempts = ra;
- }
- if (env.RETRY_DELAY_SEC) {
- var rd = parseInt(env.RETRY_DELAY_SEC, 10);
- if (!isNaN(rd)) config.retryDelaySec = rd;
- }
- if (env.REQUEST_TIMEOUT_SEC) {
- var rt = parseInt(env.REQUEST_TIMEOUT_SEC, 10);
- if (!isNaN(rt)) config.requestTimeoutSec = rt;
- }
- if (env.FINGERPRINT_JITTER_MS) {
- var fj = parseInt(env.FINGERPRINT_JITTER_MS, 10);
- if (!isNaN(fj)) config.fingerprintJitterMs = fj;
- }
- if (env.RATE_LIMIT_MAX) {
- var rlmax = parseInt(env.RATE_LIMIT_MAX, 10);
- if (!isNaN(rlmax)) config.rateLimit.maxRequests = rlmax;
- }
- if (env.RATE_LIMIT_WINDOW) {
- var rlwin = parseInt(env.RATE_LIMIT_WINDOW, 10);
- if (!isNaN(rlwin)) config.rateLimit.windowSec = rlwin;
- }
- return config;
- }
- function log(msg, level, config) {
- level = level || 'INFO';
- var shouldLog = config ? config.logRequests : DEFAULT_CONFIG.logRequests;
- if (shouldLog) {
- var ts = new Date().toISOString().split('T')[1].split('.')[0];
- console.log('[' + ts + '] [' + level + '] ' + msg);
- }
- }
- function generateUUID() {
- if (typeof crypto !== 'undefined' && crypto.randomUUID) {
- return crypto.randomUUID();
- }
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
- var r = Math.random() * 16 | 0;
- var v = c === 'x' ? r : (r & 0x3 | 0x8);
- return v.toString(16);
- });
- }
- function generateShortId(length) {
- var len = length || 12;
- return generateUUID().replace(/-/g, '').substring(0, len);
- }
- function timestamp() {
- return Math.floor(Date.now() / 1000);
- }
- function estimateTokens(text) {
- if (!text) return 0;
- return Math.max(1, Math.ceil(text.length / 4));
- }
- async function makeSapisidHash(sapisid) {
- var ts = timestamp();
- var input = ts + ' ' + sapisid + ' https://gemini.google.com';
- var encoder = new TextEncoder();
- var data = encoder.encode(input);
- var hashBuffer = await crypto.subtle.digest('SHA-1', data);
- var hashArray = Array.from(new Uint8Array(hashBuffer));
- var hashHex = hashArray.map(function (b) {
- return b.toString(16).padStart(2, '0');
- }).join('');
- return 'SAPISIDHASH ' + ts + '_' + hashHex;
- }
- function getAccountPrefix(config) {
- var authUser = config.authUser;
- if (authUser === null || authUser === undefined || authUser === '') {
- return '';
- }
- return '/u/' + authUser;
- }
- function buildPayload(prompt, modelId, thinkMode, config) {
- var inner = new Array(80).fill(null);
- inner[0] = [prompt, 0, null, null, null, null, 0];
- inner[1] = ['en'];
- inner[2] = ['', '', '', null, null, null, null, null, null, ''];
- inner[6] = [0];
- inner[7] = 1;
- inner[10] = 1;
- inner[11] = 0;
- inner[17] = [[thinkMode]];
- inner[18] = 0;
- inner[27] = 1;
- inner[30] = [4];
- inner[41] = [2];
- inner[53] = 0;
- inner[59] = generateUUID();
- inner[61] = [];
- inner[68] = 1;
- inner[79] = modelId;
- var outer = [null, JSON.stringify(inner)];
- var params = new URLSearchParams();
- params.append('f.req', JSON.stringify(outer));
- if (config.xsrfToken) {
- params.append('at', config.xsrfToken);
- }
- return params.toString();
- }
- function buildUrl(config) {
- var prefix = getAccountPrefix(config);
- var reqid = timestamp() % 1000000;
- return 'https://gemini.google.com' + prefix +
- '/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate' +
- '?bl=' + config.geminiBl +
- '&hl=en' +
- '&_reqid=' + reqid +
- '&rt=c';
- }
- async function buildHeaders(config) {
- var prefix = getAccountPrefix(config);
- var selectedUA = getRandomUserAgent();
- var selectedLanguage = getRandomAcceptLanguage();
- var headers = {
- 'Content-Type': 'application/x-www-form-urlencoded',
- 'Origin': 'https://gemini.google.com',
- 'Referer': 'https://gemini.google.com' + prefix + '/app',
- 'X-Same-Domain': '1',
- 'User-Agent': selectedUA,
- 'Accept': '*/*',
- 'Accept-Language': selectedLanguage,
- 'Sec-Fetch-Dest': 'empty',
- 'Sec-Fetch-Mode': 'cors',
- 'Sec-Fetch-Site': 'same-origin',
- };
- if (selectedUA.indexOf('Chrome') !== -1) {
- headers['Sec-Ch-Ua'] = getRandomSecChUa();
- headers['Sec-Ch-Ua-Mobile'] = '?0';
- headers['Sec-Ch-Ua-Platform'] = getRandomSecChUaPlatform();
- }
- if (prefix) {
- headers['X-Goog-AuthUser'] = String(config.authUser);
- }
- if (config.cookieString) {
- headers['Cookie'] = config.cookieString;
- }
- if (config.sapisid) {
- headers['Authorization'] = await makeSapisidHash(config.sapisid);
- }
- return headers;
- }
- async function geminiStreamGenerate(prompt, modelId, thinkMode, config) {
- if (config.fingerprintJitterMs > 0) {
- var jitter = Math.random() * config.fingerprintJitterMs;
- await new Promise(function (resolve) { setTimeout(resolve, jitter); });
- }
- var body = buildPayload(prompt, modelId, thinkMode, config);
- var headers = await buildHeaders(config);
- var url = buildUrl(config);
- var lastError;
- for (var attempt = 0; attempt < config.retryAttempts; attempt++) {
- try {
- if (attempt > 0) {
- headers = await buildHeaders(config);
- if (config.fingerprintJitterMs > 0) {
- var retryJitter = Math.random() * config.fingerprintJitterMs;
- await new Promise(function (resolve) { setTimeout(resolve, retryJitter); });
- }
- }
- var controller = new AbortController();
- var timeout = setTimeout(function () {
- controller.abort();
- }, config.requestTimeoutSec * 1000);
- var response = await fetch(url, {
- method: 'POST',
- headers: headers,
- body: body,
- signal: controller.signal,
- });
- clearTimeout(timeout);
- if (response.status === 405) {
- throw new Error('HTTP 405: Method Not Allowed - BL version may be outdated, please update geminiBl');
- }
- if (response.status === 429) {
- var retryAfter = parseInt(response.headers.get('Retry-After') || '5', 10);
- log('Received 429 rate limit, waiting ' + retryAfter + 's before retrying...', 'WARN', config);
- if (attempt < config.retryAttempts - 1) {
- await new Promise(function (resolve) { setTimeout(resolve, retryAfter * 1000); });
- continue;
- }
- throw new Error('HTTP 429: Too Many Requests - please add a valid cookie or reduce request frequency');
- }
- if (response.status === 403) {
- throw new Error('HTTP 403: Forbidden - valid cookie authentication may be required');
- }
- if (!response.ok) {
- var errorText = '';
- try {
- errorText = await response.text();
- } catch (e) {
- errorText = 'Unable to read error message';
- }
- throw new Error('HTTP ' + response.status + ': ' + errorText.substring(0, 200));
- }
- return await response.text();
- } catch (error) {
- lastError = error;
- if (attempt < config.retryAttempts - 1) {
- log('Retry ' + (attempt + 1) + '/' + config.retryAttempts + ': ' + error.message, 'WARN', config);
- var delay = config.retryDelaySec * Math.pow(2, attempt) * 1000;
- await new Promise(function (resolve) { setTimeout(resolve, delay); });
- }
- }
- }
- throw lastError;
- }
- function cleanGeminiText(text, strip) {
- if (strip === undefined) strip = true;
- text = text.replace(
- /```(?:python|javascript|text)\?code_(?:reference|stdout)&code_event_index=\d+\n[\s\S]*?```\n?/g,
- ''
- );
- return strip ? text.trim() : text;
- }
- function extractResponseText(raw) {
- var bardErr = raw.match(/BardErrorInfo\s*\[(\d+)\]/);
- if (bardErr) {
- throw new Error('Gemini upstream rejected request: BardErrorInfo [' + bardErr[1] + ']');
- }
- var texts = [];
- var lines = raw.split('\n');
- for (var i = 0; i < lines.length; i++) {
- var line = lines[i];
- if (line.indexOf('"wrb.fr"') === -1 || line.length < 200) continue;
- try {
- var arr = JSON.parse(line);
- var innerStr = arr[0][2];
- if (!innerStr || innerStr.length < 50) continue;
- var inner = JSON.parse(innerStr);
- if (Array.isArray(inner) && inner.length > 4 && inner[4]) {
- var parts = inner[4];
- for (var j = 0; j < parts.length; j++) {
- var part = parts[j];
- if (Array.isArray(part) && part.length > 1 && part[1]) {
- if (Array.isArray(part[1])) {
- var textItems = part[1];
- for (var k = 0; k < textItems.length; k++) {
- var t = textItems[k];
- if (typeof t === 'string' && t.length > 0) {
- texts.push(t);
- }
- }
- }
- }
- }
- }
- } catch (e) {
- }
- }
- var text = '';
- for (var m = texts.length - 1; m >= 0; m--) {
- if (texts[m].trim()) {
- text = texts[m];
- break;
- }
- }
- return cleanGeminiText(text);
- }
- function messagesToPrompt(messages, tools) {
- var parts = [];
- if (tools && tools.length > 0) {
- var toolDefs = [];
- for (var ti = 0; ti < tools.length; ti++) {
- var tool = tools[ti];
- var fn = (tool.type === 'function') ? (tool.function || tool) : tool;
- toolDefs.push({
- name: fn.name || tool.name || '',
- description: fn.description || tool.description || '',
- parameters: fn.parameters || tool.parameters || {},
- });
- }
- parts.push(
- '[System instruction]: You have access to tools. ' +
- 'To call a tool, respond with:\n' +
- '```tool_call\n{"name": "func_name", "arguments": {...}}\n```\n' +
- 'Only use tool_call blocks when needed.\n\n' +
- 'Available tools:\n' + JSON.stringify(toolDefs, null, 2)
- );
- }
- for (var mi = 0; mi < messages.length; mi++) {
- var msg = messages[mi];
- var role = msg.role || 'user';
- var content = msg.content || '';
- if (Array.isArray(content)) {
- var textParts = [];
- for (var ci = 0; ci < content.length; ci++) {
- var c = content[ci];
- if (c.type === 'text' || c.type === 'input_text') {
- textParts.push(c.text || '');
- }
- }
- content = textParts.join(' ');
- }
- if (role === 'system') {
- parts.push('[System instruction]: ' + content);
- } else if (role === 'assistant') {
- if (msg.tool_calls && msg.tool_calls.length > 0) {
- var tcStrs = [];
- for (var tci = 0; tci < msg.tool_calls.length; tci++) {
- var tc = msg.tool_calls[tci];
- var fn = tc.function || {};
- tcStrs.push(
- '```tool_call\n' +
- '{"name": "' + fn.name + '", "arguments": ' + (fn.arguments || '{}') + '}\n' +
- '```'
- );
- }
- parts.push('[Assistant]: ' + (content || '') + '\n' + tcStrs.join('\n'));
- } else {
- parts.push('[Assistant]: ' + content);
- }
- } else if (role === 'tool') {
- parts.push('[Tool result for ' + (msg.name || 'unknown') + ']: ' + content);
- } else {
- parts.push(content || '');
- }
- }
- return parts.filter(function (p) { return p; }).join('\n\n');
- }
- function parseToolCalls(text) {
- var toolCalls = [];
- var pattern = /```tool_call\s*\n(.*?)\n```/gs;
- var match;
- while ((match = pattern.exec(text)) !== null) {
- try {
- var data = JSON.parse(match[1].trim());
- toolCalls.push({
- id: 'call_' + generateShortId(8),
- type: 'function',
- function: {
- name: data.name,
- arguments: JSON.stringify(data.arguments || {}),
- },
- });
- } catch (e) {
- }
- }
- var cleanText = text.replace(pattern, '').trim();
- return {
- cleanText: cleanText,
- toolCalls: toolCalls
- };
- }
- function googleContentsToPrompt(req) {
- var parts = [];
- var sysInst = req.systemInstruction;
- if (sysInst && sysInst.parts) {
- var sysTextParts = [];
- for (var si = 0; si < sysInst.parts.length; si++) {
- var sp = sysInst.parts[si];
- if (sp.text) sysTextParts.push(sp.text);
- }
- var sysText = sysTextParts.join(' ');
- if (sysText) {
- parts.push('[System instruction]: ' + sysText);
- }
- }
- var contents = req.contents || [];
- for (var ci = 0; ci < contents.length; ci++) {
- var content = contents[ci];
- var role = content.role || 'user';
- var textParts = [];
- var partsArr = content.parts || [];
- for (var pi = 0; pi < partsArr.length; pi++) {
- if (partsArr[pi].text) textParts.push(partsArr[pi].text);
- }
- var text = textParts.join(' ');
- if (role === 'model') {
- parts.push('[Assistant]: ' + text);
- } else {
- parts.push(text);
- }
- }
- return parts.filter(function (p) { return p; }).join('\n\n');
- }
- var rateLimitStore = new Map();
- function checkRateLimit(clientIP, config) {
- if (!config.rateLimit || !config.rateLimit.enabled) return true;
- var now = Date.now();
- var windowMs = config.rateLimit.windowSec * 1000;
- var key = 'rl:' + clientIP;
- var timestamps = (rateLimitStore.get(key) || []).filter(function (t) {
- return now - t < windowMs;
- });
- if (timestamps.length >= config.rateLimit.maxRequests) {
- return false;
- }
- timestamps.push(now);
- rateLimitStore.set(key, timestamps);
- if (Math.random() < 0.05) {
- rateLimitStore.forEach(function (v, k) {
- var valid = v.filter(function (t) {
- return now - t < windowMs;
- });
- if (valid.length === 0) {
- rateLimitStore.delete(k);
- } else {
- rateLimitStore.set(k, valid);
- }
- });
- }
- return true;
- }
- function checkApiKey(request, config) {
- var keys = config.apiKeys || [];
- if (keys.length === 0) return true;
- var auth = request.headers.get('Authorization') || '';
- if (auth.indexOf('Bearer ') === 0) {
- var token = auth.slice(7);
- if (keys.indexOf(token) !== -1) return true;
- }
- var headerNames = ['x-api-key', 'x-goog-api-key'];
- for (var i = 0; i < headerNames.length; i++) {
- var value = request.headers.get(headerNames[i]) || '';
- if (keys.indexOf(value) !== -1) return true;
- }
- var url = new URL(request.url);
- var keyParam = url.searchParams.get('key');
- if (keyParam && keys.indexOf(keyParam) !== -1) return true;
- return false;
- }
- function sendJSON(data, status) {
- if (status === undefined) status = 200;
- var body = JSON.stringify(data);
- return new Response(body, {
- status: status,
- headers: {
- 'Content-Type': 'application/json; charset=utf-8',
- 'Access-Control-Allow-Origin': '*',
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
- 'Access-Control-Allow-Headers': '*',
- },
- });
- }
- function sendSSE(stream) {
- return new Response(stream, {
- headers: {
- 'Content-Type': 'text/event-stream; charset=utf-8',
- 'Cache-Control': 'no-cache',
- 'Connection': 'keep-alive',
- 'X-Accel-Buffering': 'no',
- 'Access-Control-Allow-Origin': '*',
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
- 'Access-Control-Allow-Headers': '*',
- },
- });
- }
- function resolveModel(modelName) {
- var thinkOverride = null;
- var actualModelName = modelName;
- if (modelName.indexOf('@think=') !== -1) {
- var parts = modelName.split('@think=');
- actualModelName = parts[0];
- thinkOverride = parseInt(parts[1], 10);
- if (isNaN(thinkOverride)) {
- return { error: 'Invalid think parameter: ' + parts[1] };
- }
- }
- var cfg = MODELS[actualModelName];
- if (!cfg) {
- return { error: 'Unknown model: ' + actualModelName };
- }
- return {
- modelName: actualModelName,
- modelId: cfg.mode,
- thinkMode: thinkOverride !== null ? thinkOverride : cfg.think,
- error: null,
- };
- }
- async function handleChatCompletions(request, body, config) {
- var resolved = resolveModel(body.model || config.defaultModel);
- if (resolved.error) {
- return sendJSON({ error: { message: resolved.error } }, 400);
- }
- var modelName = resolved.modelName;
- var modelId = resolved.modelId;
- var thinkMode = resolved.thinkMode;
- var tools = body.tools || null;
- var prompt = messagesToPrompt(body.messages || [], tools);
- if (!prompt.trim()) {
- return sendJSON({ error: { message: 'empty prompt' } }, 400);
- }
- var stream = body.stream === true;
- var chatId = 'chatcmpl-' + generateShortId(12);
- log('Chat: model=' + modelName + ', stream=' + stream + ', tokens≈' + estimateTokens(prompt), 'INFO', config);
- if (!stream || tools) {
- try {
- var raw = await geminiStreamGenerate(prompt, modelId, thinkMode, config);
- var text = extractResponseText(raw);
- var toolCalls = null;
- if (tools && text) {
- var parsed = parseToolCalls(text);
- text = parsed.cleanText;
- toolCalls = parsed.toolCalls.length > 0 ? parsed.toolCalls : null;
- }
- var msg = { role: 'assistant', content: text || null };
- if (toolCalls) {
- msg.tool_calls = toolCalls;
- }
- var finishReason = toolCalls ? 'tool_calls' : 'stop';
- if (stream) {
- var encoder = new TextEncoder();
- var nonStreamSSE = new ReadableStream({
- start: function (controller) {
- var chunk = {
- id: chatId,
- object: 'chat.completion.chunk',
- created: timestamp(),
- model: modelName,
- choices: [{ index: 0, delta: msg, finish_reason: finishReason }],
- };
- controller.enqueue(encoder.encode('data: ' + JSON.stringify(chunk) + '\n\n'));
- controller.enqueue(encoder.encode('data: [DONE]\n\n'));
- controller.close();
- },
- });
- return sendSSE(nonStreamSSE);
- }
- return sendJSON({
- id: chatId,
- object: 'chat.completion',
- created: timestamp(),
- model: modelName,
- choices: [{ index: 0, message: msg, finish_reason: finishReason }],
- usage: {
- prompt_tokens: estimateTokens(prompt),
- completion_tokens: estimateTokens(text),
- total_tokens: estimateTokens(prompt + text),
- },
- });
- } catch (error) {
- log('Upstream error: ' + error.message, 'ERROR', config);
- return sendJSON({ error: { message: 'upstream error: ' + error.message } }, 502);
- }
- }
- var streamEncoder = new TextEncoder();
- var streamBody = new ReadableStream({
- start: function (controller) {
- var heartbeatTimer = null;
- var isFinished = false;
- var clearHeartbeat = function () {
- if (heartbeatTimer) {
- clearInterval(heartbeatTimer);
- heartbeatTimer = null;
- }
- };
- var finishStream = function (reason) {
- if (isFinished) return;
- clearHeartbeat();
- isFinished = true;
- try {
- controller.enqueue(streamEncoder.encode('data: ' + JSON.stringify({
- id: chatId,
- object: 'chat.completion.chunk',
- created: timestamp(),
- model: modelName,
- choices: [{
- index: 0,
- delta: { content: "" },
- finish_reason: reason || 'stop'
- }],
- }) + '\n\n'));
- controller.enqueue(streamEncoder.encode('data: [DONE]\n\n'));
- controller.close();
- } catch (e) {
- log('Failed to finish stream: ' + e.message, 'ERROR', config);
- }
- };
- (async function () {
- try {
- controller.enqueue(streamEncoder.encode('data: ' + JSON.stringify({
- id: chatId,
- object: 'chat.completion.chunk',
- created: timestamp(),
- model: modelName,
- choices: [{
- index: 0,
- delta: { role: 'assistant' },
- finish_reason: null
- }],
- }) + '\n\n'));
- heartbeatTimer = setInterval(function () {
- if (!isFinished) {
- try {
- controller.enqueue(streamEncoder.encode(': heartbeat\n\n'));
- } catch (e) {
- clearHeartbeat();
- }
- } else {
- clearHeartbeat();
- }
- }, 2000);
- var reqBody = buildPayload(prompt, modelId, thinkMode, config);
- var headers = await buildHeaders(config);
- var url = buildUrl(config);
- var fetchController = new AbortController();
- var fetchTimeout = setTimeout(function () {
- fetchController.abort();
- }, (config.requestTimeoutSec - 2) * 1000);
- try {
- var response = await fetch(url, {
- method: 'POST',
- headers: headers,
- body: reqBody,
- signal: fetchController.signal,
- });
- clearTimeout(fetchTimeout);
- if (!response.ok) {
- var errorText = '';
- try {
- errorText = await response.text();
- } catch (e) {
- errorText = 'Unable to read error message';
- }
- throw new Error('HTTP ' + response.status + ': ' + errorText.substring(0, 200));
- }
- var reader = response.body.getReader();
- var decoder = new TextDecoder();
- var buffer = '';
- var prevText = '';
- while (true) {
- var readResult = await reader.read();
- if (readResult.done) break;
- buffer += decoder.decode(readResult.value, { stream: true });
- if (buffer.indexOf('BardErrorInfo') !== -1) {
- var match = buffer.match(/BardErrorInfo\s*\[(\d+)\]/);
- if (match) {
- throw new Error('Gemini upstream rejected request: BardErrorInfo [' + match[1] + ']');
- }
- }
- var lines = buffer.split('\n');
- buffer = lines.pop() || '';
- for (var li = 0; li < lines.length; li++) {
- var line = lines[li];
- if (line.indexOf('"wrb.fr"') === -1 || line.length < 200) continue;
- try {
- var arr = JSON.parse(line);
- var innerStr = arr[0][2];
- if (!innerStr || innerStr.length < 50) continue;
- var inner2 = JSON.parse(innerStr);
- if (Array.isArray(inner2) && inner2.length > 4 && inner2[4]) {
- var parts = inner2[4];
- for (var pi = 0; pi < parts.length; pi++) {
- var part = parts[pi];
- if (Array.isArray(part) && part.length > 1 && part[1] && Array.isArray(part[1])) {
- var textItems = part[1];
- for (var ti = 0; ti < textItems.length; ti++) {
- var t = textItems[ti];
- if (typeof t === 'string' && t.length > prevText.length) {
- var delta = t.slice(prevText.length);
- var cleaned = cleanGeminiText(delta, false);
- if (cleaned) {
- controller.enqueue(streamEncoder.encode('data: ' + JSON.stringify({
- id: chatId,
- object: 'chat.completion.chunk',
- created: timestamp(),
- model: modelName,
- choices: [{
- index: 0,
- delta: { content: cleaned },
- finish_reason: null
- }],
- }) + '\n\n'));
- }
- prevText = t;
- }
- }
- }
- }
- }
- } catch (e) {
- }
- }
- }
- } finally {
- clearTimeout(fetchTimeout);
- }
- finishStream('stop');
- } catch (error) {
- log('Stream error: ' + error.message, 'ERROR', config);
- try {
- if (!isFinished) {
- controller.enqueue(streamEncoder.encode('data: ' + JSON.stringify({
- error: { message: error.message, type: 'upstream_error' }
- }) + '\n\n'));
- }
- } catch (e) {
- }
- finishStream('error');
- }
- })();
- },
- cancel: function () {
- log('Client disconnected from stream', 'INFO', config);
- },
- });
- return sendSSE(streamBody);
- }
- async function handleResponses(request, body, config) {
- var resolved = resolveModel(body.model || config.defaultModel);
- if (resolved.error) {
- return sendJSON({ error: { message: resolved.error } }, 400);
- }
- var modelName = resolved.modelName;
- var modelId = resolved.modelId;
- var thinkMode = resolved.thinkMode;
- var messages = [];
- if (body.instructions) {
- messages.push({ role: 'system', content: body.instructions });
- }
- var inputs = body.input || [];
- if (typeof inputs === 'string') {
- inputs = [inputs];
- }
- for (var i = 0; i < inputs.length; i++) {
- var item = inputs[i];
- if (typeof item === 'string') {
- messages.push({ role: 'user', content: item });
- } else if (item.type === 'function_call_output') {
- messages.push({
- role: 'tool',
- tool_call_id: item.call_id,
- name: item.name,
- content: item.output,
- });
- } else {
- var content = item.content;
- if (Array.isArray(content)) {
- var textParts = [];
- for (var j = 0; j < content.length; j++) {
- var c = content[j];
- if (c.type === 'output_text') textParts.push(c.text || '');
- }
- content = textParts.join(' ');
- }
- messages.push({ role: item.role || 'user', content: content });
- }
- }
- var tools = body.tools;
- if (tools) {
- var normalizedTools = [];
- for (var ti = 0; ti < tools.length; ti++) {
- var t = tools[ti];
- if (t.type === 'function' && !t.function) {
- normalizedTools.push({
- type: 'function',
- function: { name: t.name, description: t.description || '', parameters: t.parameters || {} },
- });
- } else {
- normalizedTools.push(t);
- }
- }
- tools = normalizedTools;
- }
- var prompt = messagesToPrompt(messages, tools);
- if (!prompt.trim()) {
- return sendJSON({ error: { message: 'empty input' } }, 400);
- }
- try {
- var raw = await geminiStreamGenerate(prompt, modelId, thinkMode, config);
- var text = extractResponseText(raw);
- var toolCalls = null;
- if (tools && text) {
- var parsed = parseToolCalls(text);
- text = parsed.cleanText;
- toolCalls = parsed.toolCalls.length > 0 ? parsed.toolCalls : null;
- }
- var responseId = 'resp_' + generateShortId(16);
- var messageId = 'msg_' + generateShortId(12);
- var output = [];
- if (toolCalls) {
- for (var tci = 0; tci < toolCalls.length; tci++) {
- var tc = toolCalls[tci];
- output.push({
- type: 'function_call',
- id: tc.id,
- call_id: tc.id,
- name: tc.function.name,
- arguments: tc.function.arguments,
- status: 'completed',
- });
- }
- }
- if (text || !toolCalls) {
- output.push({
- type: 'message',
- id: messageId,
- role: 'assistant',
- status: 'completed',
- content: [{ type: 'output_text', text: text || '', annotations: [] }],
- });
- }
- return sendJSON({
- id: responseId,
- object: 'response',
- created_at: timestamp(),
- status: 'completed',
- model: modelName,
- output: output,
- usage: {
- input_tokens: estimateTokens(prompt),
- output_tokens: estimateTokens(text),
- total_tokens: estimateTokens(prompt + text),
- },
- });
- } catch (error) {
- return sendJSON({ error: { message: 'upstream error: ' + error.message } }, 502);
- }
- }
- async function handleGoogleAPI(request, body, stream, config) {
- var requestUrl = new URL(request.url);
- var match = requestUrl.pathname.match(/\/v1beta\/models\/([^:]+)/);
- var modelName = match ? match[1] : null;
- if (!modelName) {
- return sendJSON({ error: { message: 'model not specified in path' } }, 400);
- }
- var resolved = resolveModel(modelName);
- if (resolved.error) {
- return sendJSON({ error: { message: resolved.error } }, 400);
- }
- var modelId = resolved.modelId;
- var thinkMode = resolved.thinkMode;
- var prompt = googleContentsToPrompt(body);
- if (!prompt.trim()) {
- return sendJSON({ error: { message: 'empty content' } }, 400);
- }
- try {
- var raw = await geminiStreamGenerate(prompt, modelId, thinkMode, config);
- var text = extractResponseText(raw);
- var response = {
- candidates: [{
- content: { parts: [{ text: text || '' }], role: 'model' },
- finishReason: 'STOP',
- index: 0,
- }],
- usageMetadata: {
- promptTokenCount: estimateTokens(prompt),
- candidatesTokenCount: estimateTokens(text),
- totalTokenCount: estimateTokens(prompt + text),
- },
- modelVersion: modelName,
- };
- if (stream) {
- return new Response('data: ' + JSON.stringify(response) + '\n\n', {
- headers: {
- 'Content-Type': 'text/event-stream',
- 'Cache-Control': 'no-cache',
- 'Access-Control-Allow-Origin': '*',
- },
- });
- }
- return sendJSON(response);
- } catch (error) {
- return sendJSON({ error: { message: 'upstream error: ' + error.message } }, 502);
- }
- }
- export default {
- async fetch(request, env, ctx) {
- if (request.method === 'OPTIONS') {
- return new Response(null, {
- status: 204,
- headers: {
- 'Access-Control-Allow-Origin': '*',
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
- 'Access-Control-Allow-Headers': '*',
- 'Access-Control-Max-Age': '86400',
- },
- });
- }
- var config = getRequestConfig(env);
- var requestUrl = new URL(request.url);
- var path = requestUrl.pathname;
- var method = request.method;
- var clientIP = request.headers.get('CF-Connecting-IP') || '0.0.0.0';
- if (!checkRateLimit(clientIP, config)) {
- log('Rate limit exceeded: ' + clientIP, 'WARN', config);
- return sendJSON({
- error: {
- message: 'Too many requests, please try again later',
- type: 'rate_limit_exceeded',
- },
- }, 429);
- }
- if (path.indexOf('/v1') === 0 && !checkApiKey(request, config)) {
- return sendJSON({
- error: { message: 'invalid api key' },
- }, 401);
- }
- if (method === 'GET') {
- if (path === '/' || path === '/health') {
- return sendJSON({
- status: 'ok',
- version: '1.5.0-cf-multifingerprint',
- platform: 'Cloudflare Workers',
- models: Object.keys(MODELS),
- defaultModel: config.defaultModel,
- });
- }
- if (path === '/v1/models') {
- var modelList = [];
- var modelKeys = Object.keys(MODELS);
- for (var i = 0; i < modelKeys.length; i++) {
- var id = modelKeys[i];
- var cfg = MODELS[id];
- modelList.push({
- id: id,
- object: 'model',
- created: 1700000000,
- owned_by: 'google',
- description: cfg.desc,
- });
- }
- return sendJSON({ object: 'list', data: modelList });
- }
- if (path === '/v1beta/models') {
- var googleModels = [];
- var gKeys = Object.keys(MODELS);
- for (var j = 0; j < gKeys.length; j++) {
- var name = gKeys[j];
- var gCfg = MODELS[name];
- googleModels.push({
- name: 'models/' + name,
- displayName: name,
- description: gCfg.desc,
- supportedGenerationMethods: ['generateContent', 'streamGenerateContent'],
- });
- }
- return sendJSON({ models: googleModels });
- }
- return sendJSON({ error: { message: 'not found' } }, 404);
- }
- if (method === 'POST') {
- var body;
- try {
- body = await request.json();
- } catch (e) {
- return sendJSON({ error: { message: 'invalid JSON' } }, 400);
- }
- if (path === '/v1/chat/completions') {
- return handleChatCompletions(request, body, config);
- }
- if (path === '/v1/responses') {
- return handleResponses(request, body, config);
- }
- if (path.indexOf(':streamGenerateContent') !== -1) {
- return handleGoogleAPI(request, body, true, config);
- }
- if (path.indexOf(':generateContent') !== -1) {
- return handleGoogleAPI(request, body, false, config);
- }
- if (path.indexOf('/v1/') === 0) {
- return handleChatCompletions(request, body, config);
- }
- return sendJSON({ error: { message: 'not found' } }, 404);
- }
- return sendJSON({ error: { message: 'method not allowed' } }, 405);
- },
- };
Advertisement
Add Comment
Please, Sign In to add comment