mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-19 21:52:21 +03:00
Merge pull request #62 from diegosouzapw/refactor/open-sse-js-to-ts
refactor(open-sse): JS → TS migration + v0.8.5
This commit is contained in:
@@ -6,7 +6,22 @@
|
||||
* - /v1/audio/speech (TTS API)
|
||||
*/
|
||||
|
||||
export const AUDIO_TRANSCRIPTION_PROVIDERS = {
|
||||
interface AudioModel {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface AudioProvider {
|
||||
id: string;
|
||||
baseUrl: string;
|
||||
authType: string;
|
||||
authHeader: string;
|
||||
format?: string;
|
||||
async?: boolean;
|
||||
models: AudioModel[];
|
||||
}
|
||||
|
||||
export const AUDIO_TRANSCRIPTION_PROVIDERS: Record<string, AudioProvider> = {
|
||||
openai: {
|
||||
id: "openai",
|
||||
baseUrl: "https://api.openai.com/v1/audio/transcriptions",
|
||||
@@ -57,7 +72,7 @@ export const AUDIO_TRANSCRIPTION_PROVIDERS = {
|
||||
},
|
||||
};
|
||||
|
||||
export const AUDIO_SPEECH_PROVIDERS = {
|
||||
export const AUDIO_SPEECH_PROVIDERS: Record<string, AudioProvider> = {
|
||||
openai: {
|
||||
id: "openai",
|
||||
baseUrl: "https://api.openai.com/v1/audio/speech",
|
||||
@@ -96,21 +111,21 @@ export const AUDIO_SPEECH_PROVIDERS = {
|
||||
/**
|
||||
* Get transcription provider config by ID
|
||||
*/
|
||||
export function getTranscriptionProvider(providerId) {
|
||||
export function getTranscriptionProvider(providerId: string): AudioProvider | null {
|
||||
return AUDIO_TRANSCRIPTION_PROVIDERS[providerId] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get speech provider config by ID
|
||||
*/
|
||||
export function getSpeechProvider(providerId) {
|
||||
export function getSpeechProvider(providerId: string): AudioProvider | null {
|
||||
return AUDIO_SPEECH_PROVIDERS[providerId] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse audio model string (format: "provider/model" or just "model")
|
||||
*/
|
||||
function parseAudioModel(modelStr, registry) {
|
||||
function parseAudioModel(modelStr: string | null, registry: Record<string, AudioProvider>): { provider: string | null; model: string | null } {
|
||||
if (!modelStr) return { provider: null, model: null };
|
||||
|
||||
for (const [providerId, config] of Object.entries(registry)) {
|
||||
@@ -128,11 +143,11 @@ function parseAudioModel(modelStr, registry) {
|
||||
return { provider: null, model: modelStr };
|
||||
}
|
||||
|
||||
export function parseTranscriptionModel(modelStr) {
|
||||
export function parseTranscriptionModel(modelStr: string | null) {
|
||||
return parseAudioModel(modelStr, AUDIO_TRANSCRIPTION_PROVIDERS);
|
||||
}
|
||||
|
||||
export function parseSpeechModel(modelStr) {
|
||||
export function parseSpeechModel(modelStr: string | null) {
|
||||
return parseAudioModel(modelStr, AUDIO_SPEECH_PROVIDERS);
|
||||
}
|
||||
|
||||
@@ -6,9 +6,51 @@
|
||||
* is auto-generated from this registry.
|
||||
*/
|
||||
|
||||
// ── Types ─────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface RegistryModel {
|
||||
id: string;
|
||||
name: string;
|
||||
targetFormat?: string;
|
||||
}
|
||||
|
||||
export interface RegistryOAuth {
|
||||
clientIdEnv?: string;
|
||||
clientIdDefault?: string;
|
||||
clientSecretEnv?: string;
|
||||
clientSecretDefault?: string;
|
||||
tokenUrl?: string;
|
||||
refreshUrl?: string;
|
||||
authUrl?: string;
|
||||
initiateUrl?: string;
|
||||
pollUrlBase?: string;
|
||||
}
|
||||
|
||||
export interface RegistryEntry {
|
||||
id: string;
|
||||
alias: string;
|
||||
format: string;
|
||||
executor: string;
|
||||
baseUrl?: string;
|
||||
baseUrls?: string[];
|
||||
responsesBaseUrl?: string;
|
||||
urlSuffix?: string;
|
||||
urlBuilder?: (base: string, model: string, stream: boolean) => string;
|
||||
authType: string;
|
||||
authHeader: string;
|
||||
authPrefix?: string;
|
||||
headers?: Record<string, string>;
|
||||
extraHeaders?: Record<string, string>;
|
||||
oauth?: RegistryOAuth;
|
||||
models: RegistryModel[];
|
||||
chatPath?: string;
|
||||
clientVersion?: string;
|
||||
passthroughModels?: boolean;
|
||||
}
|
||||
|
||||
// ── Registry ──────────────────────────────────────────────────────────────
|
||||
|
||||
export const REGISTRY = {
|
||||
export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
// ─── OAuth Providers ───────────────────────────────────────────────────
|
||||
claude: {
|
||||
id: "claude",
|
||||
@@ -760,10 +802,10 @@ export const REGISTRY = {
|
||||
// ── Generator Functions ───────────────────────────────────────────────────
|
||||
|
||||
/** Generate legacy PROVIDERS object shape for constants.js backward compatibility */
|
||||
export function generateLegacyProviders() {
|
||||
const providers = {};
|
||||
export function generateLegacyProviders(): Record<string, any> {
|
||||
const providers: Record<string, any> = {};
|
||||
for (const [id, entry] of Object.entries(REGISTRY)) {
|
||||
const p = { format: entry.format };
|
||||
const p: Record<string, any> = { format: entry.format };
|
||||
|
||||
// URL(s)
|
||||
if (entry.baseUrls) {
|
||||
@@ -808,8 +850,8 @@ export function generateLegacyProviders() {
|
||||
}
|
||||
|
||||
/** Generate PROVIDER_MODELS map (alias → model list) */
|
||||
export function generateModels() {
|
||||
const models = {};
|
||||
export function generateModels(): Record<string, RegistryModel[]> {
|
||||
const models: Record<string, RegistryModel[]> = {};
|
||||
for (const entry of Object.values(REGISTRY)) {
|
||||
if (entry.models && entry.models.length > 0) {
|
||||
const key = entry.alias || entry.id;
|
||||
@@ -823,8 +865,8 @@ export function generateModels() {
|
||||
}
|
||||
|
||||
/** Generate PROVIDER_ID_TO_ALIAS map */
|
||||
export function generateAliasMap() {
|
||||
const map = {};
|
||||
export function generateAliasMap(): Record<string, string> {
|
||||
const map: Record<string, string> = {};
|
||||
for (const entry of Object.values(REGISTRY)) {
|
||||
map[entry.id] = entry.alias || entry.id;
|
||||
}
|
||||
@@ -841,12 +883,12 @@ for (const entry of Object.values(REGISTRY)) {
|
||||
}
|
||||
|
||||
/** Get registry entry by provider ID or alias */
|
||||
export function getRegistryEntry(provider) {
|
||||
export function getRegistryEntry(provider: string): RegistryEntry | null {
|
||||
return REGISTRY[provider] || _byAlias.get(provider) || null;
|
||||
}
|
||||
|
||||
/** Get all registered provider IDs */
|
||||
export function getRegisteredProviders() {
|
||||
export function getRegisteredProviders(): string[] {
|
||||
return Object.keys(REGISTRY);
|
||||
}
|
||||
|
||||
@@ -856,7 +898,7 @@ export function getRegisteredProviders() {
|
||||
* @param {string} provider - Provider ID or alias
|
||||
* @returns {"oauth"|"apikey"}
|
||||
*/
|
||||
export function getProviderCategory(provider) {
|
||||
export function getProviderCategory(provider: string): "oauth" | "apikey" {
|
||||
const entry = getRegistryEntry(provider);
|
||||
if (!entry) return "apikey"; // Safe default for unknown providers
|
||||
return entry.authType === "apikey" ? "apikey" : "oauth";
|
||||
@@ -6,7 +6,10 @@ import { HTTP_STATUS, FETCH_TIMEOUT_MS } from "../config/constants.js";
|
||||
* (buildUrl, buildHeaders, transformRequest, etc.) for each provider.
|
||||
*/
|
||||
export class BaseExecutor {
|
||||
constructor(provider, config) {
|
||||
provider: any;
|
||||
config: any;
|
||||
|
||||
constructor(provider: any, config: any) {
|
||||
this.provider = provider;
|
||||
this.config = config;
|
||||
}
|
||||
@@ -96,7 +99,7 @@ export class BaseExecutor {
|
||||
? AbortSignal.any([signal, timeoutSignal])
|
||||
: signal || timeoutSignal;
|
||||
|
||||
const fetchOptions = {
|
||||
const fetchOptions: Record<string, any> = {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify(transformedBody),
|
||||
@@ -1,3 +1,4 @@
|
||||
declare var EdgeRuntime: any;
|
||||
/**
|
||||
* CursorExecutor — Handles communication with the Cursor IDE API.
|
||||
*
|
||||
@@ -226,7 +227,7 @@ export class CursorExecutor extends BaseExecutor {
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
headers: Object.fromEntries(response.headers.entries()),
|
||||
headers: Object.fromEntries((response.headers as any).entries()),
|
||||
body: Buffer.from(await response.arrayBuffer()),
|
||||
};
|
||||
}
|
||||
@@ -290,7 +291,7 @@ export class CursorExecutor extends BaseExecutor {
|
||||
const transformedBody = this.transformRequest(model, body, stream, credentials);
|
||||
|
||||
try {
|
||||
const response = http2
|
||||
const response: any = http2
|
||||
? await this.makeHttp2Request(url, headers, transformedBody, signal)
|
||||
: await this.makeFetchRequest(url, headers, transformedBody, signal);
|
||||
|
||||
@@ -458,8 +459,7 @@ export class CursorExecutor extends BaseExecutor {
|
||||
|
||||
console.log(`[CURSOR BUFFER] Final toolCalls count: ${toolCalls.length}`);
|
||||
|
||||
const message = {
|
||||
role: "assistant",
|
||||
const message: Record<string, any> = { role: "assistant",
|
||||
content: totalContent || null,
|
||||
};
|
||||
|
||||
@@ -83,8 +83,7 @@ export class KiroExecutor extends BaseExecutor {
|
||||
let chunkIndex = 0;
|
||||
const responseId = `chatcmpl-${Date.now()}`;
|
||||
const created = Math.floor(Date.now() / 1000);
|
||||
const state = {
|
||||
endDetected: false,
|
||||
const state: Record<string, any> = { endDetected: false,
|
||||
finishEmitted: false,
|
||||
hasToolCalls: false,
|
||||
toolCallIndex: 0,
|
||||
@@ -126,7 +125,7 @@ export class KiroExecutor extends BaseExecutor {
|
||||
const content = event.payload.content;
|
||||
state.totalContentLength += content.length;
|
||||
|
||||
const chunk = {
|
||||
const chunk: Record<string, any> = {
|
||||
id: responseId,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
@@ -145,7 +144,7 @@ export class KiroExecutor extends BaseExecutor {
|
||||
|
||||
// Handle codeEvent
|
||||
if (eventType === "codeEvent" && event.payload?.content) {
|
||||
const chunk = {
|
||||
const chunk: Record<string, any> = {
|
||||
id: responseId,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
@@ -257,7 +256,7 @@ export class KiroExecutor extends BaseExecutor {
|
||||
|
||||
// Handle messageStopEvent
|
||||
if (eventType === "messageStopEvent") {
|
||||
const chunk = {
|
||||
const chunk: Record<string, any> = {
|
||||
id: responseId,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
@@ -330,7 +329,7 @@ export class KiroExecutor extends BaseExecutor {
|
||||
};
|
||||
}
|
||||
|
||||
const finishChunk = {
|
||||
const finishChunk: Record<string, any> = {
|
||||
id: responseId,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
@@ -100,12 +100,13 @@ async function handleDeepgramSpeech(providerConfig, body, modelId, token) {
|
||||
* @param {Object} options.credentials - Provider credentials { apiKey }
|
||||
* @returns {Response}
|
||||
*/
|
||||
/** @returns {Promise<any>} */
|
||||
export async function handleAudioSpeech({ body, credentials }) {
|
||||
if (!body.model) {
|
||||
return errorResponse("model is required", 400);
|
||||
return errorResponse(400, "model is required");
|
||||
}
|
||||
if (!body.input) {
|
||||
return errorResponse("input is required", 400);
|
||||
return errorResponse(400, "input is required");
|
||||
}
|
||||
|
||||
const { provider: providerId, model: modelId } = parseSpeechModel(body.model);
|
||||
@@ -113,14 +114,14 @@ export async function handleAudioSpeech({ body, credentials }) {
|
||||
|
||||
if (!providerConfig) {
|
||||
return errorResponse(
|
||||
`No speech provider found for model "${body.model}". Available: openai, hyperbolic, deepgram`,
|
||||
400
|
||||
400,
|
||||
`No speech provider found for model "${body.model}". Available: openai, hyperbolic, deepgram`
|
||||
);
|
||||
}
|
||||
|
||||
const token = credentials?.apiKey || credentials?.accessToken;
|
||||
if (!token) {
|
||||
return errorResponse(`No credentials for speech provider: ${providerId}`, 401);
|
||||
return errorResponse(401, `No credentials for speech provider: ${providerId}`);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -168,6 +169,6 @@ export async function handleAudioSpeech({ body, credentials }) {
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
return errorResponse(`Speech request failed: ${err.message}`, 500);
|
||||
return errorResponse(500, `Speech request failed: ${err.message}`);
|
||||
}
|
||||
}
|
||||
@@ -129,11 +129,11 @@ async function handleAssemblyAITranscription(providerConfig, file, modelId, toke
|
||||
}
|
||||
|
||||
if (result.status === "error") {
|
||||
return errorResponse(result.error || "AssemblyAI transcription failed", 500);
|
||||
return errorResponse(500, result.error || "AssemblyAI transcription failed");
|
||||
}
|
||||
}
|
||||
|
||||
return errorResponse("AssemblyAI transcription timed out after 120s", 504);
|
||||
return errorResponse(504, "AssemblyAI transcription timed out after 120s");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,15 +144,16 @@ async function handleAssemblyAITranscription(providerConfig, file, modelId, toke
|
||||
* @param {Object} options.credentials - Provider credentials { apiKey }
|
||||
* @returns {Response}
|
||||
*/
|
||||
/** @returns {Promise<any>} */
|
||||
export async function handleAudioTranscription({ formData, credentials }) {
|
||||
const model = formData.get("model");
|
||||
if (!model) {
|
||||
return errorResponse("model is required", 400);
|
||||
return errorResponse(400, "model is required");
|
||||
}
|
||||
|
||||
const file = formData.get("file");
|
||||
if (!file) {
|
||||
return errorResponse("file is required", 400);
|
||||
return errorResponse(400, "file is required");
|
||||
}
|
||||
|
||||
const { provider: providerId, model: modelId } = parseTranscriptionModel(model);
|
||||
@@ -160,14 +161,14 @@ export async function handleAudioTranscription({ formData, credentials }) {
|
||||
|
||||
if (!providerConfig) {
|
||||
return errorResponse(
|
||||
`No transcription provider found for model "${model}". Available: openai, groq, deepgram, assemblyai`,
|
||||
400
|
||||
400,
|
||||
`No transcription provider found for model "${model}". Available: openai, groq, deepgram, assemblyai`
|
||||
);
|
||||
}
|
||||
|
||||
const token = credentials?.apiKey || credentials?.accessToken;
|
||||
if (!token) {
|
||||
return errorResponse(`No credentials for transcription provider: ${providerId}`, 401);
|
||||
return errorResponse(401, `No credentials for transcription provider: ${providerId}`);
|
||||
}
|
||||
|
||||
// Route to provider-specific handler
|
||||
@@ -181,7 +182,7 @@ export async function handleAudioTranscription({ formData, credentials }) {
|
||||
|
||||
// Default: OpenAI/Groq-compatible multipart proxy
|
||||
const upstreamForm = new FormData();
|
||||
upstreamForm.append("file", file, file.name || "audio.wav");
|
||||
upstreamForm.append("file", /** @type {Blob} */ (file), /** @type {any} */ (file).name || "audio.wav");
|
||||
upstreamForm.append("model", modelId);
|
||||
|
||||
// Forward optional parameters
|
||||
@@ -194,7 +195,7 @@ export async function handleAudioTranscription({ formData, credentials }) {
|
||||
]) {
|
||||
const val = formData.get(key);
|
||||
if (val !== null && val !== undefined) {
|
||||
upstreamForm.append(key, val);
|
||||
upstreamForm.append(key, /** @type {string} */ (val));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,6 +222,6 @@ export async function handleAudioTranscription({ formData, credentials }) {
|
||||
headers: { "Content-Type": contentType, "Access-Control-Allow-Origin": "*" },
|
||||
});
|
||||
} catch (err) {
|
||||
return errorResponse(`Transcription request failed: ${err.message}`, 500);
|
||||
return errorResponse(500, `Transcription request failed: ${err.message}`);
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,7 @@ import { createProgressTransform, wantsProgress } from "../utils/progressTracker
|
||||
* @param {string} options.connectionId - Connection ID for usage tracking
|
||||
* @param {object} options.apiKeyInfo - API key metadata for usage attribution
|
||||
*/
|
||||
/** @param {any} options */
|
||||
export async function handleChatCore({
|
||||
body,
|
||||
modelInfo,
|
||||
@@ -52,7 +52,7 @@ export async function handleEmbedding({ body, credentials, log }) {
|
||||
}
|
||||
|
||||
// Build upstream request
|
||||
const upstreamBody = {
|
||||
const upstreamBody: Record<string, any> = {
|
||||
model: model,
|
||||
input: body.input,
|
||||
};
|
||||
@@ -232,7 +232,7 @@ async function handleOpenAIImageGeneration({
|
||||
};
|
||||
|
||||
// Build upstream request (OpenAI-compatible format)
|
||||
const upstreamBody = {
|
||||
const upstreamBody: Record<string, any> = {
|
||||
model: model,
|
||||
prompt: body.prompt,
|
||||
};
|
||||
@@ -15,9 +15,10 @@ import { errorResponse } from "../utils/error.js";
|
||||
* @param {Object} options.credentials - Provider credentials { apiKey }
|
||||
* @returns {Response}
|
||||
*/
|
||||
/** @returns {Promise<any>} */
|
||||
export async function handleModeration({ body, credentials }) {
|
||||
if (!body.input) {
|
||||
return errorResponse("input is required", 400);
|
||||
return errorResponse(400, "input is required");
|
||||
}
|
||||
|
||||
// Default to latest moderation model
|
||||
@@ -27,14 +28,14 @@ export async function handleModeration({ body, credentials }) {
|
||||
|
||||
if (!providerConfig) {
|
||||
return errorResponse(
|
||||
`No moderation provider found for model "${model}". Available: openai`,
|
||||
400
|
||||
400,
|
||||
`No moderation provider found for model "${model}". Available: openai`
|
||||
);
|
||||
}
|
||||
|
||||
const token = credentials?.apiKey || credentials?.accessToken;
|
||||
if (!token) {
|
||||
return errorResponse(`No credentials for moderation provider: ${providerId}`, 401);
|
||||
return errorResponse(401, `No credentials for moderation provider: ${providerId}`);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -63,6 +64,6 @@ export async function handleModeration({ body, credentials }) {
|
||||
headers: { "Access-Control-Allow-Origin": "*" },
|
||||
});
|
||||
} catch (err) {
|
||||
return errorResponse(`Moderation request failed: ${err.message}`, 500);
|
||||
return errorResponse(500, `Moderation request failed: ${err.message}`);
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,7 @@ function transformResponseFromProvider(providerConfig, data) {
|
||||
* @param {Object} options.credentials - Provider credentials { apiKey, accessToken }
|
||||
* @returns {Response}
|
||||
*/
|
||||
/** @returns {Promise<any>} */
|
||||
export async function handleRerank({
|
||||
model,
|
||||
query,
|
||||
@@ -77,10 +78,10 @@ export async function handleRerank({
|
||||
return_documents,
|
||||
credentials,
|
||||
}) {
|
||||
if (!model) return errorResponse("model is required", 400);
|
||||
if (!query) return errorResponse("query is required", 400);
|
||||
if (!model) return errorResponse(400, "model is required");
|
||||
if (!query) return errorResponse(400, "query is required");
|
||||
if (!documents || !Array.isArray(documents) || documents.length === 0) {
|
||||
return errorResponse("documents must be a non-empty array", 400);
|
||||
return errorResponse(400, "documents must be a non-empty array");
|
||||
}
|
||||
|
||||
const { provider: providerId, model: modelId } = parseRerankModel(model);
|
||||
@@ -88,14 +89,14 @@ export async function handleRerank({
|
||||
|
||||
if (!providerConfig) {
|
||||
return errorResponse(
|
||||
`No rerank provider found for model "${model}". Available: cohere, together, nvidia, fireworks`,
|
||||
400
|
||||
400,
|
||||
`No rerank provider found for model "${model}". Available: cohere, together, nvidia, fireworks`
|
||||
);
|
||||
}
|
||||
|
||||
const token = credentials?.apiKey || credentials?.accessToken;
|
||||
if (!token) {
|
||||
return errorResponse(`No credentials for rerank provider: ${providerId}`, 401);
|
||||
return errorResponse(401, `No credentials for rerank provider: ${providerId}`);
|
||||
}
|
||||
|
||||
const requestBody = transformRequestForProvider(providerConfig, {
|
||||
@@ -119,8 +120,8 @@ export async function handleRerank({
|
||||
if (!res.ok) {
|
||||
const errData = await res.json().catch(() => ({}));
|
||||
return errorResponse(
|
||||
errData.message || errData.error?.message || `Provider returned HTTP ${res.status}`,
|
||||
res.status
|
||||
res.status,
|
||||
errData.message || errData.error?.message || `Provider returned HTTP ${res.status}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -131,6 +132,6 @@ export async function handleRerank({
|
||||
headers: { "Access-Control-Allow-Origin": "*" },
|
||||
});
|
||||
} catch (err) {
|
||||
return errorResponse(`Rerank request failed: ${err.message}`, 500);
|
||||
return errorResponse(500, `Rerank request failed: ${err.message}`);
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ export function translateNonStreamingResponse(responseBody, targetFormat, source
|
||||
}
|
||||
}
|
||||
|
||||
const message = { role: "assistant" };
|
||||
const message: Record<string, any> = { role: "assistant" };
|
||||
if (textContent) {
|
||||
message.content = textContent;
|
||||
}
|
||||
@@ -74,7 +74,7 @@ export function translateNonStreamingResponse(responseBody, targetFormat, source
|
||||
const model = response?.model || responseBody?.model || "openai-responses";
|
||||
const finishReason = toolCalls.length > 0 ? "tool_calls" : "stop";
|
||||
|
||||
const result = {
|
||||
const result: Record<string, any> = {
|
||||
id: `chatcmpl-${response?.id || Date.now()}`,
|
||||
object: "chat.completion",
|
||||
created: createdAt,
|
||||
@@ -162,7 +162,7 @@ export function translateNonStreamingResponse(responseBody, targetFormat, source
|
||||
}
|
||||
|
||||
// Build OpenAI format message
|
||||
const message = { role: "assistant" };
|
||||
const message: Record<string, any> = { role: "assistant" };
|
||||
if (textContent) {
|
||||
message.content = textContent;
|
||||
}
|
||||
@@ -183,7 +183,7 @@ export function translateNonStreamingResponse(responseBody, targetFormat, source
|
||||
finishReason = "tool_calls";
|
||||
}
|
||||
|
||||
const result = {
|
||||
const result: Record<string, any> = {
|
||||
id: `chatcmpl-${response.responseId || Date.now()}`,
|
||||
object: "chat.completion",
|
||||
created: Math.floor(new Date(response.createTime || Date.now()).getTime() / 1000),
|
||||
@@ -241,7 +241,7 @@ export function translateNonStreamingResponse(responseBody, targetFormat, source
|
||||
}
|
||||
}
|
||||
|
||||
const message = { role: "assistant" };
|
||||
const message: Record<string, any> = { role: "assistant" };
|
||||
if (textContent) {
|
||||
message.content = textContent;
|
||||
}
|
||||
@@ -259,7 +259,7 @@ export function translateNonStreamingResponse(responseBody, targetFormat, source
|
||||
if (finishReason === "end_turn") finishReason = "stop";
|
||||
if (finishReason === "tool_use") finishReason = "tool_calls";
|
||||
|
||||
const result = {
|
||||
const result: Record<string, any> = {
|
||||
id: `chatcmpl-${responseBody.id || Date.now()}`,
|
||||
object: "chat.completion",
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
@@ -45,8 +45,11 @@ export async function handleResponsesCore({
|
||||
onCredentialsRefreshed,
|
||||
onRequestSuccess,
|
||||
onDisconnect,
|
||||
clientRawRequest: null,
|
||||
connectionId,
|
||||
});
|
||||
userAgent: null,
|
||||
comboName: null,
|
||||
} as any);
|
||||
|
||||
if (!result.success || !result.response) {
|
||||
return result;
|
||||
@@ -44,15 +44,14 @@ export function parseSSEToOpenAIResponse(rawSSE, fallbackModel) {
|
||||
}
|
||||
}
|
||||
|
||||
const message = {
|
||||
role: "assistant",
|
||||
const message: Record<string, any> = { role: "assistant",
|
||||
content: contentParts.join(""),
|
||||
};
|
||||
if (reasoningParts.length > 0) {
|
||||
message.reasoning_content = reasoningParts.join("");
|
||||
}
|
||||
|
||||
const result = {
|
||||
const result: Record<string, any> = {
|
||||
id: first.id || `chatcmpl-${Date.now()}`,
|
||||
object: "chat.completion",
|
||||
created: first.created || Math.floor(Date.now() / 1000),
|
||||
@@ -504,7 +504,7 @@ export function applyErrorState(account, status, errorText, provider = null) {
|
||||
* @param {object} account
|
||||
* @returns {number} score 0 = unhealthy, 100 = perfectly healthy
|
||||
*/
|
||||
export function getAccountHealth(account) {
|
||||
export function getAccountHealth(account, model?: any) {
|
||||
if (!account) return 0;
|
||||
let score = 100;
|
||||
score -= (account.backoffLevel || 0) * 10;
|
||||
@@ -43,7 +43,7 @@ export function selectAccountP2C(accounts, model = null) {
|
||||
* @param {string} [model] - Model name
|
||||
* @returns {{ account: object|null, state: object }}
|
||||
*/
|
||||
export function selectAccount(accounts, strategy = "fill-first", state = {}, model = null) {
|
||||
export function selectAccount(accounts, strategy = "fill-first", state: any = {}, model = null) {
|
||||
if (!accounts || accounts.length === 0) {
|
||||
return { account: null, state };
|
||||
}
|
||||
@@ -221,6 +221,7 @@ function sortModelsByUsage(models, comboName) {
|
||||
* @param {Object} options.log - Logger object
|
||||
* @returns {Promise<Response>}
|
||||
*/
|
||||
/** @param {any} options */
|
||||
export async function handleComboChat({
|
||||
body,
|
||||
combo,
|
||||
@@ -27,7 +27,7 @@ const DEFAULT_COMBO_CONFIG = {
|
||||
* @param {string} [provider] - Optional provider to apply provider-level overrides
|
||||
* @returns {Object} Resolved config
|
||||
*/
|
||||
export function resolveComboConfig(combo, settings, provider) {
|
||||
export function resolveComboConfig(combo, settings, provider?: any) {
|
||||
const global = settings?.comboDefaults || {};
|
||||
const providerOverride = provider ? settings?.providerOverrides?.[provider] || {} : {};
|
||||
const comboConfig = combo?.config || {};
|
||||
@@ -35,7 +35,7 @@ export function recordComboRequest(
|
||||
});
|
||||
}
|
||||
|
||||
const combo = metrics.get(comboName);
|
||||
const combo: any = metrics.get(comboName);
|
||||
combo.totalRequests++;
|
||||
combo.totalLatencyMs += latencyMs;
|
||||
combo.totalFallbacks += fallbackCount;
|
||||
@@ -81,7 +81,7 @@ export function recordComboRequest(
|
||||
* @returns {Object|null}
|
||||
*/
|
||||
export function getComboMetrics(comboName) {
|
||||
const combo = metrics.get(comboName);
|
||||
const combo: any = metrics.get(comboName);
|
||||
if (!combo) return null;
|
||||
|
||||
return {
|
||||
@@ -93,7 +93,7 @@ export function getComboMetrics(comboName) {
|
||||
fallbackRate:
|
||||
combo.totalRequests > 0 ? Math.round((combo.totalFallbacks / combo.totalRequests) * 100) : 0,
|
||||
byModel: Object.fromEntries(
|
||||
Object.entries(combo.byModel).map(([model, m]) => [
|
||||
Object.entries(combo.byModel).map(([model, m]: [string, any]) => [
|
||||
model,
|
||||
{
|
||||
...m,
|
||||
@@ -110,7 +110,7 @@ export function getComboMetrics(comboName) {
|
||||
* @returns {Object} Map of comboName → metrics
|
||||
*/
|
||||
export function getAllComboMetrics() {
|
||||
const result = {};
|
||||
const result: Record<string, any> = {};
|
||||
for (const [name] of metrics) {
|
||||
result[name] = getComboMetrics(name);
|
||||
}
|
||||
@@ -51,7 +51,7 @@ export function getTokenLimit(provider, model = null) {
|
||||
* @param {object} options - { provider?, model?, maxTokens?, reserveTokens? }
|
||||
* @returns {{ body: object, compressed: boolean, stats: object }}
|
||||
*/
|
||||
export function compressContext(body, options = {}) {
|
||||
export function compressContext(body, options: any = {}) {
|
||||
if (!body || !body.messages || !Array.isArray(body.messages)) {
|
||||
return { body, compressed: false, stats: {} };
|
||||
}
|
||||
@@ -156,7 +156,7 @@ export function getProviderFallbackCount(provider) {
|
||||
}
|
||||
|
||||
// Build provider URL
|
||||
export function buildProviderUrl(provider, model, stream = true, options = {}) {
|
||||
export function buildProviderUrl(provider, model, stream = true, options: any = {}) {
|
||||
if (isOpenAICompatible(provider)) {
|
||||
const apiType = getOpenAICompatibleType(provider);
|
||||
const baseUrl = options?.baseUrl || OPENAI_COMPATIBLE_DEFAULTS.baseUrl;
|
||||
@@ -293,7 +293,7 @@ export function updateFromHeaders(provider, connectionId, headers, status, model
|
||||
// Calculate optimal minTime from RPM limit
|
||||
const minTime = Math.max(0, Math.floor(60000 / limit) - 10); // Small buffer
|
||||
|
||||
const updates = { minTime };
|
||||
const updates: Record<string, any> = { minTime };
|
||||
|
||||
// If remaining is low (< 10% of limit), set reservoir to throttle immediately
|
||||
if (!isNaN(remaining)) {
|
||||
@@ -348,7 +348,7 @@ export function getRateLimitStatus(provider, connectionId) {
|
||||
* Get all active limiters status (for dashboard overview)
|
||||
*/
|
||||
export function getAllRateLimitStatus() {
|
||||
const result = {};
|
||||
const result: Record<string, any> = {};
|
||||
for (const [key, limiter] of limiters) {
|
||||
const counts = limiter.counts();
|
||||
result[key] = {
|
||||
@@ -117,7 +117,7 @@ export function acquire(modelStr, { maxConcurrency = 3, timeoutMs = 30000 } = {}
|
||||
const idx = gate.queue.findIndex((item) => item.timer === timer);
|
||||
if (idx !== -1) gate.queue.splice(idx, 1);
|
||||
const err = new Error(`Semaphore timeout after ${timeoutMs}ms for ${modelStr}`);
|
||||
err.code = "SEMAPHORE_TIMEOUT";
|
||||
(err as any).code = "SEMAPHORE_TIMEOUT";
|
||||
reject(err);
|
||||
}, timeoutMs);
|
||||
|
||||
@@ -36,7 +36,7 @@ _cleanupTimer.unref();
|
||||
* @param {object} [options] - Extra context
|
||||
* @returns {string} Session ID (hex hash)
|
||||
*/
|
||||
export function generateSessionId(body, options = {}) {
|
||||
export function generateSessionId(body, options: any = {}) {
|
||||
const parts = [];
|
||||
|
||||
// Model contributes to fingerprint
|
||||
@@ -34,7 +34,7 @@ const MAX_PATTERNS_PER_KEY = 50;
|
||||
* @param {object} context - { tool?, modelFamily?, sessionId? }
|
||||
* @returns {string[]} Array of unique signature patterns
|
||||
*/
|
||||
export function getSignatures(context = {}) {
|
||||
export function getSignatures(context: any = {}) {
|
||||
const patterns = new Set(DEFAULT_SIGNATURES);
|
||||
|
||||
// Layer 1: Tool (e.g., "cursor", "cline", "antigravity")
|
||||
@@ -61,7 +61,7 @@ export function getSignatures(context = {}) {
|
||||
* @param {string} pattern - The signature pattern (e.g., "<antThinking>")
|
||||
* @param {object} context - { tool?, modelFamily?, sessionId? }
|
||||
*/
|
||||
export function addSignature(pattern, context = {}) {
|
||||
export function addSignature(pattern: any, context: any = {}) {
|
||||
if (!pattern || typeof pattern !== "string") return;
|
||||
|
||||
const addToLayer = (layer, key) => {
|
||||
@@ -93,7 +93,7 @@ export function addSignature(pattern, context = {}) {
|
||||
* @param {object} context - { tool?, modelFamily?, sessionId? }
|
||||
* @returns {{ found: string[], cleaned: string }} Detected tags and cleaned text
|
||||
*/
|
||||
export function detectAndLearn(text, context = {}) {
|
||||
export function detectAndLearn(text: any, context: any = {}) {
|
||||
if (!text || typeof text !== "string") return { found: [], cleaned: text };
|
||||
|
||||
const found = [];
|
||||
@@ -38,7 +38,7 @@ const CLAUDE_CONFIG = {
|
||||
/**
|
||||
* Get usage data for a provider connection
|
||||
* @param {Object} connection - Provider connection with accessToken
|
||||
* @returns {Object} Usage data with quotas
|
||||
* @returns {Promise<any>} Usage data with quotas
|
||||
*/
|
||||
export async function getUsageForProvider(connection) {
|
||||
const { provider, accessToken, providerSpecificData } = connection;
|
||||
@@ -49,7 +49,7 @@ export async function getUsageForProvider(connection) {
|
||||
case "gemini-cli":
|
||||
return await getGeminiUsage(accessToken);
|
||||
case "antigravity":
|
||||
return await getAntigravityUsage(accessToken);
|
||||
return await getAntigravityUsage(accessToken, undefined);
|
||||
case "claude":
|
||||
return await getClaudeUsage(accessToken);
|
||||
case "codex":
|
||||
@@ -311,7 +311,7 @@ async function getAntigravityUsage(accessToken, providerSpecificData) {
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const quotas = {};
|
||||
const quotas: Record<string, any> = {};
|
||||
|
||||
// Parse model quotas (inspired by vscode-antigravity-cockpit)
|
||||
if (data.models) {
|
||||
@@ -328,7 +328,7 @@ async function getAntigravityUsage(accessToken, providerSpecificData) {
|
||||
"gemini-2.5-flash",
|
||||
];
|
||||
|
||||
for (const [modelKey, info] of Object.entries(data.models)) {
|
||||
for (const [modelKey, info] of Object.entries(data.models) as [string, any][]) {
|
||||
// Skip models without quota info
|
||||
if (!info.quotaInfo) {
|
||||
continue;
|
||||
@@ -1,3 +1,5 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
/**
|
||||
* Responses API Transformer
|
||||
* Converts OpenAI Chat Completions SSE to Codex Responses API SSE format
|
||||
@@ -193,7 +193,7 @@ function mergeAllOf(obj) {
|
||||
if (!obj || typeof obj !== "object") return;
|
||||
|
||||
if (obj.allOf && Array.isArray(obj.allOf)) {
|
||||
const merged = {};
|
||||
const merged: Record<string, any> = {};
|
||||
|
||||
for (const item of obj.allOf) {
|
||||
if (item.properties) {
|
||||
@@ -234,7 +234,7 @@ export function translateResponse(targetFormat, sourceFormat, chunk, state) {
|
||||
|
||||
// Attach OpenAI intermediate results for logging
|
||||
if (openaiResults && sourceFormat !== FORMATS.OPENAI && targetFormat !== FORMATS.OPENAI) {
|
||||
results._openaiIntermediate = openaiResults;
|
||||
(results as any)._openaiIntermediate = openaiResults;
|
||||
}
|
||||
|
||||
return results;
|
||||
@@ -6,7 +6,7 @@ import { adjustMaxTokens } from "../helpers/maxTokensHelper.js";
|
||||
// Antigravity body: { project, model, userAgent, requestType, requestId, request: { contents, systemInstruction, tools, toolConfig, generationConfig, sessionId } }
|
||||
export function antigravityToOpenAIRequest(model, body, stream) {
|
||||
const req = body.request || body;
|
||||
const result = {
|
||||
const result: Record<string, any> = {
|
||||
model: model,
|
||||
messages: [],
|
||||
stream: stream,
|
||||
@@ -190,7 +190,7 @@ function convertContent(content) {
|
||||
|
||||
// Assistant with tool calls
|
||||
if (toolCalls.length > 0) {
|
||||
const msg = { role: "assistant" };
|
||||
const msg: Record<string, any> = { role: "assistant" };
|
||||
if (textParts.length > 0) {
|
||||
msg.content =
|
||||
textParts.length === 1 && textParts[0].type === "text" ? textParts[0].text : textParts;
|
||||
@@ -204,7 +204,7 @@ function convertContent(content) {
|
||||
|
||||
// Regular message
|
||||
if (textParts.length > 0 || reasoningContent) {
|
||||
const msg = { role };
|
||||
const msg: Record<string, any> = { role };
|
||||
if (textParts.length > 0) {
|
||||
msg.content =
|
||||
textParts.length === 1 && textParts[0].type === "text" ? textParts[0].text : textParts;
|
||||
@@ -9,7 +9,7 @@ import { DEFAULT_THINKING_GEMINI_SIGNATURE } from "../../config/defaultThinkingS
|
||||
* skipping the OpenAI hub intermediate step.
|
||||
*/
|
||||
export function claudeToGeminiRequest(model, body, stream) {
|
||||
const result = {
|
||||
const result: Record<string, any> = {
|
||||
model: model,
|
||||
contents: [],
|
||||
generationConfig: {},
|
||||
@@ -4,7 +4,7 @@ import { adjustMaxTokens } from "../helpers/maxTokensHelper.js";
|
||||
|
||||
// Convert Claude request to OpenAI format
|
||||
export function claudeToOpenAIRequest(model, body, stream) {
|
||||
const result = {
|
||||
const result: Record<string, any> = {
|
||||
model: model,
|
||||
messages: [],
|
||||
stream: stream,
|
||||
@@ -186,7 +186,7 @@ function convertClaudeMessage(msg) {
|
||||
|
||||
// If has tool calls, return assistant message with tool_calls
|
||||
if (toolCalls.length > 0) {
|
||||
const result = { role: "assistant" };
|
||||
const result: Record<string, any> = { role: "assistant" };
|
||||
if (parts.length > 0) {
|
||||
result.content = parts.length === 1 && parts[0].type === "text" ? parts[0].text : parts;
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { adjustMaxTokens } from "../helpers/maxTokensHelper.js";
|
||||
|
||||
// Convert Gemini request to OpenAI format
|
||||
export function geminiToOpenAIRequest(model, body, stream) {
|
||||
const result = {
|
||||
const result: Record<string, any> = {
|
||||
model: model,
|
||||
messages: [],
|
||||
stream: stream,
|
||||
@@ -116,7 +116,7 @@ function convertGeminiContent(content) {
|
||||
}
|
||||
|
||||
if (toolCalls.length > 0) {
|
||||
const result = { role: "assistant" };
|
||||
const result: Record<string, any> = { role: "assistant" };
|
||||
if (parts.length > 0) {
|
||||
result.content = parts.length === 1 ? parts[0].text : parts;
|
||||
}
|
||||
@@ -21,8 +21,8 @@ export function openaiResponsesToOpenAIRequest(model, body, stream, credentials)
|
||||
const error = new Error(
|
||||
`Unsupported Responses API feature: ${tool.type} tool type is not supported by omniroute`
|
||||
);
|
||||
error.statusCode = 400;
|
||||
error.errorType = "unsupported_feature";
|
||||
(error as any).statusCode = 400;
|
||||
(error as any).errorType = "unsupported_feature";
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -31,12 +31,12 @@ export function openaiResponsesToOpenAIRequest(model, body, stream, credentials)
|
||||
const error = new Error(
|
||||
"Unsupported Responses API feature: background mode is not supported by omniroute"
|
||||
);
|
||||
error.statusCode = 400;
|
||||
error.errorType = "unsupported_feature";
|
||||
(error as any).statusCode = 400;
|
||||
(error as any).errorType = "unsupported_feature";
|
||||
throw error;
|
||||
}
|
||||
|
||||
const result = { ...body };
|
||||
const result: Record<string, any> = { ...body };
|
||||
result.messages = [];
|
||||
|
||||
// Convert instructions to system message
|
||||
@@ -159,7 +159,7 @@ export function openaiResponsesToOpenAIRequest(model, body, stream, credentials)
|
||||
* Convert OpenAI Chat Completions to OpenAI Responses API format
|
||||
*/
|
||||
export function openaiToOpenAIResponsesRequest(model, body, stream, credentials) {
|
||||
const result = {
|
||||
const result: Record<string, any> = {
|
||||
model,
|
||||
input: [],
|
||||
stream: true,
|
||||
@@ -11,7 +11,7 @@ const CLAUDE_OAUTH_TOOL_PREFIX = "proxy_";
|
||||
export function openaiToClaudeRequest(model, body, stream) {
|
||||
// Tool name mapping for Claude OAuth (capitalizedName → originalName)
|
||||
const toolNameMap = new Map();
|
||||
const result = {
|
||||
const result: Record<string, any> = {
|
||||
model: model,
|
||||
max_tokens: adjustMaxTokens(body),
|
||||
stream: stream,
|
||||
@@ -66,7 +66,7 @@ function convertMessages(messages) {
|
||||
|
||||
// Keep tool_calls structure for assistant messages
|
||||
if (msg.role === "assistant" && msg.tool_calls && msg.tool_calls.length > 0) {
|
||||
const assistantMsg = { role: "assistant" };
|
||||
const assistantMsg: Record<string, any> = { role: "assistant" };
|
||||
if (content) {
|
||||
assistantMsg.content = content;
|
||||
}
|
||||
@@ -80,8 +80,7 @@ function convertMessages(messages) {
|
||||
|
||||
result.push(assistantMsg);
|
||||
} else if (content || pendingToolResults.length > 0) {
|
||||
const msgObj = {
|
||||
role: msg.role,
|
||||
const msgObj: Record<string, any> = { role: msg.role,
|
||||
content: content || "",
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
|
||||
// Core: Convert OpenAI request to Gemini format (base for all variants)
|
||||
function openaiToGeminiBase(model, body, stream) {
|
||||
const result = {
|
||||
const result: Record<string, any> = {
|
||||
model: model,
|
||||
contents: [],
|
||||
generationConfig: {},
|
||||
@@ -253,7 +253,7 @@ export function openaiToGeminiCLIRequest(model, body, stream) {
|
||||
function wrapInCloudCodeEnvelope(model, geminiCLI, credentials = null, isAntigravity = false) {
|
||||
const projectId = credentials?.projectId || generateProjectId();
|
||||
|
||||
const envelope = {
|
||||
const envelope: Record<string, any> = {
|
||||
project: projectId,
|
||||
model: model,
|
||||
userAgent: isAntigravity ? "antigravity" : "gemini-cli",
|
||||
@@ -272,7 +272,7 @@ function wrapInCloudCodeEnvelope(model, geminiCLI, credentials = null, isAntigra
|
||||
envelope.requestType = "agent";
|
||||
|
||||
// Inject required default system prompt for Antigravity
|
||||
const defaultPart = { text: ANTIGRAVITY_DEFAULT_SYSTEM };
|
||||
const defaultPart: Record<string, any> = { text: ANTIGRAVITY_DEFAULT_SYSTEM };
|
||||
if (envelope.request.systemInstruction?.parts) {
|
||||
envelope.request.systemInstruction.parts.unshift(defaultPart);
|
||||
} else {
|
||||
@@ -297,7 +297,7 @@ function wrapInCloudCodeEnvelope(model, geminiCLI, credentials = null, isAntigra
|
||||
function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = null) {
|
||||
const projectId = credentials?.projectId || generateProjectId();
|
||||
|
||||
const envelope = {
|
||||
const envelope: Record<string, any> = {
|
||||
project: projectId,
|
||||
model: model,
|
||||
userAgent: "antigravity",
|
||||
@@ -22,7 +22,7 @@ function convertMessages(messages, tools, model) {
|
||||
const flushPending = () => {
|
||||
if (currentRole === "user") {
|
||||
const content = pendingUserContent.join("\n\n").trim() || "continue";
|
||||
const userMsg = {
|
||||
const userMsg: Record<string, any> = {
|
||||
userInputMessage: {
|
||||
content: content,
|
||||
modelId: "",
|
||||
@@ -255,7 +255,7 @@ export function buildKiroPayload(model, body, stream, credentials) {
|
||||
const timestamp = new Date().toISOString();
|
||||
finalContent = `[Context: Current time is ${timestamp}]\n\n${finalContent}`;
|
||||
|
||||
const payload = {
|
||||
const payload: Record<string, any> = {
|
||||
conversationState: {
|
||||
chatTriggerType: "MANUAL",
|
||||
conversationId: uuidv4(),
|
||||
@@ -133,7 +133,7 @@ export function claudeToOpenAIResponse(chunk, state) {
|
||||
|
||||
if (chunk.delta?.stop_reason) {
|
||||
state.finishReason = convertStopReason(chunk.delta.stop_reason);
|
||||
const finalChunk = {
|
||||
const finalChunk: Record<string, any> = {
|
||||
id: `chatcmpl-${state.messageId}`,
|
||||
object: "chat.completion.chunk",
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
@@ -226,7 +226,7 @@ export function geminiToOpenAIResponse(chunk, state) {
|
||||
finishReason = "tool_calls";
|
||||
}
|
||||
|
||||
const finalChunk = {
|
||||
const finalChunk: Record<string, any> = {
|
||||
id: `chatcmpl-${state.messageId}`,
|
||||
object: "chat.completion.chunk",
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
@@ -155,7 +155,7 @@ export function convertKiroToOpenAI(chunk, state) {
|
||||
if (eventType === "messageStopEvent" || eventType === "done" || data.messageStopEvent) {
|
||||
state.finishReason = "stop"; // Mark for usage injection in stream.js
|
||||
|
||||
const openaiChunk = {
|
||||
const openaiChunk: Record<string, any> = {
|
||||
id: state.responseId,
|
||||
object: "chat.completion.chunk",
|
||||
created: state.created,
|
||||
@@ -519,7 +519,7 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
|
||||
state.finishReasonSent = true;
|
||||
state.finishReason = "stop"; // Mark for usage injection in stream.js
|
||||
|
||||
const finalChunk = {
|
||||
const finalChunk: Record<string, any> = {
|
||||
id: state.chatId,
|
||||
object: "chat.completion.chunk",
|
||||
created: state.created,
|
||||
@@ -81,7 +81,7 @@ export function openaiToAntigravityResponse(chunk, state) {
|
||||
}
|
||||
|
||||
// Build candidate
|
||||
const candidate = { content: { role: "model", parts } };
|
||||
const candidate: Record<string, any> = { content: { role: "model", parts } };
|
||||
|
||||
// Finish reason mapping
|
||||
if (finishReason) {
|
||||
@@ -95,7 +95,7 @@ export function openaiToAntigravityResponse(chunk, state) {
|
||||
}
|
||||
|
||||
// Build response
|
||||
const response = {
|
||||
const response: Record<string, any> = {
|
||||
candidates: [candidate],
|
||||
modelVersion: state._modelVersion,
|
||||
responseId: state._responseId,
|
||||
25
open-sse/tsconfig.json
Normal file
25
open-sse/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"strict": false,
|
||||
"jsx": "react-jsx",
|
||||
"lib": ["dom", "esnext"],
|
||||
"baseUrl": "..",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@omniroute/open-sse": ["./open-sse"],
|
||||
"@omniroute/open-sse/*": ["./open-sse/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.js"
|
||||
]
|
||||
}
|
||||
@@ -131,8 +131,8 @@ export async function parseUpstreamError(response, provider = null) {
|
||||
* @param {number|null} retryAfterMs - Optional retry-after time in milliseconds
|
||||
* @returns {{ success: false, status: number, error: string, response: Response, retryAfterMs?: number }}
|
||||
*/
|
||||
export function createErrorResult(statusCode, message, retryAfterMs = null) {
|
||||
const result = {
|
||||
export function createErrorResult(statusCode: number, message: string, retryAfterMs: number | null = null) {
|
||||
const result: Record<string, any> = {
|
||||
success: false,
|
||||
status: statusCode,
|
||||
error: message,
|
||||
@@ -155,7 +155,7 @@ export function createErrorResult(statusCode, message, retryAfterMs = null) {
|
||||
* @param {string} retryAfterHuman - Human-readable retry info e.g. "reset after 30s"
|
||||
* @returns {Response}
|
||||
*/
|
||||
export function unavailableResponse(statusCode, message, retryAfter, retryAfterHuman) {
|
||||
export function unavailableResponse(statusCode, message, retryAfter?: any, retryAfterHuman?: any) {
|
||||
const retryAfterSec = Math.max(
|
||||
Math.ceil((new Date(retryAfter).getTime() - Date.now()) / 1000),
|
||||
1
|
||||
@@ -96,7 +96,7 @@ export function logger(tag) {
|
||||
const consoleFn = getConsoleFn(level);
|
||||
|
||||
if (jsonFormat) {
|
||||
const entry = {
|
||||
const entry: Record<string, any> = {
|
||||
ts: new Date().toISOString(),
|
||||
level,
|
||||
tag,
|
||||
@@ -132,7 +132,7 @@ export function createLogger(requestId = null) {
|
||||
const consoleFn = getConsoleFn(level);
|
||||
|
||||
if (jsonFormat) {
|
||||
const entry = {
|
||||
const entry: Record<string, any> = {
|
||||
ts: new Date().toISOString(),
|
||||
level,
|
||||
tag,
|
||||
@@ -34,6 +34,7 @@ async function getConfig() {
|
||||
* @param {string} providerId - Provider ID (e.g., "openai", "anthropic")
|
||||
* @returns {string|null} Proxy URL or null if no proxy configured
|
||||
*/
|
||||
/** @returns {Promise<any>} */
|
||||
export async function resolveProxy(providerId) {
|
||||
const config = await getConfig();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Transform OpenAI SSE stream to Ollama JSON lines format
|
||||
export function transformToOllama(response, model) {
|
||||
let buffer = "";
|
||||
let pendingToolCalls = {};
|
||||
let pendingToolCalls: Record<number, any> = {};
|
||||
|
||||
const transform = new TransformStream({
|
||||
transform(chunk, controller) {
|
||||
@@ -51,7 +51,7 @@ export function transformToOllama(response, model) {
|
||||
if (finishReason === "tool_calls" || finishReason === "stop") {
|
||||
const toolCallsArr = Object.values(pendingToolCalls);
|
||||
if (toolCallsArr.length > 0) {
|
||||
const formattedCalls = toolCallsArr.map((tc) => ({
|
||||
const formattedCalls = toolCallsArr.map((tc: any) => ({
|
||||
function: {
|
||||
name: tc.function.name,
|
||||
arguments: JSON.parse(tc.function.arguments || "{}"),
|
||||
@@ -21,7 +21,7 @@ const DEFAULT_INTERVAL_MS = 2000;
|
||||
* @param {AbortSignal} [options.signal] - Abort signal for cancellation
|
||||
* @returns {TransformStream}
|
||||
*/
|
||||
export function createProgressTransform({ intervalMs = DEFAULT_INTERVAL_MS, signal } = {}) {
|
||||
export function createProgressTransform({ intervalMs = DEFAULT_INTERVAL_MS, signal }: { intervalMs?: number; signal?: AbortSignal } = {}) {
|
||||
let tokenCount = 0;
|
||||
let startTime = Date.now();
|
||||
let intervalId;
|
||||
@@ -112,14 +112,14 @@ export function createProxyDispatcher(proxyUrl) {
|
||||
|
||||
const parsed = new URL(normalizedUrl);
|
||||
if (parsed.protocol === "socks5:") {
|
||||
const socksOptions = {
|
||||
const socksOptions: Record<string, any> = {
|
||||
type: 5,
|
||||
host: parsed.hostname,
|
||||
port: Number(normalizePort(parsed.port, parsed.protocol)),
|
||||
};
|
||||
if (parsed.username) socksOptions.userId = decodeURIComponent(parsed.username);
|
||||
if (parsed.password) socksOptions.password = decodeURIComponent(parsed.password);
|
||||
dispatcher = socksDispatcher(socksOptions);
|
||||
dispatcher = socksDispatcher(socksOptions as any);
|
||||
} else {
|
||||
dispatcher = new ProxyAgent(normalizedUrl);
|
||||
}
|
||||
@@ -126,7 +126,7 @@ export async function runWithProxyContext(proxyConfig, fn) {
|
||||
});
|
||||
}
|
||||
|
||||
async function patchedFetch(input, options = {}) {
|
||||
async function patchedFetch(input: any, options: any = {}) {
|
||||
if (options?.dispatcher) {
|
||||
return originalFetch(input, options);
|
||||
}
|
||||
@@ -146,7 +146,7 @@ async function patchedFetch(input, options = {}) {
|
||||
// TLS fingerprint spoofing for direct connections (no proxy configured)
|
||||
if (isTlsFingerprintEnabled() && tlsClient.available) {
|
||||
try {
|
||||
const store = tlsFingerprintContext.getStore();
|
||||
const store: any = tlsFingerprintContext.getStore();
|
||||
if (store) store.used = true;
|
||||
return await tlsClient.fetch(targetUrl, options);
|
||||
} catch (error) {
|
||||
@@ -154,7 +154,7 @@ async function patchedFetch(input, options = {}) {
|
||||
console.warn(
|
||||
`[ProxyFetch] TLS fingerprint failed, falling back to native fetch: ${message}`
|
||||
);
|
||||
const store = tlsFingerprintContext.getStore();
|
||||
const store: any = tlsFingerprintContext.getStore();
|
||||
if (store) store.used = false;
|
||||
}
|
||||
}
|
||||
@@ -44,13 +44,15 @@ const STREAM_MODE = {
|
||||
* @param {object} options.body - Request body (for input token estimation)
|
||||
* @param {function} options.onComplete - Callback when stream finishes: ({ status, usage }) => void
|
||||
*/
|
||||
export function createSSEStream(options = {}) {
|
||||
/** @param {any} options */
|
||||
export function createSSEStream(options: any = {}) {
|
||||
const {
|
||||
mode = STREAM_MODE.TRANSLATE,
|
||||
targetFormat,
|
||||
sourceFormat,
|
||||
provider = null,
|
||||
reqLogger = null,
|
||||
/** @type {any} */
|
||||
toolNameMap = null,
|
||||
model = null,
|
||||
connectionId = null,
|
||||
@@ -231,8 +233,8 @@ export function createSSEStream(options = {}) {
|
||||
const translated = translateResponse(targetFormat, sourceFormat, parsed, state);
|
||||
|
||||
// Log OpenAI intermediate chunks (if available)
|
||||
if (translated?._openaiIntermediate) {
|
||||
for (const item of translated._openaiIntermediate) {
|
||||
if ((translated as any)?._openaiIntermediate) {
|
||||
for (const item of (translated as any)._openaiIntermediate) {
|
||||
const openaiOutput = formatSSE(item, FORMATS.OPENAI);
|
||||
reqLogger?.appendOpenAIChunk?.(openaiOutput);
|
||||
}
|
||||
@@ -327,8 +329,8 @@ export function createSSEStream(options = {}) {
|
||||
const translated = translateResponse(targetFormat, sourceFormat, parsed, state);
|
||||
|
||||
// Log OpenAI intermediate chunks
|
||||
if (translated?._openaiIntermediate) {
|
||||
for (const item of translated._openaiIntermediate) {
|
||||
if ((translated as any)?._openaiIntermediate) {
|
||||
for (const item of (translated as any)._openaiIntermediate) {
|
||||
const openaiOutput = formatSSE(item, FORMATS.OPENAI);
|
||||
reqLogger?.appendOpenAIChunk?.(openaiOutput);
|
||||
}
|
||||
@@ -348,8 +350,8 @@ export function createSSEStream(options = {}) {
|
||||
const flushed = translateResponse(targetFormat, sourceFormat, null, state);
|
||||
|
||||
// Log OpenAI intermediate chunks for flushed events
|
||||
if (flushed?._openaiIntermediate) {
|
||||
for (const item of flushed._openaiIntermediate) {
|
||||
if ((flushed as any)?._openaiIntermediate) {
|
||||
for (const item of (flushed as any)._openaiIntermediate) {
|
||||
const openaiOutput = formatSSE(item, FORMATS.OPENAI);
|
||||
reqLogger?.appendOpenAIChunk?.(openaiOutput);
|
||||
}
|
||||
@@ -18,7 +18,8 @@ function getTimeString() {
|
||||
* @param {string} options.provider - Provider name
|
||||
* @param {string} options.model - Model name
|
||||
*/
|
||||
export function createStreamController({ onDisconnect, log, provider, model } = {}) {
|
||||
/** @param {any} options */
|
||||
export function createStreamController({ onDisconnect, log, provider, model }: any = {}) {
|
||||
const abortController = new AbortController();
|
||||
const startTime = Date.now();
|
||||
let disconnected = false;
|
||||
@@ -2,7 +2,7 @@ import { createRequire } from "module";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
let createSession;
|
||||
let createSession: any;
|
||||
try {
|
||||
({ createSession } = require("wreq-js"));
|
||||
} catch {
|
||||
@@ -13,7 +13,7 @@ try {
|
||||
* Get proxy URL from environment variables.
|
||||
* Priority: HTTPS_PROXY > HTTP_PROXY > ALL_PROXY
|
||||
*/
|
||||
function getProxyFromEnv() {
|
||||
function getProxyFromEnv(): string | undefined {
|
||||
return (
|
||||
process.env.HTTPS_PROXY ||
|
||||
process.env.https_proxy ||
|
||||
@@ -25,6 +25,14 @@ function getProxyFromEnv() {
|
||||
);
|
||||
}
|
||||
|
||||
interface FetchOptions {
|
||||
method?: string;
|
||||
headers?: Record<string, string>;
|
||||
body?: any;
|
||||
redirect?: string;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
/**
|
||||
* TLS Client — Chrome 124 TLS fingerprint spoofing via wreq-js
|
||||
* Singleton instance used to disguise Node.js TLS handshake as Chrome browser.
|
||||
@@ -33,8 +41,10 @@ function getProxyFromEnv() {
|
||||
* Proxy URL is read from environment variables (HTTPS_PROXY, HTTP_PROXY, ALL_PROXY).
|
||||
*/
|
||||
class TlsClient {
|
||||
session: any = null;
|
||||
available: boolean;
|
||||
|
||||
constructor() {
|
||||
this.session = null;
|
||||
this.available = !!createSession;
|
||||
}
|
||||
|
||||
@@ -43,7 +53,7 @@ class TlsClient {
|
||||
if (this.session) return this.session;
|
||||
|
||||
const proxy = getProxyFromEnv();
|
||||
const sessionOpts = {
|
||||
const sessionOpts: Record<string, any> = {
|
||||
browser: "chrome_124",
|
||||
os: "macos",
|
||||
};
|
||||
@@ -61,13 +71,13 @@ class TlsClient {
|
||||
* Fetch with Chrome 124 TLS fingerprint.
|
||||
* wreq-js Response is already fetch-compatible (headers, text(), json(), clone(), body).
|
||||
*/
|
||||
async fetch(url, options = {}) {
|
||||
async fetch(url: string, options: FetchOptions = {}) {
|
||||
const session = await this.getSession();
|
||||
if (!session) throw new Error("wreq-js not available");
|
||||
|
||||
const method = (options.method || "GET").toUpperCase();
|
||||
|
||||
const wreqOptions = {
|
||||
const wreqOptions: Record<string, any> = {
|
||||
method,
|
||||
headers: options.headers,
|
||||
body: options.body,
|
||||
@@ -92,3 +102,4 @@ class TlsClient {
|
||||
}
|
||||
|
||||
export default new TlsClient();
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "omniroute",
|
||||
"version": "0.8.0",
|
||||
"version": "0.8.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "omniroute",
|
||||
"version": "0.8.0",
|
||||
"version": "0.8.5",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"open-sse"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "omniroute",
|
||||
"version": "0.8.0",
|
||||
"version": "0.8.5",
|
||||
"description": "Smart AI Router with auto fallback — route to FREE & cheap models, zero downtime. Works with Cursor, Cline, Claude Desktop, Codex, and any OpenAI-compatible tool.",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
|
||||
Reference in New Issue
Block a user