integrate-fusion-agent

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Integrate Fusion Agent Panel

集成Fusion Agent面板

Wire a Flows/Dune app into the Fusion built-in PAIA agent using
@cognite/app-sdk
.
There are three independent capabilities — implement only the ones needed:
  1. Open the agent panel — a button that shows the sidebar/fullscreen agent UI
  2. Send the agent a message — inject context into the chat (e.g. on item click)
  3. Register an agent server — expose app state (resources) and actions the agent can call

使用
@cognite/app-sdk
将Flows/Dune应用接入Fusion内置的PAIA agent。
这里包含三个独立的功能模块——只需实现所需的模块即可:
  1. 打开agent面板——一个用于显示侧边栏/全屏agent界面的按钮
  2. 向agent发送消息——在聊天中注入上下文信息(例如点击项目时)
  3. 注册agent服务器——暴露应用状态(资源)以及agent可调用的操作

Step 0 — Understand the app

步骤0 — 了解应用

Before writing any code, read:
  • package.json
    — detect package manager and whether
    @cognite/app-sdk
    is already installed
  • src/App.tsx
    (or main entry) — understand current structure, existing SDK usage
Ask the user which of the three capabilities they need if it's not clear from context.

在编写任何代码之前,请先阅读:
  • package.json
    ——检测包管理器以及是否已安装
    @cognite/app-sdk
  • src/App.tsx
    (或主入口文件)——了解当前结构和已有的SDK使用情况
如果上下文未明确说明,请询问用户需要上述三个功能中的哪几个。

Step 1 — Install the SDK

步骤1 — 安装SDK

If
@cognite/app-sdk
is not already in
package.json
, install it:
shell
pnpm add @cognite/app-sdk     # or npm/yarn depending on the app
Minimum required version:
0.3.1

如果
package.json
中尚未包含
@cognite/app-sdk
,请安装它:
shell
pnpm add @cognite/app-sdk     # 或根据应用使用npm/yarn
最低要求版本:
0.3.1

Step 2 — Connect to the host app

步骤2 — 连接到宿主应用

All capabilities require a
HostAppAPI
instance. Obtain it once on mount and store it in React state or context. Always catch the rejection — the SDK throws when running outside Fusion (e.g. standalone
vite dev
).
Pattern for React apps:
typescript
// src/hooks/useHostApp.ts
import { useState, useEffect } from 'react';
import { connectToHostApp, type HostAppAPI } from '@cognite/app-sdk';

export function useHostApp(): HostAppAPI | null {
  const [api, setApi] = useState<HostAppAPI | null>(null);

  useEffect(() => {
    connectToHostApp({ applicationName: 'my-app' })
      .then(({ api: resolvedApi }) => {
        // IMPORTANT: use the updater form here. Comlink proxies are callable
        // objects, so setApi(proxy) causes React to invoke the proxy as a
        // state-updater function — storing a Promise instead of the proxy.
        // setApi(() => proxy) returns the proxy as the new state value.
        setApi(() => resolvedApi);
      })
      .catch(() => {
        // Running outside Fusion — agent features disabled, no-op
      });
  }, []);

  return api;
}
Call
useHostApp()
at the root of your app and pass
api
down (or put it in context). When
api
is
null
, all agent UI triggers should be hidden or disabled — not shown as broken.

所有功能都需要
HostAppAPI
实例。在组件挂载时获取一次,并将其存储在React状态或上下文当中。务必捕获拒绝状态——当在Fusion外部运行时(例如独立的
vite dev
环境),SDK会抛出异常。
React应用的实现模式:
typescript
// src/hooks/useHostApp.ts
import { useState, useEffect } from 'react';
import { connectToHostApp, type HostAppAPI } from '@cognite/app-sdk';

export function useHostApp(): HostAppAPI | null {
  const [api, setApi] = useState<HostAppAPI | null>(null);

  useEffect(() => {
    connectToHostApp({ applicationName: 'my-app' })
      .then(({ api: resolvedApi }) => {
        // 重要提示:请使用更新器形式。Comlink代理是可调用对象,
        // 因此setApi(proxy)会导致React将代理作为状态更新函数调用——存储Promise而非代理。
        // setApi(() => proxy)会将代理作为新的状态值返回。
        setApi(() => resolvedApi);
      })
      .catch(() => {
        // 在Fusion外部运行——agent功能禁用,无操作
      });
  }, []);

  return api;
}
在应用的根组件调用
useHostApp()
,并将
api
向下传递(或放入上下文)。当
api
null
时,所有agent UI触发元素应隐藏或禁用——不要显示为不可用状态。

