diff --git a/open-sse/executors/antigravity.ts b/open-sse/executors/antigravity.ts index 4edc387408..d94392a176 100644 --- a/open-sse/executors/antigravity.ts +++ b/open-sse/executors/antigravity.ts @@ -42,11 +42,20 @@ const CREDITS_EXHAUSTED_TTL_MS = 5 * 60 * 60 * 1000; // 5 hours const BARE_PRO_IDS = new Set(["gemini-3.1-pro"]); -function getChunkedOrFixedBody(bodyStr: string, stream: boolean) { +interface AntigravityContent { + role: string; + parts: unknown[]; + [key: string]: unknown; +} + +function getChunkedOrFixedBody(bodyStr: string, stream: boolean): BodyInit { if (stream) { - return (async function* () { - yield new TextEncoder().encode(bodyStr); - })(); + return new ReadableStream({ + async start(controller) { + controller.enqueue(new TextEncoder().encode(bodyStr)); + controller.close(); + }, + }); } return bodyStr; } @@ -453,7 +462,7 @@ export class AntigravityExecutor extends BaseExecutor { return { ...c, role, parts }; }) || []; - const contents: any[] = []; + const contents: AntigravityContent[] = []; for (const c of normalizedContents) { if (!Array.isArray(c.parts) || c.parts.length === 0) continue; if (contents.length > 0 && contents[contents.length - 1].role === c.role) { @@ -802,7 +811,7 @@ export class AntigravityExecutor extends BaseExecutor { let response = await fetch(url, { method: "POST", headers: finalHeaders, - body: getChunkedOrFixedBody(serializedRequest.bodyString, stream) as any, + body: getChunkedOrFixedBody(serializedRequest.bodyString, stream), ...(stream ? { duplex: "half" } : {}), signal, }); @@ -814,7 +823,7 @@ export class AntigravityExecutor extends BaseExecutor { response = await fetch(url, { method: "POST", headers: retryHeaders, - body: getChunkedOrFixedBody(serializedRequest.bodyString, stream) as any, + body: getChunkedOrFixedBody(serializedRequest.bodyString, stream), ...(stream ? { duplex: "half" } : {}), signal, }); @@ -884,7 +893,7 @@ export class AntigravityExecutor extends BaseExecutor { const creditsResp = await fetch(url, { method: "POST", headers: finalCreditsHeaders, - body: getChunkedOrFixedBody(serializedCreditsRequest.bodyString, stream) as any, + body: getChunkedOrFixedBody(serializedCreditsRequest.bodyString, stream), ...(stream ? { duplex: "half" } : {}), signal, }); diff --git a/open-sse/services/accountFallback.ts b/open-sse/services/accountFallback.ts index 7ec998d196..eb457ab10e 100644 --- a/open-sse/services/accountFallback.ts +++ b/open-sse/services/accountFallback.ts @@ -495,12 +495,21 @@ export function getModelLockoutInfo(provider, connectionId, model) { }; } +type ModelLockoutInfo = { + provider: string; + connectionId: string; + model: string; + reason: string; + remainingMs: number; + failureCount: number; +}; + /** * Get all active model lockouts (for dashboard) */ -export function getAllModelLockouts() { +export function getAllModelLockouts(): ModelLockoutInfo[] { const now = Date.now(); - const active: any[] = []; + const active: ModelLockoutInfo[] = []; for (const key of modelLockouts.keys()) { cleanupModelLockKey(key, now); } @@ -908,7 +917,7 @@ export function checkFallbackError( backoffLevel: number = 0, _model: string | null = null, provider: string | null = null, - headers: any = null, + headers: Headers | Record | null = null, profileOverride: ProviderProfile | null = null ): { shouldFallback: boolean; diff --git a/open-sse/services/usage.ts b/open-sse/services/usage.ts index 86187a01a0..13032530f1 100644 --- a/open-sse/services/usage.ts +++ b/open-sse/services/usage.ts @@ -644,10 +644,13 @@ async function getGlmUsage(apiKey: string, providerSpecificData?: Record ({ - name: String(m.model || ""), - used: toNumber(m.percentage, 0), - })) + ? (src.models as unknown[]).map((m) => { + const modelInfo = toRecord(m); + return { + name: String(modelInfo.model || ""), + used: toNumber(modelInfo.percentage, 0), + }; + }) : [], unlimited: false, }; diff --git a/open-sse/translator/helpers/geminiHelper.ts b/open-sse/translator/helpers/geminiHelper.ts index 5a2c1f825f..64a9bc96c8 100644 --- a/open-sse/translator/helpers/geminiHelper.ts +++ b/open-sse/translator/helpers/geminiHelper.ts @@ -89,52 +89,58 @@ export const DEFAULT_SAFETY_SETTINGS = [ ]; // Convert OpenAI content to Gemini parts -export function convertOpenAIContentToParts(content: any) { - const parts: any[] = []; +export function convertOpenAIContentToParts(content: unknown) { + const parts: Record[] = []; if (typeof content === "string") { parts.push({ text: content }); } else if (Array.isArray(content)) { for (const item of content) { - if (item.type === "text") { - parts.push({ text: item.text }); + const rec = toRecord(item); + if (rec.type === "text") { + parts.push({ text: rec.text }); } else { // 1. Handle Gemini native inline_data injected into OpenAI arrays (e.g. Cherry Studio) - const geminiInline = item.inline_data || item.inlineData; + const geminiInline = toRecord(rec.inline_data || rec.inlineData); if (geminiInline?.data) { parts.push({ inlineData: { - mimeType: geminiInline.mime_type || geminiInline.mimeType || "application/pdf", - data: geminiInline.data.replace(/^data:[a-zA-Z0-9/+-]+;base64,/, ""), + mimeType: String( + geminiInline.mime_type || geminiInline.mimeType || "application/pdf" + ), + data: String(geminiInline.data).replace(/^data:[a-zA-Z0-9/+-]+;base64,/, ""), }, }); continue; } // 2. Handle Claude-style source blocks commonly used by AI clients - if (item.source?.type === "base64" && item.source?.data) { + const source = toRecord(rec.source); + if (source?.type === "base64" && source?.data) { parts.push({ inlineData: { - mimeType: item.source.media_type || "application/pdf", - data: item.source.data.replace(/^data:[a-zA-Z0-9/+-]+;base64,/, ""), + mimeType: String(source.media_type || "application/pdf"), + data: String(source.data).replace(/^data:[a-zA-Z0-9/+-]+;base64,/, ""), }, }); continue; } // 3. Handle raw data strings (e.g. {"type": "file", "data": "JVBER...", "mime_type": "..."}) - const rawDataStr = item.data || item.file?.data || item.document?.data; + const file = toRecord(rec.file); + const doc = toRecord(rec.document); + const rawDataStr = rec.data || file?.data || doc?.data; const mimeTypeFallback = - item.mime_type || - item.media_type || - item.file?.mime_type || - item.document?.mime_type || + rec.mime_type || + rec.media_type || + file?.mime_type || + doc?.mime_type || "application/octet-stream"; if (typeof rawDataStr === "string" && !rawDataStr.startsWith("http")) { const rawData = rawDataStr.replace(/^data:[a-zA-Z0-9/+-]+;base64,/, ""); parts.push({ inlineData: { - mimeType: mimeTypeFallback, + mimeType: String(mimeTypeFallback), data: rawData, }, }); @@ -142,8 +148,11 @@ export function convertOpenAIContentToParts(content: any) { } // 4. Standard OpenAI Data URIs - const fileData = - item.image_url?.url || item.file_url?.url || item.file?.url || item.document?.url; + const imageUrl = toRecord(rec.image_url); + const fileUrl = toRecord(rec.file_url); + const fileObj = toRecord(rec.file); + const docObj = toRecord(rec.document); + const fileData = imageUrl?.url || fileUrl?.url || fileObj?.url || docObj?.url; if (typeof fileData === "string" && fileData.startsWith("data:")) { const commaIndex = fileData.indexOf(","); if (commaIndex !== -1) { diff --git a/open-sse/utils/error.ts b/open-sse/utils/error.ts index 8a84469d4a..05e908a7de 100644 --- a/open-sse/utils/error.ts +++ b/open-sse/utils/error.ts @@ -8,6 +8,14 @@ import type { ModelCooldownErrorPayload } from "@/types"; * Strips stack traces and internal file paths from error messages before they * reach the client. */ +interface ErrorResponseBody { + error: { + message: string; + type?: string; + code?: string; + }; +} + function sanitizeErrorMessage(message: unknown): string { const str = typeof message === "string" ? message : String(message ?? ""); // If the message contains a stack trace (lines starting with " at "), @@ -22,7 +30,7 @@ function sanitizeErrorMessage(message: unknown): string { * @param {string} message - Error message * @returns {object} Error response object */ -export function buildErrorBody(statusCode, message) { +export function buildErrorBody(statusCode: number, message: string): ErrorResponseBody { const errorInfo = getErrorInfo(statusCode); return { @@ -220,10 +228,10 @@ export function createErrorResult( ) { const body = buildErrorBody(statusCode, message); if (errorCode) { - (body.error as any).code = errorCode; + body.error.code = errorCode; } if (errorType) { - (body.error as any).type = errorType; + body.error.type = errorType; } const result: { diff --git a/open-sse/utils/logger.ts b/open-sse/utils/logger.ts index cb2528b12e..0b56430d31 100644 --- a/open-sse/utils/logger.ts +++ b/open-sse/utils/logger.ts @@ -185,5 +185,6 @@ export function createLogger(requestId: string | null = null): RequestScopedLogg * Module-level default logger (no requestId — for startup/config messages). */ export const defaultLogger = createLogger(); +export const log = defaultLogger; export default logger; diff --git a/src/lib/db/apiKeys.ts b/src/lib/db/apiKeys.ts index 65c33b2427..70f414b0ad 100644 --- a/src/lib/db/apiKeys.ts +++ b/src/lib/db/apiKeys.ts @@ -403,7 +403,7 @@ function parseRateLimits(value: unknown): RateLimitRule[] | null { const parsed = JSON.parse(value); if (!Array.isArray(parsed)) return null; return parsed.filter( - (rule: any) => + (rule: RateLimitRule) => typeof rule === "object" && rule !== null && typeof rule.limit === "number" && diff --git a/src/lib/db/settings.ts b/src/lib/db/settings.ts index c76fd50dd9..375377be9b 100644 --- a/src/lib/db/settings.ts +++ b/src/lib/db/settings.ts @@ -272,7 +272,10 @@ export async function getPricingWithSources(): Promise<{ export async function getPricingForModel(provider: string, model: string) { const pricing = await getPricing(); - const findKeyInsensitive = (obj: Record | undefined | null, key: string) => { + const findKeyInsensitive = ( + obj: Record | undefined | null, + key: string + ): T | undefined => { if (!obj || !key) return undefined; const lowerKey = key.toLowerCase(); for (const [k, v] of Object.entries(obj)) { diff --git a/src/shared/constants/providers.ts b/src/shared/constants/providers.ts index 3c01070b24..26ba7f9934 100644 --- a/src/shared/constants/providers.ts +++ b/src/shared/constants/providers.ts @@ -1894,6 +1894,19 @@ export const SYSTEM_PROVIDERS = { }, }; +// Cloud Agent Providers +export const CLOUD_AGENT_PROVIDERS = { + jules: { id: "jules", alias: "jules", name: "Jules AI", icon: "smart_toy", color: "#6366F1" }, + devin: { id: "devin", alias: "devin", name: "Devin AI", icon: "auto_fix_high", color: "#10B981" }, + "codex-cloud": { + id: "codex-cloud", + alias: "codex-cloud", + name: "Codex Cloud", + icon: "cloud", + color: "#3B82F6", + }, +}; + // All providers (combined) export const AI_PROVIDERS = { ...FREE_PROVIDERS,