integrate-fusion-agent
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseIntegrate Fusion Agent Panel
集成Fusion Agent面板
Wire a Flows/Dune app into the Fusion built-in PAIA agent using .
@cognite/app-sdkThere are three independent capabilities — implement only the ones needed:
- Open the agent panel — a button that shows the sidebar/fullscreen agent UI
- Send the agent a message — inject context into the chat (e.g. on item click)
- Register an agent server — expose app state (resources) and actions the agent can call
使用将Flows/Dune应用接入Fusion内置的PAIA agent。
@cognite/app-sdk这里包含三个独立的功能模块——只需实现所需的模块即可:
- 打开agent面板——一个用于显示侧边栏/全屏agent界面的按钮
- 向agent发送消息——在聊天中注入上下文信息(例如点击项目时)
- 注册agent服务器——暴露应用状态(资源)以及agent可调用的操作
Step 0 — Understand the app
步骤0 — 了解应用
Before writing any code, read:
- — detect package manager and whether
package.jsonis already installed@cognite/app-sdk - (or main entry) — understand current structure, existing SDK usage
src/App.tsx
Ask the user which of the three capabilities they need if it's not clear from context.
在编写任何代码之前,请先阅读:
- ——检测包管理器以及是否已安装
package.json@cognite/app-sdk - (或主入口文件)——了解当前结构和已有的SDK使用情况
src/App.tsx
如果上下文未明确说明,请询问用户需要上述三个功能中的哪几个。
Step 1 — Install the SDK
步骤1 — 安装SDK
If is not already in , install it:
@cognite/app-sdkpackage.jsonshell
pnpm add @cognite/app-sdk # or npm/yarn depending on the appMinimum required version:
0.3.1如果中尚未包含,请安装它:
package.json@cognite/app-sdkshell
pnpm add @cognite/app-sdk # 或根据应用使用npm/yarn最低要求版本:
0.3.1Step 2 — Connect to the host app
步骤2 — 连接到宿主应用
All capabilities require a 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 ).
HostAppAPIvite devPattern 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 at the root of your app and pass down (or put it in context). When is , all agent UI triggers should be hidden or disabled — not shown as broken.
useHostApp()apiapinull所有功能都需要实例。在组件挂载时获取一次,并将其存储在React状态或上下文当中。务必捕获拒绝状态——当在Fusion外部运行时(例如独立的环境),SDK会抛出异常。
HostAppAPIvite devReact应用的实现模式:
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;
}在应用的根组件调用,并将向下传递(或放入上下文)。当为时,所有agent UI触发元素应隐藏或禁用——不要显示为不可用状态。
useHostApp()apiapinullStep 3 — Opening the agent panel
步骤3 — 打开agent面板
Wire a persistent toolbar button (or equivalent trigger) to .
api.sendAgentLayoutModetypescript
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 is not null — agent features are unavailable outside Fusion.
apitsx
{api && (
<button onClick={() => api.sendAgentLayoutMode({ mode: 'sidebar' })}>
Open Assistant
</button>
)}将持久化工具栏按钮(或等效触发元素)与关联。
api.sendAgentLayoutModetypescript
import { type AgentLayoutPayload } from '@cognite/app-sdk';
// 以侧边栏形式打开(最常用)
await api.sendAgentLayoutMode({ mode: 'sidebar' });
// 其他模式
await api.sendAgentLayoutMode({ mode: 'fullscreen' });
await api.sendAgentLayoutMode({ mode: 'closed' });仅当不为null时才渲染该按钮——在Fusion外部agent功能不可用。
apitsx
{api && (
<button onClick={() => api.sendAgentLayoutMode({ mode: 'sidebar' })}>
Open Assistant
</button>
)}Step 4 — Sending the agent a message
步骤4 — 向agent发送消息
Use on contextual triggers (e.g. "Analyse this item" button). Always pair it with so the panel is visible.
sendAgentMessagesendAgentLayoutModetypescript
// 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 when the user is starting a new task from a specific item. Omit it when you want to continue an existing conversation.
newSession: trueThe message text should include relevant context the agent can act on immediately — item names, IDs, current state summary.
在上下文触发事件(例如“分析此项目”按钮)中使用。务必搭配使用,以确保面板可见。
sendAgentMessagesendAgentLayoutModetypescript
// 打开侧边栏然后注入上下文
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 as you would a function docstring — the agent reads it to decide when to fetch the resource.
descriptiontypescript
// 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 returns an array of content parts:
read()- — structured data (preferred; agent reasons over it directly)
{ type: 'json', data: unknown } - — free-form text
{ type: 'text', text: string }
资源是agent查看应用状态的窗口。将编写为函数文档字符串——agent会读取该内容以决定何时获取资源。
descriptiontypescript
// 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()- — 结构化数据(优先选择;agent可直接对其进行推理)
{ type: 'json', data: unknown } - — 自由格式文本
{ type: 'text', text: string }
Actions
操作
Actions are tools the agent can invoke. Use names and Zod for parameter schemas. The on each field is the agent's documentation.
snake_case.describe()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 that the action is destructive, and require the user to have approved before the agent calls it.
descriptiontypescript
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可调用的工具。使用命名,并使用Zod定义参数 schema。每个字段的内容是agent的文档说明。
snake_case.describe()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在调用操作前不会向用户请求确认——因此对于写入数据的操作要格外谨慎。在中明确说明该操作具有破坏性,并要求用户在agent调用前已批准。
descriptiontypescript
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 near the root of your component tree, after is available.
useAgentServer(api)apitypescript
// 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)apiStep 6 — Wire it all together
步骤6 — 整合所有功能
Call at the root, pass to , and thread it down to any UI triggers:
useHostApp()apiuseAgentServertsx
// 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>
);
}在根组件调用,将传递给,并将其传递给所有UI触发元素:
useHostApp()apiuseAgentServertsx
// 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 | | Effect |
|---|---|---|
| Inside Fusion | Resolves with | All features work |
Standalone | Rejects | Agent features silently disabled |
This is handled by the hook above — no extra conditionals needed elsewhere.
useHostApp| 环境 | | 影响 |
|---|---|---|
| Fusion内部 | 返回包含 | 所有功能正常工作 |
独立 | 拒绝请求 | Agent功能自动禁用 |
上述钩子已处理此逻辑——无需在其他地方添加额外条件判断。
useHostAppTesting
测试
Because and are pure factories that accept services as arguments, test them directly without mounting React:
buildAgentActionsbuildAgentResourcestypescript
// 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' });由于和是接受服务作为参数的纯工厂函数,无需挂载React即可直接测试:
buildAgentActionsbuildAgentResourcestypescript
// 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)setApi(resolvedApi)
存储的是Promise而非代理
setApi(resolvedApi)Comlink proxies are callable objects. React's setter, when given a function, calls it as to compute the new state. Because a Comlink proxy responds to function calls (forwarding them to the remote), causes React to invoke the proxy, and the resulting Promise becomes the state value.
useStatefn(prevState)setApi(proxy)Symptom: appears non-null (a Promise is truthy), but calling or checking returns nonsense.
apiapi.sendAgentLayoutMode(...)typeof api.sendAgentLayoutModeFix: Always use the updater form: .
setApi(() => resolvedApi)Comlink代理是可调用对象。React的 setter在接收函数时,会调用来计算新状态。由于Comlink代理会响应函数调用(将其转发到远程),会导致React调用代理,进而将生成的Promise作为状态值存储。
useStatefn(prevState)setApi(proxy)症状: 看起来非空(Promise为真值),但调用或检查会返回无意义的结果。
apiapi.sendAgentLayoutMode(...)typeof api.sendAgentLayoutMode修复方案: 始终使用更新器形式:。
setApi(() => resolvedApi)typeof proxy.method === 'function'
is always true
typeof proxy.method === 'function'truetypeof proxy.method === 'function'
始终为true
typeof proxy.method === 'function'trueComlink Proxy objects return for any property access via . This means you cannot use guards to detect whether a method is actually supported by the host. Use or on the call instead.
'function'typeoftypeoftry/catch.catch()Comlink代理对象通过访问任何属性时都会返回。这意味着你无法使用守卫来检测宿主是否实际支持某个方法。请改用或在调用时使用。
typeof'function'typeoftry/catch.catch()Checklist
检查清单
- installed
@cognite/app-sdk@0.3.1+ - hook uses
useHostApp— NOTsetApi(() => resolvedApi)setApi(resolvedApi) - hook catches rejection (outside Fusion), stores
useHostAppin stateapi - Agent UI buttons only render when is not null
api - registered on mount, unregistered on unmount
useAgentServer - and
registerAgentServercalls haveunregisterAgentServerhandlers.catch() - Resource fields explain what data is returned and when to read it
description - Action fields are
namesnake_case - Mutating actions warn in their that confirmation is required
description - Services injected into action/resource factories (not imported directly) — enables unit testing
- 已安装
@cognite/app-sdk@0.3.1+ - 钩子使用
useHostApp——而非setApi(() => resolvedApi)setApi(resolvedApi) - 钩子捕获拒绝状态(在Fusion外部),并将
useHostApp存储在状态中api - Agent UI按钮仅在不为null时才渲染
api - 在挂载时注册,卸载时注销
useAgentServer - 和
registerAgentServer调用带有unregisterAgentServer处理函数.catch() - 资源的字段说明了返回的数据内容以及读取时机
description - 操作的字段使用
name命名snake_case - 变更类操作在中警告需要确认
description - 服务注入到操作/资源工厂函数中(而非直接导入)——支持单元测试