Step 3 — Opening the agent panel

步骤3 — 打开agent面板

Wire a persistent toolbar button (or equivalent trigger) to
api.sendAgentLayoutMode
.
typescript
import { type AgentLayoutPayload } from '@cognite/app-sdk';

// Open as sidebar (most common)
await api.sendAgentLayoutMode({ mode: 'sidebar' });

// Other modes
await api.sendAgentLayoutMode({ mode: 'fullscreen' });
await api.sendAgentLayoutMode({ mode: 'closed' });
The button should only render when
api
is not null — agent features are unavailable outside Fusion.
tsx
{api && (
  <button onClick={() => api.sendAgentLayoutMode({ mode: 'sidebar' })}>
    Open Assistant
  </button>
)}

将持久化工具栏按钮(或等效触发元素)与
api.sendAgentLayoutMode
关联。
typescript
import { type AgentLayoutPayload } from '@cognite/app-sdk';

// 以侧边栏形式打开(最常用)
await api.sendAgentLayoutMode({ mode: 'sidebar' });

// 其他模式
await api.sendAgentLayoutMode({ mode: 'fullscreen' });
await api.sendAgentLayoutMode({ mode: 'closed' });
仅当
api
不为null时才渲染该按钮——在Fusion外部agent功能不可用。
tsx
{api && (
  <button onClick={() => api.sendAgentLayoutMode({ mode: 'sidebar' })}>
    Open Assistant
  </button>
)}

Step 4 — Sending the agent a message

步骤4 — 向agent发送消息

Use
sendAgentMessage
on contextual triggers (e.g. "Analyse this item" button). Always pair it with
sendAgentLayoutMode
so the panel is visible.
typescript
// Open sidebar then inject context
await api.sendAgentLayoutMode({ mode: 'sidebar' });
await api.sendAgentMessage({
  message: `Analyse the schedule for "${itemName}" and suggest how to reduce total duration.`,
  newSession: true,   // clears previous conversation — appropriate for contextual entry points
});
Use
newSession: true
when the user is starting a new task from a specific item. Omit it when you want to continue an existing conversation.
The message text should include relevant context the agent can act on immediately — item names, IDs, current state summary.

在上下文触发事件(例如“分析此项目”按钮)中使用
sendAgentMessage
。务必搭配
sendAgentLayoutMode
使用,以确保面板可见。
typescript
// 打开侧边栏然后注入上下文
await api.sendAgentLayoutMode({ mode: 'sidebar' });
await api.sendAgentMessage({
  message: `分析"${itemName}"的进度计划,并提出缩短总时长的建议。`,
  newSession: true,   // 清除之前的对话——适合上下文入口点
});
当用户从特定项目开始新任务时,使用
newSession: true
。如果希望继续现有对话,则省略该参数。
消息文本应包含agent可立即处理的相关上下文——项目名称、ID、当前状态摘要等。

Step 5 — Registering an agent server

步骤5 — 注册agent服务器

An agent server exposes resources (read-only app state the agent can read) and actions (tools the agent can invoke). Register once on mount, unregister on unmount.
agent服务器会暴露资源(agent可读取的只读应用状态)和操作(agent可调用的工具)。在组件挂载时注册一次,卸载时注销。

Recommended file structure

推荐的文件结构

Separate concerns so each piece is independently testable:
src/features/agent/
  agentActions.ts     — pure factory: (deps) => Action[]
  agentResources.ts   — pure factory: (deps) => Resource[]
  useAgentServer.ts   — useEffect lifecycle hook; calls the factories and registers
分离关注点,使每个部分都可独立测试:
src/features/agent/
  agentActions.ts     — 纯工厂函数:(deps) => Action[]
  agentResources.ts   — 纯工厂函数:(deps) => Resource[]
  useAgentServer.ts   — useEffect生命周期钩子;调用工厂函数并完成注册

Resources

资源

Resources are the agent's window into app state. Write
description
as you would a function docstring — the agent reads it to decide when to fetch the resource.
typescript
// src/features/agent/agentResources.ts
import { createAgentResource } from '@cognite/app-sdk';
import type { StorageService } from '../storage/StorageService';

