Fix cloud
This commit is contained in:
@@ -52,11 +52,20 @@ export async function handleComboChat({ body, models, handleSingleModel, log })
|
|||||||
let errorText = result.statusText || "";
|
let errorText = result.statusText || "";
|
||||||
try {
|
try {
|
||||||
const errorBody = await result.clone().json();
|
const errorBody = await result.clone().json();
|
||||||
errorText = errorBody.error || errorBody.message || errorText;
|
errorText = errorBody?.error ?? errorBody?.message ?? errorText;
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore JSON parse errors
|
// Ignore JSON parse errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalize error text to string (Worker-safe)
|
||||||
|
if (typeof errorText !== "string") {
|
||||||
|
try {
|
||||||
|
errorText = JSON.stringify(errorText);
|
||||||
|
} catch {
|
||||||
|
errorText = String(errorText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if should fallback to next model
|
// Check if should fallback to next model
|
||||||
const { shouldFallback } = checkFallbackError(result.status, errorText);
|
const { shouldFallback } = checkFallbackError(result.status, errorText);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { FORMATS } from "../formats.js";
|
|||||||
import { adjustMaxTokens } from "../helpers/maxTokensHelper.js";
|
import { adjustMaxTokens } from "../helpers/maxTokensHelper.js";
|
||||||
|
|
||||||
// Convert Claude request to OpenAI format
|
// Convert Claude request to OpenAI format
|
||||||
function claudeToOpenAIRequest(model, body, stream) {
|
export function claudeToOpenAIRequest(model, body, stream) {
|
||||||
const result = {
|
const result = {
|
||||||
model: model,
|
model: model,
|
||||||
messages: [],
|
messages: [],
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { FORMATS } from "../formats.js";
|
|||||||
import { adjustMaxTokens } from "../helpers/maxTokensHelper.js";
|
import { adjustMaxTokens } from "../helpers/maxTokensHelper.js";
|
||||||
|
|
||||||
// Convert Gemini request to OpenAI format
|
// Convert Gemini request to OpenAI format
|
||||||
function geminiToOpenAIRequest(model, body, stream) {
|
export function geminiToOpenAIRequest(model, body, stream) {
|
||||||
const result = {
|
const result = {
|
||||||
model: model,
|
model: model,
|
||||||
messages: [],
|
messages: [],
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { FORMATS } from "../formats.js";
|
|||||||
/**
|
/**
|
||||||
* Convert OpenAI Responses API request to OpenAI Chat Completions format
|
* Convert OpenAI Responses API request to OpenAI Chat Completions format
|
||||||
*/
|
*/
|
||||||
function openaiResponsesToOpenAIRequest(model, body, stream, credentials) {
|
export function openaiResponsesToOpenAIRequest(model, body, stream, credentials) {
|
||||||
if (!body.input) return body;
|
if (!body.input) return body;
|
||||||
|
|
||||||
const result = { ...body };
|
const result = { ...body };
|
||||||
@@ -134,7 +134,7 @@ function openaiResponsesToOpenAIRequest(model, body, stream, credentials) {
|
|||||||
/**
|
/**
|
||||||
* Convert OpenAI Chat Completions to OpenAI Responses API format
|
* Convert OpenAI Chat Completions to OpenAI Responses API format
|
||||||
*/
|
*/
|
||||||
function openaiToOpenAIResponsesRequest(model, body, stream, credentials) {
|
export function openaiToOpenAIResponsesRequest(model, body, stream, credentials) {
|
||||||
const result = {
|
const result = {
|
||||||
model,
|
model,
|
||||||
input: [],
|
input: [],
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { adjustMaxTokens } from "../helpers/maxTokensHelper.js";
|
|||||||
const CLAUDE_OAUTH_TOOL_PREFIX = "proxy_";
|
const CLAUDE_OAUTH_TOOL_PREFIX = "proxy_";
|
||||||
|
|
||||||
// Convert OpenAI request to Claude format
|
// Convert OpenAI request to Claude format
|
||||||
function openaiToClaudeRequest(model, body, stream) {
|
export function openaiToClaudeRequest(model, body, stream) {
|
||||||
// Tool name mapping for Claude OAuth (capitalizedName → originalName)
|
// Tool name mapping for Claude OAuth (capitalizedName → originalName)
|
||||||
const toolNameMap = new Map();
|
const toolNameMap = new Map();
|
||||||
const result = {
|
const result = {
|
||||||
|
|||||||
@@ -191,12 +191,12 @@ function openaiToGeminiBase(model, body, stream) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OpenAI -> Gemini (standard API)
|
// OpenAI -> Gemini (standard API)
|
||||||
function openaiToGeminiRequest(model, body, stream) {
|
export function openaiToGeminiRequest(model, body, stream) {
|
||||||
return openaiToGeminiBase(model, body, stream);
|
return openaiToGeminiBase(model, body, stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenAI -> Gemini CLI (Cloud Code Assist)
|
// OpenAI -> Gemini CLI (Cloud Code Assist)
|
||||||
function openaiToGeminiCLIRequest(model, body, stream) {
|
export function openaiToGeminiCLIRequest(model, body, stream) {
|
||||||
const gemini = openaiToGeminiBase(model, body, stream);
|
const gemini = openaiToGeminiBase(model, body, stream);
|
||||||
const isClaude = model.toLowerCase().includes("claude");
|
const isClaude = model.toLowerCase().includes("claude");
|
||||||
|
|
||||||
@@ -386,7 +386,7 @@ function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = nu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OpenAI -> Antigravity (Sandbox Cloud Code with wrapper)
|
// OpenAI -> Antigravity (Sandbox Cloud Code with wrapper)
|
||||||
function openaiToAntigravityRequest(model, body, stream, credentials = null) {
|
export function openaiToAntigravityRequest(model, body, stream, credentials = null) {
|
||||||
const isClaude = model.toLowerCase().includes("claude");
|
const isClaude = model.toLowerCase().includes("claude");
|
||||||
|
|
||||||
if (isClaude) {
|
if (isClaude) {
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ function convertMessages(messages, tools, model) {
|
|||||||
/**
|
/**
|
||||||
* Build Kiro payload from OpenAI format
|
* Build Kiro payload from OpenAI format
|
||||||
*/
|
*/
|
||||||
function buildKiroPayload(model, body, stream, credentials) {
|
export function buildKiroPayload(model, body, stream, credentials) {
|
||||||
const messages = body.messages || [];
|
const messages = body.messages || [];
|
||||||
const tools = body.tools || [];
|
const tools = body.tools || [];
|
||||||
const maxTokens = 32000;
|
const maxTokens = 32000;
|
||||||
@@ -278,5 +278,3 @@ function buildKiroPayload(model, body, stream, credentials) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
register(FORMATS.OPENAI, FORMATS.KIRO, buildKiroPayload, null);
|
register(FORMATS.OPENAI, FORMATS.KIRO, buildKiroPayload, null);
|
||||||
|
|
||||||
export { buildKiroPayload };
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ function createChunk(state, delta, finishReason = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert Claude stream chunk to OpenAI format
|
// Convert Claude stream chunk to OpenAI format
|
||||||
function claudeToOpenAIResponse(chunk, state) {
|
export function claudeToOpenAIResponse(chunk, state) {
|
||||||
if (!chunk) return null;
|
if (!chunk) return null;
|
||||||
|
|
||||||
const results = [];
|
const results = [];
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { register } from "../index.js";
|
|||||||
import { FORMATS } from "../formats.js";
|
import { FORMATS } from "../formats.js";
|
||||||
|
|
||||||
// Convert Gemini response chunk to OpenAI format
|
// Convert Gemini response chunk to OpenAI format
|
||||||
function geminiToOpenAIResponse(chunk, state) {
|
export function geminiToOpenAIResponse(chunk, state) {
|
||||||
if (!chunk) return null;
|
if (!chunk) return null;
|
||||||
|
|
||||||
// Handle Antigravity wrapper
|
// Handle Antigravity wrapper
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { FORMATS } from "../formats.js";
|
|||||||
* Parse Kiro SSE event and convert to OpenAI format
|
* Parse Kiro SSE event and convert to OpenAI format
|
||||||
* Kiro events: assistantResponseEvent, codeEvent, supplementaryWebLinksEvent, etc.
|
* Kiro events: assistantResponseEvent, codeEvent, supplementaryWebLinksEvent, etc.
|
||||||
*/
|
*/
|
||||||
function convertKiroToOpenAI(chunk, state) {
|
export function convertKiroToOpenAI(chunk, state) {
|
||||||
|
|
||||||
if (!chunk) return null;
|
if (!chunk) return null;
|
||||||
|
|
||||||
@@ -181,5 +181,3 @@ function convertKiroToOpenAI(chunk, state) {
|
|||||||
|
|
||||||
// Register translator
|
// Register translator
|
||||||
register(FORMATS.KIRO, FORMATS.OPENAI, null, convertKiroToOpenAI);
|
register(FORMATS.KIRO, FORMATS.OPENAI, null, convertKiroToOpenAI);
|
||||||
|
|
||||||
export { convertKiroToOpenAI };
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { FORMATS } from "../formats.js";
|
|||||||
* Translate OpenAI chunk to Responses API events
|
* Translate OpenAI chunk to Responses API events
|
||||||
* @returns {Array} Array of events with { event, data } structure
|
* @returns {Array} Array of events with { event, data } structure
|
||||||
*/
|
*/
|
||||||
function openaiToOpenAIResponsesResponse(chunk, state) {
|
export function openaiToOpenAIResponsesResponse(chunk, state) {
|
||||||
if (!chunk) {
|
if (!chunk) {
|
||||||
return flushEvents(state);
|
return flushEvents(state);
|
||||||
}
|
}
|
||||||
@@ -359,7 +359,7 @@ function flushEvents(state) {
|
|||||||
* Translate OpenAI Responses API chunk to OpenAI Chat Completions format
|
* Translate OpenAI Responses API chunk to OpenAI Chat Completions format
|
||||||
* This is for when Codex returns data and we need to send it to an OpenAI-compatible client
|
* This is for when Codex returns data and we need to send it to an OpenAI-compatible client
|
||||||
*/
|
*/
|
||||||
function openaiResponsesToOpenAIResponse(chunk, state) {
|
export function openaiResponsesToOpenAIResponse(chunk, state) {
|
||||||
if (!chunk) {
|
if (!chunk) {
|
||||||
// Flush: send final chunk with finish_reason
|
// Flush: send final chunk with finish_reason
|
||||||
if (!state.finishReasonSent && state.started) {
|
if (!state.finishReasonSent && state.started) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function stopTextBlock(state, results) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert OpenAI stream chunk to Claude format
|
// Convert OpenAI stream chunk to Claude format
|
||||||
function openaiToClaudeResponse(chunk, state) {
|
export function openaiToClaudeResponse(chunk, state) {
|
||||||
if (!chunk || !chunk.choices?.[0]) return null;
|
if (!chunk || !chunk.choices?.[0]) return null;
|
||||||
|
|
||||||
const results = [];
|
const results = [];
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ import { translateResponse, initState } from "../translator/index.js";
|
|||||||
import { FORMATS } from "../translator/formats.js";
|
import { FORMATS } from "../translator/formats.js";
|
||||||
import { saveRequestUsage, trackPendingRequest, appendRequestLog } from "@/lib/usageDb.js";
|
import { saveRequestUsage, trackPendingRequest, appendRequestLog } from "@/lib/usageDb.js";
|
||||||
|
|
||||||
|
// Singleton TextEncoder/Decoder for performance (reuse across all streams)
|
||||||
|
const sharedDecoder = new TextDecoder();
|
||||||
|
const sharedEncoder = new TextEncoder();
|
||||||
|
|
||||||
// Get HH:MM:SS timestamp
|
// Get HH:MM:SS timestamp
|
||||||
function getTimeString() {
|
function getTimeString() {
|
||||||
return new Date().toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
return new Date().toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
||||||
@@ -91,9 +95,9 @@ function logUsage(provider, usage, model = null, connectionId = null) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse SSE data line
|
// Parse SSE data line (optimized - reduce string operations)
|
||||||
function parseSSELine(line) {
|
function parseSSELine(line) {
|
||||||
if (!line || !line.startsWith("data:")) return null;
|
if (!line || line.charCodeAt(0) !== 100) return null; // 'd' = 100
|
||||||
|
|
||||||
const data = line.slice(5).trim();
|
const data = line.slice(5).trim();
|
||||||
if (data === "[DONE]") return { done: true };
|
if (data === "[DONE]") return { done: true };
|
||||||
@@ -178,8 +182,6 @@ export function createSSEStream(options = {}) {
|
|||||||
connectionId = null
|
connectionId = null
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
let buffer = "";
|
let buffer = "";
|
||||||
let usage = null;
|
let usage = null;
|
||||||
|
|
||||||
@@ -188,7 +190,7 @@ export function createSSEStream(options = {}) {
|
|||||||
|
|
||||||
return new TransformStream({
|
return new TransformStream({
|
||||||
transform(chunk, controller) {
|
transform(chunk, controller) {
|
||||||
const text = decoder.decode(chunk, { stream: true });
|
const text = sharedDecoder.decode(chunk, { stream: true });
|
||||||
buffer += text;
|
buffer += text;
|
||||||
reqLogger?.appendProviderChunk?.(text);
|
reqLogger?.appendProviderChunk?.(text);
|
||||||
|
|
||||||
@@ -215,7 +217,7 @@ export function createSSEStream(options = {}) {
|
|||||||
output = line + "\n";
|
output = line + "\n";
|
||||||
}
|
}
|
||||||
reqLogger?.appendConvertedChunk?.(output);
|
reqLogger?.appendConvertedChunk?.(output);
|
||||||
controller.enqueue(encoder.encode(output));
|
controller.enqueue(sharedEncoder.encode(output));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,7 +230,7 @@ export function createSSEStream(options = {}) {
|
|||||||
if (parsed && parsed.done) {
|
if (parsed && parsed.done) {
|
||||||
const output = "data: [DONE]\n\n";
|
const output = "data: [DONE]\n\n";
|
||||||
reqLogger?.appendConvertedChunk?.(output);
|
reqLogger?.appendConvertedChunk?.(output);
|
||||||
controller.enqueue(encoder.encode(output));
|
controller.enqueue(sharedEncoder.encode(output));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,7 +253,7 @@ export function createSSEStream(options = {}) {
|
|||||||
for (const item of translated) {
|
for (const item of translated) {
|
||||||
const output = formatSSE(item, sourceFormat);
|
const output = formatSSE(item, sourceFormat);
|
||||||
reqLogger?.appendConvertedChunk?.(output);
|
reqLogger?.appendConvertedChunk?.(output);
|
||||||
controller.enqueue(encoder.encode(output));
|
controller.enqueue(sharedEncoder.encode(output));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,7 +262,7 @@ export function createSSEStream(options = {}) {
|
|||||||
flush(controller) {
|
flush(controller) {
|
||||||
trackPendingRequest(model, provider, connectionId, false);
|
trackPendingRequest(model, provider, connectionId, false);
|
||||||
try {
|
try {
|
||||||
const remaining = decoder.decode();
|
const remaining = sharedDecoder.decode();
|
||||||
if (remaining) buffer += remaining;
|
if (remaining) buffer += remaining;
|
||||||
|
|
||||||
if (mode === STREAM_MODE.PASSTHROUGH) {
|
if (mode === STREAM_MODE.PASSTHROUGH) {
|
||||||
@@ -270,7 +272,7 @@ export function createSSEStream(options = {}) {
|
|||||||
output = "data: " + buffer.slice(5);
|
output = "data: " + buffer.slice(5);
|
||||||
}
|
}
|
||||||
reqLogger?.appendConvertedChunk?.(output);
|
reqLogger?.appendConvertedChunk?.(output);
|
||||||
controller.enqueue(encoder.encode(output));
|
controller.enqueue(sharedEncoder.encode(output));
|
||||||
}
|
}
|
||||||
if (usage) {
|
if (usage) {
|
||||||
logUsage(provider, usage, model, connectionId);
|
logUsage(provider, usage, model, connectionId);
|
||||||
@@ -299,7 +301,7 @@ export function createSSEStream(options = {}) {
|
|||||||
for (const item of translated) {
|
for (const item of translated) {
|
||||||
const output = formatSSE(item, sourceFormat);
|
const output = formatSSE(item, sourceFormat);
|
||||||
reqLogger?.appendConvertedChunk?.(output);
|
reqLogger?.appendConvertedChunk?.(output);
|
||||||
controller.enqueue(encoder.encode(output));
|
controller.enqueue(sharedEncoder.encode(output));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -320,14 +322,14 @@ export function createSSEStream(options = {}) {
|
|||||||
for (const item of flushed) {
|
for (const item of flushed) {
|
||||||
const output = formatSSE(item, sourceFormat);
|
const output = formatSSE(item, sourceFormat);
|
||||||
reqLogger?.appendConvertedChunk?.(output);
|
reqLogger?.appendConvertedChunk?.(output);
|
||||||
controller.enqueue(encoder.encode(output));
|
controller.enqueue(sharedEncoder.encode(output));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send [DONE] and log usage
|
// Send [DONE] and log usage
|
||||||
const doneOutput = "data: [DONE]\n\n";
|
const doneOutput = "data: [DONE]\n\n";
|
||||||
reqLogger?.appendConvertedChunk?.(doneOutput);
|
reqLogger?.appendConvertedChunk?.(doneOutput);
|
||||||
controller.enqueue(encoder.encode(doneOutput));
|
controller.enqueue(sharedEncoder.encode(doneOutput));
|
||||||
|
|
||||||
if (state?.usage) {
|
if (state?.usage) {
|
||||||
logUsage(state.provider || targetFormat, state.usage, model, connectionId);
|
logUsage(state.provider || targetFormat, state.usage, model, connectionId);
|
||||||
|
|||||||
Reference in New Issue
Block a user