mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
New doc-sync gate verify-export-jsdoc walks every module-level exported name under packages/*/*/src and requires description prose everywhere, plus @param per parameter and @returns on non-void annotated returns for function-like exports, public class methods, properties, and accessors. The parsing + check helpers move out of gen-cordis-catalog.ts into a shared scripts/jsdoc.ts so 'documented' means one thing on both gated surfaces. Deliberate exemptions (documented in the RFC): heritage-declared class members (the seam declaration is the doc's one home — the one checker query in an otherwise pure-AST walk), cordis plugin-protocol slots (name/inject/reusable/Config/apply, top-level and static), constructors, overload implementations, declare-module augmentation bodies, and re-export statements (checked at the defining module). The 203 under-documented exports the gate found at adoption are filled in this change, so the gate lands green; generated catalogs/graphs are regenerated for the shifted line pointers. RFC: docs/rfc/implemented/process/2026-07-06-export-surface-jsdoc-gate.md
105 lines
4.5 KiB
TypeScript
105 lines
4.5 KiB
TypeScript
/**
|
|
* The model-facing `web_fetch` tool: retrieve the content of a specific URL.
|
|
* Execution goes through `ctx.web` — this module owns the model-facing schema,
|
|
* argument validation, and PRESENTATION (HTML→markdown, truncation formatting),
|
|
* while the fetch provider owns safe retrieval (transport, redirects, caps).
|
|
*/
|
|
|
|
import type { Context } from 'cordis'
|
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
import type { GenericCallView } from '@deepseek-ai/dsh-tools'
|
|
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
|
import type { WebFetchBody, WebFetchResult } from '@deepseek-ai/dsh-web'
|
|
import { assertNever } from '@deepseek-ai/dsh-llm'
|
|
import type {} from '@deepseek-ai/dsh-system-prompt'
|
|
import { htmlToMarkdown } from './html.ts'
|
|
|
|
/**
|
|
* Validate value constraints the schema DSL can't express: a non-blank `url`,
|
|
* and a positive `timeout_ms` when present. Throws a plain `Error` otherwise.
|
|
*
|
|
* @param args - the schema-validated `web_fetch` arguments.
|
|
* @returns the arguments renamed to the seam's camelCase request fields.
|
|
*/
|
|
export function parseFetchArgs(args: { url: string; timeout_ms?: number }): { url: string; timeoutMs?: number } {
|
|
if (args.url.trim().length === 0) throw new Error('url must be a non-empty string')
|
|
if (args.timeout_ms !== undefined && (!Number.isFinite(args.timeout_ms) || args.timeout_ms <= 0)) {
|
|
throw new Error('timeout_ms must be a positive number')
|
|
}
|
|
return { url: args.url, ...args.timeout_ms !== undefined ? { timeoutMs: args.timeout_ms } : {} }
|
|
}
|
|
|
|
/**
|
|
* Render a fetched body to model-facing markdown text.
|
|
*
|
|
* @param body - the decoded body; `html` is converted via
|
|
* {@link htmlToMarkdown}, `text` passes through verbatim.
|
|
* @returns the text for the tool's output block.
|
|
*/
|
|
export function renderBody(body: WebFetchBody): string {
|
|
switch (body.kind) {
|
|
case 'html':
|
|
return htmlToMarkdown(body.content)
|
|
case 'text':
|
|
return body.content
|
|
/* v8 ignore next 2 -- WebFetchBody is a closed union; this arm is unreachable and only makes adding a kind a compile error. */
|
|
default:
|
|
return assertNever(body, 'unhandled web fetch body kind')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format a fetch result as one model-facing text block.
|
|
*
|
|
* @param result - the seam's fetch outcome.
|
|
* @returns a `Fetched <url> (HTTP <status>)` header, the rendered body, and a
|
|
* fetch-something-narrower notice when the provider truncated the content.
|
|
*/
|
|
export function formatFetchOutput(result: WebFetchResult): string {
|
|
const header = `Fetched ${result.url} (HTTP ${result.statusCode})`
|
|
const footer = result.truncated ? '\n\n(Content truncated. Fetch a more specific URL or section for the full text.)' : ''
|
|
return `${header}\n\n${renderBody(result.body)}${footer}`
|
|
}
|
|
|
|
/**
|
|
* Pending-call presentation: a fetch card titled by the URL.
|
|
*
|
|
* @param args - the raw tool arguments; only `url` feeds the view.
|
|
* @returns the generic card view (`kind: 'fetch'`) shown while the call runs.
|
|
*/
|
|
export function presentFetchCall(args: { url: string; timeout_ms?: number }): GenericCallView {
|
|
return { card: 'generic', title: args.url, kind: 'fetch', rawInput: args.url }
|
|
}
|
|
|
|
/**
|
|
* Register the `web_fetch` tool and its system-prompt guidance.
|
|
*
|
|
* @param ctx - context whose `tools` and `systemPrompt` registries receive the
|
|
* registrations; both are effect-scoped and unregister on plugin dispose.
|
|
*/
|
|
export function applyWebFetchTool(ctx: Context): void {
|
|
ctx.systemPrompt.section({
|
|
name: 'tool:web_fetch',
|
|
order: 111,
|
|
text: 'Use the web_fetch tool to retrieve the content of a specific HTTP(S) URL (for example a result from web_search). It returns the page content decoded to text. Cite the URL as a markdown link when you use its content.',
|
|
})
|
|
|
|
ctx.tools.register(defineTool({
|
|
name: 'web_fetch',
|
|
description: 'Fetch the content of a specific HTTP(S) URL and return it decoded to text.',
|
|
parameters: {
|
|
url: { type: 'string', required: true, description: 'The HTTP(S) URL to fetch.' },
|
|
timeout_ms: { type: 'number', description: 'Optional fetch timeout in milliseconds (capped by the provider).' },
|
|
},
|
|
async execute(args, exec): Promise<ContentBlock[]> {
|
|
const input = parseFetchArgs(args)
|
|
const result = await ctx.web.fetch(
|
|
{ url: input.url, ...input.timeoutMs !== undefined ? { timeoutMs: input.timeoutMs } : {} },
|
|
exec.signal ? { signal: exec.signal } : undefined,
|
|
)
|
|
return [{ type: 'text', text: formatFetchOutput(result) }]
|
|
},
|
|
presentCall: presentFetchCall,
|
|
}))
|
|
}
|