import { describe, expect, it, vi } from 'vitest' import { Context } from 'cordis' import TurndownService from 'turndown' import { CallId } from '@deepseek-ai/dsh-llm' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRegistry, { type ToolExecutionResult } from '@deepseek-ai/dsh-tools' import WebService from '@deepseek-ai/dsh-web' import type { WebSearchProvider, WebSearchResult } from '@deepseek-ai/dsh-web' import * as ToolWeb from '@deepseek-ai/dsh-tool-web' import { formatSearchOutput, formatFetchOutput, parseSearchArgs, parseFetchArgs, presentSearchCall, presentFetchCall, WEB_SEARCH_MAX_RESULTS, } from '@deepseek-ai/dsh-tool-web' const testToolSignal = new AbortController().signal const available = true function searchProvider(result: WebSearchResult, isAvailable = available): WebSearchProvider { return { id: 'stub-search', available: () => isAvailable, search: () => Promise.resolve(result) } } /** Mount the real registry, seam, and tool-web; return an executor helper. */ async function mountTools(opts: { config?: ToolWeb.Config webConfig?: ConstructorParameters[1] search?: WebSearchProvider fetchProvider?: import('@deepseek-ai/dsh-web').WebFetchProvider } = {}): Promise<{ ctx: Context; fiber: Awaited>; call: (name: string, args: unknown) => Promise }> { const ctx = new Context() await ctx.plugin(SystemPrompt) await ctx.plugin(ToolRegistry) await ctx.plugin(WebService, opts.webConfig ?? {}) if (opts.search) ctx.web.registerSearchProvider(opts.search) if (opts.fetchProvider) ctx.web.registerFetchProvider(opts.fetchProvider) const fiber = await ctx.plugin(ToolWeb, opts.config ?? {}) let counter = 0 const call = (name: string, args: unknown) => ctx.tools.execute({ signal: testToolSignal, callId: CallId(`call-${++counter}`), name, arguments: args }) return { ctx, fiber, call } } describe('search formatting', () => { it('renders content, sources with titles/hostnames, snippets, and a citation reminder', () => { const out = formatSearchOutput({ content: 'an answer', truncated: false, sources: [ { url: 'https://a.test/x', title: 'A', snippet: 'about a', publishedAt: '2026-01-01' }, { url: 'https://b.test/y' }, ], }) expect(out).toContain('an answer') expect(out).toContain('[A](https://a.test/x) — about a (2026-01-01)') expect(out).toContain('[b.test](https://b.test/y)') expect(out).toContain('Cite the relevant URLs') }) it('reports no results when there is neither content nor sources', () => { expect(formatSearchOutput({ sources: [], truncated: false })) .toContain('No results found.') }) it('renders content alone when there are no sources', () => { const out = formatSearchOutput({ content: 'just an answer', sources: [], truncated: false }) expect(out).toContain('just an answer') expect(out).not.toContain('No results found.') expect(out).not.toContain('Sources:') }) it('notes truncation', () => { const out = formatSearchOutput({ sources: [{ url: 'https://a.test' }], truncated: true }) expect(out).toContain('Showing the first 1 sources') }) it('validates the query', () => { expect(() => parseSearchArgs({ query: ' ' })).toThrow('non-empty') expect(parseSearchArgs({ query: 'hi' })).toEqual({ query: 'hi' }) }) it('falls back to the raw URL as a source label when the URL is unparseable', () => { const out = formatSearchOutput({ truncated: false, sources: [{ url: 'not a url' }] }) expect(out).toContain('[not a url](not a url)') }) it('presents a search call as a search-kind card titled by the query', () => { expect(presentSearchCall({ query: 'find me' })).toEqual({ card: 'generic', title: 'find me', kind: 'search', rawInput: 'find me' }) }) }) describe('fetch formatting', () => { const NO_CAP = 1_000_000 const HEADER = 'Fetched https://a.test (HTTP 200)\n\n' const renderHtml = (content: string) => formatFetchOutput({ url: 'https://a.test', statusCode: 200, truncated: false, body: { kind: 'html', content }, }, NO_CAP).slice(HEADER.length) it('renders an html body to markdown text with a status header', () => { const out = formatFetchOutput({ url: 'https://a.test', statusCode: 200, truncated: false, body: { kind: 'html', content: '

Title

Body text

' }, }, NO_CAP) expect(out).toContain('Fetched https://a.test (HTTP 200)') expect(out).toContain('# Title') expect(out).toContain('Body text') }) it('passes a text body through and notes truncation', () => { const out = formatFetchOutput({ url: 'https://a.test', statusCode: 200, truncated: true, body: { kind: 'text', content: 'plain' }, }, NO_CAP) expect(out).toContain('plain') expect(out).toContain('Content truncated') }) it('caps the complete output and notes truncation, even when markdown escaping expands the body', () => { // 1,000 underscores render as 2,000 escaped characters — conversion can // outgrow a provider-side body cap, so the bound applies to the output. const out = formatFetchOutput({ url: 'https://a.test', statusCode: 200, truncated: false, body: { kind: 'html', content: `

${'_'.repeat(1000)}

` }, }, 500) expect(out.length).toBeLessThanOrEqual(500) expect(out).toContain('Fetched https://a.test (HTTP 200)') expect(out).toContain('\\_\\_') expect(out).toContain('Content truncated') // Exact and tiny caps: the complete result is bounded, header and footer included. const exact = formatFetchOutput({ url: 'https://a.test', statusCode: 200, truncated: false, body: { kind: 'text', content: 'abc' }, }, 'Fetched https://a.test (HTTP 200)\n\nabc'.length) expect(exact).toBe('Fetched https://a.test (HTTP 200)\n\nabc') const tiny = formatFetchOutput({ url: 'https://a.test', statusCode: 200, truncated: true, body: { kind: 'text', content: 'abcdef' }, }, 10) expect(tiny.length).toBeLessThanOrEqual(10) expect(tiny).toBe('Fetched ht') }) it('dispatches text and html bodies', () => { expect(formatFetchOutput({ url: 'https://a.test', statusCode: 200, truncated: false, body: { kind: 'text', content: 'x' }, }, NO_CAP)).toBe(`${HEADER}x`) expect(renderHtml('

y

')).toBe('y') }) it('converts html via turndown: entities, links, tables, nesting; drops script/style/noscript', () => { expect(renderHtml('

Tom & Jerry © Résumé

link')) .toBe('Tom & Jerry © Résumé\n\n[link](https://a.test)') expect(renderHtml('

Heading

  • one
  • two
')) .toBe('## Heading\n\n- one\n- two') expect(renderHtml('
AB
12
')) .toBe('| A | B |\n| --- | --- |\n| 1 | 2 |') expect(renderHtml('
LRC
123
')) .toBe('| L | R | C |\n| :--- | ---: | :---: |\n| 1 | 2 | 3 |') expect(renderHtml('

bold italic

quoted

')) .toBe('**bold _italic_**\n\n> quoted') }) it('does not expand numeric colspan attributes into unbounded output', () => { const table = '
A
B
' expect(renderHtml(table)).toBe('| A |\n| --- |\n| B |') }) it('passes deeply nested html through raw without attempting conversion', () => { // Unclosed-tag nesting makes the synchronous conversion superlinear // (seconds at 20k levels, during which the cooperative timeout cannot // fire), so the depth preflight skips conversion entirely; this must // return fast, not merely not-throw. const depth = 20_000 const pathological = '
'.repeat(depth) + 'x' + '
'.repeat(depth) const started = Date.now() expect(formatFetchOutput({ url: 'https://a.test', statusCode: 200, truncated: false, body: { kind: 'html', content: pathological }, }, NO_CAP)).toBe(`${HEADER}${pathological}`) expect(Date.now() - started).toBeLessThan(2_000) }) it('comments and mismatched closing tags cannot hide deep nesting from the preflight', () => { const pathological = '
'.repeat(600) + 'x' expect(formatFetchOutput({ url: 'https://a.test', statusCode: 200, truncated: false, body: { kind: 'html', content: pathological }, }, NO_CAP)).toBe(`${HEADER}${pathological}`) const abruptlyClosedComments = '
'.repeat(600) + 'x' expect(formatFetchOutput({ url: 'https://a.test', statusCode: 200, truncated: false, body: { kind: 'html', content: abruptlyClosedComments }, }, NO_CAP)).toBe(`${HEADER}${abruptlyClosedComments}`) }) it('the preflight accepts ordinary closed, void, self-closing, quoted, and raw-text markup', () => { const paragraphs = '

\'>x

'.repeat(600) const script = `` expect(renderHtml(`<1bad>${paragraphs}${script}`)) .not.toContain('x