插件 Hooks 与前端 Artifact 渲染 深度解析
这一页拆扩展面:插件能接入 chat、tool、permission、compaction;前端 artifact renderer 用 registry 让科学结果可视化不污染聊天路径。
导入链与契约
插件输入在 tooling/plugin/src/index.ts:26 给出 client、project、directory、worktree、serverUrl 和 shell,Hooks 从 tooling/plugin/src/index.ts:148 开始声明。前端渲染侧,frontend/workspace/src/science/renderers/registry.ts:41 用 Map 存 renderer,frontend/workspace/src/science/renderers/registry.ts:43 通过 register 扩展 kind。两边都是 registry/hook 风格。
26export type PluginInput = { 27 client: ReturnType<typeof createOpenScienceClient> 28 project: Project 29 directory: string 30 worktree: string 31 serverUrl: URL 32 $: BunShell 33} 34 35export type Plugin = (input: PluginInput) => Promise<Hooks>
核心实现,逐段拆
▸Hooks 细到 chat 参数、工具调用和权限询问
Hooks 在 tooling/plugin/src/index.ts:149 支持 event,在 tooling/plugin/src/index.ts:151 支持 tool 定义,在 tooling/plugin/src/index.ts:171 可改 chat params,在 tooling/plugin/src/index.ts:179 可介入 permission.ask,在 tooling/plugin/src/index.ts:184 和 tooling/plugin/src/index.ts:188 分别包住工具执行前后。它的扩展点覆盖了模型、工具和治理三条线。
148export interface Hooks { 149 event?: (input: { event: Event }) => Promise<void> 150 config?: (input: Config) => Promise<void> 151 tool?: { 152 [key: string]: ToolDefinition 153 } 154 auth?: AuthHook 155 /** 156 * Called when a new message is received 157 */ 158 "chat.message"?: ( 159 input: { 160 sessionID: string 161 agent?: string 162 model?: { providerID: string; modelID: string } 163 messageID?: string 164 variant?: string 165 }, 166 output: { message: UserMessage; parts: Part[] }, 167 ) => Promise<void> 168 /** 169 * Modify parameters sent to LLM 170 */ 171 "chat.params"?: ( 172 input: { sessionID: string; agent: string; model: Model; provider: ProviderContext; message: UserMessage }, 173 output: { temperature: number; topP: number; topK: number; options: Record<string, any> }, 174 ) => Promise<void> 175 "chat.headers"?: ( 176 input: { sessionID: string; agent: string; model: Model; provider: ProviderContext; message: UserMessage }, 177 output: { headers: Record<string, string> }, 178 ) => Promise<void> 179 "permission.ask"?: (input: Permission, output: { status: "ask" | "deny" | "allow" }) => Promise<void> 180 "command.execute.before"?: ( 181 input: { command: string; sessionID: string; arguments: string }, 182 output: { parts: Part[] }, 183 ) => Promise<void> 184 "tool.execute.before"?: ( 185 input: { tool: string; sessionID: string; callID: string }, 186 output: { args: any }, 187 ) => Promise<void> 188 "tool.execute.after"?: ( 189 input: { tool: string; sessionID: string; callID: string }, 190 output: { 191 title: string 192 output: string 193 metadata: any 194 }, 195 ) => Promise<void> 196 "experimental.chat.messages.transform"?: ( 197 input: {}, 198 output: { 199 messages: { 200 info: Message 201 parts: Part[] 202 }[] 203 }, 204 ) => Promise<void> 205 "experimental.chat.system.transform"?: ( 206 input: { sessionID?: string; model: Model }, 207 output: { 208 system: string[] 209 }, 210 ) => Promise<void> 211 /** 212 * Called before session compaction starts. Allows plugins to customize 213 * the compaction prompt. 214 * 215 * - `context`: Additional context strings appended to the default prompt 216 * - `prompt`: If set, replaces the default compaction prompt entirely 217 */ 218 "experimental.session.compacting"?: ( 219 input: { sessionID: string }, 220 output: { context: string[]; prompt?: string }, 221 ) => Promise<void> 222 "experimental.text.complete"?: ( 223 input: { sessionID: string; messageID: string; partID: string }, 224 output: { text: string }, 225 ) => Promise<void> 226}
▸Artifact renderer 让科学对象成为一等 UI 元素
前端 registry 在 frontend/workspace/src/science/renderers/registry.ts:14 列出 protein-structure、genome-track、chem-2d、chem-3d、msa、sequence、pdf、latex 等 kind;frontend/workspace/src/science/renderers/registry.ts:25 定义 renderer props,frontend/workspace/src/science/renderers/registry.ts:43 到 frontend/workspace/src/science/renderers/registry.ts:57 提供 register/get/has/kinds。这样新增渲染器不需要改聊天主路径。
13/** Known artifact kinds. Extend as renderer agents add support. */ 14export type ArtifactKind = 15 | "protein-structure" // molstar / 3dmol — .pdb / .cif / mmCIF 16 | "genome-track" // igv.js — BAM/VCF/BED/bigWig tracks 17 | "chem-2d" // RDKit — 2D molecule depiction from SMILES/MOL 18 | "chem-3d" // 3Dmol — 3D molecule from MOL/SDF/XYZ 19 | "msa" // multiple sequence alignment viewer 20 | "sequence" // linear nucleotide/protein sequence 21 | "pdf" // pdfjs-dist — inline PDF pages 22 | "latex" // katex — rendered math / equations 23 | (string & {}) 24 25/** Props every science renderer receives. */ 26export interface ArtifactRenderProps { 27 /** The artifact kind (redundant with lookup key; handy for shared renderers). */ 28 kind: ArtifactKind 29 /** 30 * The artifact payload. Shape is kind-specific — e.g. { pdb: string } for 31 * protein-structure, { smiles: string } for chem-2d, { tex: string } for latex. 32 * Renderers validate/narrow this themselves. 33 */ 34 data: unknown 35 /** Optional display height hint in px. */ 36 height?: number 37} 38 39export type ArtifactRenderer = Component<ArtifactRenderProps> 40 41const renderers = new Map<string, ArtifactRenderer>() 42 43export function register(kind: ArtifactKind, renderer: ArtifactRenderer): void { 44 renderers.set(kind, renderer) 45} 46 47export function get(kind: ArtifactKind): ArtifactRenderer | undefined { 48 return renderers.get(kind) 49} 50 51export function has(kind: ArtifactKind): boolean { 52 return renderers.has(kind) 53} 54 55export function kinds(): string[] { 56 return [...renderers.keys()] 57} 58 59/** 60 * Side-effect registrations live in `./index.ts` so `ScienceArtifact` can import 61 * that module once and have every renderer registered. 62 * 63 * AUTO: renderers registered here by integration 64 * (feature agents: create ./impl/<kind>.tsx exporting a renderer, then add a 65 * `register("<kind>", <Renderer>)` call in ./index.ts during integration — do 66 * NOT edit this registry file or the chat rendering path yourself.) 67 */
关键流程
扩展系统的优点是两端都很薄。插件只需要返回 Hooks,见 tooling/plugin/src/index.ts:35;renderer 只需要注册 kind 和 component,见 frontend/workspace/src/science/renderers/registry.ts:43。与此相对,配置层会从 `.openscience` 目录加载插件,证据在 backend/cli/src/config/config.ts:166、backend/cli/src/config/config.ts:178、backend/cli/src/config/config.ts:185,所以插件目录的信任边界必须清楚。
▸为什么这样拆
这个子模块的拆法不是按文件大小,而是按运行时责任边界来切。主页面已经把 backend/cli/src/index.ts:55、backend/cli/src/config/config.ts:75、backend/cli/src/provider/provider.ts:57、backend/cli/src/science/connectors/types.ts:76 和 tooling/plugin/src/index.ts:148 串成整体;子页面只展开其中一个闭环,避免把所有源码证据挤成行号清单。
边界 / 失败模式
- Hooks 可改 tool args,
tooling/plugin/src/index.ts:184的能力很强,应该配合权限策略使用。 - Renderer registry 接受 string 扩展 kind,
frontend/workspace/src/science/renderers/registry.ts:23灵活但也需要命名约定。 - 配置加载会自动安装依赖,
backend/cli/src/config/config.ts:272和backend/cli/src/config/config.ts:281需要可信插件源。