mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 20:00:47 +00:00
feat(gemini): add Gemini 3.6 Flash tier routing and 3.5 Flash Lite
Add gemini-3.6-flash tiered (high/medium/low) for Antigravity routing via upstreamModelId "gemini-3.6-flash-tiered(level)" + thinkingLevel, plus gemini-3.6-flash and gemini-3.5-flash-lite direct API models. - getModelUpstreamId: split (level) suffix before lookup, re-append after - Antigravity executor: preserve transformed body.model - MITM extractModel: parse thinkingLevel for tiered model (default medium) - Isolate Cloud Code endpoints: discovery (loadCodeAssist/onboardUser/ quota) on PROD cloudcode-pa, chat transport on daily-cloudcode-pa to bypass prod 429
This commit is contained in:
+42
-1
@@ -86,4 +86,45 @@ function getToolForHost(host) {
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = { IS_DEV, LSOF_BIN, TARGET_HOSTS, URL_PATTERNS, MODEL_SYNONYMS, MODEL_PATTERNS, MODEL_NO_MAP, LOG_BLACKLIST_URL_PARTS, getToolForHost };
|
||||
function isBinaryData(buffer) {
|
||||
if (!buffer || buffer.length === 0) return false;
|
||||
const sample = buffer.slice(0, Math.min(100, buffer.length));
|
||||
let nonPrintable = 0;
|
||||
for (let i = 0; i < sample.length; i++) {
|
||||
const byte = sample[i];
|
||||
if (byte < 0x20 && byte !== 0x09 && byte !== 0x0A && byte !== 0x0D) {
|
||||
nonPrintable++;
|
||||
}
|
||||
if (byte > 0x7E) nonPrintable++;
|
||||
}
|
||||
return (nonPrintable / sample.length) > 0.3;
|
||||
}
|
||||
|
||||
// Extract model from URL path (Gemini), body (OpenAI/Anthropic), or Kiro conversationState.
|
||||
function extractModel(url, body) {
|
||||
const urlMatch = url.match(/\/models\/([^/:]+)/);
|
||||
const urlModel = urlMatch?.[1] || null;
|
||||
|
||||
if (isBinaryData(body)) return urlModel;
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(body.toString());
|
||||
if (parsed.conversationState) {
|
||||
return parsed.conversationState.currentMessage?.userInputMessage?.modelId || null;
|
||||
}
|
||||
const model = urlModel || parsed.model || null;
|
||||
if (String(model).replace(/^models\//, "") === "gemini-3.6-flash-tiered") {
|
||||
const rawLevel = parsed.request?.generationConfig?.thinkingConfig?.thinkingLevel
|
||||
|| parsed.generationConfig?.thinkingConfig?.thinkingLevel;
|
||||
const level = ["high", "medium", "low"].includes(String(rawLevel).toLowerCase())
|
||||
? String(rawLevel).toLowerCase()
|
||||
: "medium";
|
||||
return `gemini-3.6-flash-${level}`;
|
||||
}
|
||||
return model;
|
||||
} catch {
|
||||
return urlModel;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { IS_DEV, LSOF_BIN, TARGET_HOSTS, URL_PATTERNS, MODEL_SYNONYMS, MODEL_PATTERNS, MODEL_NO_MAP, LOG_BLACKLIST_URL_PARTS, getToolForHost, extractModel };
|
||||
|
||||
+1
-37
@@ -7,7 +7,7 @@ const dns = require("dns");
|
||||
const { promisify } = require("util");
|
||||
const { execSync } = require("child_process");
|
||||
const { log, err, dumpRequest, createResponseDumper, clearDumpDir } = require("./logger");
|
||||
const { IS_DEV, LSOF_BIN, TARGET_HOSTS, URL_PATTERNS, MODEL_SYNONYMS, MODEL_PATTERNS, MODEL_NO_MAP, getToolForHost } = require("./config");
|
||||
const { IS_DEV, LSOF_BIN, TARGET_HOSTS, URL_PATTERNS, MODEL_SYNONYMS, MODEL_PATTERNS, MODEL_NO_MAP, getToolForHost, extractModel } = require("./config");
|
||||
const { DATA_DIR, MITM_DIR } = require("./paths");
|
||||
const { generateCert, getCertForDomain } = require("./cert/generate");
|
||||
const { getMitmAlias } = require("./dbReader");
|
||||
@@ -96,42 +96,6 @@ function collectBodyRaw(req) {
|
||||
});
|
||||
}
|
||||
|
||||
// Extract model from URL path (Gemini), body (OpenAI/Anthropic), or Kiro conversationState
|
||||
function extractModel(url, body) {
|
||||
const urlMatch = url.match(/\/models\/([^/:]+)/);
|
||||
if (urlMatch) return urlMatch[1];
|
||||
|
||||
// Skip parsing if body is binary (AWS EventStream, Protocol Buffers, etc.)
|
||||
if (isBinaryData(body)) return null;
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(body.toString());
|
||||
if (parsed.conversationState) {
|
||||
return parsed.conversationState.currentMessage?.userInputMessage?.modelId || null;
|
||||
}
|
||||
return parsed.model || null;
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
// Detect binary data vs JSON text
|
||||
function isBinaryData(buffer) {
|
||||
if (!buffer || buffer.length === 0) return false;
|
||||
// AWS EventStream signature: first 4 bytes = frame length (big-endian uint32)
|
||||
// Check for non-printable chars in first 100 bytes (common in binary protocols)
|
||||
const sample = buffer.slice(0, Math.min(100, buffer.length));
|
||||
let nonPrintable = 0;
|
||||
for (let i = 0; i < sample.length; i++) {
|
||||
const byte = sample[i];
|
||||
// Count non-ASCII printable chars (excluding whitespace)
|
||||
if (byte < 0x20 && byte !== 0x09 && byte !== 0x0A && byte !== 0x0D) {
|
||||
nonPrintable++;
|
||||
}
|
||||
if (byte > 0x7E) nonPrintable++;
|
||||
}
|
||||
// If >30% non-printable, treat as binary
|
||||
return (nonPrintable / sample.length) > 0.3;
|
||||
}
|
||||
|
||||
function getMappedModel(tool, model) {
|
||||
if (!model) return null;
|
||||
try {
|
||||
|
||||
@@ -8,9 +8,12 @@ export const MITM_TOOLS = {
|
||||
description: "Google Antigravity IDE with MITM",
|
||||
configType: "mitm",
|
||||
mitmDomain: "daily-cloudcode-pa.googleapis.com",
|
||||
modelAliases: ["gemini-3.5-flash-low", "gemini-3-flash-agent", "gemini-3.5-flash-extra-low", "gemini-3.1-pro-low", "gemini-pro-agent", "claude-sonnet-4-6", "claude-opus-4-6-thinking", "gpt-oss-120b-medium", "gemini-3-flash"],
|
||||
modelAliases: ["gemini-3.6-flash-high", "gemini-3.6-flash-medium", "gemini-3.6-flash-low", "gemini-3.5-flash-low", "gemini-3-flash-agent", "gemini-3.5-flash-extra-low", "gemini-3.1-pro-low", "gemini-pro-agent", "claude-sonnet-4-6", "claude-opus-4-6-thinking", "gpt-oss-120b-medium", "gemini-3-flash"],
|
||||
defaultModels: [
|
||||
{ id: "gemini-3.5-flash-low", name: "Gemini 3.5 Flash (Medium) / Default", alias: "gemini-3.5-flash-low" },
|
||||
{ id: "gemini-3.6-flash-high", name: "Gemini 3.6 Flash (High)", alias: "gemini-3.6-flash-high" },
|
||||
{ id: "gemini-3.6-flash-medium", name: "Gemini 3.6 Flash (Medium)", alias: "gemini-3.6-flash-medium" },
|
||||
{ id: "gemini-3.6-flash-low", name: "Gemini 3.6 Flash (Low)", alias: "gemini-3.6-flash-low" },
|
||||
{ id: "gemini-3.5-flash-low", name: "Gemini 3.5 Flash (Medium) / Default", alias: "gemini-3.5-flash-low", mandatory: true },
|
||||
{ id: "gemini-3-flash-agent", name: "Gemini 3.5 Flash (High)", alias: "gemini-3-flash-agent" },
|
||||
{ id: "gemini-3.5-flash-extra-low", name: "Gemini 3.5 Flash (Low)", alias: "gemini-3.5-flash-extra-low" },
|
||||
{ id: "gemini-3.1-pro-low", name: "Gemini 3.1 Pro (Low)", alias: "gemini-3.1-pro-low" },
|
||||
|
||||
@@ -219,7 +219,7 @@ async function handleSingleModelChat(body, modelStr, clientRawRequest = null, re
|
||||
|
||||
// Ensure real project ID is available for providers that need it (P0 fix: cold miss)
|
||||
if ((provider === "antigravity" || provider === "gemini-cli") && !refreshedCredentials.projectId) {
|
||||
const pid = await getProjectIdForConnection(credentials.connectionId, refreshedCredentials.accessToken);
|
||||
const pid = await getProjectIdForConnection(credentials.connectionId, refreshedCredentials.accessToken, provider);
|
||||
if (pid) {
|
||||
refreshedCredentials.projectId = pid;
|
||||
// Persist to DB in background so subsequent requests have it immediately
|
||||
|
||||
Reference in New Issue
Block a user