export function buildAgentResources(storage: StorageService) {
  return [
    createAgentResource({
      uri: 'my-app://current-state',
      name: 'Current application state',
      description:
        'The current list of items visible in the app, their statuses, and any active filters. Read this before answering questions about what the user is looking at.',
      async read() {
        const data = storage.getAll();
        return [{ type: 'json', data }];
      },
    }),
  ];
}
Each resource's
read()
returns an array of content parts:
  • { type: 'json', data: unknown }
    — structured data (preferred; agent reasons over it directly)
  • { type: 'text', text: string }
    — free-form text
资源是agent查看应用状态的窗口。将
description
编写为函数文档字符串——agent会读取该内容以决定何时获取资源。
typescript
// src/features/agent/agentResources.ts
import { createAgentResource } from '@cognite/app-sdk';
import type { StorageService } from '../storage/StorageService';

export function buildAgentResources(storage: StorageService) {
  return [
    createAgentResource({
      uri: 'my-app://current-state',
      name: 'Current application state',
      description:
        '应用中当前可见的项目列表、它们的状态以及任何活跃的筛选条件。在回答用户正在查看的内容相关问题前,请读取此资源。',
      async read() {
        const data = storage.getAll();
        return [{ type: 'json', data }];
      },
    }),
  ];
}
每个资源的
read()
方法返回一个内容部分数组:
  • { type: 'json', data: unknown }
    — 结构化数据(优先选择;agent可直接对其进行推理)
  • { type: 'text', text: string }
    — 自由格式文本

Actions

操作

Actions are tools the agent can invoke. Use
snake_case
names and Zod for parameter schemas. The
.describe()
on each field is the agent's documentation.
typescript
// src/features/agent/agentActions.ts
import { createAgentAction } from '@cognite/app-sdk';
import { z } from 'zod';
import type { DataService } from '../data/DataService';

export function buildAgentActions(dataService: DataService) {
  return [
    createAgentAction({
      name: 'get_item_details',
      description: 'Retrieve full details for a specific item by ID. Returns all fields including history.',
      parameters: z.object({
        item_id: z.string().describe('The ID of the item to retrieve'),
      }),
      async handler({ item_id }) {
        const item = await dataService.getItem(item_id);
        return { content: [{ type: 'json', data: item }] };
      },
    }),
  ];
}
Mutating actions: The agent does NOT ask the user for confirmation before calling actions — so use caution with actions that write data. Be explicit in the
description
that the action is destructive, and require the user to have approved before the agent calls it.
typescript
createAgentAction({
  name: 'update_item_status',
  description:
    'Update the status of an item. Call this ONLY when the user has explicitly approved the change. The UI updates immediately.',
  parameters: z.object({
    item_id: z.string().describe('The item to update'),
    status: z.enum(['active', 'closed', 'pending']).describe('The new status'),
  }),
  async handler({ item_id, status }) {
    storage.updateStatus(item_id, status);
    return { content: [{ type: 'json', data: { success: true } }] };
  },
})
操作是agent可调用的工具。使用
snake_case
命名,并使用Zod定义参数 schema。每个字段的
.describe()
内容是agent的文档说明。
typescript
// src/features/agent/agentActions.ts
import { createAgentAction } from '@cognite/app-sdk';
import { z } from 'zod';
import type { DataService } from '../data/DataService';

export function buildAgentActions(dataService: DataService) {
  return [
    createAgentAction({
      name: 'get_item_details',
      description: '通过ID检索特定项目的完整详情。返回包括历史记录在内的所有字段。',
      parameters: z.object({
        item_id: z.string().describe('要检索的项目ID'),
      }),
      async handler({ item_id }) {
        const item = await dataService.getItem(item_id);
        return { content: [{ type: 'json', data: item }] };
      },
    }),
  ];
}
变更类操作: agent在调用操作前不会向用户请求确认——因此对于写入数据的操作要格外谨慎。在
description
中明确说明该操作具有破坏性,并要求用户在agent调用前已批准。
typescript
createAgentAction({
  name: 'update_item_status',
  description:
    '更新项目的状态。仅当用户明确批准更改时才可调用此操作。UI会立即更新。',
  parameters: z.object({
    item_id: z.string().describe('要更新的项目'),
    status: z.enum(['active', 'closed', 'pending']).describe('新状态'),
  }),
  async handler({ item_id, status }) {
    storage.updateStatus(item_id, status);
    return { content: [{ type: 'json', data: { success: true } }] };
  },
})

