mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
feat(dsh-plugin-browserskill): add the web client half with a browser_screenshot toolview
Make the package dual-face: a dsh.client declaration (platform 'web') plus an exports["./client"] bundle built in dsh's closure-factory shape (window.__ModuleLoader__.load handoff, platform modules external, everything else inlined, CSS Modules via lightningcss). The client registers a keyed 'tool.call.toolview' view for browser_screenshot that keeps the terminal block (command + output) and, when the settled result carries an image block, resolves the durable attachment through the client session's authorized readAttachment RPC and renders it with the shared MessageImage thumbnail/lightbox atoms. Path-only results render unchanged; every other browser_* tool keeps the stock terminal card. Tests: view-model derivation (running/ok/error, image vs path-only), component rendering through @testing-library/react + happy-dom, and the registration key. biome.json now excludes the package's lib/ build output (same treatment as dist/).
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* dsh-plugin-browserskill browser half: registers the `browser_screenshot`
|
||||
* keyed toolview (`tool.call.toolview` slot) so screenshot results render the
|
||||
* captured image inline in the Web UI instead of a bare JSON block. Every
|
||||
* other browser_* tool keeps the stock terminal card.
|
||||
*
|
||||
* The image bytes never enter the session log: the view resolves the durable
|
||||
* attachment reference through the client session's authorized
|
||||
* `readAttachment` RPC and hands a blob URL to the shared MessageImage atom.
|
||||
*/
|
||||
|
||||
import type { ImageAttachmentRef } from "@deepseek-ai/dsh-attachment";
|
||||
import type { ClientContext, ISessions, SessionId } from "@deepseek-ai/dsh-client-runtime/client";
|
||||
import type { ToolCallViewProps } from "@deepseek-ai/dsh-client-ui-tool/client";
|
||||
import { createElement } from "react";
|
||||
import { ScreenshotToolView } from "./ScreenshotToolView";
|
||||
|
||||
/** Required services: the slot registry plus session-scoped attachment reads. */
|
||||
export const inject = ["slots", "sessions"];
|
||||
|
||||
/** Resolve one durable image attachment into a browser blob URL. */
|
||||
async function loadSessionImage(
|
||||
sessions: ISessions,
|
||||
sessionId: SessionId,
|
||||
attachment: ImageAttachmentRef,
|
||||
): Promise<string> {
|
||||
const session = sessions.binding(sessionId)?.session;
|
||||
if (session === undefined) {
|
||||
throw new Error(`screenshot toolview: session "${String(sessionId)}" is not bound`);
|
||||
}
|
||||
const result = await session.readAttachment(attachment.attachmentId);
|
||||
if (!result.ok) {
|
||||
throw new Error(
|
||||
`screenshot toolview: readAttachment failed: ${result.error.code}: ${result.error.message}`,
|
||||
);
|
||||
}
|
||||
const bytes = Uint8Array.from(result.value.data);
|
||||
return URL.createObjectURL(
|
||||
new Blob([bytes.buffer as ArrayBuffer], { type: result.value.attachment.mediaType }),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Client plugin body: register the keyed view with its session-bound loader.
|
||||
* @param ctx - client root context.
|
||||
*/
|
||||
export function apply(ctx: ClientContext): void {
|
||||
const sessions = ctx.get("sessions") as unknown as ISessions;
|
||||
ctx.slots.inject("tool.call.toolview", () =>
|
||||
ctx.slots.register(
|
||||
{ name: "tool.call.toolview", key: "browser_screenshot" },
|
||||
(props: ToolCallViewProps) =>
|
||||
createElement(ScreenshotToolView, {
|
||||
...props,
|
||||
loadImage: (attachment: ImageAttachmentRef) =>
|
||||
loadSessionImage(sessions, props.sessionId, attachment),
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user