/** * `@deepseek-ai/dsh-web-fetch-local`: registers an anonymous public HTTP(S) * `WebFetchProvider` with `ctx.web`. A function/namespace plugin (NOT a * default-export service): it registers INTO the seam's fetch registry, like the * search providers register into the search registry. * * @module @deepseek-ai/dsh-web-fetch-local */ import type { Context } from 'cordis' import z from 'schemastery' import type {} from '@deepseek-ai/dsh-web' import { LocalFetchProvider } from './provider.ts' import type { LocalFetchLimits } from './provider.ts' const MAX_NODE_TIMER_DELAY_MS = 2_147_483_647 export { LOCAL_FETCH_PROVIDER_ID, LocalFetchProvider, } from './provider.ts' export type { LocalFetchLimits } from './provider.ts' /** Default `User-Agent`: an explicit product agent, never a browser disguise. */ export const DEFAULT_USER_AGENT = 'deepseek-harness/0.0.1 (+https://github.com/deepseek-ai)' /** Cordis plugin name used by loader diagnostics. */ export const name = 'web-fetch-local' /** The web seam this provider registers into. */ export const inject = ['web'] /** Plugin config: the provider's transport and size limits plus its `User-Agent` (all defaulted). */ export interface Config { /** Maximum accepted request URL length. */ maxUrlLength?: number /** Maximum response body size in bytes. */ maxResponseBytes?: number /** Maximum decoded body length in characters. */ maxBodyChars?: number /** Default fetch timeout in milliseconds, within Node's timer range. */ timeoutMs?: number /** Maximum number of same-origin redirect hops to follow. */ maxRedirects?: number /** `User-Agent` header sent on every request. */ userAgent?: string } export const Config: z = z.object({ maxUrlLength: z.number().default(2048), maxResponseBytes: z.number().default(5_000_000), maxBodyChars: z.number().default(100_000), timeoutMs: z.number().default(30_000), maxRedirects: z.number().default(5), userAgent: z.string().default(DEFAULT_USER_AGENT), }) /** The shape after schemastery applies its defaults to every field. */ type ResolvedConfig = Required /** A resource limit (byte/char/length/timeout cap) must be a positive finite number. */ function assertPositiveFinite(name: string, value: number): void { if (!Number.isFinite(value) || value <= 0) { throw new Error(`web-fetch-local: ${name} must be a positive finite number`) } } /** Node coerces larger timer delays to 1 ms, so reject them at configuration time. */ function assertTimeoutMs(value: number): void { assertPositiveFinite('timeoutMs', value) if (value > MAX_NODE_TIMER_DELAY_MS) { throw new Error(`web-fetch-local: timeoutMs must be no greater than ${MAX_NODE_TIMER_DELAY_MS}`) } } /** The redirect hop cap must be a non-negative integer (0 follows no redirects). */ function assertNonNegativeInteger(name: string, value: number): void { if (!Number.isInteger(value) || value < 0) { throw new Error(`web-fetch-local: ${name} must be a non-negative integer`) } } /** Register the local HTTP(S) fetch provider with `ctx.web`. */ export function apply(ctx: Context, config: Config): void { // schemastery (Config) has already filled every defaulted field. const resolved = config as ResolvedConfig assertPositiveFinite('maxUrlLength', resolved.maxUrlLength) assertPositiveFinite('maxResponseBytes', resolved.maxResponseBytes) assertPositiveFinite('maxBodyChars', resolved.maxBodyChars) assertTimeoutMs(resolved.timeoutMs) assertNonNegativeInteger('maxRedirects', resolved.maxRedirects) const limits: LocalFetchLimits = { maxUrlLength: resolved.maxUrlLength, maxResponseBytes: resolved.maxResponseBytes, maxBodyChars: resolved.maxBodyChars, timeoutMs: resolved.timeoutMs, maxRedirects: resolved.maxRedirects, userAgent: resolved.userAgent, } ctx.web.registerFetchProvider(new LocalFetchProvider(limits)) }