Lifecycle hook

生命周期钩子

typescript
// src/features/agent/useAgentServer.ts
import { useEffect } from 'react';
import { createAgentServer, registerAgentServer, type HostAppAPI } from '@cognite/app-sdk';
import { buildAgentActions } from './agentActions';
import { buildAgentResources } from './agentResources';
import { useStorageService } from '../storage/StorageServiceContext';
import { useDataService } from '../data/DataServiceContext';

export function useAgentServer(api: HostAppAPI | null): void {
  const storage = useStorageService();
  const dataService = useDataService();

  useEffect(() => {
    if (!api) return;

    const server = createAgentServer({
      uri: 'my-app',   // namespaced by Fusion with instance ID — no need to be globally unique
      actions: buildAgentActions(dataService),
      resources: buildAgentResources(storage),
    });

    void registerAgentServer(api, server).catch((err: unknown) => {
      console.warn('[agent] registerAgentServer failed:', err);
    });

    return () => {
      void api.unregisterAgentServer('my-app').catch((err: unknown) => {
        console.warn('[agent] unregisterAgentServer failed:', err);
      });
    };
  }, [api, storage, dataService]);
}
Call
useAgentServer(api)
near the root of your component tree, after
api
is available.

typescript
// src/features/agent/useAgentServer.ts
import { useEffect } from 'react';
import { createAgentServer, registerAgentServer, type HostAppAPI } from '@cognite/app-sdk';
import { buildAgentActions } from './agentActions';
import { buildAgentResources } from './agentResources';
import { useStorageService } from '../storage/StorageServiceContext';
import { useDataService } from '../data/DataServiceContext';

export function useAgentServer(api: HostAppAPI | null): void {
  const storage = useStorageService();
  const dataService = useDataService();

  useEffect(() => {
    if (!api) return;

    const server = createAgentServer({
      uri: 'my-app',   // Fusion会使用实例ID进行命名空间隔离——无需全局唯一
      actions: buildAgentActions(dataService),
      resources: buildAgentResources(storage),
    });

    void registerAgentServer(api, server).catch((err: unknown) => {
      console.warn('[agent] registerAgentServer failed:', err);
    });

    return () => {
      void api.unregisterAgentServer('my-app').catch((err: unknown) => {
        console.warn('[agent] unregisterAgentServer failed:', err);
      });
    };
  }, [api, storage, dataService]);
}
在组件树的根附近调用
useAgentServer(api)
,等待
api
可用后执行。

Step 6 — Wire it all together

步骤6 — 整合所有功能

Call
useHostApp()
at the root, pass
api
to
useAgentServer
, and thread it down to any UI triggers:
tsx
// src/App.tsx
function App() {
  const api = useHostApp();
  useAgentServer(api);   // registers resources + actions when api is ready

  return (
    <AppLayout>
      <MainContent />
      {api && (
        <ToolbarButton onClick={() => api.sendAgentLayoutMode({ mode: 'sidebar' })}>
          Open Assistant
        </ToolbarButton>
      )}
    </AppLayout>
  );
}

在根组件调用
useHostApp()
,将
api
传递给
useAgentServer
,并将其传递给所有UI触发元素:
tsx
// src/App.tsx
function App() {
  const api = useHostApp();
  useAgentServer(api);   // 当api就绪时注册资源和操作

  return (
    <AppLayout>
      <MainContent />
      {api && (
        <ToolbarButton onClick={() => api.sendAgentLayoutMode({ mode: 'sidebar' })}>
          Open Assistant
        </ToolbarButton>
      )}
    </AppLayout>
  );
}

Dev vs. production

开发环境 vs 生产环境

Environment
connectToHostApp
Effect
Inside FusionResolves with
{ api }
All features work
Standalone
vite dev
RejectsAgent features silently disabled
This is handled by the
useHostApp
hook above — no extra conditionals needed elsewhere.

环境
connectToHostApp
行为
影响
Fusion内部返回包含
{ api }
的解析结果
所有功能正常工作
独立
vite dev
环境
拒绝请求Agent功能自动禁用
上述
useHostApp
钩子已处理此逻辑——无需在其他地方添加额外条件判断。

