Fix AG MITM
This commit is contained in:
@@ -12,20 +12,19 @@ export const FORMATS = {
|
|||||||
CURSOR: "cursor"
|
CURSOR: "cursor"
|
||||||
};
|
};
|
||||||
|
|
||||||
// Map endpoint suffix → source format (takes priority over body-based detection)
|
|
||||||
const ENDPOINT_FORMAT_MAP = {
|
|
||||||
"/v1/responses": FORMATS.OPENAI_RESPONSES,
|
|
||||||
"/v1/chat/completions": FORMATS.OPENAI,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detect source format from request URL pathname.
|
* Detect source format from request URL pathname + body.
|
||||||
* Returns null if no matching endpoint found.
|
* Returns null to fall back to body-based detection.
|
||||||
*/
|
*/
|
||||||
export function detectFormatByEndpoint(pathname) {
|
export function detectFormatByEndpoint(pathname, body) {
|
||||||
for (const [segment, format] of Object.entries(ENDPOINT_FORMAT_MAP)) {
|
// /v1/responses is always openai-responses
|
||||||
if (pathname.includes(segment)) return format;
|
if (pathname.includes("/v1/responses")) return FORMATS.OPENAI_RESPONSES;
|
||||||
|
|
||||||
|
// /v1/chat/completions + input[] → treat as openai (Cursor CLI sends Responses body via chat endpoint)
|
||||||
|
if (pathname.includes("/v1/chat/completions") && Array.isArray(body?.input)) {
|
||||||
|
return FORMATS.OPENAI;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,8 +84,10 @@ export function createSSEStream(options = {}) {
|
|||||||
|
|
||||||
// Ensure OpenAI-required fields are present on streaming chunks (Letta compat)
|
// Ensure OpenAI-required fields are present on streaming chunks (Letta compat)
|
||||||
let fieldsInjected = false;
|
let fieldsInjected = false;
|
||||||
if (!parsed.object) { parsed.object = "chat.completion.chunk"; fieldsInjected = true; }
|
if (parsed.choices !== undefined) {
|
||||||
if (!parsed.created) { parsed.created = Math.floor(Date.now() / 1000); fieldsInjected = true; }
|
if (!parsed.object) { parsed.object = "chat.completion.chunk"; fieldsInjected = true; }
|
||||||
|
if (!parsed.created) { parsed.created = Math.floor(Date.now() / 1000); fieldsInjected = true; }
|
||||||
|
}
|
||||||
|
|
||||||
// Strip Azure-specific non-standard fields from streaming chunks
|
// Strip Azure-specific non-standard fields from streaming chunks
|
||||||
if (parsed.prompt_filter_results !== undefined) {
|
if (parsed.prompt_filter_results !== undefined) {
|
||||||
|
|||||||
@@ -196,13 +196,15 @@ export function extractUsage(chunk) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Gemini format (Antigravity)
|
// Gemini format (Antigravity)
|
||||||
if (chunk.usageMetadata && typeof chunk.usageMetadata === "object") {
|
// Antigravity wraps usageMetadata inside response: { response: { usageMetadata: {...} } }
|
||||||
|
const usageMeta = chunk.usageMetadata || chunk.response?.usageMetadata;
|
||||||
|
if (usageMeta && typeof usageMeta === "object") {
|
||||||
return normalizeUsage({
|
return normalizeUsage({
|
||||||
prompt_tokens: chunk.usageMetadata?.promptTokenCount || 0,
|
prompt_tokens: usageMeta.promptTokenCount || 0,
|
||||||
completion_tokens: chunk.usageMetadata?.candidatesTokenCount || 0,
|
completion_tokens: usageMeta.candidatesTokenCount || 0,
|
||||||
total_tokens: chunk.usageMetadata?.totalTokenCount,
|
total_tokens: usageMeta.totalTokenCount,
|
||||||
cached_tokens: chunk.usageMetadata?.cachedContentTokenCount,
|
cached_tokens: usageMeta.cachedContentTokenCount,
|
||||||
reasoning_tokens: chunk.usageMetadata?.thoughtsTokenCount
|
reasoning_tokens: usageMeta.thoughtsTokenCount
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "9router-app",
|
"name": "9router-app",
|
||||||
"version": "0.3.19",
|
"version": "0.3.23",
|
||||||
"description": "9Router web dashboard",
|
"description": "9Router web dashboard",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ export const OAUTH_PROVIDERS = {
|
|||||||
codex: { id: "codex", alias: "cx", name: "OpenAI Codex", icon: "code", color: "#3B82F6" },
|
codex: { id: "codex", alias: "cx", name: "OpenAI Codex", icon: "code", color: "#3B82F6" },
|
||||||
github: { id: "github", alias: "gh", name: "GitHub Copilot", icon: "code", color: "#333333" },
|
github: { id: "github", alias: "gh", name: "GitHub Copilot", icon: "code", color: "#333333" },
|
||||||
cursor: { id: "cursor", alias: "cu", name: "Cursor IDE", icon: "edit_note", color: "#00D4AA" },
|
cursor: { id: "cursor", alias: "cu", name: "Cursor IDE", icon: "edit_note", color: "#00D4AA" },
|
||||||
// kilocode: { id: "kilocode", alias: "kc", name: "Kilo Code", icon: "code", color: "#FF6B35", textIcon: "KC" },
|
kilocode: { id: "kilocode", alias: "kc", name: "Kilo Code", icon: "code", color: "#FF6B35", textIcon: "KC" },
|
||||||
// cline: { id: "cline", alias: "cl", name: "Cline", icon: "smart_toy", color: "#5B9BD5", textIcon: "CL" },
|
cline: { id: "cline", alias: "cl", name: "Cline", icon: "smart_toy", color: "#5B9BD5", textIcon: "CL" },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const APIKEY_PROVIDERS = {
|
export const APIKEY_PROVIDERS = {
|
||||||
|
|||||||
@@ -181,8 +181,8 @@ async function handleSingleModelChat(body, modelStr, clientRawRequest = null, re
|
|||||||
connectionId: credentials.connectionId,
|
connectionId: credentials.connectionId,
|
||||||
userAgent,
|
userAgent,
|
||||||
apiKey,
|
apiKey,
|
||||||
// Detect source format by endpoint — /chat/completions is always openai, /responses is always openai-responses
|
// Detect source format by endpoint + body
|
||||||
sourceFormatOverride: request?.url ? detectFormatByEndpoint(new URL(request.url).pathname) : null,
|
sourceFormatOverride: request?.url ? detectFormatByEndpoint(new URL(request.url).pathname, body) : null,
|
||||||
onCredentialsRefreshed: async (newCreds) => {
|
onCredentialsRefreshed: async (newCreds) => {
|
||||||
await updateProviderCredentials(credentials.connectionId, {
|
await updateProviderCredentials(credentials.connectionId, {
|
||||||
accessToken: newCreds.accessToken,
|
accessToken: newCreds.accessToken,
|
||||||
|
|||||||
Reference in New Issue
Block a user