feat(web): render web_search/web_fetch output as a web card
Consume the card:'web' result view (structured sources + answer for search, the URL and HTTP status for fetch) the web backend PR added. WebBlock (ui-primitives) draws both kinds via the kind discriminant: a citation list of safe external links (the MarkdownText protocol allowlist, title-or-hostname label), a truncation indicator, a height cap matching TerminalBlock; a fetch summary for the other kind. web-card-model is the single resultView derivation; a keyed WebRow registers under web_search and web_fetch with the card resident under its summary. The generic fallback and the details panel are web-aware. Fixture gains web_search and web_fetch turns for the built-boot snapshot.
This commit is contained in:
@@ -136,6 +136,54 @@ const TERMINAL_EXIT_STATUS: Record<string, { exitCode: number } | { signal: stri
|
||||
[TERMINAL_OUTPUT_FIXTURE]: { exitCode: 1 },
|
||||
}
|
||||
|
||||
/**
|
||||
* One inline-authored source for the `web_search` fixture. Structurally the
|
||||
* contract's `WebSource`; authored locally because the fixture cannot import the
|
||||
* web tool, and kept minimal so a missing optional field renders its fallback.
|
||||
*/
|
||||
interface WebSourceFixture {
|
||||
url: string
|
||||
title?: string
|
||||
snippet?: string
|
||||
publishedAt?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* The structured `web_search` result view for fixture turn 66, authored inline
|
||||
* because this client-side fixture cannot import the web tool that projects it.
|
||||
* The sources exercise the citation list's features: a titled source with a
|
||||
* snippet and a date, a source with no title (its hostname labels the link), and
|
||||
* a source with a snippet but no date. `truncated` marks the capped indicator.
|
||||
*/
|
||||
const WEB_SEARCH_RESULT: { answer: string; sources: WebSourceFixture[]; truncated: boolean } = {
|
||||
answer: 'DeepSeek Harness is a plugin-based agent harness on vendored Cordis where **every capability is a plugin**.',
|
||||
sources: [
|
||||
{
|
||||
url: 'https://github.com/deepseek-ai/deepseek-harness',
|
||||
title: 'DeepSeek Harness — plugin-based agent harness',
|
||||
snippet: 'Everything is a plugin: session, tools, agent-loop, and LLM adapters all mount on the same Cordis context.',
|
||||
publishedAt: '2026-07-01',
|
||||
},
|
||||
{
|
||||
url: 'https://www.deepseek.com/blog/harness-architecture',
|
||||
snippet: 'The capability-seam pattern splits each capability into interface, implementation, and consumer packages.',
|
||||
},
|
||||
{
|
||||
url: 'https://docs.deepseek.com/harness/plugins',
|
||||
title: 'Writing a harness plugin',
|
||||
publishedAt: '2026-06-15',
|
||||
},
|
||||
],
|
||||
truncated: true,
|
||||
}
|
||||
|
||||
/** The `web_fetch` result view for fixture turn 67, authored inline for the same reason. */
|
||||
const WEB_FETCH_RESULT: { url: string; statusCode: number; truncated: boolean } = {
|
||||
url: 'https://www.deepseek.com/blog/harness-architecture',
|
||||
statusCode: 200,
|
||||
truncated: false,
|
||||
}
|
||||
|
||||
const DEEPSEEK_REASONING = {
|
||||
efforts: [
|
||||
{ id: 'off', name: 'Off' },
|
||||
@@ -296,8 +344,20 @@ function buildAlphaLog(): SessionEvent[] {
|
||||
// strip empty and take the todo surfaces' own coverage with it.
|
||||
toolTurn(65, 'bash', '{"command":"pnpm run check","cwd":"/tmp/fixture/deep/nested"}', TERMINAL_OUTPUT_FIXTURE)
|
||||
|
||||
// Turns 66-67: the web render intent — a web_search whose result view carries
|
||||
// structured sources plus an answer (the citation list, one source lacking a
|
||||
// title so its hostname labels the link, the capped indicator on), and a
|
||||
// web_fetch whose result view carries the fetched URL and its HTTP status.
|
||||
// Both keep a generic pending call view and add the `web` card only at
|
||||
// result time, which is the contract's result-only web shape. Named after
|
||||
// the real tools so they hit the keyed WebRow registration. Ordered BEFORE
|
||||
// the todo turn for the same reason turn 65 is: the standing plan retires at
|
||||
// the next turn/start, so a turn after it would empty the dock's plan strip.
|
||||
toolTurn(66, 'web_search', '{"query":"deepseek harness architecture"}', 'Search results for deepseek harness architecture.')
|
||||
toolTurn(67, 'web_fetch', '{"url":"https://www.deepseek.com/blog/harness-architecture"}', '# Harness architecture\n\nEverything is a plugin.')
|
||||
|
||||
const todoArgs = JSON.stringify({ todos: fixtureTodos })
|
||||
toolTurn(66, 'todo_write', todoArgs, 'Updated todo list: 1 pending, 1 in progress, 1 completed.')
|
||||
toolTurn(68, 'todo_write', todoArgs, 'Updated todo list: 1 pending, 1 in progress, 1 completed.')
|
||||
// The real tool appends the snapshot mid-execution — between tool/call and
|
||||
// tool/result — so the fixture reproduces that exact ordering (the last
|
||||
// toolTurn events run ... tool/call, tool/result, step/end, turn/end).
|
||||
@@ -336,6 +396,13 @@ function presentCall(name: string, argsRaw: string): ToolCallView | undefined {
|
||||
return { card: 'generic', title: `Edit ${str(args.file_path)}`, kind: 'edit', rawInput: args }
|
||||
case 'write':
|
||||
return { card: 'generic', title: `Write ${str(args.file_path)}`, kind: 'edit', rawInput: args }
|
||||
// The web tools keep a GENERIC pending card and add the `web` result card
|
||||
// only at result time (the contract's result-only web shape); their pending
|
||||
// kind matches the result kind so a call and its result read as one category.
|
||||
case 'web_search':
|
||||
return { card: 'generic', title: `Search ${str(args.query)}`, kind: 'search', rawInput: args }
|
||||
case 'web_fetch':
|
||||
return { card: 'generic', title: `Fetch ${str(args.url)}`, kind: 'fetch', rawInput: args }
|
||||
default:
|
||||
return undefined // echo et al: the documented no-view fallback path
|
||||
}
|
||||
@@ -344,6 +411,16 @@ function presentCall(name: string, argsRaw: string): ToolCallView | undefined {
|
||||
function presentResult(name: string, argsRaw: string, resultText: string): ToolResultView | undefined {
|
||||
const call = presentCall(name, argsRaw)
|
||||
if (call === undefined) return undefined
|
||||
// The web tools keep a generic pending card, so their result card is chosen
|
||||
// by tool name rather than by the pending card tag: the structured `web`
|
||||
// card the frontend consumes, with the model-facing text kept as the
|
||||
// capability-less fallback content.
|
||||
if (name === 'web_search') {
|
||||
return { card: 'web', kind: 'search', ...WEB_SEARCH_RESULT, content: text(resultText) }
|
||||
}
|
||||
if (name === 'web_fetch') {
|
||||
return { card: 'web', kind: 'fetch', ...WEB_FETCH_RESULT, content: text(resultText) }
|
||||
}
|
||||
switch (call.card) {
|
||||
case 'terminal':
|
||||
// The sample's own exit status, authored beside it: re-parsing the
|
||||
|
||||
Reference in New Issue
Block a user