Testing

测试

Because
buildAgentActions
and
buildAgentResources
are pure factories that accept services as arguments, test them directly without mounting React:
typescript
// agentActions.test.ts
const mockDataService = { getItem: vi.fn().mockResolvedValue({ id: '1', name: 'Test' }) };
const [getItemAction] = buildAgentActions(mockDataService);

const result = await getItemAction.handler({ item_id: '1' });
expect(result.content[0].data).toEqual({ id: '1', name: 'Test' });

由于
buildAgentActions
buildAgentResources
是接受服务作为参数的纯工厂函数,无需挂载React即可直接测试:
typescript
// agentActions.test.ts
const mockDataService = { getItem: vi.fn().mockResolvedValue({ id: '1', name: 'Test' }) };
const [getItemAction] = buildAgentActions(mockDataService);

const result = await getItemAction.handler({ item_id: '1' });
expect(result.content[0].data).toEqual({ id: '1', name: 'Test' });

Known pitfalls

常见陷阱

setApi(resolvedApi)
stores a Promise, not the proxy

setApi(resolvedApi)
存储的是Promise而非代理

Comlink proxies are callable objects. React's
useState
setter, when given a function, calls it as
fn(prevState)
to compute the new state. Because a Comlink proxy responds to function calls (forwarding them to the remote),
setApi(proxy)
causes React to invoke the proxy, and the resulting Promise becomes the state value.
Symptom:
api
appears non-null (a Promise is truthy), but calling
api.sendAgentLayoutMode(...)
or checking
typeof api.sendAgentLayoutMode
returns nonsense.
Fix: Always use the updater form:
setApi(() => resolvedApi)
.
Comlink代理是可调用对象。React的
useState
setter在接收函数时,会调用
fn(prevState)
来计算新状态。由于Comlink代理会响应函数调用(将其转发到远程),
setApi(proxy)
会导致React调用代理,进而将生成的Promise作为状态值存储。
症状:
api
看起来非空(Promise为真值),但调用
api.sendAgentLayoutMode(...)
或检查
typeof api.sendAgentLayoutMode
会返回无意义的结果。
修复方案: 始终使用更新器形式:
setApi(() => resolvedApi)

typeof proxy.method === 'function'
is always
true

typeof proxy.method === 'function'
始终为
true

Comlink Proxy objects return
'function'
for any property access via
typeof
. This means you cannot use
typeof
guards to detect whether a method is actually supported by the host. Use
try/catch
or
.catch()
on the call instead.

Comlink代理对象通过
typeof
访问任何属性时都会返回
'function'
。这意味着你无法使用
typeof
守卫来检测宿主是否实际支持某个方法。请改用
try/catch
或在调用时使用
.catch()

Checklist

检查清单

  • @cognite/app-sdk@0.3.1+
    installed
  • useHostApp
    hook uses
    setApi(() => resolvedApi)
    — NOT
    setApi(resolvedApi)
  • useHostApp
    hook catches rejection (outside Fusion), stores
    api
    in state
  • Agent UI buttons only render when
    api
    is not null
  • useAgentServer
    registered on mount, unregistered on unmount
  • registerAgentServer
    and
    unregisterAgentServer
    calls have
    .catch()
    handlers
  • Resource
    description
    fields explain what data is returned and when to read it
  • Action
    name
    fields are
    snake_case
  • Mutating actions warn in their
    description
    that confirmation is required
  • Services injected into action/resource factories (not imported directly) — enables unit testing
  • 已安装
    @cognite/app-sdk@0.3.1+
  • useHostApp
    钩子使用
    setApi(() => resolvedApi)
    ——而非
    setApi(resolvedApi)
  • useHostApp
    钩子捕获拒绝状态(在Fusion外部),并将
    api
    存储在状态中
  • Agent UI按钮仅在
    api
    不为null时才渲染
  • useAgentServer
    在挂载时注册,卸载时注销
  • registerAgentServer
    unregisterAgentServer
    调用带有
    .catch()
    处理函数
  • 资源的
    description
    字段说明了返回的数据内容以及读取时机
  • 操作的
    name
    字段使用
    snake_case
    命名
  • 变更类操作在
    description
    中警告需要确认
  • 服务注入到操作/资源工厂函数中(而非直接导入)——支持单元测试