openscienceScience
§ · Nested detail page

插件 Hooks 与前端 Artifact 渲染 深度解析

这一页拆扩展面:插件能接入 chat、tool、permission、compaction;前端 artifact renderer 用 registry 让科学结果可视化不污染聊天路径。

§ 00 · IMPORT CHAIN

导入链与契约

插件输入在 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 风格。

src/tooling/plugin/src/index.ts · L26-35TypeScript
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>
§ 01 · CORE IMPL

核心实现,逐段拆

▸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 分别包住工具执行前后。它的扩展点覆盖了模型、工具和治理三条线。

src/tooling/plugin/src/index.ts · L148-226TypeScript
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。这样新增渲染器不需要改聊天主路径。

src/frontend/workspace/src/science/renderers/registry.ts · L13-67TypeScript
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 */
§ 02 · KEY FLOW

关键流程

Plugin package → Hooks → chat/tool/permission interception Science artifact → renderer registry → inline workspace view

扩展系统的优点是两端都很薄。插件只需要返回 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 串成整体;子页面只展开其中一个闭环,避免把所有源码证据挤成行号清单。

§ 03 · EDGE CASES

边界 / 失败模式

  • 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 需要可信插件源。