mirror of
https://github.com/decolua/9router.git
synced 2026-05-08 12:01:28 +00:00
Fix AG
This commit is contained in:
@@ -2,18 +2,10 @@
|
|||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
output: "standalone",
|
output: "standalone",
|
||||||
serverExternalPackages: ["better-sqlite3"],
|
serverExternalPackages: ["better-sqlite3"],
|
||||||
outputFileTracingExcludes: {
|
|
||||||
"*": [
|
|
||||||
"**/Cookies/**",
|
|
||||||
"**/AppData/Local/**",
|
|
||||||
"**/node_modules/.cache/**",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
images: {
|
images: {
|
||||||
unoptimized: true
|
unoptimized: true
|
||||||
},
|
},
|
||||||
env: {},
|
env: {},
|
||||||
turbopack: {},
|
|
||||||
webpack: (config, { isServer }) => {
|
webpack: (config, { isServer }) => {
|
||||||
// Ignore fs/path modules in browser bundle
|
// Ignore fs/path modules in browser bundle
|
||||||
if (!isServer) {
|
if (!isServer) {
|
||||||
|
|||||||
@@ -62,8 +62,8 @@ export const CLIENT_METADATA = {
|
|||||||
// Internal anti-loop header
|
// Internal anti-loop header
|
||||||
export const INTERNAL_REQUEST_HEADER = { name: "x-request-source", value: "local" };
|
export const INTERNAL_REQUEST_HEADER = { name: "x-request-source", value: "local" };
|
||||||
|
|
||||||
// Prefix added to client tools when forwarding to Antigravity provider (anti-ban cloaking)
|
// Suffix added to client tools when forwarding to Antigravity provider (anti-ban cloaking)
|
||||||
export const AG_TOOL_PREFIX = "ide_";
|
export const AG_TOOL_SUFFIX = "_ide";
|
||||||
|
|
||||||
// AG native default tools — kept as decoys with neutral description/properties
|
// AG native default tools — kept as decoys with neutral description/properties
|
||||||
// These names must match exactly what AG sends in the real request log
|
// These names must match exactly what AG sends in the real request log
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import { BaseExecutor } from "./base.js";
|
import { BaseExecutor } from "./base.js";
|
||||||
import { PROVIDERS } from "../config/providers.js";
|
import { PROVIDERS } from "../config/providers.js";
|
||||||
import { OAUTH_ENDPOINTS, ANTIGRAVITY_HEADERS, INTERNAL_REQUEST_HEADER, AG_DEFAULT_TOOLS, AG_TOOL_PREFIX } from "../config/appConstants.js";
|
import { OAUTH_ENDPOINTS, ANTIGRAVITY_HEADERS, INTERNAL_REQUEST_HEADER, AG_DEFAULT_TOOLS, AG_TOOL_SUFFIX } from "../config/appConstants.js";
|
||||||
import { HTTP_STATUS } from "../config/runtimeConfig.js";
|
import { HTTP_STATUS } from "../config/runtimeConfig.js";
|
||||||
import { deriveSessionId } from "../utils/sessionManager.js";
|
import { deriveSessionId } from "../utils/sessionManager.js";
|
||||||
import { proxyAwareFetch } from "../utils/proxyFetch.js";
|
import { proxyAwareFetch } from "../utils/proxyFetch.js";
|
||||||
@@ -259,9 +259,9 @@ export class AntigravityExecutor extends BaseExecutor {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Cloak tools before sending to Antigravity provider (anti-ban):
|
* Cloak tools before sending to Antigravity provider (anti-ban):
|
||||||
* - Rename client tools with ide_ prefix
|
* - Rename client tools with _ide suffix
|
||||||
* - Inject AG default decoy tools (same names, neutral description/properties)
|
* - Inject AG default decoy tools after client tools
|
||||||
* Returns { cloakedBody, toolNameMap } where toolNameMap maps prefixed → original
|
* Returns { cloakedBody, toolNameMap } where toolNameMap maps suffixed → original
|
||||||
*/
|
*/
|
||||||
static cloakTools(body) {
|
static cloakTools(body) {
|
||||||
const tools = body.request?.tools;
|
const tools = body.request?.tools;
|
||||||
@@ -270,28 +270,28 @@ export class AntigravityExecutor extends BaseExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const toolNameMap = new Map();
|
const toolNameMap = new Map();
|
||||||
const allDeclarations = [];
|
const clientDeclarations = [];
|
||||||
|
|
||||||
// First: add AG decoy tools (to appear first in the list)
|
// First: collect renamed client tools
|
||||||
allDeclarations.push(...AG_DECOY_TOOLS);
|
|
||||||
|
|
||||||
// Second: add renamed client tools
|
|
||||||
for (const toolGroup of tools) {
|
for (const toolGroup of tools) {
|
||||||
if (!toolGroup.functionDeclarations) continue;
|
if (!toolGroup.functionDeclarations) continue;
|
||||||
|
|
||||||
for (const func of toolGroup.functionDeclarations) {
|
for (const func of toolGroup.functionDeclarations) {
|
||||||
// Skip if already an AG default tool name
|
// Skip if already an AG default tool name
|
||||||
if (AG_DEFAULT_TOOLS.has(func.name)) {
|
if (AG_DEFAULT_TOOLS.has(func.name)) {
|
||||||
allDeclarations.push(func);
|
clientDeclarations.push(func);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prefixed = `${AG_TOOL_PREFIX}${func.name}`;
|
const suffixed = `${func.name}${AG_TOOL_SUFFIX}`;
|
||||||
toolNameMap.set(prefixed, func.name);
|
toolNameMap.set(suffixed, func.name);
|
||||||
allDeclarations.push({ ...func, name: prefixed });
|
clientDeclarations.push({ ...func, name: suffixed });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Client tools first, then AG decoy tools
|
||||||
|
const allDeclarations = [...clientDeclarations, ...AG_DECOY_TOOLS];
|
||||||
|
|
||||||
// Rename tool names in conversation history (contents)
|
// Rename tool names in conversation history (contents)
|
||||||
const cloakedContents = body.request?.contents?.map(msg => {
|
const cloakedContents = body.request?.contents?.map(msg => {
|
||||||
if (!msg.parts) return msg;
|
if (!msg.parts) return msg;
|
||||||
@@ -303,7 +303,7 @@ export class AntigravityExecutor extends BaseExecutor {
|
|||||||
...part,
|
...part,
|
||||||
functionCall: {
|
functionCall: {
|
||||||
...part.functionCall,
|
...part.functionCall,
|
||||||
name: `${AG_TOOL_PREFIX}${part.functionCall.name}`
|
name: `${part.functionCall.name}${AG_TOOL_SUFFIX}`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -314,7 +314,7 @@ export class AntigravityExecutor extends BaseExecutor {
|
|||||||
...part,
|
...part,
|
||||||
functionResponse: {
|
functionResponse: {
|
||||||
...part.functionResponse,
|
...part.functionResponse,
|
||||||
name: `${AG_TOOL_PREFIX}${part.functionResponse.name}`
|
name: `${part.functionResponse.name}${AG_TOOL_SUFFIX}`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -325,7 +325,7 @@ export class AntigravityExecutor extends BaseExecutor {
|
|||||||
return { ...msg, parts: cloakedParts };
|
return { ...msg, parts: cloakedParts };
|
||||||
});
|
});
|
||||||
|
|
||||||
// Single functionDeclarations group with decoys first, then renamed client tools
|
// Single functionDeclarations group: client tools first, then decoys
|
||||||
return {
|
return {
|
||||||
cloakedBody: {
|
cloakedBody: {
|
||||||
...body,
|
...body,
|
||||||
@@ -340,111 +340,111 @@ export class AntigravityExecutor extends BaseExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AG decoy tools — same names as AG native defaults, redirect to ide_ prefixed tools
|
// AG decoy tools — same names as AG native defaults, redirect to _ide suffixed tools
|
||||||
const AG_DECOY_TOOLS = [
|
const AG_DECOY_TOOLS = [
|
||||||
{
|
{
|
||||||
name: "browser_subagent",
|
name: "browser_subagent",
|
||||||
description: "Use ide_ prefixed tools instead",
|
description: "This tool is currently unavailable.",
|
||||||
parameters: { type: "OBJECT", properties: {}, required: [] }
|
parameters: { type: "OBJECT", properties: {}, required: [] }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "command_status",
|
name: "command_status",
|
||||||
description: "Use ide_ prefixed tools instead",
|
description: "This tool is currently unavailable.",
|
||||||
parameters: { type: "OBJECT", properties: {}, required: [] }
|
parameters: { type: "OBJECT", properties: {}, required: [] }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "find_by_name",
|
name: "find_by_name",
|
||||||
description: "Use ide_ prefixed tools instead",
|
description: "This tool is currently unavailable.",
|
||||||
parameters: { type: "OBJECT", properties: {}, required: [] }
|
parameters: { type: "OBJECT", properties: {}, required: [] }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "generate_image",
|
name: "generate_image",
|
||||||
description: "Use ide_ prefixed tools instead",
|
description: "This tool is currently unavailable.",
|
||||||
parameters: { type: "OBJECT", properties: {}, required: [] }
|
parameters: { type: "OBJECT", properties: {}, required: [] }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "grep_search",
|
name: "grep_search",
|
||||||
description: "Use ide_ prefixed tools instead",
|
description: "This tool is currently unavailable.",
|
||||||
parameters: { type: "OBJECT", properties: {}, required: [] }
|
parameters: { type: "OBJECT", properties: {}, required: [] }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "list_dir",
|
name: "list_dir",
|
||||||
description: "Use ide_ prefixed tools instead",
|
description: "This tool is currently unavailable.",
|
||||||
parameters: { type: "OBJECT", properties: {}, required: [] }
|
parameters: { type: "OBJECT", properties: {}, required: [] }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "list_resources",
|
name: "list_resources",
|
||||||
description: "Use ide_ prefixed tools instead",
|
description: "This tool is currently unavailable.",
|
||||||
parameters: { type: "OBJECT", properties: {}, required: [] }
|
parameters: { type: "OBJECT", properties: {}, required: [] }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "mcp_sequential-thinking_sequentialthinking",
|
name: "mcp_sequential-thinking_sequentialthinking",
|
||||||
description: "Use ide_ prefixed tools instead",
|
description: "This tool is currently unavailable.",
|
||||||
parameters: { type: "OBJECT", properties: {}, required: [] }
|
parameters: { type: "OBJECT", properties: {}, required: [] }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multi_replace_file_content",
|
name: "multi_replace_file_content",
|
||||||
description: "Use ide_ prefixed tools instead",
|
description: "This tool is currently unavailable.",
|
||||||
parameters: { type: "OBJECT", properties: {}, required: [] }
|
parameters: { type: "OBJECT", properties: {}, required: [] }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "notify_user",
|
name: "notify_user",
|
||||||
description: "Use ide_ prefixed tools instead",
|
description: "This tool is currently unavailable.",
|
||||||
parameters: { type: "OBJECT", properties: {}, required: [] }
|
parameters: { type: "OBJECT", properties: {}, required: [] }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "read_resource",
|
name: "read_resource",
|
||||||
description: "Use ide_ prefixed tools instead",
|
description: "This tool is currently unavailable.",
|
||||||
parameters: { type: "OBJECT", properties: {}, required: [] }
|
parameters: { type: "OBJECT", properties: {}, required: [] }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "read_terminal",
|
name: "read_terminal",
|
||||||
description: "Use ide_ prefixed tools instead",
|
description: "This tool is currently unavailable.",
|
||||||
parameters: { type: "OBJECT", properties: {}, required: [] }
|
parameters: { type: "OBJECT", properties: {}, required: [] }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "read_url_content",
|
name: "read_url_content",
|
||||||
description: "Use ide_ prefixed tools instead",
|
description: "This tool is currently unavailable.",
|
||||||
parameters: { type: "OBJECT", properties: {}, required: [] }
|
parameters: { type: "OBJECT", properties: {}, required: [] }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "replace_file_content",
|
name: "replace_file_content",
|
||||||
description: "Use ide_ prefixed tools instead",
|
description: "This tool is currently unavailable.",
|
||||||
parameters: { type: "OBJECT", properties: {}, required: [] }
|
parameters: { type: "OBJECT", properties: {}, required: [] }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "run_command",
|
name: "run_command",
|
||||||
description: "Use ide_ prefixed tools instead",
|
description: "This tool is currently unavailable.",
|
||||||
parameters: { type: "OBJECT", properties: {}, required: [] }
|
parameters: { type: "OBJECT", properties: {}, required: [] }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "search_web",
|
name: "search_web",
|
||||||
description: "Use ide_ prefixed tools instead",
|
description: "This tool is currently unavailable.",
|
||||||
parameters: { type: "OBJECT", properties: {}, required: [] }
|
parameters: { type: "OBJECT", properties: {}, required: [] }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "send_command_input",
|
name: "send_command_input",
|
||||||
description: "Use ide_ prefixed tools instead",
|
description: "This tool is currently unavailable.",
|
||||||
parameters: { type: "OBJECT", properties: {}, required: [] }
|
parameters: { type: "OBJECT", properties: {}, required: [] }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "task_boundary",
|
name: "task_boundary",
|
||||||
description: "Use ide_ prefixed tools instead",
|
description: "This tool is currently unavailable.",
|
||||||
parameters: { type: "OBJECT", properties: {}, required: [] }
|
parameters: { type: "OBJECT", properties: {}, required: [] }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "view_content_chunk",
|
name: "view_content_chunk",
|
||||||
description: "Use ide_ prefixed tools instead",
|
description: "This tool is currently unavailable.",
|
||||||
parameters: { type: "OBJECT", properties: {}, required: [] }
|
parameters: { type: "OBJECT", properties: {}, required: [] }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "view_file",
|
name: "view_file",
|
||||||
description: "Use ide_ prefixed tools instead",
|
description: "This tool is currently unavailable.",
|
||||||
parameters: { type: "OBJECT", properties: {}, required: [] }
|
parameters: { type: "OBJECT", properties: {}, required: [] }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "write_to_file",
|
name: "write_to_file",
|
||||||
description: "Use ide_ prefixed tools instead",
|
description: "This tool is currently unavailable.",
|
||||||
parameters: { type: "OBJECT", properties: {}, required: [] }
|
parameters: { type: "OBJECT", properties: {}, required: [] }
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,16 +1,9 @@
|
|||||||
import { register } from "../index.js";
|
import { register } from "../index.js";
|
||||||
import { FORMATS } from "../formats.js";
|
import { FORMATS } from "../formats.js";
|
||||||
|
import { DEFAULT_THINKING_GEMINI_SIGNATURE } from "../../config/defaultThinkingSignature.js";
|
||||||
import { ANTIGRAVITY_DEFAULT_SYSTEM } from "../../config/appConstants.js";
|
import { ANTIGRAVITY_DEFAULT_SYSTEM } from "../../config/appConstants.js";
|
||||||
import { openaiToClaudeRequestForAntigravity } from "./openai-to-claude.js";
|
import { openaiToClaudeRequestForAntigravity } from "./openai-to-claude.js";
|
||||||
|
|
||||||
// Decode base64url → standard base64 (for restoring thoughtSignature)
|
|
||||||
function fromBase64Url(b64url) {
|
|
||||||
let b64 = b64url.replace(/-/g, "+").replace(/_/g, "/");
|
|
||||||
// Restore padding
|
|
||||||
while (b64.length % 4) b64 += "=";
|
|
||||||
return b64;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateUUID() {
|
function generateUUID() {
|
||||||
return crypto.randomUUID();
|
return crypto.randomUUID();
|
||||||
}
|
}
|
||||||
@@ -65,32 +58,26 @@ function openaiToGeminiBase(model, body, stream) {
|
|||||||
result.generationConfig.maxOutputTokens = body.max_tokens;
|
result.generationConfig.maxOutputTokens = body.max_tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strip embedded thoughtSignature from a tool_call id ("rawId_TSIG_sig" → "rawId")
|
// Build tool_call_id -> name map
|
||||||
const stripSig = (id) => {
|
|
||||||
const sep = id ? id.indexOf("_TSIG_") : -1;
|
|
||||||
return sep !== -1 ? id.slice(0, sep) : id;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Build tool_call_id -> name map (keyed by rawId)
|
|
||||||
const tcID2Name = {};
|
const tcID2Name = {};
|
||||||
if (body.messages && Array.isArray(body.messages)) {
|
if (body.messages && Array.isArray(body.messages)) {
|
||||||
for (const msg of body.messages) {
|
for (const msg of body.messages) {
|
||||||
if (msg.role === "assistant" && msg.tool_calls) {
|
if (msg.role === "assistant" && msg.tool_calls) {
|
||||||
for (const tc of msg.tool_calls) {
|
for (const tc of msg.tool_calls) {
|
||||||
if (tc.type === "function" && tc.id && tc.function?.name) {
|
if (tc.type === "function" && tc.id && tc.function?.name) {
|
||||||
tcID2Name[stripSig(tc.id)] = tc.function.name;
|
tcID2Name[tc.id] = tc.function.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build tool responses cache (keyed by rawId)
|
// Build tool responses cache
|
||||||
const toolResponses = {};
|
const toolResponses = {};
|
||||||
if (body.messages && Array.isArray(body.messages)) {
|
if (body.messages && Array.isArray(body.messages)) {
|
||||||
for (const msg of body.messages) {
|
for (const msg of body.messages) {
|
||||||
if (msg.role === "tool" && msg.tool_call_id) {
|
if (msg.role === "tool" && msg.tool_call_id) {
|
||||||
toolResponses[stripSig(msg.tool_call_id)] = msg.content;
|
toolResponses[msg.tool_call_id] = msg.content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,6 +102,18 @@ function openaiToGeminiBase(model, body, stream) {
|
|||||||
} else if (role === "assistant") {
|
} else if (role === "assistant") {
|
||||||
const parts = [];
|
const parts = [];
|
||||||
|
|
||||||
|
// Thinking/reasoning → thought part with signature
|
||||||
|
if (msg.reasoning_content) {
|
||||||
|
parts.push({
|
||||||
|
thought: true,
|
||||||
|
text: msg.reasoning_content
|
||||||
|
});
|
||||||
|
parts.push({
|
||||||
|
thoughtSignature: DEFAULT_THINKING_GEMINI_SIGNATURE,
|
||||||
|
text: ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (content) {
|
if (content) {
|
||||||
const text = typeof content === "string" ? content : extractTextContent(content);
|
const text = typeof content === "string" ? content : extractTextContent(content);
|
||||||
if (text) {
|
if (text) {
|
||||||
@@ -128,22 +127,15 @@ function openaiToGeminiBase(model, body, stream) {
|
|||||||
if (tc.type !== "function") continue;
|
if (tc.type !== "function") continue;
|
||||||
|
|
||||||
const args = tryParseJSON(tc.function?.arguments || "{}");
|
const args = tryParseJSON(tc.function?.arguments || "{}");
|
||||||
// Extract thoughtSignature embedded in ID as "rawId_TSIG_base64urlsig"
|
parts.push({
|
||||||
const sepIdx = tc.id.indexOf("_TSIG_");
|
thoughtSignature: DEFAULT_THINKING_GEMINI_SIGNATURE,
|
||||||
const rawId = sepIdx !== -1 ? tc.id.slice(0, sepIdx) : tc.id;
|
|
||||||
const encodedSig = sepIdx !== -1 ? tc.id.slice(sepIdx + 6) : "";
|
|
||||||
const fcPart = {
|
|
||||||
functionCall: {
|
functionCall: {
|
||||||
id: rawId,
|
id: tc.id,
|
||||||
name: sanitizeGeminiFunctionName(tc.function.name),
|
name: sanitizeGeminiFunctionName(tc.function.name),
|
||||||
args: args
|
args: args
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
if (encodedSig) {
|
toolCallIds.push(tc.id);
|
||||||
fcPart.thoughtSignature = fromBase64Url(encodedSig);
|
|
||||||
}
|
|
||||||
parts.push(fcPart);
|
|
||||||
toolCallIds.push(rawId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parts.length > 0) {
|
if (parts.length > 0) {
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
import { register } from "../index.js";
|
import { register } from "../index.js";
|
||||||
import { FORMATS } from "../formats.js";
|
import { FORMATS } from "../formats.js";
|
||||||
|
|
||||||
// Encode base64 → base64url (safe for tool_call IDs: only [a-zA-Z0-9_-])
|
|
||||||
function toBase64Url(b64) {
|
|
||||||
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert Gemini response chunk to OpenAI format
|
// Convert Gemini response chunk to OpenAI format
|
||||||
export function geminiToOpenAIResponse(chunk, state) {
|
export function geminiToOpenAIResponse(chunk, state) {
|
||||||
if (!chunk) return null;
|
if (!chunk) return null;
|
||||||
@@ -23,7 +18,6 @@ export function geminiToOpenAIResponse(chunk, state) {
|
|||||||
state.messageId = response.responseId || `msg_${Date.now()}`;
|
state.messageId = response.responseId || `msg_${Date.now()}`;
|
||||||
state.model = response.modelVersion || "gemini";
|
state.model = response.modelVersion || "gemini";
|
||||||
state.functionIndex = 0;
|
state.functionIndex = 0;
|
||||||
state.pendingThoughtSignature = null;
|
|
||||||
results.push({
|
results.push({
|
||||||
id: `chatcmpl-${state.messageId}`,
|
id: `chatcmpl-${state.messageId}`,
|
||||||
object: "chat.completion.chunk",
|
object: "chat.completion.chunk",
|
||||||
@@ -40,18 +34,14 @@ export function geminiToOpenAIResponse(chunk, state) {
|
|||||||
// Process parts
|
// Process parts
|
||||||
if (content?.parts) {
|
if (content?.parts) {
|
||||||
for (const part of content.parts) {
|
for (const part of content.parts) {
|
||||||
const partThoughtSig = part.thoughtSignature || part.thought_signature || "";
|
const hasThoughtSig = part.thoughtSignature || part.thought_signature;
|
||||||
const isThought = part.thought === true;
|
const isThought = part.thought === true;
|
||||||
|
|
||||||
// Accumulate thoughtSignature across parts: a sig-only part precedes the functionCall part
|
// Handle thought signature (thinking mode)
|
||||||
if (partThoughtSig) {
|
if (hasThoughtSig) {
|
||||||
state.pendingThoughtSignature = partThoughtSig;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasTextContent = part.text !== undefined && part.text !== "";
|
const hasTextContent = part.text !== undefined && part.text !== "";
|
||||||
const hasFunctionCall = !!part.functionCall;
|
const hasFunctionCall = !!part.functionCall;
|
||||||
|
|
||||||
// Emit reasoning/thought text
|
|
||||||
if (hasTextContent) {
|
if (hasTextContent) {
|
||||||
results.push({
|
results.push({
|
||||||
id: `chatcmpl-${state.messageId}`,
|
id: `chatcmpl-${state.messageId}`,
|
||||||
@@ -68,23 +58,65 @@ export function geminiToOpenAIResponse(chunk, state) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit function call, attaching the best available thoughtSignature
|
|
||||||
if (hasFunctionCall) {
|
if (hasFunctionCall) {
|
||||||
const rawName = part.functionCall.name;
|
const rawName = part.functionCall.name;
|
||||||
// Restore original tool name from mapping (AG cloaking)
|
// Restore original tool name from mapping (AG cloaking)
|
||||||
const fcName = state.toolNameMap?.get(rawName) || rawName;
|
const fcName = state.toolNameMap?.get(rawName) || rawName;
|
||||||
const fcArgs = part.functionCall.args || {};
|
const fcArgs = part.functionCall.args || {};
|
||||||
const toolCallIndex = state.functionIndex++;
|
const toolCallIndex = state.functionIndex++;
|
||||||
// Use signature from this part, or the one carried from a preceding part
|
|
||||||
const thoughtSig = partThoughtSig || state.pendingThoughtSignature || "";
|
|
||||||
if (thoughtSig) state.pendingThoughtSignature = null; // consumed
|
|
||||||
// Encode signature using _TSIG_ delimiter and base64url for safe tool_call ID
|
|
||||||
const toolCallId = thoughtSig
|
|
||||||
? `${fcName}-${Date.now()}-${toolCallIndex}_TSIG_${toBase64Url(thoughtSig)}`
|
|
||||||
: `${fcName}-${Date.now()}-${toolCallIndex}`;
|
|
||||||
|
|
||||||
const toolCall = {
|
const toolCall = {
|
||||||
id: toolCallId,
|
id: `${fcName}-${Date.now()}-${toolCallIndex}`,
|
||||||
|
index: toolCallIndex,
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: fcName,
|
||||||
|
arguments: JSON.stringify(fcArgs)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
state.toolCalls.set(toolCallIndex, toolCall);
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
id: `chatcmpl-${state.messageId}`,
|
||||||
|
object: "chat.completion.chunk",
|
||||||
|
created: Math.floor(Date.now() / 1000),
|
||||||
|
model: state.model,
|
||||||
|
choices: [{
|
||||||
|
index: 0,
|
||||||
|
delta: { tool_calls: [toolCall] },
|
||||||
|
finish_reason: null
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text content (non-thinking)
|
||||||
|
if (part.text !== undefined && part.text !== "") {
|
||||||
|
results.push({
|
||||||
|
id: `chatcmpl-${state.messageId}`,
|
||||||
|
object: "chat.completion.chunk",
|
||||||
|
created: Math.floor(Date.now() / 1000),
|
||||||
|
model: state.model,
|
||||||
|
choices: [{
|
||||||
|
index: 0,
|
||||||
|
delta: { content: part.text },
|
||||||
|
finish_reason: null
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function call
|
||||||
|
if (part.functionCall) {
|
||||||
|
const rawName = part.functionCall.name;
|
||||||
|
// Restore original tool name from mapping (AG cloaking)
|
||||||
|
const fcName = state.toolNameMap?.get(rawName) || rawName;
|
||||||
|
const fcArgs = part.functionCall.args || {};
|
||||||
|
const toolCallIndex = state.functionIndex++;
|
||||||
|
|
||||||
|
const toolCall = {
|
||||||
|
id: `${fcName}-${Date.now()}-${toolCallIndex}`,
|
||||||
index: toolCallIndex,
|
index: toolCallIndex,
|
||||||
type: "function",
|
type: "function",
|
||||||
function: {
|
function: {
|
||||||
@@ -209,5 +241,4 @@ export function geminiToOpenAIResponse(chunk, state) {
|
|||||||
register(FORMATS.GEMINI, FORMATS.OPENAI, null, geminiToOpenAIResponse);
|
register(FORMATS.GEMINI, FORMATS.OPENAI, null, geminiToOpenAIResponse);
|
||||||
register(FORMATS.GEMINI_CLI, FORMATS.OPENAI, null, geminiToOpenAIResponse);
|
register(FORMATS.GEMINI_CLI, FORMATS.OPENAI, null, geminiToOpenAIResponse);
|
||||||
register(FORMATS.ANTIGRAVITY, FORMATS.OPENAI, null, geminiToOpenAIResponse);
|
register(FORMATS.ANTIGRAVITY, FORMATS.OPENAI, null, geminiToOpenAIResponse);
|
||||||
register(FORMATS.VERTEX, FORMATS.OPENAI, null, geminiToOpenAIResponse);
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "9router-app",
|
"name": "9router-app",
|
||||||
"version": "0.3.69",
|
"version": "0.3.72",
|
||||||
"description": "9Router web dashboard",
|
"description": "9Router web dashboard",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --webpack --port 20128",
|
"dev": "next dev --webpack --port 20128",
|
||||||
"build": "cross-env NODE_ENV=production next build",
|
"build": "NODE_ENV=production next build --webpack",
|
||||||
"start": "cross-env NODE_ENV=production next start",
|
"start": "NODE_ENV=production next start",
|
||||||
"dev:bun": "bun --bun next dev --webpack --port 20128",
|
"dev:bun": "bun --bun next dev --webpack --port 20128",
|
||||||
"build:bun": "NODE_ENV=production bun --bun next build --webpack",
|
"build:bun": "NODE_ENV=production bun --bun next build --webpack",
|
||||||
"start:bun": "NODE_ENV=production bun ./.next/standalone/server.js"
|
"start:bun": "NODE_ENV=production bun ./.next/standalone/server.js"
|
||||||
@@ -44,7 +44,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4.1.18",
|
"@tailwindcss/postcss": "^4.1.18",
|
||||||
"cross-env": "^10.1.0",
|
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "16.1.6",
|
"eslint-config-next": "16.1.6",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
|
|||||||
@@ -1185,9 +1185,6 @@ function PassthroughModelRow({ modelId, fullModel, copied, onCopy, onDeleteAlias
|
|||||||
|
|
||||||
<div className="flex items-center gap-1 mt-1">
|
<div className="flex items-center gap-1 mt-1">
|
||||||
<code className="text-xs text-text-muted font-mono bg-sidebar px-1.5 py-0.5 rounded">{fullModel}</code>
|
<code className="text-xs text-text-muted font-mono bg-sidebar px-1.5 py-0.5 rounded">{fullModel}</code>
|
||||||
{isFree && (
|
|
||||||
<span className="text-[10px] font-medium px-1.5 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Free</span>
|
|
||||||
)}
|
|
||||||
<div className="relative group/btn">
|
<div className="relative group/btn">
|
||||||
<button
|
<button
|
||||||
onClick={() => onCopy(fullModel, `model-${modelId}`)}
|
onClick={() => onCopy(fullModel, `model-${modelId}`)}
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ export async function POST(request) {
|
|||||||
if (!settings.agents) settings.agents = {};
|
if (!settings.agents) settings.agents = {};
|
||||||
if (!settings.agents.defaults) settings.agents.defaults = {};
|
if (!settings.agents.defaults) settings.agents.defaults = {};
|
||||||
if (!settings.agents.defaults.model) settings.agents.defaults.model = {};
|
if (!settings.agents.defaults.model) settings.agents.defaults.model = {};
|
||||||
|
if (!settings.agents.defaults.models) settings.agents.defaults.models = {};
|
||||||
if (!settings.models) settings.models = {};
|
if (!settings.models) settings.models = {};
|
||||||
if (!settings.models.providers) settings.models.providers = {};
|
if (!settings.models.providers) settings.models.providers = {};
|
||||||
|
|
||||||
@@ -102,7 +103,13 @@ export async function POST(request) {
|
|||||||
const normalizedBaseUrl = baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1`;
|
const normalizedBaseUrl = baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1`;
|
||||||
|
|
||||||
// Update agents.defaults.model.primary
|
// Update agents.defaults.model.primary
|
||||||
settings.agents.defaults.model.primary = `9router/${model}`;
|
const fullModelId = `9router/${model}`;
|
||||||
|
settings.agents.defaults.model.primary = fullModelId;
|
||||||
|
|
||||||
|
// IMPORTANT: Add to allowlist in agents.defaults.models
|
||||||
|
if (!settings.agents.defaults.models[fullModelId]) {
|
||||||
|
settings.agents.defaults.models[fullModelId] = {};
|
||||||
|
}
|
||||||
|
|
||||||
// Update models.providers.9router
|
// Update models.providers.9router
|
||||||
settings.models.providers["9router"] = {
|
settings.models.providers["9router"] = {
|
||||||
@@ -161,6 +168,17 @@ export async function DELETE() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove 9router models from agents.defaults.models allowlist
|
||||||
|
if (settings.agents?.defaults?.models) {
|
||||||
|
const keysToRemove = Object.keys(settings.agents.defaults.models).filter((k) => k.startsWith("9router/"));
|
||||||
|
for (const key of keysToRemove) {
|
||||||
|
delete settings.agents.defaults.models[key];
|
||||||
|
}
|
||||||
|
if (Object.keys(settings.agents.defaults.models).length === 0) {
|
||||||
|
delete settings.agents.defaults.models;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reset agents.defaults.model.primary if it uses 9router
|
// Reset agents.defaults.model.primary if it uses 9router
|
||||||
if (settings.agents?.defaults?.model?.primary?.startsWith("9router/")) {
|
if (settings.agents?.defaults?.model?.primary?.startsWith("9router/")) {
|
||||||
delete settings.agents.defaults.model.primary;
|
delete settings.agents.defaults.model.primary;
|
||||||
|
|||||||
Reference in New Issue
Block a user