Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions open-sse/executors/deepseek-web-with-auto-refresh.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
}

Expand All @@ -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) {
Expand All @@ -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();
}
}
Comment thread
thanet-s marked this conversation as resolved.
Resolved

private async doRefreshSession(): Promise<void> {
if (!this.currentUserToken) {
this.sessionValid = false;
Expand Down
2 changes: 1 addition & 1 deletion open-sse/executors/deepseek-web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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":"..."})
Expand Down
121 changes: 121 additions & 0 deletions tests/unit/deepseek-web.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down