index.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. /**
  2. * `@deepseek-ai/dsh-web-fetch-local`: registers an anonymous public HTTP(S)
  3. * `WebFetchProvider` with `ctx.web`. A function/namespace plugin (NOT a
  4. * default-export service): it registers INTO the seam's fetch registry, like the
  5. * search providers register into the search registry.
  6. *
  7. * @module @deepseek-ai/dsh-web-fetch-local
  8. */
  9. import type { Context } from 'cordis'
  10. import z from 'schemastery'
  11. import type {} from '@deepseek-ai/dsh-web'
  12. import { LocalFetchProvider } from './provider.ts'
  13. import type { LocalFetchLimits } from './provider.ts'
  14. const MAX_NODE_TIMER_DELAY_MS = 2_147_483_647
  15. export {
  16. LOCAL_FETCH_PROVIDER_ID,
  17. LocalFetchProvider,
  18. } from './provider.ts'
  19. export type { LocalFetchLimits } from './provider.ts'
  20. /** Default `User-Agent`: an explicit product agent, never a browser disguise. */
  21. export const DEFAULT_USER_AGENT = 'deepseek-harness/0.0.1 (+https://github.com/deepseek-ai)'
  22. /** Cordis plugin name used by loader diagnostics. */
  23. export const name = 'web-fetch-local'
  24. /** The web seam this provider registers into. */
  25. export const inject = ['web']
  26. /** Plugin config: the provider's transport and size limits plus its `User-Agent` (all defaulted). */
  27. export interface Config {
  28. /** Maximum accepted request URL length. */
  29. maxUrlLength?: number
  30. /** Maximum response body size in bytes. */
  31. maxResponseBytes?: number
  32. /** Maximum decoded body length in characters. */
  33. maxBodyChars?: number
  34. /** Default fetch timeout in milliseconds, within Node's timer range. */
  35. timeoutMs?: number
  36. /** Maximum number of same-origin redirect hops to follow. */
  37. maxRedirects?: number
  38. /** `User-Agent` header sent on every request. */
  39. userAgent?: string
  40. }
  41. export const Config: z<Config> = z.object({
  42. maxUrlLength: z.number().default(2048),
  43. maxResponseBytes: z.number().default(5_000_000),
  44. maxBodyChars: z.number().default(100_000),
  45. timeoutMs: z.number().default(30_000),
  46. maxRedirects: z.number().default(5),
  47. userAgent: z.string().default(DEFAULT_USER_AGENT),
  48. })
  49. /** The shape after schemastery applies its defaults to every field. */
  50. type ResolvedConfig = Required<Config>
  51. /** A resource limit (byte/char/length/timeout cap) must be a positive finite number. */
  52. function assertPositiveFinite(name: string, value: number): void {
  53. if (!Number.isFinite(value) || value <= 0) {
  54. throw new Error(`web-fetch-local: ${name} must be a positive finite number`)
  55. }
  56. }
  57. /** Node coerces larger timer delays to 1 ms, so reject them at configuration time. */
  58. function assertTimeoutMs(value: number): void {
  59. assertPositiveFinite('timeoutMs', value)
  60. if (value > MAX_NODE_TIMER_DELAY_MS) {
  61. throw new Error(`web-fetch-local: timeoutMs must be no greater than ${MAX_NODE_TIMER_DELAY_MS}`)
  62. }
  63. }
  64. /** The redirect hop cap must be a non-negative integer (0 follows no redirects). */
  65. function assertNonNegativeInteger(name: string, value: number): void {
  66. if (!Number.isInteger(value) || value < 0) {
  67. throw new Error(`web-fetch-local: ${name} must be a non-negative integer`)
  68. }
  69. }
  70. /** Register the local HTTP(S) fetch provider with `ctx.web`. */
  71. export function apply(ctx: Context, config: Config): void {
  72. // schemastery (Config) has already filled every defaulted field.
  73. const resolved = config as ResolvedConfig
  74. assertPositiveFinite('maxUrlLength', resolved.maxUrlLength)
  75. assertPositiveFinite('maxResponseBytes', resolved.maxResponseBytes)
  76. assertPositiveFinite('maxBodyChars', resolved.maxBodyChars)
  77. assertTimeoutMs(resolved.timeoutMs)
  78. assertNonNegativeInteger('maxRedirects', resolved.maxRedirects)
  79. const limits: LocalFetchLimits = {
  80. maxUrlLength: resolved.maxUrlLength,
  81. maxResponseBytes: resolved.maxResponseBytes,
  82. maxBodyChars: resolved.maxBodyChars,
  83. timeoutMs: resolved.timeoutMs,
  84. maxRedirects: resolved.maxRedirects,
  85. userAgent: resolved.userAgent,
  86. }
  87. ctx.web.registerFetchProvider(new LocalFetchProvider(limits))
  88. }