diff --git a/open-sse/translator/formats.js b/open-sse/translator/formats.js index c32835d..9ad2245 100644 --- a/open-sse/translator/formats.js +++ b/open-sse/translator/formats.js @@ -12,3 +12,20 @@ export const FORMATS = { 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. + * Returns null if no matching endpoint found. + */ +export function detectFormatByEndpoint(pathname) { + for (const [segment, format] of Object.entries(ENDPOINT_FORMAT_MAP)) { + if (pathname.includes(segment)) return format; + } + return null; +} + diff --git a/open-sse/translator/request/openai-responses.js b/open-sse/translator/request/openai-responses.js index 66e0541..a47d27f 100644 --- a/open-sse/translator/request/openai-responses.js +++ b/open-sse/translator/request/openai-responses.js @@ -143,6 +143,9 @@ export function openaiResponsesToOpenAIRequest(model, body, stream, credentials) * Convert OpenAI Chat Completions to OpenAI Responses API format */ export function openaiToOpenAIResponsesRequest(model, body, stream, credentials) { + // Body already in Responses API format (e.g. Cursor CLI calling /chat/completions with input[]) + if (body.input) return { ...body, model, stream: true }; + const result = { model, input: [], diff --git a/package.json b/package.json index 11b684e..729e02a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "9router-app", - "version": "0.3.18", + "version": "0.3.19", "description": "9Router web dashboard", "private": true, "scripts": { diff --git a/src/sse/handlers/chat.js b/src/sse/handlers/chat.js index 5a1bd03..9c65ea7 100644 --- a/src/sse/handlers/chat.js +++ b/src/sse/handlers/chat.js @@ -13,6 +13,7 @@ import { handleChatCore } from "open-sse/handlers/chatCore.js"; import { errorResponse, unavailableResponse } from "open-sse/utils/error.js"; import { handleComboChat } from "open-sse/services/combo.js"; import { HTTP_STATUS } from "open-sse/config/constants.js"; +import { detectFormatByEndpoint } from "open-sse/translator/formats.js"; import * as log from "../utils/logger.js"; import { updateProviderCredentials, checkAndRefreshToken } from "../services/tokenRefresh.js"; import { getProjectIdForConnection } from "open-sse/services/projectId.js"; @@ -180,6 +181,8 @@ async function handleSingleModelChat(body, modelStr, clientRawRequest = null, re connectionId: credentials.connectionId, userAgent, apiKey, + // Detect source format by endpoint — /chat/completions is always openai, /responses is always openai-responses + sourceFormatOverride: request?.url ? detectFormatByEndpoint(new URL(request.url).pathname) : null, onCredentialsRefreshed: async (newCreds) => { await updateProviderCredentials(credentials.connectionId, { accessToken: newCreds.accessToken,