mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-02 13:22:11 +03:00
* fix(pollinations): wire session pool + add idle pruning PollinationsExecutor.getPool() returned null because poolConfig was never set. Now sets DEFAULT_POOL_CONFIG in constructor so anonymous requests get fingerprint rotation and 429 cooldown management. Also: - Use acquireBlocking() instead of acquire() to wait for available session - Add startAutoPrune() for periodic idle session cleanup (5min idle timeout) - Improve execute() error handling with proper finally block * fix(duckduckgo-web): wire session pool for fingerprint rotation DuckDuckGoWebExecutor had poolConfig set but never called getPool() in execute(). Added session acquisition via acquireBlocking() and merges fingerprint headers into fetch calls for rate limit evasion. * fix: address PR #3049 review comments - duckduckgo-web: fix session leak (add finally release), fix race condition (report status before release), remove duplicate sessionHeaders - pollinations: fix race condition (move release to finally, report before) * fix: address all PR #3049 review comments - duckduckgo-web: add pool reporting on 429/500 early returns (was missing) - duckduckgo-web: add sessionHeaders to retry fetch on 401/403 - sessionPool: add .unref() to setInterval to prevent keeping process alive --------- Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
99 lines
2.5 KiB
TypeScript
99 lines
2.5 KiB
TypeScript
import { BaseExecutor } from "./base.ts";
|
|
import { PROVIDERS } from "../config/constants.ts";
|
|
import { DEFAULT_POOL_CONFIG } from "../services/sessionPool/types.ts";
|
|
import type { ExecuteInput } from "./base.ts";
|
|
|
|
export class PollinationsExecutor extends BaseExecutor {
|
|
constructor() {
|
|
super("pollinations", PROVIDERS["pollinations"] || { format: "openai" });
|
|
this.poolConfig = DEFAULT_POOL_CONFIG;
|
|
}
|
|
|
|
buildUrl(_model: string, _stream: boolean, urlIndex = 0, _credentials = null): string {
|
|
const baseUrls = this.getBaseUrls();
|
|
return (
|
|
baseUrls[urlIndex] || baseUrls[0] || "https://gen.pollinations.ai/v1/chat/completions"
|
|
);
|
|
}
|
|
|
|
buildHeaders(credentials: any, stream = true): Record<string, string> {
|
|
const key = credentials?.apiKey || credentials?.accessToken;
|
|
|
|
const headers: Record<string, string> = {
|
|
"Content-Type": "application/json",
|
|
};
|
|
|
|
if (key) {
|
|
headers.Authorization = `Bearer ${key}`;
|
|
}
|
|
|
|
if (stream) {
|
|
headers["Accept"] = "text/event-stream";
|
|
}
|
|
|
|
return headers;
|
|
}
|
|
|
|
transformRequest(model: string, body: any, stream: boolean, _credentials: any): any {
|
|
if (typeof body === "object" && body !== null) {
|
|
body.model = model;
|
|
body.stream = stream;
|
|
body.jsonMode = true;
|
|
}
|
|
return body;
|
|
}
|
|
|
|
async execute(input: ExecuteInput) {
|
|
const isAnonymous = !input.credentials?.apiKey && !input.credentials?.accessToken;
|
|
|
|
if (!isAnonymous) {
|
|
return super.execute(input);
|
|
}
|
|
|
|
const pool = this.getPool();
|
|
|
|
// Use acquireBlocking for anonymous requests to wait for available session
|
|
let session;
|
|
try {
|
|
session = pool ? await pool.acquireBlocking(10_000) : null;
|
|
} catch {
|
|
// Pool exhausted — fall through to direct request without fingerprint
|
|
session = null;
|
|
}
|
|
|
|
if (session) {
|
|
const fpHeaders = session.buildHeaders();
|
|
input.upstreamExtraHeaders = {
|
|
...fpHeaders,
|
|
...input.upstreamExtraHeaders,
|
|
};
|
|
}
|
|
|
|
try {
|
|
const result = await super.execute(input);
|
|
|
|
if (session && pool) {
|
|
const status = result.response.status;
|
|
if (status === 429) {
|
|
pool.reportCooldown(session);
|
|
} else if (status >= 500) {
|
|
pool.reportDead(session);
|
|
} else {
|
|
pool.reportSuccess(session);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
} catch (err) {
|
|
if (session && pool) {
|
|
pool.reportCooldown(session);
|
|
}
|
|
throw err;
|
|
} finally {
|
|
session?.release();
|
|
}
|
|
}
|
|
}
|
|
|
|
export default PollinationsExecutor;
|