Spaces:
Runtime error
Runtime error
File size: 3,730 Bytes
cd8bd0a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | /**
* WebExecutorWrapper β Wraps any web executor with session pool support
*
* This is the integration bridge between the session pool and OmniRoute's
* executor system. It intercepts the fetch() call to add session-pool
* headers (fingerprint-based User-Agent, Sec-CH-UA, etc.) and handles
* 429/5xx responses with pool-level cooldown management.
*
* Future: For cookie-based providers (ChatGPT Web, DeepSeek Web, etc.)
* the wrapper will also inject cookies from the Playwright-authenticated
* session.
*/
import { Session } from "./session.ts";
import { SessionPool } from "./sessionPool.ts";
export interface WebExecutorRequest {
url: string;
method?: string;
headers?: Record<string, string>;
body?: string;
signal?: AbortSignal;
[key: string]: unknown;
}
export interface WebExecutorResponse {
status: number;
statusText?: string;
headers?: Record<string, string>;
body: string;
ok: boolean;
[key: string]: unknown;
}
export interface WebExecutorFn {
(req: WebExecutorRequest): Promise<WebExecutorResponse>;
}
/**
* Decorate a web executor function with session pool support.
*
* Before the underlying executor fires:
* 1. Acquires a session from the pool (reusable, fingerprint-isolated)
* 2. Merges session headers (UA + Sec-CH-UA) into the request
* 3. Handles 429 β pool cooldown, 5xx β session death
*
* For zero-auth providers like Pollinations, Puter, etc. this is all
* that's needed for "truly unlimited" β the fingerprint rotation alone
* defeats burst-based rate limiting.
*/
export function withSessionPool(
executor: WebExecutorFn,
pool: SessionPool,
options?: {
/** When true, wraps the response body for error handling */
wrapResponse?: boolean;
},
): WebExecutorFn {
const wrapResponse = options?.wrapResponse ?? true;
return async (req: WebExecutorRequest): Promise<WebExecutorResponse> => {
// Acquire session from pool (blocking with backoff)
let session: Session | null = null;
try {
session = await pool.acquireBlocking();
} catch (err) {
return {
status: 503,
statusText: "Service Unavailable",
body: JSON.stringify({
error: "session_pool_exhausted",
message: `[SessionPool:${pool.provider}] ${(err as Error).message}`,
}),
ok: false,
headers: {},
};
}
try {
// Build request with session fingerprint headers
const sessionHeaders = session.buildHeaders(req.headers);
const poolReq: WebExecutorRequest = {
...req,
headers: sessionHeaders,
};
// Execute the underlying web request
const res = await executor(poolReq);
// Handle response status
if (res.status === 429) {
pool.reportCooldown(session);
if (wrapResponse) {
return {
...res,
body: JSON.stringify({
error: "pool_rate_limited",
message: `[SessionPool:${pool.provider}] Rate limited, session ${session.id} in cooldown`,
}),
};
}
return res;
}
if (res.status >= 500) {
pool.reportDead(session);
return res;
}
// Success
pool.reportSuccess(session);
pool.totalRequests++;
return res;
} catch (err) {
// Network error β cooldown, not dead (may be transient)
pool.reportCooldown(session);
return {
status: 502,
statusText: "Bad Gateway",
body: JSON.stringify({
error: "pool_network_error",
message: (err as Error).message,
}),
ok: false,
headers: {},
};
} finally {
session.release();
}
};
}
|