npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@deeplinker/link-app

v0.1.0

Published

Browser SDK for connecting third-party web apps to DeepLinker LinkApp.

Readme

@deeplinker/link-app

@deeplinker/link-app 是 DeepLinker 面向第三方 Web 应用的浏览器 SDK。

它的作用是把一个普通业务页面接入 DeepLinker,让 AI 不只是“看见页面”,而是能理解页面当前状态,并在用户授权的边界内调用页面声明过的业务动作。

它能做什么

接入 LinkApp 之后,你的 Web 应用可以获得这些能力:

  • 向 DeepLinker 暴露当前页面上下文,例如当前订单、选中的客户、正在编辑的 SQL、画布节点、筛选条件或业务状态。
  • 声明一组 AI 可以调用的受控工具,例如读取当前数据、添加备注、运行查询、更新画布、生成反馈或写入表单草稿。
  • 让工具调用继续运行在原页面里,复用当前用户的登录态、权限系统和业务后端。
  • 对有副作用的工具调用要求用户确认,避免 AI 未经授权修改数据。
  • 从页面主动发起一次 DeepLinker 助手运行,把当前页面状态带入对话。
  • 在页面中显示一个轻量状态 UI,让开发者和用户知道 LinkApp 是否已连接、工具是否正在执行、是否等待授权。

典型场景包括:

  • CRM、工单、订单、审批等业务系统,让 AI 基于当前记录给出分析,并调用页面动作写入备注或更新状态。
  • 数据看板和 BI 页面,让 AI 读取当前筛选条件、解释图表、生成查询或拉取局部数据。
  • 教学实验、SQL 编辑器、代码练习页面,让 AI 读取学生当前进度,并调用页面工具运行、检查、反馈。
  • 画布、流程图、白板类应用,让 AI 理解节点和连线,并通过受控工具创建、布局或选择元素。
  • 内部运营工具,让 AI 在既有权限边界内协助执行重复操作。

LinkApp 不会绕过你的应用权限。工具 handler 仍然在你的页面 JavaScript 中执行,调用的也是你原有的前端状态、API client 和用户会话。你需要决定暴露哪些工具、如何校验参数、哪些动作必须授权。

安装

pnpm add @deeplinker/link-app

@deeplinker/link-app 是 ESM 浏览器包,没有运行时 npm 依赖。

运行环境要求

SDK 需要运行在提供 window.viewlink 桥接对象的 DeepLinker 宿主环境里,例如 DeepLinker 浏览器或 WebView 环境。

如果页面在普通浏览器标签页中打开,SDK 可以被正常 import,但注册句柄会保持未激活状态,调用 requestAssistantRun() 会抛出 viewlink_bridge_unavailable

快速接入

import {
  registerApp,
  type LinkAppToolCallResult,
} from "@deeplinker/link-app";

const handle = registerApp({
  appName: "订单工作台",
  contextFields: [
    {
      key: "description",
      label: "使用说明",
      scope: "app",
      showInModel: true,
      value: [
        "订单工作台用于查看和处理当前订单。",
        "当用户询问当前订单时,可以读取 orderId、status 和 selectedItems。",
        "只有在用户明确要求写入备注时,才调用 add_order_note。",
      ].join("\n"),
    },
    {
      key: "orderId",
      label: "订单",
      showInModel: true,
      showIn: ["contextBar", "messageRef"],
    },
    {
      key: "status",
      label: "状态",
      showInModel: true,
      showIn: ["contextBar"],
    },
  ],
  tools: [
    {
      name: "add_order_note",
      description: "给当前订单添加一条内部备注。",
      parameters: {
        type: "object",
        properties: {
          text: { type: "string" },
        },
        required: ["text"],
        additionalProperties: false,
      },
      requiresAuthorization: true,
      timeoutMs: 10_000,
      handler: async (args): Promise<LinkAppToolCallResult> => {
        const text = typeof args === "object" && args && "text" in args
          ? String(args.text)
          : "";

        if (!text.trim()) {
          return {
            isError: true,
            content: [{ type: "text", text: "缺少备注内容。" }],
          };
        }

        const noteId = await addOrderNote(text);
        return {
          content: [{ type: "text", text: "备注已添加。" }],
          structuredContent: { noteId },
        };
      },
    },
  ],
}, {
  ui: { floatingStatus: true },
});

