diff --git a/app/api/common.ts b/app/api/common.ts index b7e41fa26..c37ebfe69 100644 --- a/app/api/common.ts +++ b/app/api/common.ts @@ -90,6 +90,14 @@ export async function requestOpenai(req: NextRequest) { const fetchUrl = cloudflareAIGatewayUrl(`${baseUrl}/${path}`); console.log("fetchUrl", fetchUrl); + + let payload = await req.text(); + if (baseUrl.includes("openrouter.ai")) { + const body = JSON.parse(payload); + body["include_reasoning"] = true; + payload = JSON.stringify(body); + } + const fetchOptions: RequestInit = { headers: { "Content-Type": "application/json", @@ -100,7 +108,7 @@ export async function requestOpenai(req: NextRequest) { }), }, method: req.method, - body: req.body, + body: payload, // to fix #2485: https://stackoverflow.com/questions/55920957/cloudflare-worker-typeerror-one-time-use-body redirect: "manual", // @ts-ignore @@ -111,10 +119,7 @@ export async function requestOpenai(req: NextRequest) { // #1815 try to refuse gpt4 request if (serverConfig.customModels && req.body) { try { - const clonedBody = await req.text(); - fetchOptions.body = clonedBody; - - const jsonBody = JSON.parse(clonedBody) as { model?: string }; + const jsonBody = JSON.parse(payload) as { model?: string }; // not undefined and is false if ( diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts index c6f3fc425..abdbb82d4 100644 --- a/app/client/platforms/openai.ts +++ b/app/client/platforms/openai.ts @@ -2,10 +2,10 @@ // azure and openai, using same models. so using same LLMApi. import { ApiPath, - OPENAI_BASE_URL, - DEFAULT_MODELS, - OpenaiPath, Azure, + DEFAULT_MODELS, + OPENAI_BASE_URL, + OpenaiPath, REQUEST_TIMEOUT_MS, ServiceProvider, } from "@/app/constant"; @@ -18,13 +18,13 @@ import { } from "@/app/store"; import { collectModelsWithDefaultModel } from "@/app/utils/model"; import { - preProcessImageContent, - uploadImage, base64Image2Blob, + preProcessImageContent, streamWithThink, + uploadImage, } from "@/app/utils/chat"; import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare"; -import { ModelSize, DalleQuality, DalleStyle } from "@/app/typing"; +import { DalleQuality, DalleStyle, ModelSize } from "@/app/typing"; import { ChatOptions, @@ -39,9 +39,9 @@ import Locale from "../../locales"; import { getClientConfig } from "@/app/config/client"; import { getMessageTextContent, - isVisionModel, - isDalle3 as _isDalle3, getTimeoutMSByModel, + isDalle3 as _isDalle3, + isVisionModel, } from "@/app/utils"; import { fetch } from "@/app/utils/stream"; @@ -294,6 +294,13 @@ export class ChatGPTApi implements LLMApi { useChatStore.getState().currentSession().mask?.plugin || [], ); // console.log("getAsTools", tools, funcs); + + // Add "include_reasoning" for OpenRouter: https://openrouter.ai/announcements/reasoning-tokens-for-thinking-models + if (chatPath.includes("openrouter.ai")) { + // @ts-ignore + requestPayload["include_reasoning"] = true; + } + streamWithThink( chatPath, requestPayload, @@ -310,6 +317,7 @@ export class ChatGPTApi implements LLMApi { content: string; tool_calls: ChatMessageTool[]; reasoning_content: string | null; + reasoning: string | null; }; }>; @@ -335,7 +343,9 @@ export class ChatGPTApi implements LLMApi { } } - const reasoning = choices[0]?.delta?.reasoning_content; + const reasoning = + choices[0]?.delta?.reasoning_content || + choices[0]?.delta?.reasoning; const content = choices[0]?.delta?.content; // Skip if both content and reasoning_content are empty or null @@ -411,6 +421,7 @@ export class ChatGPTApi implements LLMApi { options.onError?.(e as Error); } } + async usage() { const formatDate = (d: Date) => `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, "0")}-${d @@ -514,4 +525,5 @@ export class ChatGPTApi implements LLMApi { })); } } + export { OpenaiPath }; diff --git a/app/utils/chat.ts b/app/utils/chat.ts index cae775512..e18cd007b 100644 --- a/app/utils/chat.ts +++ b/app/utils/chat.ts @@ -1,7 +1,7 @@ import { CACHE_URL_PREFIX, - UPLOAD_URL, REQUEST_TIMEOUT_MS, + UPLOAD_URL, } from "@/app/constant"; import { MultimodalContent, RequestMessage } from "@/app/client/api"; import Locale from "@/app/locales"; @@ -111,6 +111,7 @@ export async function preProcessImageContentForAlibabaDashScope( } const imageCaches: Record = {}; + export function cacheImageToBase64Image(imageUrl: string) { if (imageUrl.includes(CACHE_URL_PREFIX)) { if (!imageCaches[imageUrl]) { @@ -385,6 +386,7 @@ export function stream( openWhenHidden: true, }); } + console.debug("[ChatAPI] start"); chatApi(chatPath, headers, requestPayload, tools); // call fetchEventSource } @@ -627,16 +629,9 @@ export function streamWithThink( if (remainText.length > 0) { remainText += "\n"; } - remainText += "> " + chunk.content; - } else { - // Handle newlines in thinking content - if (chunk.content.includes("\n\n")) { - const lines = chunk.content.split("\n\n"); - remainText += lines.join("\n\n> "); - } else { - remainText += chunk.content; - } + remainText += "> "; } + remainText += chunk.content.replaceAll("\n", "\n> "); } else { // If in normal mode if (isInThinkingMode || isThinkingChanged) { @@ -662,6 +657,7 @@ export function streamWithThink( openWhenHidden: true, }); } + console.debug("[ChatAPI] start"); chatApi(chatPath, headers, requestPayload, tools); // call fetchEventSource }