The audit swept every packages/*/* plugin for the new AGENTS.md
convention (no hardcoded tunables in plugins) and exposes each finding
as a defaulted, validated Config field. Defaults are the previously
hardcoded values throughout, so no deployment or golden changes.
- tool-fs (had NO Config): readLimit, readMaxLineLength, readMaxBytes,
readStreamMinSize. The caps thread through ReadToolCaps/ReadWindow —
read-render already documented that the consumer applies the caps, so
they become explicit per-request fields.
- tool-web: searchMaxResults (WEB_SEARCH_MAX_RESULTS stays as the
schemastery default). Also fixes the stale GREP_LIMIT references in
search.ts and the web-capability-seam RFC (no such constant exists).
- bash-local: graceMs (SIGTERM->SIGKILL escalation grace). The
RunInternals.graceMs test seam is gone: graceMs is now a required
SpawnSpec field filled from config, so tests exercise the real
config path and the defaults live in exactly one place.
- subagent-acp: disposeEofGraceMs / disposeGraceMs. The AcpRunSpec
fields become required for the same one-defaulting-layer reason.
- session-persistence-sqlite: journalMode ('wal' default; the
rollback-journal modes serve filesystems where WAL's shared-memory
files do not work, e.g. network mounts).
- hooks-claude + hooks-codex: stderrSummaryMaxChars for the persisted
hook/result stderr summary. The duplicated summarize() helpers merge
into hook-protocol's summarizeStderr(stderr, maxChars), beside the
HookResultRecord field it feeds, with the bound parameterized the
same way runHook's defaultTimeoutMs already is.
- compact-basic: charsPerToken for the token estimator (default 4, the
English-text heuristic; CJK-heavy deployments need ~1-2 or compaction
fires far too late). Also corrects the BasicCompactService class doc,
which claimed defaults the required-field config never had.
- fs-local: deletes the dead STREAM_MIN_SIZE constant and the dead
FsIoInternals.streamMinSize seam — the read-routing bound lives in
the consumer (tool-fs), where it is now config. This is item 1 of
the proposed prune-write-only-fs-surface RFC, annotated accordingly.
Every new field gets range validation (following the existing
assertPositiveFinite pattern), a README row, and tests covering the
configured behavior, the schema default, and load-time rejection.
74 lines
3.1 KiB
TypeScript
74 lines
3.1 KiB
TypeScript
/**
|
|
* The model-facing web tool suite (`web_search`, `web_fetch`) over the `ctx.web`
|
|
* seam. This root plugin registers the tools the product has ENABLED, composing
|
|
* the per-tool registration helpers (`applyWebSearchTool`, `applyWebFetchTool`).
|
|
*
|
|
* The package owns model-facing concerns only — tool names, JSON schemas,
|
|
* argument validation, prompt sections, result-cap constants, result formatting,
|
|
* HTML→markdown presentation. All web access goes through `ctx.web`; this
|
|
* package never imports a concrete provider package.
|
|
*
|
|
* Tool registration follows product/app ENABLEMENT, not backend availability: a
|
|
* tool stays visible even when its selected provider is missing/misconfigured,
|
|
* and execution fails with a structured `WebError` (resolved by the seam at call
|
|
* time). That keeps the model schema stable without making plugin load order,
|
|
* credential state, or HMR timing part of the model-facing contract.
|
|
*
|
|
* @module @deepseek-ai/dsh-tool-web
|
|
*/
|
|
|
|
import type { Context } from 'cordis'
|
|
import z from 'schemastery'
|
|
import type {} from '@deepseek-ai/dsh-web'
|
|
import { applyWebSearchTool, WEB_SEARCH_MAX_RESULTS } from './search.ts'
|
|
import { applyWebFetchTool } from './fetch.ts'
|
|
|
|
export { WEB_SEARCH_MAX_RESULTS, applyWebSearchTool, formatSearchOutput, parseSearchArgs, presentSearchCall } from './search.ts'
|
|
export { applyWebFetchTool, formatFetchOutput, parseFetchArgs, presentFetchCall, renderBody } from './fetch.ts'
|
|
export { htmlToMarkdown } from './html.ts'
|
|
|
|
/** Cordis plugin name used by loader diagnostics. */
|
|
export const name = 'tool-web'
|
|
|
|
/** Services required by the web tool suite. */
|
|
export const inject = ['tools', 'web', 'systemPrompt']
|
|
|
|
export interface Config {
|
|
/** Register `web_search`. Defaults to true. */
|
|
search?: boolean
|
|
/** Register `web_fetch`. Defaults to true. */
|
|
fetch?: boolean
|
|
/** Upper bound on sources returned by one `web_search` call. */
|
|
searchMaxResults?: number
|
|
}
|
|
|
|
export const Config: z<Config> = z.object({
|
|
search: z.boolean().default(true),
|
|
fetch: z.boolean().default(true),
|
|
searchMaxResults: z.number().default(WEB_SEARCH_MAX_RESULTS),
|
|
})
|
|
|
|
/** The shape after schemastery applies its defaults to every field. */
|
|
type ResolvedConfig = Required<Config>
|
|
|
|
/** The result cap must be a positive integer (it bounds a provider's source list). */
|
|
function assertPositiveInteger(name: string, value: number): void {
|
|
if (!Number.isInteger(value) || value < 1) {
|
|
throw new Error(`tool-web: ${name} must be a positive integer`)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register the enabled web tools. `search`/`fetch` default to true; a product
|
|
* that wants only one disables the other in config. The tools' disposers are
|
|
* fiber-scoped (the effect-based registries clean up on dispose), so no manual
|
|
* teardown is needed.
|
|
*/
|
|
export function apply(ctx: Context, config: Config): void {
|
|
// schemastery (Config) has already filled every defaulted field.
|
|
const resolved = config as ResolvedConfig
|
|
assertPositiveInteger('searchMaxResults', resolved.searchMaxResults)
|
|
if (resolved.search) applyWebSearchTool(ctx, resolved.searchMaxResults)
|
|
if (resolved.fetch) applyWebFetchTool(ctx)
|
|
}
|