mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-02 21:32:10 +03:00
refactor(open-sse): phase 7 — zero @ts-ignore, zero TSC errors
- Remove ALL 231 @ts-ignore annotations (231 → 0) - Fix 237 TypeScript errors with proper typings: - Type 30+ variable declarations as Record<string, any> - Add optional params: model?, provider?, retryAfter?, retryAfterHuman? - Replace @ts-ignore with 'as any' casts for custom Error/Array properties - Add EdgeRuntime global declaration for edge runtime compat - Import fs/path in responsesTransformer.ts - Fix function argument mismatches (resolveComboConfig, unavailableResponse) - Build: tsc --noEmit passes with 0 errors
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
declare var EdgeRuntime: any;
|
||||
/**
|
||||
* CursorExecutor — Handles communication with the Cursor IDE API.
|
||||
*
|
||||
@@ -36,7 +37,6 @@ import zlib from "zlib";
|
||||
// Detect cloud environment
|
||||
const isCloudEnv = () => {
|
||||
if (typeof caches !== "undefined" && typeof caches === "object") return true;
|
||||
// @ts-ignore — EdgeRuntime is a global in edge runtimes
|
||||
if (typeof EdgeRuntime !== "undefined") return true;
|
||||
return false;
|
||||
};
|
||||
@@ -227,8 +227,7 @@ export class CursorExecutor extends BaseExecutor {
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
// @ts-ignore — Headers.entries() exists at runtime
|
||||
headers: Object.fromEntries(response.headers.entries()),
|
||||
headers: Object.fromEntries((response.headers as any).entries()),
|
||||
body: Buffer.from(await response.arrayBuffer()),
|
||||
};
|
||||
}
|
||||
@@ -292,25 +291,21 @@ 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);
|
||||
|
||||
// @ts-ignore
|
||||
if (response.status !== 200) {
|
||||
// @ts-ignore
|
||||
const errorText = response.body?.toString() || "Unknown error";
|
||||
const errorResponse = new Response(
|
||||
JSON.stringify({
|
||||
error: {
|
||||
// @ts-ignore
|
||||
message: `[${response.status}]: ${errorText}`,
|
||||
type: "invalid_request_error",
|
||||
code: "",
|
||||
},
|
||||
}),
|
||||
{
|
||||
// @ts-ignore
|
||||
status: response.status,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
@@ -320,9 +315,7 @@ export class CursorExecutor extends BaseExecutor {
|
||||
|
||||
const transformedResponse =
|
||||
stream !== false
|
||||
// @ts-ignore
|
||||
? this.transformProtobufToSSE(response.body, model, body)
|
||||
// @ts-ignore
|
||||
: this.transformProtobufToJSON(response.body, model, body);
|
||||
|
||||
return { response: transformedResponse, url, headers, transformedBody: body };
|
||||
|
||||
@@ -125,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,
|
||||
@@ -144,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,
|
||||
@@ -256,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,
|
||||
@@ -329,7 +329,7 @@ export class KiroExecutor extends BaseExecutor {
|
||||
};
|
||||
}
|
||||
|
||||
const finishChunk = {
|
||||
const finishChunk: Record<string, any> = {
|
||||
id: responseId,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
@@ -345,7 +345,6 @@ export class KiroExecutor extends BaseExecutor {
|
||||
|
||||
// Include usage in final chunk if available
|
||||
if (state.usage) {
|
||||
// @ts-ignore
|
||||
finishChunk.usage = state.usage;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,15 +52,13 @@ export async function handleEmbedding({ body, credentials, log }) {
|
||||
}
|
||||
|
||||
// Build upstream request
|
||||
const upstreamBody = {
|
||||
const upstreamBody: Record<string, any> = {
|
||||
model: model,
|
||||
input: body.input,
|
||||
};
|
||||
|
||||
// Pass optional parameters
|
||||
// @ts-ignore
|
||||
if (body.dimensions !== undefined) upstreamBody.dimensions = body.dimensions;
|
||||
// @ts-ignore
|
||||
if (body.encoding_format !== undefined) upstreamBody.encoding_format = body.encoding_format;
|
||||
|
||||
// Build headers
|
||||
|
||||
@@ -232,21 +232,16 @@ async function handleOpenAIImageGeneration({
|
||||
};
|
||||
|
||||
// Build upstream request (OpenAI-compatible format)
|
||||
const upstreamBody = {
|
||||
const upstreamBody: Record<string, any> = {
|
||||
model: model,
|
||||
prompt: body.prompt,
|
||||
};
|
||||
|
||||
// Pass optional parameters
|
||||
// @ts-ignore
|
||||
if (body.n !== undefined) upstreamBody.n = body.n;
|
||||
// @ts-ignore
|
||||
if (body.size !== undefined) upstreamBody.size = body.size;
|
||||
// @ts-ignore
|
||||
if (body.quality !== undefined) upstreamBody.quality = body.quality;
|
||||
// @ts-ignore
|
||||
if (body.response_format !== undefined) upstreamBody.response_format = body.response_format;
|
||||
// @ts-ignore
|
||||
if (body.style !== undefined) upstreamBody.style = body.style;
|
||||
|
||||
// Build headers
|
||||
|
||||
@@ -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,
|
||||
@@ -91,7 +91,6 @@ export function translateNonStreamingResponse(responseBody, targetFormat, source
|
||||
if (usage && typeof usage === "object") {
|
||||
const inputTokens = usage.input_tokens || 0;
|
||||
const outputTokens = usage.output_tokens || 0;
|
||||
// @ts-ignore
|
||||
result.usage = {
|
||||
prompt_tokens: inputTokens,
|
||||
completion_tokens: outputTokens,
|
||||
@@ -99,20 +98,16 @@ export function translateNonStreamingResponse(responseBody, targetFormat, source
|
||||
};
|
||||
|
||||
if (usage.reasoning_tokens > 0) {
|
||||
// @ts-ignore
|
||||
result.usage.completion_tokens_details = {
|
||||
reasoning_tokens: usage.reasoning_tokens,
|
||||
};
|
||||
}
|
||||
if (usage.cache_read_input_tokens > 0 || usage.cache_creation_input_tokens > 0) {
|
||||
// @ts-ignore
|
||||
result.usage.prompt_tokens_details = {};
|
||||
if (usage.cache_read_input_tokens > 0) {
|
||||
// @ts-ignore
|
||||
result.usage.prompt_tokens_details.cached_tokens = usage.cache_read_input_tokens;
|
||||
}
|
||||
if (usage.cache_creation_input_tokens > 0) {
|
||||
// @ts-ignore
|
||||
result.usage.prompt_tokens_details.cache_creation_tokens =
|
||||
usage.cache_creation_input_tokens;
|
||||
}
|
||||
@@ -188,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),
|
||||
@@ -204,14 +199,12 @@ export function translateNonStreamingResponse(responseBody, targetFormat, source
|
||||
|
||||
// Add usage if available (match streaming translator: add thoughtsTokenCount to prompt_tokens)
|
||||
if (usage) {
|
||||
// @ts-ignore
|
||||
result.usage = {
|
||||
prompt_tokens: (usage.promptTokenCount || 0) + (usage.thoughtsTokenCount || 0),
|
||||
completion_tokens: usage.candidatesTokenCount || 0,
|
||||
total_tokens: usage.totalTokenCount || 0,
|
||||
};
|
||||
if (usage.thoughtsTokenCount > 0) {
|
||||
// @ts-ignore
|
||||
result.usage.completion_tokens_details = {
|
||||
reasoning_tokens: usage.thoughtsTokenCount,
|
||||
};
|
||||
@@ -266,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),
|
||||
@@ -281,7 +274,6 @@ export function translateNonStreamingResponse(responseBody, targetFormat, source
|
||||
};
|
||||
|
||||
if (responseBody.usage) {
|
||||
// @ts-ignore
|
||||
result.usage = {
|
||||
prompt_tokens: responseBody.usage.input_tokens || 0,
|
||||
completion_tokens: responseBody.usage.output_tokens || 0,
|
||||
|
||||
@@ -37,7 +37,6 @@ export async function handleResponsesCore({
|
||||
convertedBody.stream = true;
|
||||
|
||||
// Call chat core handler
|
||||
// @ts-ignore
|
||||
const result = await handleChatCore({
|
||||
body: convertedBody,
|
||||
modelInfo,
|
||||
@@ -46,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;
|
||||
|
||||
@@ -51,7 +51,7 @@ export function parseSSEToOpenAIResponse(rawSSE, fallbackModel) {
|
||||
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),
|
||||
@@ -66,7 +66,6 @@ export function parseSSEToOpenAIResponse(rawSSE, fallbackModel) {
|
||||
};
|
||||
|
||||
if (usage) {
|
||||
// @ts-ignore
|
||||
result.usage = usage;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -27,9 +27,7 @@ export function selectAccountP2C(accounts, model = null) {
|
||||
const a = accounts[i];
|
||||
const b = accounts[j];
|
||||
|
||||
// @ts-ignore — getAccountHealth accepts optional model
|
||||
const healthA = getAccountHealth(a, model);
|
||||
// @ts-ignore
|
||||
const healthB = getAccountHealth(b, model);
|
||||
|
||||
return healthA >= healthB ? a : b;
|
||||
@@ -45,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 };
|
||||
}
|
||||
@@ -61,7 +59,6 @@ export function selectAccount(accounts, strategy = "fill-first", state = {}, mod
|
||||
};
|
||||
|
||||
case "round-robin": {
|
||||
// @ts-ignore
|
||||
const lastIndex = state.lastIndex ?? -1;
|
||||
const nextIndex = (lastIndex + 1) % accounts.length;
|
||||
return {
|
||||
|
||||
@@ -36,7 +36,6 @@ function normalizeModelEntry(entry) {
|
||||
* @returns {Object|null} Full combo object or null if not a combo
|
||||
*/
|
||||
export function getComboFromData(modelStr, combosData) {
|
||||
// @ts-ignore - combosData can be object with .combos property
|
||||
const combos = Array.isArray(combosData) ? combosData : combosData?.combos || [];
|
||||
const combo = combos.find((c) => c.name === modelStr);
|
||||
if (combo && combo.models && combo.models.length > 0) {
|
||||
@@ -71,7 +70,6 @@ export function validateComboDAG(comboName, allCombos, visited = new Set(), dept
|
||||
}
|
||||
visited.add(comboName);
|
||||
|
||||
// @ts-ignore - allCombos can be object with .combos property
|
||||
const combos = Array.isArray(allCombos) ? allCombos : allCombos?.combos || [];
|
||||
const combo = combos.find((c) => c.name === comboName);
|
||||
if (!combo || !combo.models) return;
|
||||
@@ -251,7 +249,6 @@ export async function handleComboChat({
|
||||
|
||||
// Use config cascade if settings provided
|
||||
const config = settings
|
||||
// @ts-ignore
|
||||
? resolveComboConfig(combo, settings)
|
||||
: { ...getDefaultComboConfig(), ...(combo.config || {}) };
|
||||
const maxRetries = config.maxRetries ?? 1;
|
||||
@@ -453,7 +450,6 @@ export async function handleComboChat({
|
||||
|
||||
if (allBreakersOpen) {
|
||||
log.warn("COMBO", "All models have circuit breaker OPEN — aborting");
|
||||
// @ts-ignore - partial args are valid for this error case
|
||||
return unavailableResponse(
|
||||
503,
|
||||
"All providers temporarily unavailable (circuit breakers open)"
|
||||
@@ -498,7 +494,6 @@ async function handleRoundRobinCombo({
|
||||
}) {
|
||||
const models = combo.models || [];
|
||||
const config = settings
|
||||
// @ts-ignore
|
||||
? resolveComboConfig(combo, settings)
|
||||
: { ...getDefaultComboConfig(), ...(combo.config || {}) };
|
||||
const concurrency = config.concurrencyPerModel ?? 3;
|
||||
@@ -516,7 +511,6 @@ async function handleRoundRobinCombo({
|
||||
|
||||
const modelCount = orderedModels.length;
|
||||
if (modelCount === 0) {
|
||||
// @ts-ignore - partial args are valid for this error case
|
||||
return unavailableResponse(406, "Round-robin combo has no models");
|
||||
}
|
||||
|
||||
@@ -706,7 +700,6 @@ async function handleRoundRobinCombo({
|
||||
|
||||
if (allBreakersOpen) {
|
||||
log.warn("COMBO-RR", "All models have circuit breaker OPEN — aborting");
|
||||
// @ts-ignore - partial args are valid for this error case
|
||||
return unavailableResponse(
|
||||
503,
|
||||
"All providers temporarily unavailable (circuit breakers open)"
|
||||
|
||||
@@ -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,14 +93,11 @@ 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,
|
||||
{
|
||||
// @ts-ignore
|
||||
...m,
|
||||
// @ts-ignore
|
||||
avgLatencyMs: m.requests > 0 ? Math.round(m.totalLatencyMs / m.requests) : 0,
|
||||
// @ts-ignore
|
||||
successRate: m.requests > 0 ? Math.round((m.successes / m.requests) * 100) : 0,
|
||||
},
|
||||
])
|
||||
@@ -113,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,16 +51,13 @@ 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: {} };
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const provider = options.provider || "default";
|
||||
// @ts-ignore
|
||||
const maxTokens = options.maxTokens || getTokenLimit(provider, body.model || options.model);
|
||||
// @ts-ignore
|
||||
const reserveTokens = options.reserveTokens || 16000; // Reserve for response
|
||||
const targetTokens = maxTokens - reserveTokens;
|
||||
|
||||
|
||||
@@ -156,15 +156,13 @@ 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);
|
||||
// @ts-ignore
|
||||
const baseUrl = options?.baseUrl || OPENAI_COMPATIBLE_DEFAULTS.baseUrl;
|
||||
return buildOpenAICompatibleUrl(baseUrl, apiType);
|
||||
}
|
||||
if (isAnthropicCompatible(provider)) {
|
||||
// @ts-ignore
|
||||
const baseUrl = options?.baseUrl || ANTHROPIC_COMPATIBLE_DEFAULTS.baseUrl;
|
||||
return buildAnthropicCompatibleUrl(baseUrl);
|
||||
}
|
||||
@@ -176,7 +174,6 @@ export function buildProviderUrl(provider, model, stream = true, options = {}) {
|
||||
if (entry) {
|
||||
// Multi-URL providers (e.g. antigravity)
|
||||
if (entry.baseUrls) {
|
||||
// @ts-ignore
|
||||
const urlIndex = options?.baseUrlIndex || 0;
|
||||
const baseUrl = entry.baseUrls[urlIndex] || entry.baseUrls[0];
|
||||
if (entry.urlBuilder) return entry.urlBuilder(baseUrl, model, stream);
|
||||
|
||||
@@ -293,16 +293,13 @@ 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)) {
|
||||
if (remaining < limit * 0.1) {
|
||||
// @ts-ignore
|
||||
updates.reservoir = remaining;
|
||||
// @ts-ignore
|
||||
updates.reservoirRefreshAmount = limit;
|
||||
// @ts-ignore
|
||||
updates.reservoirRefreshInterval = resetMs;
|
||||
console.log(
|
||||
`⚠️ [RATE-LIMIT] ${provider}:${connectionId.slice(0, 8)} — ${remaining}/${limit} remaining, throttling`
|
||||
@@ -310,11 +307,8 @@ export function updateFromHeaders(provider, connectionId, headers, status, model
|
||||
} else if (remaining > limit * 0.5) {
|
||||
// Plenty of headroom — relax the limiter
|
||||
updates.minTime = 0;
|
||||
// @ts-ignore
|
||||
updates.reservoir = null;
|
||||
// @ts-ignore
|
||||
updates.reservoirRefreshAmount = null;
|
||||
// @ts-ignore
|
||||
updates.reservoirRefreshInterval = null;
|
||||
}
|
||||
}
|
||||
@@ -354,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,8 +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}`);
|
||||
// @ts-ignore — custom error code property
|
||||
err.code = "SEMAPHORE_TIMEOUT";
|
||||
(err as any).code = "SEMAPHORE_TIMEOUT";
|
||||
reject(err);
|
||||
}, timeoutMs);
|
||||
|
||||
|
||||
@@ -36,14 +36,13 @@ _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
|
||||
if (body.model) parts.push(`model:${body.model}`);
|
||||
|
||||
// Provider binding
|
||||
// @ts-ignore
|
||||
if (options.provider) parts.push(`provider:${options.provider}`);
|
||||
|
||||
// System prompt hash (first 32 chars of system content)
|
||||
@@ -69,7 +68,6 @@ export function generateSessionId(body, options = {}) {
|
||||
}
|
||||
|
||||
// Connection ID for sticky routing
|
||||
// @ts-ignore
|
||||
if (options.connectionId) parts.push(`conn:${options.connectionId}`);
|
||||
|
||||
if (parts.length === 0) return null;
|
||||
|
||||
@@ -34,27 +34,21 @@ 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")
|
||||
// @ts-ignore
|
||||
if (context.tool && layers.tool.has(context.tool)) {
|
||||
// @ts-ignore
|
||||
for (const p of layers.tool.get(context.tool)) patterns.add(p);
|
||||
}
|
||||
|
||||
// Layer 2: Model family (e.g., "claude-sonnet", "claude-opus")
|
||||
// @ts-ignore
|
||||
if (context.modelFamily && layers.family.has(context.modelFamily)) {
|
||||
// @ts-ignore
|
||||
for (const p of layers.family.get(context.modelFamily)) patterns.add(p);
|
||||
}
|
||||
|
||||
// Layer 3: Session-specific
|
||||
// @ts-ignore
|
||||
if (context.sessionId && layers.session.has(context.sessionId)) {
|
||||
// @ts-ignore
|
||||
for (const p of layers.session.get(context.sessionId)) patterns.add(p);
|
||||
}
|
||||
|
||||
@@ -67,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) => {
|
||||
@@ -86,11 +80,8 @@ export function addSignature(pattern, context = {}) {
|
||||
}
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
addToLayer(layers.tool, context.tool);
|
||||
// @ts-ignore
|
||||
addToLayer(layers.family, context.modelFamily);
|
||||
// @ts-ignore
|
||||
addToLayer(layers.session, context.sessionId);
|
||||
}
|
||||
|
||||
@@ -102,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 = [];
|
||||
|
||||
@@ -49,8 +49,7 @@ export async function getUsageForProvider(connection) {
|
||||
case "gemini-cli":
|
||||
return await getGeminiUsage(accessToken);
|
||||
case "antigravity":
|
||||
// @ts-ignore
|
||||
return await getAntigravityUsage(accessToken);
|
||||
return await getAntigravityUsage(accessToken, undefined);
|
||||
case "claude":
|
||||
return await getClaudeUsage(accessToken);
|
||||
case "codex":
|
||||
@@ -312,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) {
|
||||
@@ -329,20 +328,17 @@ 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
|
||||
// @ts-ignore
|
||||
if (!info.quotaInfo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip internal models and non-important models
|
||||
// @ts-ignore
|
||||
if (info.isInternal || !importantModels.includes(modelKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const remainingFraction = info.quotaInfo.remainingFraction || 0;
|
||||
const remainingPercentage = remainingFraction * 100;
|
||||
|
||||
@@ -358,11 +354,9 @@ async function getAntigravityUsage(accessToken, providerSpecificData) {
|
||||
quotas[modelKey] = {
|
||||
used,
|
||||
total,
|
||||
// @ts-ignore
|
||||
resetAt: parseResetTime(info.quotaInfo.resetTime),
|
||||
remainingPercentage,
|
||||
unlimited: false,
|
||||
// @ts-ignore
|
||||
displayName: info.displayName || modelKey,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -31,7 +33,6 @@ async function getPath() {
|
||||
// Create log directory for responses (Node.js only)
|
||||
export function createResponsesLogger(model, logsDir = null) {
|
||||
// Skip logging in worker environment (no fs)
|
||||
// @ts-ignore - fs is available as a Node.js global or dynamically imported
|
||||
if (typeof fs.mkdirSync !== "function") {
|
||||
return null;
|
||||
}
|
||||
@@ -39,11 +40,9 @@ export function createResponsesLogger(model, logsDir = null) {
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "").slice(0, 15);
|
||||
const uniqueId = Math.random().toString(36).slice(2, 8);
|
||||
const baseDir = logsDir || (typeof process !== "undefined" ? process.cwd() : ".");
|
||||
// @ts-ignore - path is available as a Node.js global
|
||||
const logDir = path.join(baseDir, "logs", `responses_${model}_${timestamp}_${uniqueId}`);
|
||||
|
||||
try {
|
||||
// @ts-ignore
|
||||
fs.mkdirSync(logDir, { recursive: true });
|
||||
} catch {
|
||||
return null;
|
||||
@@ -61,9 +60,7 @@ export function createResponsesLogger(model, logsDir = null) {
|
||||
},
|
||||
flush: () => {
|
||||
try {
|
||||
// @ts-ignore
|
||||
fs.writeFileSync(path.join(logDir, "1_input_stream.txt"), inputEvents.join("\n"));
|
||||
// @ts-ignore
|
||||
fs.writeFileSync(path.join(logDir, "2_output_stream.txt"), outputEvents.join("\n"));
|
||||
} catch (e) {
|
||||
console.log("[RESPONSES] Failed to write logs:", e.message);
|
||||
|
||||
@@ -193,22 +193,17 @@ 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) {
|
||||
// @ts-ignore
|
||||
if (!merged.properties) merged.properties = {};
|
||||
// @ts-ignore
|
||||
Object.assign(merged.properties, item.properties);
|
||||
}
|
||||
if (item.required && Array.isArray(item.required)) {
|
||||
// @ts-ignore
|
||||
if (!merged.required) merged.required = [];
|
||||
for (const req of item.required) {
|
||||
// @ts-ignore
|
||||
if (!merged.required.includes(req)) {
|
||||
// @ts-ignore
|
||||
merged.required.push(req);
|
||||
}
|
||||
}
|
||||
@@ -216,9 +211,7 @@ function mergeAllOf(obj) {
|
||||
}
|
||||
|
||||
delete obj.allOf;
|
||||
// @ts-ignore
|
||||
if (merged.properties) obj.properties = { ...obj.properties, ...merged.properties };
|
||||
// @ts-ignore
|
||||
if (merged.required) obj.required = [...(obj.required || []), ...merged.required];
|
||||
}
|
||||
|
||||
|
||||
@@ -234,8 +234,7 @@ export function translateResponse(targetFormat, sourceFormat, chunk, state) {
|
||||
|
||||
// Attach OpenAI intermediate results for logging
|
||||
if (openaiResults && sourceFormat !== FORMATS.OPENAI && targetFormat !== FORMATS.OPENAI) {
|
||||
// @ts-ignore — custom expando property for logging
|
||||
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,
|
||||
@@ -17,19 +17,15 @@ export function antigravityToOpenAIRequest(model, body, stream) {
|
||||
const config = req.generationConfig;
|
||||
if (config.maxOutputTokens) {
|
||||
const tempBody = { max_tokens: config.maxOutputTokens, tools: req.tools };
|
||||
// @ts-ignore
|
||||
result.max_tokens = adjustMaxTokens(tempBody);
|
||||
}
|
||||
if (config.temperature !== undefined) {
|
||||
// @ts-ignore
|
||||
result.temperature = config.temperature;
|
||||
}
|
||||
if (config.topP !== undefined) {
|
||||
// @ts-ignore
|
||||
result.top_p = config.topP;
|
||||
}
|
||||
if (config.topK !== undefined) {
|
||||
// @ts-ignore
|
||||
result.top_k = config.topK;
|
||||
}
|
||||
|
||||
@@ -38,13 +34,10 @@ export function antigravityToOpenAIRequest(model, body, stream) {
|
||||
const budget = config.thinkingConfig.thinkingBudget || 0;
|
||||
if (budget > 0) {
|
||||
if (budget <= 2048) {
|
||||
// @ts-ignore
|
||||
result.reasoning_effort = "low";
|
||||
} else if (budget <= 16384) {
|
||||
// @ts-ignore
|
||||
result.reasoning_effort = "medium";
|
||||
} else {
|
||||
// @ts-ignore
|
||||
result.reasoning_effort = "high";
|
||||
}
|
||||
}
|
||||
@@ -75,12 +68,10 @@ export function antigravityToOpenAIRequest(model, body, stream) {
|
||||
|
||||
// Tools
|
||||
if (req.tools && Array.isArray(req.tools)) {
|
||||
// @ts-ignore
|
||||
result.tools = [];
|
||||
for (const tool of req.tools) {
|
||||
if (tool.functionDeclarations) {
|
||||
for (const func of tool.functionDeclarations) {
|
||||
// @ts-ignore
|
||||
result.tools.push({
|
||||
type: "function",
|
||||
function: {
|
||||
@@ -213,14 +204,12 @@ function convertContent(content) {
|
||||
|
||||
// Regular message
|
||||
if (textParts.length > 0 || reasoningContent) {
|
||||
const msg = { role };
|
||||
const msg: Record<string, any> = { role };
|
||||
if (textParts.length > 0) {
|
||||
// @ts-ignore
|
||||
msg.content =
|
||||
textParts.length === 1 && textParts[0].type === "text" ? textParts[0].text : textParts;
|
||||
}
|
||||
if (reasoningContent) {
|
||||
// @ts-ignore
|
||||
msg.reasoning_content = reasoningContent;
|
||||
}
|
||||
return msg;
|
||||
|
||||
@@ -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: {},
|
||||
@@ -18,19 +18,15 @@ export function claudeToGeminiRequest(model, body, stream) {
|
||||
|
||||
// ── Generation config ──────────────────────────────────────────
|
||||
if (body.temperature !== undefined) {
|
||||
// @ts-ignore
|
||||
result.generationConfig.temperature = body.temperature;
|
||||
}
|
||||
if (body.top_p !== undefined) {
|
||||
// @ts-ignore
|
||||
result.generationConfig.topP = body.top_p;
|
||||
}
|
||||
if (body.top_k !== undefined) {
|
||||
// @ts-ignore
|
||||
result.generationConfig.topK = body.top_k;
|
||||
}
|
||||
if (body.max_tokens !== undefined) {
|
||||
// @ts-ignore
|
||||
result.generationConfig.maxOutputTokens = body.max_tokens;
|
||||
}
|
||||
|
||||
@@ -43,7 +39,6 @@ export function claudeToGeminiRequest(model, body, stream) {
|
||||
systemText = String(body.system);
|
||||
}
|
||||
if (systemText) {
|
||||
// @ts-ignore
|
||||
result.systemInstruction = {
|
||||
role: "user",
|
||||
parts: [{ text: systemText }],
|
||||
@@ -157,14 +152,12 @@ export function claudeToGeminiRequest(model, body, stream) {
|
||||
}
|
||||
}
|
||||
if (functionDeclarations.length > 0) {
|
||||
// @ts-ignore
|
||||
result.tools = [{ functionDeclarations }];
|
||||
}
|
||||
}
|
||||
|
||||
// ── Thinking config ────────────────────────────────────────────
|
||||
if (body.thinking?.type === "enabled" && body.thinking.budget_tokens) {
|
||||
// @ts-ignore
|
||||
result.generationConfig.thinkingConfig = {
|
||||
thinkingBudget: body.thinking.budget_tokens,
|
||||
include_thoughts: true,
|
||||
|
||||
@@ -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,
|
||||
@@ -12,13 +12,11 @@ export function claudeToOpenAIRequest(model, body, stream) {
|
||||
|
||||
// Max tokens
|
||||
if (body.max_tokens) {
|
||||
// @ts-ignore
|
||||
result.max_tokens = adjustMaxTokens(body);
|
||||
}
|
||||
|
||||
// Temperature
|
||||
if (body.temperature !== undefined) {
|
||||
// @ts-ignore
|
||||
result.temperature = body.temperature;
|
||||
}
|
||||
|
||||
@@ -57,7 +55,6 @@ export function claudeToOpenAIRequest(model, body, stream) {
|
||||
|
||||
// Tools
|
||||
if (body.tools && Array.isArray(body.tools)) {
|
||||
// @ts-ignore
|
||||
result.tools = body.tools.map((tool) => ({
|
||||
type: "function",
|
||||
function: {
|
||||
@@ -70,7 +67,6 @@ export function claudeToOpenAIRequest(model, body, stream) {
|
||||
|
||||
// Tool choice
|
||||
if (body.tool_choice) {
|
||||
// @ts-ignore
|
||||
result.tool_choice = convertToolChoice(body.tool_choice);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -15,15 +15,12 @@ export function geminiToOpenAIRequest(model, body, stream) {
|
||||
const config = body.generationConfig;
|
||||
if (config.maxOutputTokens) {
|
||||
const tempBody = { max_tokens: config.maxOutputTokens, tools: body.tools };
|
||||
// @ts-ignore
|
||||
result.max_tokens = adjustMaxTokens(tempBody);
|
||||
}
|
||||
if (config.temperature !== undefined) {
|
||||
// @ts-ignore
|
||||
result.temperature = config.temperature;
|
||||
}
|
||||
if (config.topP !== undefined) {
|
||||
// @ts-ignore
|
||||
result.top_p = config.topP;
|
||||
}
|
||||
}
|
||||
@@ -51,12 +48,10 @@ export function geminiToOpenAIRequest(model, body, stream) {
|
||||
|
||||
// Tools
|
||||
if (body.tools && Array.isArray(body.tools)) {
|
||||
// @ts-ignore
|
||||
result.tools = [];
|
||||
for (const tool of body.tools) {
|
||||
if (tool.functionDeclarations) {
|
||||
for (const func of tool.functionDeclarations) {
|
||||
// @ts-ignore
|
||||
result.tools.push({
|
||||
type: "function",
|
||||
function: {
|
||||
|
||||
@@ -21,10 +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`
|
||||
);
|
||||
// @ts-ignore — custom error property
|
||||
error.statusCode = 400;
|
||||
// @ts-ignore — custom error property
|
||||
error.errorType = "unsupported_feature";
|
||||
(error as any).statusCode = 400;
|
||||
(error as any).errorType = "unsupported_feature";
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -33,14 +31,12 @@ export function openaiResponsesToOpenAIRequest(model, body, stream, credentials)
|
||||
const error = new Error(
|
||||
"Unsupported Responses API feature: background mode is not supported by omniroute"
|
||||
);
|
||||
// @ts-ignore — custom error property
|
||||
error.statusCode = 400;
|
||||
// @ts-ignore — custom error property
|
||||
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
|
||||
@@ -163,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,
|
||||
@@ -178,7 +174,6 @@ export function openaiToOpenAIResponsesRequest(model, body, stream, credentials)
|
||||
if (msg.role === "system") {
|
||||
// Use first system message as instructions
|
||||
if (!hasSystemMessage) {
|
||||
// @ts-ignore
|
||||
result.instructions = typeof msg.content === "string" ? msg.content : "";
|
||||
hasSystemMessage = true;
|
||||
}
|
||||
@@ -280,13 +275,11 @@ export function openaiToOpenAIResponsesRequest(model, body, stream, credentials)
|
||||
|
||||
// If no system message, leave instructions empty
|
||||
if (!hasSystemMessage) {
|
||||
// @ts-ignore
|
||||
result.instructions = "";
|
||||
}
|
||||
|
||||
// Convert tools format
|
||||
if (body.tools && Array.isArray(body.tools)) {
|
||||
// @ts-ignore
|
||||
result.tools = body.tools.map((tool) => {
|
||||
if (tool.type === "function") {
|
||||
return {
|
||||
@@ -302,11 +295,8 @@ export function openaiToOpenAIResponsesRequest(model, body, stream, credentials)
|
||||
}
|
||||
|
||||
// Pass through other relevant fields
|
||||
// @ts-ignore
|
||||
if (body.temperature !== undefined) result.temperature = body.temperature;
|
||||
// @ts-ignore
|
||||
if (body.max_tokens !== undefined) result.max_tokens = body.max_tokens;
|
||||
// @ts-ignore
|
||||
if (body.top_p !== undefined) result.top_p = body.top_p;
|
||||
|
||||
return result;
|
||||
|
||||
@@ -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,
|
||||
@@ -19,12 +19,10 @@ export function openaiToClaudeRequest(model, body, stream) {
|
||||
|
||||
// Temperature
|
||||
if (body.temperature !== undefined) {
|
||||
// @ts-ignore
|
||||
result.temperature = body.temperature;
|
||||
}
|
||||
|
||||
// Messages
|
||||
// @ts-ignore
|
||||
result.messages = [];
|
||||
const systemParts = [];
|
||||
|
||||
@@ -48,7 +46,6 @@ export function openaiToClaudeRequest(model, body, stream) {
|
||||
|
||||
const flushCurrentMessage = () => {
|
||||
if (currentRole && currentParts.length > 0) {
|
||||
// @ts-ignore
|
||||
result.messages.push({ role: currentRole, content: currentParts });
|
||||
currentParts = [];
|
||||
}
|
||||
@@ -68,7 +65,6 @@ export function openaiToClaudeRequest(model, body, stream) {
|
||||
flushCurrentMessage();
|
||||
|
||||
if (toolResultBlocks.length > 0) {
|
||||
// @ts-ignore
|
||||
result.messages.push({ role: "user", content: toolResultBlocks });
|
||||
}
|
||||
|
||||
@@ -94,9 +90,7 @@ export function openaiToClaudeRequest(model, body, stream) {
|
||||
flushCurrentMessage();
|
||||
|
||||
// Add cache_control to last assistant message
|
||||
// @ts-ignore
|
||||
for (let i = result.messages.length - 1; i >= 0; i--) {
|
||||
// @ts-ignore
|
||||
const message = result.messages[i];
|
||||
if (
|
||||
message.role === "assistant" &&
|
||||
@@ -117,19 +111,16 @@ export function openaiToClaudeRequest(model, body, stream) {
|
||||
|
||||
if (systemParts.length > 0) {
|
||||
const systemText = systemParts.join("\n");
|
||||
// @ts-ignore
|
||||
result.system = [
|
||||
claudeCodePrompt,
|
||||
{ type: "text", text: systemText, cache_control: { type: "ephemeral", ttl: "1h" } },
|
||||
];
|
||||
} else {
|
||||
// @ts-ignore
|
||||
result.system = [claudeCodePrompt];
|
||||
}
|
||||
|
||||
// Tools - convert from OpenAI format to Claude format with prefix for OAuth
|
||||
if (body.tools && Array.isArray(body.tools)) {
|
||||
// @ts-ignore
|
||||
result.tools = body.tools.map((tool) => {
|
||||
const toolData = tool.type === "function" && tool.function ? tool.function : tool;
|
||||
const originalName = toolData.name;
|
||||
@@ -148,22 +139,18 @@ export function openaiToClaudeRequest(model, body, stream) {
|
||||
};
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
if (result.tools.length > 0) {
|
||||
// @ts-ignore
|
||||
result.tools[result.tools.length - 1].cache_control = { type: "ephemeral", ttl: "1h" };
|
||||
}
|
||||
}
|
||||
|
||||
// Tool choice
|
||||
if (body.tool_choice) {
|
||||
// @ts-ignore
|
||||
result.tool_choice = convertOpenAIToolChoice(body.tool_choice);
|
||||
}
|
||||
|
||||
// Thinking configuration
|
||||
if (body.thinking) {
|
||||
// @ts-ignore
|
||||
result.thinking = {
|
||||
type: body.thinking.type || "enabled",
|
||||
...(body.thinking.budget_tokens && { budget_tokens: body.thinking.budget_tokens }),
|
||||
@@ -173,7 +160,6 @@ export function openaiToClaudeRequest(model, body, stream) {
|
||||
|
||||
// Attach toolNameMap to result for response translation
|
||||
if (toolNameMap.size > 0) {
|
||||
// @ts-ignore
|
||||
result._toolNameMap = toolNameMap;
|
||||
}
|
||||
|
||||
@@ -310,23 +296,17 @@ function openaiToClaudeRequestForAntigravity(model, body, stream) {
|
||||
const result = openaiToClaudeRequest(model, body, stream);
|
||||
|
||||
// Remove Claude Code system prompt, keep only user's system messages
|
||||
// @ts-ignore
|
||||
if (result.system && Array.isArray(result.system)) {
|
||||
// @ts-ignore
|
||||
result.system = result.system.filter(
|
||||
(block) => !block.text || !block.text.includes("You are Claude Code")
|
||||
);
|
||||
// @ts-ignore
|
||||
if (result.system.length === 0) {
|
||||
// @ts-ignore
|
||||
delete result.system;
|
||||
}
|
||||
}
|
||||
|
||||
// Strip prefix from tool names for Antigravity (doesn't use Claude OAuth)
|
||||
// @ts-ignore
|
||||
if (result.tools && Array.isArray(result.tools)) {
|
||||
// @ts-ignore
|
||||
result.tools = result.tools.map((tool) => {
|
||||
if (tool.name && tool.name.startsWith(CLAUDE_OAUTH_TOOL_PREFIX)) {
|
||||
return {
|
||||
@@ -339,9 +319,7 @@ function openaiToClaudeRequestForAntigravity(model, body, stream) {
|
||||
}
|
||||
|
||||
// Strip prefix from tool_use in messages
|
||||
// @ts-ignore
|
||||
if (result.messages && Array.isArray(result.messages)) {
|
||||
// @ts-ignore
|
||||
result.messages = result.messages.map((msg) => {
|
||||
if (!msg.content || !Array.isArray(msg.content)) {
|
||||
return msg;
|
||||
|
||||
@@ -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: {},
|
||||
@@ -30,19 +30,15 @@ function openaiToGeminiBase(model, body, stream) {
|
||||
|
||||
// Generation config
|
||||
if (body.temperature !== undefined) {
|
||||
// @ts-ignore
|
||||
result.generationConfig.temperature = body.temperature;
|
||||
}
|
||||
if (body.top_p !== undefined) {
|
||||
// @ts-ignore
|
||||
result.generationConfig.topP = body.top_p;
|
||||
}
|
||||
if (body.top_k !== undefined) {
|
||||
// @ts-ignore
|
||||
result.generationConfig.topK = body.top_k;
|
||||
}
|
||||
if (body.max_tokens !== undefined) {
|
||||
// @ts-ignore
|
||||
result.generationConfig.maxOutputTokens = body.max_tokens;
|
||||
}
|
||||
|
||||
@@ -78,7 +74,6 @@ function openaiToGeminiBase(model, body, stream) {
|
||||
const content = msg.content;
|
||||
|
||||
if (role === "system" && body.messages.length > 1) {
|
||||
// @ts-ignore
|
||||
result.systemInstruction = {
|
||||
role: "user",
|
||||
parts: [{ text: typeof content === "string" ? content : extractTextContent(content) }],
|
||||
@@ -200,7 +195,6 @@ function openaiToGeminiBase(model, body, stream) {
|
||||
}
|
||||
|
||||
if (functionDeclarations.length > 0) {
|
||||
// @ts-ignore
|
||||
result.tools = [{ functionDeclarations }];
|
||||
}
|
||||
}
|
||||
@@ -222,7 +216,6 @@ export function openaiToGeminiCLIRequest(model, body, stream) {
|
||||
if (body.reasoning_effort) {
|
||||
const budgetMap = { low: 1024, medium: 8192, high: 32768 };
|
||||
const budget = budgetMap[body.reasoning_effort] || 8192;
|
||||
// @ts-ignore
|
||||
gemini.generationConfig.thinkingConfig = {
|
||||
thinkingBudget: budget,
|
||||
include_thoughts: true,
|
||||
@@ -231,7 +224,6 @@ export function openaiToGeminiCLIRequest(model, body, stream) {
|
||||
|
||||
// Thinking config from Claude format
|
||||
if (body.thinking?.type === "enabled" && body.thinking.budget_tokens) {
|
||||
// @ts-ignore
|
||||
gemini.generationConfig.thinkingConfig = {
|
||||
thinkingBudget: body.thinking.budget_tokens,
|
||||
include_thoughts: true,
|
||||
@@ -239,9 +231,7 @@ export function openaiToGeminiCLIRequest(model, body, stream) {
|
||||
}
|
||||
|
||||
// Clean schema for tools
|
||||
// @ts-ignore
|
||||
if (gemini.tools?.[0]?.functionDeclarations) {
|
||||
// @ts-ignore
|
||||
for (const fn of gemini.tools[0].functionDeclarations) {
|
||||
if (fn.parameters) {
|
||||
const cleanedSchema = cleanJSONSchemaForAntigravity(fn.parameters);
|
||||
@@ -263,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",
|
||||
@@ -279,11 +269,10 @@ function wrapInCloudCodeEnvelope(model, geminiCLI, credentials = null, isAntigra
|
||||
|
||||
// Antigravity specific fields
|
||||
if (isAntigravity) {
|
||||
// @ts-ignore
|
||||
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 {
|
||||
@@ -292,14 +281,12 @@ function wrapInCloudCodeEnvelope(model, geminiCLI, credentials = null, isAntigra
|
||||
|
||||
// Add toolConfig for Antigravity
|
||||
if (geminiCLI.tools?.length > 0) {
|
||||
// @ts-ignore
|
||||
envelope.request.toolConfig = {
|
||||
functionCallingConfig: { mode: "VALIDATED" },
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Keep safetySettings for Gemini CLI
|
||||
// @ts-ignore
|
||||
envelope.request.safetySettings = geminiCLI.safetySettings;
|
||||
}
|
||||
|
||||
@@ -310,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",
|
||||
@@ -386,9 +373,7 @@ function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = nu
|
||||
}
|
||||
}
|
||||
if (functionDeclarations.length > 0) {
|
||||
// @ts-ignore
|
||||
envelope.request.tools = [{ functionDeclarations }];
|
||||
// @ts-ignore
|
||||
envelope.request.toolConfig = {
|
||||
functionCallingConfig: { mode: "VALIDATED" },
|
||||
};
|
||||
@@ -409,7 +394,6 @@ function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = nu
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
envelope.request.systemInstruction = { role: "user", parts: systemParts };
|
||||
|
||||
return envelope;
|
||||
|
||||
@@ -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: "",
|
||||
@@ -30,7 +30,6 @@ function convertMessages(messages, tools, model) {
|
||||
};
|
||||
|
||||
if (pendingToolResults.length > 0) {
|
||||
// @ts-ignore
|
||||
userMsg.userInputMessage.userInputMessageContext = {
|
||||
toolResults: pendingToolResults,
|
||||
};
|
||||
@@ -38,12 +37,9 @@ function convertMessages(messages, tools, model) {
|
||||
|
||||
// Add tools to first user message
|
||||
if (tools && tools.length > 0 && history.length === 0) {
|
||||
// @ts-ignore
|
||||
if (!userMsg.userInputMessage.userInputMessageContext) {
|
||||
// @ts-ignore
|
||||
userMsg.userInputMessage.userInputMessageContext = {};
|
||||
}
|
||||
// @ts-ignore
|
||||
userMsg.userInputMessage.userInputMessageContext.tools = tools.map((t) => {
|
||||
const name = t.function?.name || t.name;
|
||||
let description = t.function?.description || t.description || "";
|
||||
@@ -259,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(),
|
||||
@@ -278,18 +274,13 @@ export function buildKiroPayload(model, body, stream, credentials) {
|
||||
};
|
||||
|
||||
if (profileArn) {
|
||||
// @ts-ignore
|
||||
payload.profileArn = profileArn;
|
||||
}
|
||||
|
||||
if (maxTokens || temperature !== undefined || topP !== undefined) {
|
||||
// @ts-ignore
|
||||
payload.inferenceConfig = {};
|
||||
// @ts-ignore
|
||||
if (maxTokens) payload.inferenceConfig.maxTokens = maxTokens;
|
||||
// @ts-ignore
|
||||
if (temperature !== undefined) payload.inferenceConfig.temperature = temperature;
|
||||
// @ts-ignore
|
||||
if (topP !== undefined) payload.inferenceConfig.topP = topP;
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
@@ -161,7 +161,6 @@ export function claudeToOpenAIResponse(chunk, state) {
|
||||
const completionTokens = outputTokens;
|
||||
const totalTokens = promptTokens + completionTokens;
|
||||
|
||||
// @ts-ignore
|
||||
finalChunk.usage = {
|
||||
prompt_tokens: promptTokens,
|
||||
completion_tokens: completionTokens,
|
||||
@@ -170,14 +169,11 @@ export function claudeToOpenAIResponse(chunk, state) {
|
||||
|
||||
// Add prompt_tokens_details if cached tokens exist
|
||||
if (cachedTokens > 0 || cacheCreationTokens > 0) {
|
||||
// @ts-ignore
|
||||
finalChunk.usage.prompt_tokens_details = {};
|
||||
if (cachedTokens > 0) {
|
||||
// @ts-ignore
|
||||
finalChunk.usage.prompt_tokens_details.cached_tokens = cachedTokens;
|
||||
}
|
||||
if (cacheCreationTokens > 0) {
|
||||
// @ts-ignore
|
||||
finalChunk.usage.prompt_tokens_details.cache_creation_tokens = cacheCreationTokens;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
@@ -242,7 +242,6 @@ export function geminiToOpenAIResponse(chunk, state) {
|
||||
|
||||
// Include usage in final chunk for downstream translators
|
||||
if (state.usage) {
|
||||
// @ts-ignore
|
||||
finalChunk.usage = state.usage;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -171,7 +171,6 @@ export function convertKiroToOpenAI(chunk, state) {
|
||||
|
||||
// Include usage in final chunk if available
|
||||
if (state.usage && typeof state.usage === "object") {
|
||||
// @ts-ignore
|
||||
openaiChunk.usage = state.usage;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -535,7 +535,6 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
|
||||
|
||||
// Include usage in final chunk if available
|
||||
if (state.usage && typeof state.usage === "object") {
|
||||
// @ts-ignore
|
||||
finalChunk.usage = state.usage;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
@@ -91,12 +91,11 @@ export function openaiToAntigravityResponse(chunk, state) {
|
||||
tool_calls: "STOP",
|
||||
content_filter: "SAFETY",
|
||||
};
|
||||
// @ts-ignore
|
||||
candidate.finishReason = reasonMap[finishReason] || "STOP";
|
||||
}
|
||||
|
||||
// Build response
|
||||
const response = {
|
||||
const response: Record<string, any> = {
|
||||
candidates: [candidate],
|
||||
modelVersion: state._modelVersion,
|
||||
responseId: state._responseId,
|
||||
@@ -105,18 +104,15 @@ export function openaiToAntigravityResponse(chunk, state) {
|
||||
// Usage metadata
|
||||
const usage = chunk.usage || state._usage;
|
||||
if (usage) {
|
||||
// @ts-ignore
|
||||
response.usageMetadata = {
|
||||
promptTokenCount: usage.prompt_tokens || 0,
|
||||
candidatesTokenCount: usage.completion_tokens || 0,
|
||||
totalTokenCount: usage.total_tokens || 0,
|
||||
};
|
||||
if (usage.completion_tokens_details?.reasoning_tokens) {
|
||||
// @ts-ignore
|
||||
response.usageMetadata.thoughtsTokenCount = usage.completion_tokens_details.reasoning_tokens;
|
||||
}
|
||||
if (usage.prompt_tokens_details?.cached_tokens) {
|
||||
// @ts-ignore
|
||||
response.usageMetadata.cachedContentTokenCount = usage.prompt_tokens_details.cached_tokens;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,6 @@ export function createErrorResult(statusCode: number, message: string, retryAfte
|
||||
result.retryAfterMs = retryAfterMs;
|
||||
}
|
||||
|
||||
// @ts-ignore - boolean is assignable to false at runtime
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -156,7 +155,7 @@ export function createErrorResult(statusCode: number, message: string, retryAfte
|
||||
* @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
|
||||
@@ -180,7 +179,6 @@ export function unavailableResponse(statusCode, message, retryAfter, retryAfterH
|
||||
* @returns {string} Formatted error message
|
||||
*/
|
||||
export function formatProviderError(error, provider, model, statusCode) {
|
||||
// @ts-ignore - Error may have custom 'code' property
|
||||
const code = statusCode || error.code || "FETCH_FAILED";
|
||||
const message = error.message || "Unknown error";
|
||||
return `[${code}]: ${message}`;
|
||||
|
||||
@@ -119,8 +119,7 @@ export function createProxyDispatcher(proxyUrl) {
|
||||
};
|
||||
if (parsed.username) socksOptions.userId = decodeURIComponent(parsed.username);
|
||||
if (parsed.password) socksOptions.password = decodeURIComponent(parsed.password);
|
||||
// @ts-ignore - socksOptions conforms to SocksProxies at runtime
|
||||
dispatcher = socksDispatcher(socksOptions);
|
||||
dispatcher = socksDispatcher(socksOptions as any);
|
||||
} else {
|
||||
dispatcher = new ProxyAgent(normalizedUrl);
|
||||
}
|
||||
|
||||
@@ -233,10 +233,8 @@ export function createSSEStream(options: any = {}) {
|
||||
const translated = translateResponse(targetFormat, sourceFormat, parsed, state);
|
||||
|
||||
// Log OpenAI intermediate chunks (if available)
|
||||
// @ts-ignore - _openaiIntermediate is a custom property on translated arrays
|
||||
if (translated?._openaiIntermediate) {
|
||||
// @ts-ignore
|
||||
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);
|
||||
}
|
||||
@@ -331,10 +329,8 @@ export function createSSEStream(options: any = {}) {
|
||||
const translated = translateResponse(targetFormat, sourceFormat, parsed, state);
|
||||
|
||||
// Log OpenAI intermediate chunks
|
||||
// @ts-ignore - _openaiIntermediate is a custom property
|
||||
if (translated?._openaiIntermediate) {
|
||||
// @ts-ignore
|
||||
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);
|
||||
}
|
||||
@@ -354,10 +350,8 @@ export function createSSEStream(options: any = {}) {
|
||||
const flushed = translateResponse(targetFormat, sourceFormat, null, state);
|
||||
|
||||
// Log OpenAI intermediate chunks for flushed events
|
||||
// @ts-ignore - _openaiIntermediate is a custom property
|
||||
if (flushed?._openaiIntermediate) {
|
||||
// @ts-ignore
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user