@deeplinker/link-app
v0.1.0
Published
Browser SDK for connecting third-party web apps to DeepLinker LinkApp.
Maintainers
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_request或run_arbitrary_js。 - 不要把密钥、token 或不该进入模型上下文的敏感数据放进
setContext()。
