/** * TheirStack-specific thresholds backed by observed job-search latency. They * are deliberately not global provider defaults: a larger timeout would hide * unrelated provider failures and hold runtime permits longer for every tool. * * The reusable part is the deadline ordering below: * * client > runtime permit wait + provider request + response serialization * * A provider with the same measured failure mode should add its own shared * request classifier and budgets, consumed by both its server runtime hook and * the SDK timeout resolver. If several providers need that shape, promote the * provider policies into a shared registry while keeping each provider's * classifier and measured thresholds separate. */ export const THEIRSTACK_SLOW_JOB_SEARCH_PROVIDER_TIMEOUT_MS = 135_000; /** * The provider request may spend up to 60 seconds waiting for its runtime * permit before the provider timeout starts. Keep another 15 seconds for the * execute route to serialize and return the response. */ export const THEIRSTACK_SLOW_JOB_SEARCH_CLIENT_TIMEOUT_MS = 60_000 + THEIRSTACK_SLOW_JOB_SEARCH_PROVIDER_TIMEOUT_MS + 15_000; /** * TheirStack company search is synchronous but can spend the full upstream * request window before returning a large result set. The SDK default is only * 60 seconds, so keep it alive through the server's 120-second provider * deadline plus the same permit and response grace used for slow job search. */ export const THEIRSTACK_COMPANY_SEARCH_CLIENT_TIMEOUT_MS = 210_000; const LONG_JOB_SEARCH_WINDOW_MS = 365 * 24 * 60 * 60 * 1_000; const DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/; function parseDateOnly(value: unknown): number | null { if (typeof value !== 'string' || !DATE_ONLY_PATTERN.test(value)) { return null; } const timestamp = Date.parse(`${value}T00:00:00.000Z`); if (!Number.isFinite(timestamp)) { return null; } return new Date(timestamp).toISOString().slice(0, 10) === value ? timestamp : null; } function usesLongExplicitDateWindow(payload: Record): boolean { const postedAtGte = parseDateOnly(payload.posted_at_gte); const postedAtLte = parseDateOnly(payload.posted_at_lte); if (postedAtGte !== null && postedAtLte !== null) { return postedAtLte - postedAtGte >= LONG_JOB_SEARCH_WINDOW_MS; } if (postedAtGte !== null) { return Date.now() - postedAtGte >= LONG_JOB_SEARCH_WINDOW_MS; } // With no lower bound, `posted_at_lte` searches the provider's full history // through the supplied date and needs the same slow-search budget. return postedAtLte !== null && payload.posted_at_gte == null; } function parseMaxAgeDays(value: unknown): number | null { if (typeof value === 'number') { return Number.isFinite(value) ? value : null; } if (typeof value !== 'string' || !/^-?\d+$/.test(value.trim())) { return null; } const parsed = Number(value.trim()); return Number.isSafeInteger(parsed) ? parsed : null; } /** * Classifies the TheirStack request shapes known to perform substantially more * work. Keeping this predicate shared prevents the server from accepting a * slow request that the SDK abandons at its default deadline. * * Do not add other providers to this predicate. Give each provider an * evidence-backed classifier so its normal requests keep the global timeout. */ export function usesExtendedTheirstackJobSearchBudget( endpointId: string, payload: Record, ): boolean { if (endpointId !== 'theirstack_job_search') { return false; } if (payload.include_total_results === true) { return true; } const maxAgeDays = parseMaxAgeDays(payload.posted_at_max_age_days); return maxAgeDays !== null && maxAgeDays >= 365 ? true : usesLongExplicitDateWindow(payload); } /** SDK-only timeout selection. Provider request timeouts remain action-local. */ export function resolveTheirstackClientTimeoutMs( endpointId: string, payload: Record, ): number | null { if (endpointId === 'theirstack_company_search') { return THEIRSTACK_COMPANY_SEARCH_CLIENT_TIMEOUT_MS; } return usesExtendedTheirstackJobSearchBudget(endpointId, payload) ? THEIRSTACK_SLOW_JOB_SEARCH_CLIENT_TIMEOUT_MS : null; }