changes
This commit is contained in:
1 parent
faabf7ba7a
commit
0454aecf70
36 files changed
+719
-227
No files matched your search
@@ -244,11 +244,7 @@ export class GeminiController {
|
||||
}
|
||||
|
||||
private writeObservableSseResponse(res: FastifyReply, stream: Observable<unknown>): void {
|
||||
if (
|
||||
!res.raw ||
|
||||
!isFunction(res.raw.writeHead) ||
|
||||
!isFunction(res.raw.write)
|
||||
) {
|
||||
if (!res.raw || !isFunction(res.raw.writeHead) || !isFunction(res.raw.write)) {
|
||||
res.header('Content-Type', 'text/event-stream');
|
||||
res.header('Cache-Control', 'no-cache');
|
||||
res.header('Connection', 'keep-alive');
|
||||
|
||||
@@ -4,12 +4,19 @@ export interface OpenAIChatRequest {
|
||||
temperature?: number;
|
||||
top_p?: number;
|
||||
max_tokens?: number;
|
||||
max_completion_tokens?: number;
|
||||
stream?: boolean;
|
||||
stream_options?: { include_usage?: boolean };
|
||||
size?: string;
|
||||
quality?: string;
|
||||
tools?: OpenAITool[];
|
||||
tool_choice?: string | { type: string; function?: { name: string } };
|
||||
response_format?: { type?: string };
|
||||
seed?: number;
|
||||
presence_penalty?: number;
|
||||
frequency_penalty?: number;
|
||||
n?: number;
|
||||
logprobs?: boolean;
|
||||
extra?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,10 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { isEmpty, isFunction, isNil, isObjectLike, isPlainObject, isString } from 'lodash-es';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { ProxyService } from './proxy.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { trafficLogger } from '../../../utils/traffic-logger';
|
||||
import {
|
||||
OpenAIChatRequest,
|
||||
AnthropicChatRequest,
|
||||
@@ -66,11 +68,17 @@ export class ProxyController {
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to list models';
|
||||
this.logger.error(message, error instanceof Error ? error.stack : undefined);
|
||||
res.status(HttpStatus.INTERNAL_SERVER_ERROR).send({
|
||||
error: {
|
||||
message,
|
||||
type: 'server_error',
|
||||
},
|
||||
const status = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
const { type, code } = this.resolveOpenAIErrorTypeAndCode(status, message);
|
||||
const errorPayload: Record<string, unknown> = {
|
||||
message,
|
||||
type,
|
||||
};
|
||||
if (code) {
|
||||
errorPayload.code = code;
|
||||
}
|
||||
res.status(status).send({
|
||||
error: errorPayload,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -296,16 +304,23 @@ export class ProxyController {
|
||||
}
|
||||
|
||||
private async respondOpenAIChatCompletions(body: OpenAIChatRequest, res: FastifyReply) {
|
||||
const requestId = uuidv4();
|
||||
const startTime = Date.now();
|
||||
trafficLogger.logInbound(requestId, '/v1/chat/completions', body, undefined, { stream: body.stream });
|
||||
|
||||
try {
|
||||
const result = await this.proxyService.handleChatCompletions(body);
|
||||
const result = await this.proxyService.handleChatCompletions(body, requestId);
|
||||
|
||||
if (body.stream && this.isObservableLike(result)) {
|
||||
this.writeSseResponse(res, result);
|
||||
this.writeSseResponse(res, result, requestId, '/v1/chat/completions', startTime);
|
||||
return;
|
||||
} else {
|
||||
const duration = Date.now() - startTime;
|
||||
trafficLogger.logOutbound(requestId, '/v1/chat/completions', HttpStatus.OK, result, duration);
|
||||
res.status(HttpStatus.OK).send(result);
|
||||
}
|
||||
} catch (error) {
|
||||
trafficLogger.logError(requestId, '/v1/chat/completions', error);
|
||||
this.sendOpenAIErrorResponse(res, '/v1/chat/completions', error);
|
||||
}
|
||||
}
|
||||
@@ -719,12 +734,14 @@ export class ProxyController {
|
||||
return isObjectLike(value) && isFunction((value as { subscribe?: unknown }).subscribe);
|
||||
}
|
||||
|
||||
private writeSseResponse(res: FastifyReply, stream: Observable<unknown>): void {
|
||||
if (
|
||||
!res.raw ||
|
||||
!isFunction(res.raw.writeHead) ||
|
||||
!isFunction(res.raw.write)
|
||||
) {
|
||||
private writeSseResponse(
|
||||
res: FastifyReply,
|
||||
stream: Observable<unknown>,
|
||||
requestId?: string,
|
||||
endpoint?: string,
|
||||
startTime?: number,
|
||||
): void {
|
||||
if (!res.raw || !isFunction(res.raw.writeHead) || !isFunction(res.raw.write)) {
|
||||
res.header('Content-Type', 'text/event-stream');
|
||||
res.header('Cache-Control', 'no-cache');
|
||||
res.header('Connection', 'keep-alive');
|
||||
@@ -742,6 +759,10 @@ export class ProxyController {
|
||||
Connection: 'keep-alive',
|
||||
});
|
||||
|
||||
if (isFunction(res.raw.flushHeaders)) {
|
||||
res.raw.flushHeaders();
|
||||
}
|
||||
|
||||
const subscription = stream.subscribe({
|
||||
next: (chunk) => {
|
||||
if (res.raw.writableEnded) {
|
||||
@@ -749,19 +770,32 @@ export class ProxyController {
|
||||
}
|
||||
const payload = isString(chunk) ? chunk : String(chunk ?? '');
|
||||
res.raw.write(payload);
|
||||
if (requestId && endpoint) {
|
||||
trafficLogger.logSseChunk(requestId, endpoint, chunk);
|
||||
}
|
||||
if (isFunction((res.raw as any).flush)) {
|
||||
(res.raw as any).flush();
|
||||
}
|
||||
},
|
||||
error: (error) => {
|
||||
if (res.raw.writableEnded) {
|
||||
return;
|
||||
}
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const status = this.resolveErrorHttpStatus(message);
|
||||
const { type, code } = this.resolveOpenAIErrorTypeAndCode(status, message);
|
||||
const errorPayload: Record<string, unknown> = {
|
||||
message,
|
||||
type,
|
||||
};
|
||||
if (code) {
|
||||
errorPayload.code = code;
|
||||
}
|
||||
res.raw.write(
|
||||
`data: ${JSON.stringify({
|
||||
error: {
|
||||
message,
|
||||
type: 'server_error',
|
||||
},
|
||||
})}\n\n`,
|
||||
error: errorPayload,
|
||||
})}
|
||||
\n\n`,
|
||||
);
|
||||
res.raw.end();
|
||||
},
|
||||
@@ -981,11 +1015,16 @@ export class ProxyController {
|
||||
const message = overrideMessage ?? this.resolveErrorMessageText(error);
|
||||
const status = this.resolveErrorHttpStatus(message);
|
||||
this.logProxyEndpointError(endpoint, status, message, error);
|
||||
const { type, code } = this.resolveOpenAIErrorTypeAndCode(status, message);
|
||||
const errorPayload: Record<string, unknown> = {
|
||||
message,
|
||||
type,
|
||||
};
|
||||
if (code) {
|
||||
errorPayload.code = code;
|
||||
}
|
||||
res.status(status).send({
|
||||
error: {
|
||||
message,
|
||||
type: 'server_error',
|
||||
},
|
||||
error: errorPayload,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1045,6 +1084,32 @@ export class ProxyController {
|
||||
return HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
private resolveOpenAIErrorTypeAndCode(
|
||||
status: HttpStatus,
|
||||
message: string,
|
||||
): { type: string; code?: string } {
|
||||
const lowered = message.toLowerCase();
|
||||
if (status === HttpStatus.UNAUTHORIZED) {
|
||||
return { type: 'invalid_request_error', code: 'invalid_api_key' };
|
||||
}
|
||||
if (status === HttpStatus.FORBIDDEN) {
|
||||
return { type: 'invalid_request_error', code: 'permission_denied' };
|
||||
}
|
||||
if (status === HttpStatus.TOO_MANY_REQUESTS) {
|
||||
return { type: 'rate_limit_error', code: 'rate_limit_exceeded' };
|
||||
}
|
||||
if (status === HttpStatus.BAD_GATEWAY || status === HttpStatus.GATEWAY_TIMEOUT) {
|
||||
return { type: 'server_error', code: 'bad_gateway' };
|
||||
}
|
||||
if (status === HttpStatus.SERVICE_UNAVAILABLE) {
|
||||
return { type: 'server_error', code: 'temporarily_unavailable' };
|
||||
}
|
||||
if (lowered.includes('invalid') || lowered.includes('malformed') || lowered.includes('bad request')) {
|
||||
return { type: 'invalid_request_error', code: 'invalid_request_error' };
|
||||
}
|
||||
return { type: 'server_error', code: 'internal_error' };
|
||||
}
|
||||
|
||||
private logProxyEndpointError(
|
||||
endpoint: string,
|
||||
status: HttpStatus,
|
||||
|
||||
@@ -190,6 +190,12 @@ export class ProxyService {
|
||||
|
||||
lastError = error;
|
||||
await this.applyUpstreamPenalty(token.id, effectiveTargetModel, error);
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
const penaltyDecision = this.classifyUpstreamFailure(errorMessage);
|
||||
if (!penaltyDecision.retry) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw lastError || new Error('Request failed after retries');
|
||||
@@ -363,6 +369,12 @@ export class ProxyService {
|
||||
}
|
||||
|
||||
await this.applyUpstreamPenalty(token.id, effectiveTargetModel, lastError);
|
||||
|
||||
const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
|
||||
const penaltyDecision = this.classifyUpstreamFailure(errorMessage);
|
||||
if (!penaltyDecision.retry) {
|
||||
throw lastError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,6 +464,12 @@ export class ProxyService {
|
||||
}
|
||||
|
||||
await this.applyUpstreamPenalty(token.id, effectiveTargetModel, lastError);
|
||||
|
||||
const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
|
||||
const penaltyDecision = this.classifyUpstreamFailure(errorMessage);
|
||||
if (!penaltyDecision.retry) {
|
||||
throw lastError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -728,7 +746,7 @@ export class ProxyService {
|
||||
token.token.upstream_proxy_url,
|
||||
extraHeaders,
|
||||
);
|
||||
return this.processStreamResponse(stream, request.model);
|
||||
return this.processStreamResponse(stream, request.model, request.stream_options);
|
||||
} catch (streamError) {
|
||||
this.logger.warn(
|
||||
`Stream path failed for model=${request.model}; falling back to non-stream generation: ${
|
||||
@@ -787,7 +805,7 @@ export class ProxyService {
|
||||
token.token.upstream_proxy_url,
|
||||
extraHeaders,
|
||||
);
|
||||
return this.processStreamResponse(stream, request.model);
|
||||
return this.processStreamResponse(stream, request.model, request.stream_options);
|
||||
}
|
||||
|
||||
const response = await this.generateInternalWithStreamFallback(
|
||||
@@ -806,6 +824,12 @@ export class ProxyService {
|
||||
}
|
||||
|
||||
await this.applyUpstreamPenalty(token.id, effectiveTargetModel, lastError);
|
||||
|
||||
const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
|
||||
const penaltyDecision = this.classifyUpstreamFailure(errorMessage);
|
||||
if (!penaltyDecision.retry) {
|
||||
throw lastError;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw lastError || new Error('Request failed after retries');
|
||||
@@ -928,18 +952,38 @@ export class ProxyService {
|
||||
private processStreamResponse(
|
||||
upstreamStream: NodeJS.ReadableStream,
|
||||
model: string,
|
||||
streamOptions?: { include_usage?: boolean },
|
||||
): Observable<string> {
|
||||
return new Observable<string>((subscriber) => {
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
let hasEmittedChunk = false;
|
||||
let hasSentDone = false;
|
||||
let isThinking = false;
|
||||
let toolCallIndex = 0;
|
||||
let lastUsageMetadata: GeminiResponse['usageMetadata'];
|
||||
|
||||
const streamId = `chatcmpl-${uuidv4()}`;
|
||||
const created = Math.floor(Date.now() / 1000);
|
||||
|
||||
// Emit initial role chunk immediately to prevent strict clients (like Hermes) from timing out during long TTFT
|
||||
const initialChunk = {
|
||||
id: streamId,
|
||||
object: 'chat.completion.chunk',
|
||||
created: created,
|
||||
model: model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: { role: 'assistant', content: '' },
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
subscriber.next(`data: ${JSON.stringify(initialChunk)}\n\n`);
|
||||
hasEmittedChunk = true;
|
||||
|
||||
const pushChunk = (payload: Record<string, unknown>): void => {
|
||||
hasEmittedChunk = true;
|
||||
subscriber.next(`data: ${JSON.stringify(payload)}\n\n`);
|
||||
};
|
||||
|
||||
@@ -960,8 +1004,17 @@ export class ProxyService {
|
||||
const candidate = json.candidates?.[0];
|
||||
const parts = candidate?.content?.parts || [];
|
||||
|
||||
if (json.usageMetadata) {
|
||||
lastUsageMetadata = json.usageMetadata;
|
||||
}
|
||||
|
||||
for (const part of parts) {
|
||||
if (part.thought && part.text) {
|
||||
let textToEmit = part.text;
|
||||
if (!isThinking) {
|
||||
isThinking = true;
|
||||
textToEmit = `<think>\n${textToEmit}`;
|
||||
}
|
||||
const reasoningChunk = {
|
||||
id: streamId,
|
||||
object: 'chat.completion.chunk',
|
||||
@@ -970,7 +1023,7 @@ export class ProxyService {
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: { reasoning_content: part.text },
|
||||
delta: { content: textToEmit },
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
@@ -980,6 +1033,7 @@ export class ProxyService {
|
||||
}
|
||||
|
||||
if (part.functionCall) {
|
||||
const currentToolIndex = toolCallIndex++;
|
||||
const toolCallChunk = {
|
||||
id: streamId,
|
||||
object: 'chat.completion.chunk',
|
||||
@@ -991,7 +1045,7 @@ export class ProxyService {
|
||||
delta: {
|
||||
tool_calls: [
|
||||
{
|
||||
index: 0,
|
||||
index: currentToolIndex,
|
||||
id: part.functionCall.id || `${part.functionCall.name}-${uuidv4()}`,
|
||||
type: 'function',
|
||||
function: {
|
||||
@@ -1031,6 +1085,11 @@ export class ProxyService {
|
||||
}
|
||||
|
||||
if (part.text) {
|
||||
let textToEmit = part.text;
|
||||
if (isThinking && !part.thought) {
|
||||
isThinking = false;
|
||||
textToEmit = `\n</think>\n\n${textToEmit}`;
|
||||
}
|
||||
const contentChunk = {
|
||||
id: streamId,
|
||||
object: 'chat.completion.chunk',
|
||||
@@ -1039,7 +1098,7 @@ export class ProxyService {
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: { content: part.text },
|
||||
delta: { content: textToEmit },
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
@@ -1049,6 +1108,24 @@ export class ProxyService {
|
||||
}
|
||||
|
||||
if (candidate?.finishReason) {
|
||||
if (isThinking) {
|
||||
isThinking = false;
|
||||
const closingChunk = {
|
||||
id: streamId,
|
||||
object: 'chat.completion.chunk',
|
||||
created: created,
|
||||
model: model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: { content: '\n</think>\n\n' },
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
pushChunk(closingChunk);
|
||||
}
|
||||
|
||||
const finishChunk = {
|
||||
id: streamId,
|
||||
object: 'chat.completion.chunk',
|
||||
@@ -1091,6 +1168,23 @@ export class ProxyService {
|
||||
],
|
||||
});
|
||||
}
|
||||
if (streamOptions?.include_usage && lastUsageMetadata) {
|
||||
const usageChunk = {
|
||||
id: streamId,
|
||||
object: 'chat.completion.chunk',
|
||||
created,
|
||||
model,
|
||||
choices: [],
|
||||
usage: {
|
||||
prompt_tokens: lastUsageMetadata.promptTokenCount || 0,
|
||||
completion_tokens: lastUsageMetadata.candidatesTokenCount || 0,
|
||||
total_tokens:
|
||||
(lastUsageMetadata.promptTokenCount || 0) +
|
||||
(lastUsageMetadata.candidatesTokenCount || 0),
|
||||
},
|
||||
};
|
||||
pushChunk(usageChunk);
|
||||
}
|
||||
if (!hasSentDone) {
|
||||
subscriber.next('data: [DONE]\n\n');
|
||||
hasSentDone = true;
|
||||
@@ -1098,12 +1192,17 @@ export class ProxyService {
|
||||
subscriber.complete();
|
||||
});
|
||||
|
||||
upstreamStream.on('error', (err: unknown) => {
|
||||
// Convert to clean Error to avoid circular reference issues (socket objects)
|
||||
upstreamStream.on('error', (err) => {
|
||||
this.logger.error('OpenAI-compatible stream error:', err);
|
||||
const cleanError = err instanceof Error ? new Error(err.message) : new Error(String(err));
|
||||
this.logger.error(`OpenAI-compatible stream error: ${cleanError.message}`);
|
||||
subscriber.error(cleanError);
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (typeof (upstreamStream as any).destroy === 'function') {
|
||||
(upstreamStream as any).destroy();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1420,7 +1519,14 @@ export class ProxyService {
|
||||
if (normalized === 'MAX_TOKENS') {
|
||||
return 'length';
|
||||
}
|
||||
if (normalized === 'SAFETY' || normalized === 'RECITATION') {
|
||||
if (
|
||||
normalized === 'SAFETY' ||
|
||||
normalized === 'RECITATION' ||
|
||||
normalized === 'OTHER' ||
|
||||
normalized === 'IMAGE_SAFETY' ||
|
||||
normalized === 'PROHIBITED_CONTENT' ||
|
||||
normalized === 'SPII'
|
||||
) {
|
||||
return 'content_filter';
|
||||
}
|
||||
|
||||
@@ -1507,6 +1613,11 @@ export class ProxyService {
|
||||
},
|
||||
}));
|
||||
|
||||
let finalContent = textContent;
|
||||
if (reasoningContent) {
|
||||
finalContent = `<think>\n${reasoningContent}\n</think>\n\n${textContent}`;
|
||||
}
|
||||
|
||||
return {
|
||||
id: `chatcmpl-${uuidv4()}`,
|
||||
object: 'chat.completion',
|
||||
@@ -1517,9 +1628,8 @@ export class ProxyService {
|
||||
index: 0,
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: textContent || null,
|
||||
content: finalContent ? finalContent : toolCalls.length > 0 ? null : '',
|
||||
tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
|
||||
reasoning_content: reasoningContent || undefined,
|
||||
},
|
||||
finish_reason: this.mapAnthropicStopReasonToOpenAIFinishReason(
|
||||
claudeResponse.stop_reason,
|
||||
@@ -1585,7 +1695,19 @@ export class ProxyService {
|
||||
|
||||
if (error instanceof UpstreamRequestError) {
|
||||
const status = error.status;
|
||||
if (status === 401 || status === 403) {
|
||||
if (status === 401) {
|
||||
this.logger.warn(`Upstream returned 401 for account ${accountId}; attempting token refresh`);
|
||||
const refreshed = await this.tokenManager.forceRefreshToken(accountId);
|
||||
if (refreshed) {
|
||||
this.logger.log(`Token refreshed successfully for account ${accountId}; retry permitted`);
|
||||
return;
|
||||
}
|
||||
this.logger.warn(`Token refresh failed for account ${accountId}; marking as forbidden`);
|
||||
this.tokenManager.markAsForbidden(accountId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (status === 403) {
|
||||
this.tokenManager.markAsForbidden(accountId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -363,10 +363,7 @@ export class RateLimitTracker {
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
isString(detail.metadata?.retryDelay) &&
|
||||
!isEmpty(detail.metadata.retryDelay.trim())
|
||||
) {
|
||||
if (isString(detail.metadata?.retryDelay) && !isEmpty(detail.metadata.retryDelay.trim())) {
|
||||
const parsedDelay = parseDurationToSeconds(detail.metadata.retryDelay);
|
||||
if (parsedDelay !== null) {
|
||||
return parsedDelay;
|
||||
|
||||
@@ -3,8 +3,8 @@ import { isString } from 'lodash-es';
|
||||
import { getGeminiNexusVersion } from '../../../utils/geminiNexusVersion';
|
||||
import { logger } from '../../../utils/logger';
|
||||
|
||||
const REMOTE_VERSION_URL = 'https://geminiNexus-auto-updater-974169037036.us-central1.run.app';
|
||||
const CHANGELOG_URL = 'https://geminiNexus.google/changelog';
|
||||
const REMOTE_VERSION_URL = 'https://antigravity-auto-updater-974169037036.us-central1.run.app';
|
||||
const CHANGELOG_URL = 'https://antigravity.google/changelog';
|
||||
export const FALLBACK_VERSION = '1.22.2';
|
||||
const DEFAULT_REMOTE_TIMEOUT_MS = 2500;
|
||||
const VERSION_REGEX = /\d+\.\d+\.\d+/g;
|
||||
@@ -57,7 +57,7 @@ function getArchTag(): string {
|
||||
}
|
||||
|
||||
export function buildUserAgent(version: string): string {
|
||||
return `geminiNexus/${version} ${getPlatformTag()}/${getArchTag()}`;
|
||||
return `antigravity/${version} ${getPlatformTag()}/${getArchTag()}`;
|
||||
}
|
||||
|
||||
function compareSemverVersions(left: string, right: string): number {
|
||||
|
||||
@@ -181,6 +181,37 @@ export class TokenManagerService implements OnModuleInit {
|
||||
this.setAccountCooldown(accountIdOrEmail, 'forbidden', this.forbiddenCooldownMs);
|
||||
}
|
||||
|
||||
async forceRefreshToken(accountIdOrEmail: string): Promise<boolean> {
|
||||
const accountId = this.resolveAccountId(accountIdOrEmail) ?? accountIdOrEmail;
|
||||
const tokenData = this.tokens.get(accountId);
|
||||
if (!tokenData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const nowSeconds = Math.floor(Date.now() / 1000);
|
||||
const newTokens = await GoogleAPIService.refreshAccessToken(
|
||||
tokenData.refresh_token,
|
||||
tokenData.upstream_proxy_url,
|
||||
tokenData.oauth_client_key,
|
||||
);
|
||||
tokenData.access_token = newTokens.access_token;
|
||||
tokenData.expires_in = newTokens.expires_in;
|
||||
tokenData.expiry_timestamp = nowSeconds + newTokens.expires_in;
|
||||
tokenData.oauth_client_key = this.normalizeRefreshedOauthClientKey(
|
||||
tokenData,
|
||||
newTokens.oauth_client_key,
|
||||
);
|
||||
await this.persistTokenState(accountId, tokenData);
|
||||
this.tokens.set(accountId, tokenData);
|
||||
this.logger.log(`Access token force-refreshed for ${tokenData.email}`);
|
||||
return true;
|
||||
} catch (e) {
|
||||
this.logger.error(`Failed to force-refresh access token for ${tokenData.email}`, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async markFromUpstreamError(params: {
|
||||
accountIdOrEmail: string;
|
||||
status?: number;
|
||||
@@ -661,11 +692,7 @@ export class TokenManagerService implements OnModuleInit {
|
||||
}
|
||||
|
||||
const limitCandidate = modelInfo.max_output_tokens ?? modelInfo.max_tokens;
|
||||
if (
|
||||
isNumber(limitCandidate) &&
|
||||
Number.isFinite(limitCandidate) &&
|
||||
limitCandidate > 0
|
||||
) {
|
||||
if (isNumber(limitCandidate) && Number.isFinite(limitCandidate) && limitCandidate > 0) {
|
||||
modelLimits[normalizedModel] = Math.floor(limitCandidate);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user