handle.setContext({
  orderId: {
    $viewlink: "fieldValue",
    modelValue: "ord_123",
    displayValue: "#123",
  },
  status: "awaiting_review",
});

当页面、路由或组件销毁时,调用 unregister()

handle.unregister();

核心概念

App 注册

registerApp() 用来告诉 DeepLinker:当前页面是什么应用,它有哪些上下文字段,以及 AI 可以调用哪些工具。

const handle = registerApp({
  appName: "订单工作台",
  contextFields: [],
  tools: [],
});

返回的 handle 用于更新上下文、主动发起助手运行,以及注销当前应用。

上下文

上下文是页面主动提供给 AI 的当前状态。它比截图或 DOM 读取更稳定,因为你可以直接提供业务语义。

handle.setContext({
  selectedRowCount: 3,
  currentFilter: {
    $viewlink: "fieldValue",
    modelValue: { status: "open", assignee: "me" },
    displayValue: "我的未处理订单",
  },
  staleSelection: { $viewlink: "clear" },
});

contextFields 声明稳定字段,setContext() 更新当前值。SDK 会对上下文更新做轻量 debounce。

字段常用配置:

  • key: 稳定字段 id。
  • label: 展示给用户看的短标签。
  • scope: "app" 表示长期说明,"state" 表示动态状态。
  • showInModel: 是否放入 AI 上下文。
  • showIn: 是否显示在 DeepLinker UI 中,例如 "contextBar""messageRef"

当 AI 需要结构化数据,但 UI 只适合展示短文本时,使用 $viewlink: "fieldValue"

{
  $viewlink: "fieldValue",
  modelValue: { id: "ord_123", risk: "high" },
  displayValue: "#123,高风险"
}

需要清除某个动态字段时,传入:

{ $viewlink: "clear" }

工具

工具是 AI 能够调用的页面能力。每个工具都应该是明确、可控、可校验的业务动作。

{
  name: "assign_order",
  description: "把当前订单分配给指定同事。",
  parameters: {
    type: "object",
    properties: {
      userId: { type: "string" },
    },
    required: ["userId"],
    additionalProperties: false,
  },
  requiresAuthorization: true,
  timeoutMs: 15_000,
  handler: async (args, context) => {
    const userId = String((args as { userId?: unknown }).userId ?? "");
    await assignOrder(userId);

    return {
      content: [{ type: "text", text: "订单已分配。" }],
      structuredContent: {
        callId: context.callId,
        runId: context.runId,
      },
    };
  },
}

工具返回值使用 LinkAppToolCallResult

return {
  content: [{ type: "text", text: "操作完成。" }],
  structuredContent: { updated: true },
};

对于预期内的业务失败,建议返回 isError: true,而不是直接抛异常:

return {
  isError: true,
  content: [{ type: "text", text: "订单已关闭,不能继续分配。" }],
};

如果 handler 抛异常或超过 timeoutMs,SDK 会自动转换成错误结果返回给 DeepLinker。

授权

对会修改数据、提交表单、发送消息、创建记录或触发外部副作用的工具,应设置:

requiresAuthorization: true

默认情况下,SDK 会用一个轻量浮层请求用户确认。你也可以自定义授权策略:

import {
  alwaysAllowPolicy,
  alwaysDenyPolicy,
  confirmPolicy,
  registerApp,
} from "@deeplinker/link-app";

registerApp(registration, {
  authorizationPolicy: confirmPolicy(),
});

registerApp(registration, {
  authorizationPolicy: async (call) => {
    if (call.name === "read_order") {
      return "allow";
    }

    return window.confirm(`允许 AI 调用 ${call.name}?`) ? "allow" : "deny";
  },
});

alwaysAllowPolicy() 只建议用于本地 demo 或只读工具。alwaysDenyPolicy() 主要用于测试。

从页面主动发起 AI 运行

页面可以用 requestAssistantRun() 请求当前 DeepLinker 聊天基于最新 LinkApp 上下文启动一次助手运行。

