index.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. /**
  2. * Model-facing `web_search` and `web_fetch` tools over `ctx.web`. This package owns schemas,
  3. * validation, prompt guidance, limits, and presentation, never concrete providers. Enablement
  4. * controls tool registration; an enabled tool remains visible when its provider is unavailable
  5. * and fails with a structured error at execution time.
  6. * @module @deepseek-ai/dsh-tool-web
  7. */
  8. import type { Context } from 'cordis'
  9. import z from 'schemastery'
  10. import type {} from '@deepseek-ai/dsh-web'
  11. import { applyWebSearchTool, WEB_SEARCH_MAX_RESULTS } from './search.ts'
  12. import { applyWebFetchTool } from './fetch.ts'
  13. export { WEB_SEARCH_MAX_RESULTS, applyWebSearchTool, formatSearchOutput, parseSearchArgs, presentSearchCall } from './search.ts'
  14. export { applyWebFetchTool, formatFetchOutput, parseFetchArgs, presentFetchCall } from './fetch.ts'
  15. /** Cordis plugin name used by loader diagnostics. */
  16. export const name = 'tool-web'
  17. /** Services required by the web tool suite. */
  18. export const inject = ['tools', 'web', 'systemPrompt']
  19. /** Default cooperative tool-call timeout budget (ms) for the web tools. */
  20. export const DEFAULT_WEB_TOOL_TIMEOUT_MS = 30_000
  21. /**
  22. * Default cap on one `web_fetch` output and on source characters converted
  23. * synchronously. This leaves headroom above the local provider's default
  24. * 100,000-character body cap while bounding custom providers and rendered output.
  25. */
  26. export const DEFAULT_FETCH_MAX_OUTPUT_CHARS = 200_000
  27. /** Plugin config: which web tools to register, the source cap, per-tool budgets, and the fetch output cap. */
  28. export interface Config {
  29. /** Register `web_search`. Defaults to true. */
  30. search?: boolean
  31. /** Register `web_fetch`. Defaults to true. */
  32. fetch?: boolean
  33. /** Upper bound on sources returned by one `web_search` call. */
  34. searchMaxResults?: number
  35. /** Cooperative timeout budget (ms) for `web_fetch`. Defaults to 30000. */
  36. fetchTimeoutMs?: number
  37. /** Cooperative timeout budget (ms) for `web_search`. Defaults to 30000. */
  38. searchTimeoutMs?: number
  39. /** Cap on source characters converted and complete `web_fetch` output characters. Defaults to 200000. */
  40. fetchMaxOutputChars?: number
  41. }
  42. export const Config: z<Config> = z.object({
  43. search: z.boolean().default(true),
  44. fetch: z.boolean().default(true),
  45. searchMaxResults: z.number().default(WEB_SEARCH_MAX_RESULTS),
  46. fetchTimeoutMs: z.number().default(DEFAULT_WEB_TOOL_TIMEOUT_MS),
  47. searchTimeoutMs: z.number().default(DEFAULT_WEB_TOOL_TIMEOUT_MS),
  48. fetchMaxOutputChars: z.number().default(DEFAULT_FETCH_MAX_OUTPUT_CHARS),
  49. })
  50. /** The shape after schemastery applies its defaults to every field. */
  51. type ResolvedConfig = Required<Config>
  52. /** Configured count, timeout, and character caps must be positive integers. */
  53. function assertPositiveInteger(name: string, value: number): void {
  54. if (!Number.isInteger(value) || value < 1) {
  55. throw new Error(`tool-web: ${name} must be a positive integer`)
  56. }
  57. }
  58. /**
  59. * Register the enabled web tools. `search`/`fetch` default to true; a product
  60. * that wants only one disables the other in config. Each tool's cooperative
  61. * timeout budget (`fetchTimeoutMs`/`searchTimeoutMs`, default 30000) is resolved
  62. * here and attached to the tool as `ToolDefinition.timeoutMs` for
  63. * `@deepseek-ai/dsh-timeout-policy` to enforce. The tools' disposers are
  64. * fiber-scoped (the effect-based registries clean up on dispose), so no manual
  65. * teardown is needed.
  66. */
  67. export function apply(ctx: Context, config: Config): void {
  68. // schemastery (Config) has already filled every defaulted field.
  69. const resolved = config as ResolvedConfig
  70. assertPositiveInteger('searchMaxResults', resolved.searchMaxResults)
  71. assertPositiveInteger('fetchTimeoutMs', resolved.fetchTimeoutMs)
  72. assertPositiveInteger('searchTimeoutMs', resolved.searchTimeoutMs)
  73. assertPositiveInteger('fetchMaxOutputChars', resolved.fetchMaxOutputChars)
  74. if (resolved.search) applyWebSearchTool(ctx, resolved.searchMaxResults, resolved.searchTimeoutMs)
  75. if (resolved.fetch) applyWebFetchTool(ctx, resolved.fetchTimeoutMs, resolved.fetchMaxOutputChars)
  76. }