diff --git a/packages/dsh-web/src/client.css b/packages/dsh-web/src/client.css index f03fccb..d2f1255 100644 --- a/packages/dsh-web/src/client.css +++ b/packages/dsh-web/src/client.css @@ -182,6 +182,30 @@ white-space: nowrap; } +.guionai-web__tool-details { + display: flex; + flex-wrap: wrap; + gap: 6px 14px; + margin: 0; + color: var(--dsw-alias-label-tertiary); + font-size: 12px; + line-height: 18px; +} + +.guionai-web__tool-details div { + display: flex; + gap: 4px; +} + +.guionai-web__tool-details dt::after { + content: ":"; +} + +.guionai-web__tool-details dd { + margin: 0; + color: var(--dsw-alias-label-secondary); +} + .guionai-web__tool-body { display: grid; gap: 8px; diff --git a/packages/dsh-web/src/client.ts b/packages/dsh-web/src/client.ts index 369fe9e..ceac0e8 100644 --- a/packages/dsh-web/src/client.ts +++ b/packages/dsh-web/src/client.ts @@ -321,6 +321,7 @@ export function apply(ctx: ClientContext): void { { name: "tool.call.toolview", key, + ...(key === "web_fetch" ? { priority: -1 } : {}), inject: () => ({}), } as never, ((props: ToolCallViewProps) => @@ -358,6 +359,7 @@ function WebResearchToolCard({ ? "Find documentation" : "Fetch documentation"; const summary = toolSummary(name, args); + const details = name === "web_fetch" ? fetchDetails(args) : []; return createElement( "section", @@ -383,6 +385,20 @@ function WebResearchToolCard({ summary ? createElement("p", { className: "guionai-web__tool-summary" }, summary) : null, + details.length > 0 + ? createElement( + "dl", + { className: "guionai-web__tool-details" }, + ...details.map((detail) => + createElement( + "div", + { key: detail.label }, + createElement("dt", null, detail.label), + createElement("dd", null, detail.value), + ), + ), + ) + : null, running ? null : error @@ -539,6 +555,37 @@ function toolSummary( } } +export function fetchDetails( + args: Record, +): Array<{ label: string; value: string }> { + const browserRendered = args.render === "agent-browser"; + const details = [ + { + label: "Backend", + value: browserRendered ? "Browser rendered" : "Direct fetch", + }, + ]; + if (browserRendered && typeof args.waitMs === "number") { + details.push({ label: "Wait", value: formatWait(args.waitMs) }); + } + details.push({ label: "Result", value: fetchResultMode(args) }); + return details; +} + +function formatWait(waitMs: number): string { + return waitMs >= 1_000 && waitMs % 1_000 === 0 + ? `${waitMs / 1_000} s` + : `${waitMs} ms`; +} + +function fetchResultMode(args: Record): string { + if (typeof args.section_id === "string" && args.section_id !== "") + return `Section: ${args.section_id}`; + if (args.tree === true) return "Heading tree"; + if (args.full === true) return "Full document"; + return "Adaptive document"; +} + function excerpt(output: string): string { const compact = output.replace(/\s+/g, " ").trim(); return compact.length <= 360 ? compact : `${compact.slice(0, 357)}…`; diff --git a/packages/dsh-web/test/client.test.ts b/packages/dsh-web/test/client.test.ts index 9f27745..a1f2cc9 100644 --- a/packages/dsh-web/test/client.test.ts +++ b/packages/dsh-web/test/client.test.ts @@ -10,6 +10,7 @@ import { apply, decodeSettings, describeCredentialStatus, + fetchDetails, persistProviderSelection, removeCredential, writeCredential, @@ -50,8 +51,32 @@ function fakeApi(overrides: Record = {}) { } describe("DSH settings client credential surface", () => { - it("registers dedicated views for fetch, links, and docs without replacing search views", () => { - const registrations: Array<{ key: string }> = []; + it("labels the fetch backend, wait, and retrieval mode from its request", () => { + expect( + fetchDetails({ + render: "agent-browser", + waitMs: 2_000, + section_id: "installation", + }), + ).toEqual([ + { label: "Backend", value: "Browser rendered" }, + { label: "Wait", value: "2 s" }, + { label: "Result", value: "Section: installation" }, + ]); + expect(fetchDetails({ tree: true })).toEqual([ + { label: "Backend", value: "Direct fetch" }, + { label: "Result", value: "Heading tree" }, + ]); + expect(fetchDetails({ full: true })).toEqual([ + { label: "Backend", value: "Direct fetch" }, + { label: "Result", value: "Full document" }, + ]); + }); + + it("shadows the host fetch view and registers dedicated views for links and docs", () => { + const registrations: Array<{ key: string; priority?: number }> = [ + { key: "web_fetch", priority: 0 }, + ]; const ctx = { effect: () => undefined, get: () => ({ api: {} }), @@ -68,18 +93,30 @@ describe("DSH settings client credential surface", () => { // Exhaust the generator so every keyed registration is observed. } }, - register: (spec: { key: string }) => { + register: (spec: { key: string; priority?: number }) => { + if ( + registrations.some( + (registration) => + registration.key === spec.key && + (registration.priority ?? 0) === (spec.priority ?? 0), + ) + ) { + throw new Error(`duplicate keyed slot entry: ${spec.key}`); + } registrations.push(spec); return spec; }, }, }; apply(ctx as any); - expect(registrations.map((registration) => registration.key)).toEqual([ - SETTINGS_NAMESPACE, - "web_fetch", - "web_links", - "web_docs", + expect( + registrations.map(({ key, priority }) => ({ key, priority })), + ).toEqual([ + { key: "web_fetch", priority: 0 }, + { key: SETTINGS_NAMESPACE }, + { key: "web_fetch", priority: -1 }, + { key: "web_links" }, + { key: "web_docs" }, ]); });