try {
  const response = await handle.requestAssistantRun(
    "请检查当前订单风险,并建议下一步处理动作。",
  );

  console.log(response.runId);
} catch (error) {
  console.error(error);
}

常见错误:

  • assistant_run_invalid_prompt: prompt 为空。
  • viewlink_bridge_unavailable: 页面没有连接到 DeepLinker。
  • assistant_run_bridge_unavailable: 当前 DeepLinker 宿主版本不支持该 API。
  • link_app_not_registered: 当前应用已经注销或尚未注册成功。

React 接入示例

import { useEffect, useRef } from "react";
import { registerApp, type LinkAppHandle } from "@deeplinker/link-app";

export function OrderPage({ order }: { order: { id: string; status: string } }) {
  const linkAppRef = useRef<LinkAppHandle | null>(null);

  useEffect(() => {
    const handle = registerApp({
      appName: "订单工作台",
      contextFields: [
        { key: "orderId", label: "订单", showInModel: true },
        { key: "status", label: "状态", showInModel: true },
      ],
    }, {
      ui: { floatingStatus: true },
    });

    linkAppRef.current = handle;
    return () => {
      handle.unregister();
      linkAppRef.current = null;
    };
  }, []);

  useEffect(() => {
    linkAppRef.current?.setContext({
      orderId: {
        $viewlink: "fieldValue",
        modelValue: order.id,
        displayValue: `#${order.id}`,
      },
      status: order.status,
    });
  }, [order.id, order.status]);

  return null;
}

浮动状态 UI

启用内置状态和授权浮层:

registerApp(registration, {
  ui: {
    floatingStatus: true,
    position: "bottom-right",
  },
});

也可以提供自定义渲染器:

registerApp(registration, {
  ui: {
    floatingStatus: true,
    renderer(state, actions, container) {
      container.textContent = state.active ? "已连接" : "等待 DeepLinker";

      return () => {
        container.textContent = "";
      };
    },
  },
});

自定义 renderer 会收到:

  • state.active: 是否已连接。
  • state.status: 当前注册状态。
  • state.calls: 最近工具调用记录。
  • state.pendingAuthorization: 等待用户授权的工具调用。
  • actions.approvePending(): 允许当前待授权调用。
  • actions.denyPending(): 拒绝当前待授权调用。
  • actions.clearCalls(): 清空已结束的调用记录。
  • actions.destroy(): 销毁浮动 UI。

API 概览

registerApp(registration, options?)

注册当前页面应用。

const handle = registerApp(registration, options);

registration 字段:

  • appName: 应用名称。
  • contextFields: 可选,上下文字段声明。
  • tools: 可选,工具声明和 handler。

options 字段:

  • ui.floatingStatus: 是否启用浮动状态 UI。
  • ui.position: 浮动 UI 位置,支持 "bottom-right""bottom-left""top-right""top-left"
  • ui.renderer: 自定义 UI renderer。
  • authorizationPolicy: 自定义工具授权策略。

handle.setContext(context)

更新当前页面上下文。

handle.setContext({
  currentTab: "risk",
  selectedOrderId: "ord_123",
});

handle.requestAssistantRun(prompt)

请求当前 DeepLinker 聊天启动一次助手运行。

await handle.requestAssistantRun("解释当前页面中的异常风险。");

handle.unregister()

注销当前 LinkApp。

handle.unregister();

handle.isActive

当前 LinkApp 是否已经与 DeepLinker 桥接成功。

if (handle.isActive) {
  console.log("LinkApp 已连接");
}

handle.linkAppInstanceId

当前注册实例 id。尚未注册成功时为 null

安全建议

  • 把工具参数当作不可信输入,永远在 handler 内做校验。
  • 工具描述要清晰、具体,让 AI 知道何时该调用、何时不该调用。
  • 默认优先暴露只读工具。
  • 修改数据或触发外部副作用的工具必须要求授权。
  • handler 应继续使用当前用户已有的登录态、权限和后端 API,不要在 SDK 层绕过权限。
  • 对预期内业务失败返回 isError: true,避免把正常业务状态伪装成系统异常。
  • 不要暴露过大的万能工具,例如 execute_any_requestrun_arbitrary_js
  • 不要把密钥、token 或不该进入模型上下文的敏感数据放进 setContext()