mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
fix(deepseek-web): lazy start session refresh (#2742)
Integrated into release/v3.8.4
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import type { ExecuteInput } from "./base.ts";
|
||||
import { DeepSeekWebExecutor, acquireAccessToken, tokenCache } from "./deepseek-web.ts";
|
||||
import {
|
||||
DeepSeekWebExecutor,
|
||||
acquireAccessToken,
|
||||
extractUserToken,
|
||||
tokenCache,
|
||||
} from "./deepseek-web.ts";
|
||||
|
||||
interface AutoRefreshConfig {
|
||||
sessionRefreshInterval?: number;
|
||||
@@ -28,15 +33,12 @@ export class DeepSeekWebWithAutoRefreshExecutor extends DeepSeekWebExecutor {
|
||||
autoRefresh: true,
|
||||
...config,
|
||||
};
|
||||
if (this.refreshConfig.autoRefresh) {
|
||||
this.startAutoRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
override async execute(input: ExecuteInput) {
|
||||
this.retryCount = 0;
|
||||
const creds = input.credentials as unknown as Record<string, unknown>;
|
||||
this.currentUserToken = (creds.apiKey as string) || (creds.accessToken as string) || "";
|
||||
this.setCurrentUserToken(extractUserToken(creds));
|
||||
return this.executeWithRetry(input);
|
||||
}
|
||||
|
||||
@@ -62,6 +64,10 @@ export class DeepSeekWebWithAutoRefreshExecutor extends DeepSeekWebExecutor {
|
||||
private startAutoRefresh(): void {
|
||||
if (this.refreshTimer) clearInterval(this.refreshTimer);
|
||||
this.refreshTimer = setInterval(async () => {
|
||||
if (!this.currentUserToken) {
|
||||
this.sessionValid = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await this.doRefreshSession();
|
||||
} catch (error) {
|
||||
@@ -70,6 +76,20 @@ export class DeepSeekWebWithAutoRefreshExecutor extends DeepSeekWebExecutor {
|
||||
}, this.refreshConfig.sessionRefreshInterval);
|
||||
}
|
||||
|
||||
private setCurrentUserToken(userToken: string | null): void {
|
||||
if (!userToken) {
|
||||
return;
|
||||
}
|
||||
if (this.currentUserToken === userToken) {
|
||||
return;
|
||||
}
|
||||
this.currentUserToken = userToken;
|
||||
this.sessionValid = false;
|
||||
if (this.refreshConfig.autoRefresh) {
|
||||
this.startAutoRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
private async doRefreshSession(): Promise<void> {
|
||||
if (!this.currentUserToken) {
|
||||
this.sessionValid = false;
|
||||
|
||||
@@ -53,7 +53,7 @@ function evictOldest(cache: Map<string, unknown>): void {
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
function extractUserToken(credentials: Record<string, unknown>): string | null {
|
||||
export function extractUserToken(credentials: Record<string, unknown>): string | null {
|
||||
const raw = credentials?.apiKey || credentials?.accessToken;
|
||||
if (typeof raw !== "string" || raw.length === 0) return null;
|
||||
// Handle JSON-wrapped tokens (DeepSeek stores token as {"value":"..."})
|
||||
|
||||
@@ -470,6 +470,127 @@ test("isSessionValid starts false", () => {
|
||||
assert.equal(exec.isSessionValid(), false);
|
||||
});
|
||||
|
||||
test("auto-refresh stays idle until a userToken is provided", async () => {
|
||||
const errors = [];
|
||||
const originalError = console.error;
|
||||
console.error = (...args) => {
|
||||
errors.push(args);
|
||||
};
|
||||
|
||||
const exec = new DeepSeekWebWithAutoRefreshExecutor({
|
||||
autoRefresh: true,
|
||||
sessionRefreshInterval: 5,
|
||||
});
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 25));
|
||||
assert.equal(errors.length, 0);
|
||||
} finally {
|
||||
exec.destroy();
|
||||
console.error = originalError;
|
||||
}
|
||||
});
|
||||
|
||||
test("execute without DeepSeek credentials does not start auto-refresh", async () => {
|
||||
const errors = [];
|
||||
const originalError = console.error;
|
||||
console.error = (...args) => {
|
||||
errors.push(args);
|
||||
};
|
||||
|
||||
const exec = new DeepSeekWebWithAutoRefreshExecutor({
|
||||
autoRefresh: true,
|
||||
sessionRefreshInterval: 5,
|
||||
});
|
||||
try {
|
||||
const result = await exec.execute({
|
||||
model: "default",
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: true,
|
||||
credentials: {},
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
assert.equal(result.response.status, 400);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 25));
|
||||
assert.equal(errors.length, 0);
|
||||
} finally {
|
||||
exec.destroy();
|
||||
console.error = originalError;
|
||||
}
|
||||
});
|
||||
|
||||
test("execute without DeepSeek credentials preserves an active auto-refresh session", async () => {
|
||||
const mock = await mockDeepSeekFlow();
|
||||
const exec = new DeepSeekWebWithAutoRefreshExecutor({
|
||||
autoRefresh: true,
|
||||
sessionRefreshInterval: 60_000,
|
||||
});
|
||||
try {
|
||||
const validResult = await exec.execute({
|
||||
model: "default",
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: true,
|
||||
credentials: { apiKey: "test-user-token-active-refresh" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
assert.ok(validResult.response.ok);
|
||||
|
||||
const activeTimer = exec.refreshTimer;
|
||||
assert.ok(activeTimer);
|
||||
assert.equal(exec.currentUserToken, "test-user-token-active-refresh");
|
||||
|
||||
const invalidResult = await exec.execute({
|
||||
model: "default",
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: true,
|
||||
credentials: {},
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
assert.equal(invalidResult.response.status, 400);
|
||||
assert.equal(exec.currentUserToken, "test-user-token-active-refresh");
|
||||
assert.equal(exec.refreshTimer, activeTimer);
|
||||
} finally {
|
||||
exec.destroy();
|
||||
mock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test("execute with a new DeepSeek userToken restarts auto-refresh", async () => {
|
||||
const mock = await mockDeepSeekFlow();
|
||||
const exec = new DeepSeekWebWithAutoRefreshExecutor({
|
||||
autoRefresh: true,
|
||||
sessionRefreshInterval: 60_000,
|
||||
});
|
||||
try {
|
||||
const firstResult = await exec.execute({
|
||||
model: "default",
|
||||
body: { messages: [{ role: "user", content: "first" }] },
|
||||
stream: true,
|
||||
credentials: { apiKey: "test-user-token-refresh-1" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
assert.ok(firstResult.response.ok);
|
||||
|
||||
const firstTimer = exec.refreshTimer;
|
||||
assert.ok(firstTimer);
|
||||
|
||||
const secondResult = await exec.execute({
|
||||
model: "default",
|
||||
body: { messages: [{ role: "user", content: "second" }] },
|
||||
stream: true,
|
||||
credentials: { apiKey: "test-user-token-refresh-2" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
assert.ok(secondResult.response.ok);
|
||||
assert.equal(exec.currentUserToken, "test-user-token-refresh-2");
|
||||
assert.ok(exec.refreshTimer);
|
||||
assert.notEqual(exec.refreshTimer, firstTimer);
|
||||
} finally {
|
||||
exec.destroy();
|
||||
mock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Abort handling ──────────────────────────────────────────────────────
|
||||
|
||||
test("execute: handles abort signal gracefully", async () => {
|
||||
|
||||
Reference in New Issue
Block a user