fix(tool-web): align fetch card truncation, drop view content copies, sync card docs
Address the code-review bot findings on the web result card: - web_fetch's card truncated now derives from the shared renderFetchOutput helper, matching the effective truncation the model-facing text reflects (provider cap, source cut, or output cap), instead of the provider-only flag. - Drop the redundant content copy from both web result views; a UI without the web capability falls back to the raw tool/result content. Narrow the TUI transcript view.content access accordingly. - Set the result-state title from the call args (query/url) so a window- truncated replay keeps a title. - Project meta from the seam result types rather than hand-rolled value types. - Sync the card vocabulary across core tools README, docs/core-data-structures, the adding-a-tool cookbook, and the tool-web package README (both languages, re-recorded pairings); regenerate the cordis api-catalog and cordis-inspect snapshot; revise the Agent Note.
This commit is contained in:
@@ -140,35 +140,36 @@ describe('web_search presentation meta and result view', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('presents a completed search as a web/search card carrying the structured sources and fallback content', () => {
|
||||
it('presents a completed search as a web/search card carrying the structured sources, titled by the query', () => {
|
||||
const meta = searchMetaFromValue({
|
||||
content: 'an answer', truncated: true,
|
||||
sources: [{ url: 'https://a.test', title: 'A', snippet: 'snip', publishedAt: '2026-07-20' }],
|
||||
})
|
||||
expect(presentSearchResult(toolResult(meta, 'rendered'))).toEqual({
|
||||
expect(presentSearchResult({ query: 'q' }, toolResult(meta, 'rendered'))).toEqual({
|
||||
card: 'web',
|
||||
kind: 'search',
|
||||
title: 'q',
|
||||
answer: 'an answer',
|
||||
truncated: true,
|
||||
sources: [{ url: 'https://a.test', title: 'A', snippet: 'snip', publishedAt: '2026-07-20' }],
|
||||
content: [{ type: 'text', text: 'rendered' }],
|
||||
})
|
||||
})
|
||||
|
||||
it('omits the answer from the view when meta carries none', () => {
|
||||
const meta = searchMetaFromValue({ truncated: false, sources: [{ url: 'https://a.test' }] })
|
||||
const view = presentSearchResult(toolResult(meta))
|
||||
const view = presentSearchResult({ query: 'q' }, toolResult(meta))
|
||||
expect(view).toBeDefined()
|
||||
expect(view && 'answer' in view).toBe(false)
|
||||
expect(view && 'content' in view).toBe(false)
|
||||
})
|
||||
|
||||
it('falls back to the generic card on an error result', () => {
|
||||
const meta = searchMetaFromValue({ truncated: false, sources: [{ url: 'https://a.test' }] })
|
||||
expect(presentSearchResult(toolResult(meta, 'body', true))).toBeUndefined()
|
||||
expect(presentSearchResult({ query: 'q' }, toolResult(meta, 'body', true))).toBeUndefined()
|
||||
})
|
||||
|
||||
it('falls back to the generic card on absent or malformed meta', () => {
|
||||
expect(presentSearchResult(toolResult(undefined))).toBeUndefined()
|
||||
expect(presentSearchResult({ query: 'q' }, toolResult(undefined))).toBeUndefined()
|
||||
expect(searchMetaFromResult(undefined)).toBeUndefined()
|
||||
expect(searchMetaFromResult(null)).toBeUndefined()
|
||||
expect(searchMetaFromResult('nope')).toBeUndefined()
|
||||
@@ -358,30 +359,54 @@ describe('fetch formatting', () => {
|
||||
})
|
||||
|
||||
describe('web_fetch presentation meta and result view', () => {
|
||||
it('projects url, status, and truncation into meta', () => {
|
||||
expect(fetchMetaFromValue({ url: 'https://a.test', statusCode: 404, truncated: true }))
|
||||
const NO_CAP = 1_000_000
|
||||
|
||||
it('projects url, status, and the provider truncation into meta', () => {
|
||||
expect(fetchMetaFromValue({ url: 'https://a.test', statusCode: 404, truncated: true, body: { kind: 'text', content: 'x' } }, NO_CAP))
|
||||
.toEqual({ url: 'https://a.test', statusCode: 404, truncated: true })
|
||||
})
|
||||
|
||||
it('presents a completed fetch as a web/fetch card carrying the summary and the markdown body as fallback content', () => {
|
||||
const meta = fetchMetaFromValue({ url: 'https://a.test', statusCode: 200, truncated: false })
|
||||
expect(presentFetchResult(toolResult(meta, '# Title'))).toEqual({
|
||||
it('projects truncated: true when the output cap cut a body the provider did not, matching the render footer', () => {
|
||||
// The provider reports truncated: false, but conversion outgrows the cap, so
|
||||
// the render text carries the truncation footer. The meta must agree.
|
||||
const value = {
|
||||
url: 'https://a.test', statusCode: 200, truncated: false,
|
||||
body: { kind: 'html' as const, content: `<p>${'_'.repeat(1000)}</p>` },
|
||||
}
|
||||
const meta = fetchMetaFromValue(value, 500) as { truncated: boolean }
|
||||
expect(meta.truncated).toBe(true)
|
||||
expect(formatFetchOutput(value, 500)).toContain('Content truncated')
|
||||
})
|
||||
|
||||
it('projects truncated: false when neither the provider nor the cap cut the body', () => {
|
||||
const value = {
|
||||
url: 'https://a.test', statusCode: 200, truncated: false,
|
||||
body: { kind: 'text' as const, content: 'short' },
|
||||
}
|
||||
const meta = fetchMetaFromValue(value, NO_CAP) as { truncated: boolean }
|
||||
expect(meta.truncated).toBe(false)
|
||||
expect(formatFetchOutput(value, NO_CAP)).not.toContain('Content truncated')
|
||||
})
|
||||
|
||||
it('presents a completed fetch as a web/fetch card carrying the summary, titled by the url, without content', () => {
|
||||
const meta = fetchMetaFromValue({ url: 'https://a.test', statusCode: 200, truncated: false, body: { kind: 'text', content: '# Title' } }, NO_CAP)
|
||||
expect(presentFetchResult({ url: 'https://a.test' }, toolResult(meta, '# Title'))).toEqual({
|
||||
card: 'web',
|
||||
kind: 'fetch',
|
||||
title: 'https://a.test',
|
||||
url: 'https://a.test',
|
||||
statusCode: 200,
|
||||
truncated: false,
|
||||
content: [{ type: 'text', text: '# Title' }],
|
||||
})
|
||||
})
|
||||
|
||||
it('falls back to the generic card on an error result', () => {
|
||||
const meta = fetchMetaFromValue({ url: 'https://a.test', statusCode: 200, truncated: false })
|
||||
expect(presentFetchResult(toolResult(meta, 'body', true))).toBeUndefined()
|
||||
const meta = fetchMetaFromValue({ url: 'https://a.test', statusCode: 200, truncated: false, body: { kind: 'text', content: 'ok' } }, NO_CAP)
|
||||
expect(presentFetchResult({ url: 'https://a.test' }, toolResult(meta, 'body', true))).toBeUndefined()
|
||||
})
|
||||
|
||||
it('falls back to the generic card on absent or malformed meta', () => {
|
||||
expect(presentFetchResult(toolResult(undefined))).toBeUndefined()
|
||||
expect(presentFetchResult({ url: 'https://a.test' }, toolResult(undefined))).toBeUndefined()
|
||||
expect(fetchMetaFromResult(undefined)).toBeUndefined()
|
||||
expect(fetchMetaFromResult(null)).toBeUndefined()
|
||||
expect(fetchMetaFromResult('nope')).toBeUndefined()
|
||||
|
||||
Reference in New Issue
Block a user