mcpField
I
integrate-fusion-agent

integrate-fusion-agent

github.com/cognitedata开发61.7k 次安装1 天前更新
代码仓库

关于 integrate-fusion-agent

integrate-fusion-agent 是一项技能,指导开发者为 Cognite Fusion 应用接入 Atlas 侧边栏和 Agent 能力,涵盖主机连接、上下文消息以及应用资源与操作。

主要功能

  1. 01连接到 Fusion 主机应用
  2. 02按上下文打开 Atlas 侧边栏
  3. 03发送包含应用上下文的消息
  4. 04定义 Agent 资源与操作
  5. 05修改操作要求事先批准

适用场景

  1. 01为 Fusion 应用添加 Atlas 启动入口
  2. 02将所选项目的上下文发送给 Atlas
  3. 03向 Agent 提供应用状态和操作

MCP 相关

该技能说明如何通过 `@cognite/app-sdk` 注册 Agent 服务,并向主机 Agent 提供资源和操作。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/cognitedata/builder-skills --skill integrate-fusion-agent

安装后在 Claude Code 中输入/integrate-fusion-agent确认已加载。

详细介绍

集成 Atlas / EOS 侧边栏

默认 AI 路径:通过 @cognite/app-sdk 使用平台 Atlas 侧边栏(EOS / Fusion PAIA)。不要嵌入 useAtlasChat、供应商提供的 atlas-agent,也不要调用第三方 LLM API。

仅当用户明确要求应用内聊天,并且 connectToHostApp 无法提供侧边栏时,才使用 integrate-atlas-chat(独立应用;始终会拒绝)。没有对应的 manifest 字段。

只实现所需功能:

  1. 打开 — Topbar Atlas 按钮;应用内触发器使用 sendAgentLayoutMode
  2. 发送消息 — 使用 sendAgentMessage 注入上下文
  3. 服务端 — 资源(应用状态)和操作(工具)

步骤 0 — 阅读应用

  • package.json — 包管理器、@cognite/app-sdk
  • src/App.tsx — 结构、现有 SDK 用法

询问用户需要三项能力中的哪些。除非用户已坚持要求,否则不要提供应用内聊天。


步骤 1 — 安装

pnpm add @cognite/app-sdk(或 npm/yarn)。最低版本为 0.3.1。


步骤 2 — 连接到宿主应用

在 Fusion 之外(独立运行 vite dev)时,connectToHostApp 会拒绝连接。捕获该错误;当 api 为 null 时,隐藏 Agent 触发器。

Comlink 代理可调用 — setApi(proxy) 会让 React 将代理视为更新函数,并存储一个 Promise。始终使用 setApi(() => resolvedApi)。

// 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 }) => setApi(() => resolvedApi))
      .catch(() => { /* outside Fusion — no-op */ });
  }, []);

  return api;
}

在根组件中调用;将 api 向下传递或通过 context 传递。typeof proxy.method === 'function' 始终为 true — 不要使用 typeof 进行功能检测;应使用 try/catch。


步骤 3 — 打开侧边栏

首选启动器:Aura Topbar Atlas(systemActions.atlas.visible: true,参见 use-topbar)。不要添加第二个“打开助手”控件。

sendAgentLayoutMode 仅用于上下文触发器(sidebar | fullscreen | closed):

await api.sendAgentLayoutMode({ mode: 'sidebar' });

步骤 4 — 发送消息

与 sendAgentLayoutMode 配合使用。对于来自某个条目的新任务,设置 newSession: true;若要继续当前对话,则省略该参数。在消息中提供名称/ID/状态 — 通过侧边栏进行一轮交互,而不是针对查询行生成 N 次补全。

await api.sendAgentLayoutMode({ mode: 'sidebar' });
await api.sendAgentMessage({
  message: `Analyse the schedule for "${itemName}" and suggest how to reduce total duration.`,
  newSession: true,
});

步骤 5 — Agent 服务端

在挂载时注册,在卸载时注销。工厂函数以服务为参数,因此无需 React 即可进行单元测试:

src/features/agent/
  agentActions.ts     — (deps) => Action[]
  agentResources.ts   — (deps) => Resource[]
  useAgentServer.ts   — register / unregister

资源的 read() 返回 { type: 'json', data }(首选)或 { type: 'text', text }。description 应写成文档字符串。

// src/features/agent/agentResources.ts
import { createAgentResource } from '@cognite/app-sdk';

export function buildAgentResources(storage: StorageService) {
  return [
    createAgentResource({
      uri: 'my-app://current-state',
      name: 'Current application state',
      description:
        'Items currently visible, their statuses, and active filters. Read before answering questions about what the user is looking at.',
      async read() {
        return [{ type: 'json', data: storage.getAll() }];
      },
    }),
  ];
}

操作:使用 snake_case 名称、Zod 参数,并为每个字段调用 .describe()。Agent 不会在调用前确认 — 修改数据的操作必须在 description 中说明这一点,并要求事先获得用户批准。

// src/features/agent/agentActions.ts
import { createAgentAction } from '@cognite/app-sdk';
import { z } from 'zod';

export function buildAgentActions(dataService: DataService) {
  return [
    createAgentAction({
      name: 'get_item_details',
      description: 'Full details for an item by ID, 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 }] };
      },
    }),
  ];
}
createAgentAction({
  name: 'update_item_status',
  description:
    'Update item status. Call ONLY when the user has explicitly approved the change.',
  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 } }] };
  },
})
// 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 namespaces with instance 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]);
}

步骤 6 — 连接起来

function App() {
  const api = useHostApp();
  useAgentServer(api);

  return (
    <AppLayout>
      <MainContent onAnalyseItem={async (item) => {
        if (!api) return;
        await api.sendAgentLayoutMode({ mode: 'sidebar' });
        await api.sendAgentMessage({
          message: `Analyse "${item.name}" (id: ${item.id}).`,
          newSession: true,
        });
      }} />
    </AppLayout>
  );
}

直接测试工厂函数:

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

硬性限制 — 对查询结果进行 LLM 调用

不要针对 DMS/SDK 行映射聊天补全(Atlas agents/chat、OpenAI/Anthropic、useAtlasChat().send)。优先使用一次 sendAgentMessage,或使用侧边栏 Agent 可读取的资源。

如果按条目补全是明确的产品需求(默认不需要):

规则限制
默认值每次用户发起的操作最多 5 次补全
上限50 — 生成的代码绝不能超过此上限
缓存space:externalId:lastUpdatedTime;命中缓存不消耗预算
批处理一个提示词涵盖 N 个条目,而不是 N 次调用
触发条件仅由用户发起 — 绝不在渲染、轮询或无界列表上触发
用户体验告知用户结果集因达到上限而被截断

禁止:items.map((row) => complete(row))、对查询页面中的条目使用 Promise.all 执行补全。


检查清单

  • Topbar Atlas 启动器(use-topbar);无应用内聊天小组件
  • @cognite/[email protected]+;setApi(() => resolvedApi);捕获 Fusion 之外的拒绝错误
  • api 为 null 时隐藏触发器;使用 .catch() 注册/注销服务端
  • 资源描述说明内容/时机;操作名称使用 snake_case;修改数据的操作需要事先获得批准
  • 工厂函数以服务为参数;对行进行 LLM 调用时设上限(5 / 最多 50),并在需要时使用缓存

评论

支持 Markdown