Skip to content

JSAPI ​

在你的页面(浏览器端)里引入:

html
<script src="https://<你的机灵兔部署域名>/api/open/jsapi/jlt.js"></script>

方法速查 ​

方法需要能力说明
ready(cb) / getContext()—容器就绪/上下文(用户/组织/平台)
requestAuthCode()—重新取一次性免登码
selectUsers(opts?)—(用户亲手挑即授权)系统选人组件
openChat({user_id}) 或 openChat({group_id})—跳到与该成员的单聊,或打开某个群聊(群聊变体,v1.6.0)
alert / confirm / toast—系统样式对话框/轻提示
setTitle / close—窗口标题/关闭
minimize / toggleMaximize / isMaximized / toggleFullscreen / isFullscreen / startDragging—窗口无边框控制(v1.7.0,仅桌面端):应用自绘顶部栏时接管窗口
reload / reloadForce—就地刷新 / 强制刷新(重签免登码 + 绕缓存,v1.7.0)
setToolbarVisible(visible)—显示/隐藏壳自带顶部工具栏(v1.7.0,仅桌面端)
openGrants()—唤起「始终允许」授权管理面板(v1.7.0,仅桌面端)
chooseFile(opts?)—(用户亲手选即同意)原生打开文件对话框(v1.6.0,仅桌面端)
saveFileAs(opts)—(用户亲手选保存位置即同意)原生另存为对话框(v1.6.0,仅桌面端)
share(opts)im向同组织成员分享文本/链接(v1.6.0,壳弹选人组件,以用户身份发出)
localFetch({url…})local从用户电脑抓网页(桌面端 · 首访确认,可「始终允许」,按 应用+组织+类别 记忆 30 天)
browserExtract({url…})local无头浏览器提取(桌面端 · 首访确认,可「始终允许」,与抓取类别分开记忆)
agent({messages…})ai + local完整 AI 能力:交给客户端内置 Agent,可用技能/系统工具/本地数据库(桌面端 · 每轮确认全文展示任务,v1.6.1 起无「始终允许」;本机命令按危险度分级确认,且不开放带登录态的浏览器工具)
agentStop()同上停止当前运行中的 agent 轮次
startChat({message})ai把任务改到主窗口新建对话(v1.8.0,仅桌面端):jlt.agent 不开放带登录态的浏览器工具,需要真实登录浏览器的任务改走这里——无返回值,过程/结果不回传
revealFile({file_id})—在文件管理器里高亮显示 Agent 的本机产物(files[].id,桌面端)
browserOpen({url…})local开隔离浏览器会话:多步爬取/自动化(桌面端 + 目标/新 origin 逐次确认,无「始终允许」)
browserAct({session_id,action,…})local会话内受控动作:click/type/fill/press/wait/back
browserRead / browserSnapshot / browserScreenshot / browserCloselocal读正文 / 可访问性快照(ref)/ 截图 dataURL / 关会话

会挂载全局 window.jlt。也可以使用平台提供的 TypeScript 封装 @ai-im/org-app-sdk(纯类型 + Promise 化,协议实现仍是上面这份 JS,见 SDK 参考)。

平台差异 ​

同一份 SDK 自动适配桌面与移动容器,所有方法两端行为一致;差异只有:

差异点桌面端移动端
getContext().platform"desktop"(独立应用窗口)"mobile"(内嵌 WebView 页)
openChat聚焦主窗口并打开单聊/群聊,应用窗口保留直接推入聊天页,返回键回到应用
对话框/选人壳的原生 DOM 弹层系统原生弹窗/选人页
窗口标题设置独立窗口标题设置页面导航栏标题
chooseFile / saveFileAs✅(v1.6.0,不需要 scope)❌ 返回“仅桌面端支持本机文件访问”
minimize / toggleMaximize / toggleFullscreen / startDragging 等窗口控制✅(v1.7.0,不需要 scope)❌ 返回“unknown method”(无独立窗口可控制)
share✅(需 im 能力)❌ 返回“仅桌面端支持分享”
localFetch / browserExtract✅(需 local 能力;首访用户确认,可「始终允许」,30 天有效)❌ 返回“仅桌面端支持本地能力”
agent / agentStop✅(需 ai + local 能力 + 每轮用户确认)❌ 返回“仅桌面端支持完整 AI 能力”
startChat✅(需 ai 能力 + 每次确认,无返回值)❌ 返回“unknown method”(无主窗口/多对话概念)

在普通浏览器里打开时所有调用 15 秒超时 reject——务必先判断 window.jlt 做降级(完整对照见 Demo)。例外: agent 不受 15 秒限制(一轮任务可能运行很久),由最终响应或错误收尾。

API ​

jlt.ready(callback) ​

容器就绪后触发一次,参数是当前上下文。重复调用会立即用已缓存的上下文触发。

ts
jlt.ready((ctx) => {
  console.log(ctx.user.nickname, ctx.org_id);
});

jlt.getContext(): Promise<Context> ​

跟 ready 等价的 Promise 形式。

ts
interface Context {
  app_id: string;
  org_id: string;
  org_name: string; // 当前组织上下文的名称——同一个应用可能被多个组织安装,
                     // 展示用;分区/鉴权仍以 org_id 为准,组织可以改名
  user: { user_id: string; nickname: string; avatar_url: string };
  locale: string;   // "zh-CN" | "en" | "zh-TW" | "ja"
  platform: string; // "desktop" | "mobile"
}

jlt.requestAuthCode(): Promise<{ auth_code: string; expires_in: number }> ​

重新签发一枚一次性免登授权码(5 分钟有效),用于会话续期。

jlt.setTitle(title: string): Promise<void> ​

设置应用窗口标题。

jlt.close(): Promise<void> ​

关闭应用窗口。

窗口无边框控制(v1.7.0,仅桌面端) ​

应用窗口没有 OS 原生标题栏(无边框),默认由壳自带的顶部工具栏提供最小化/ 最大化/关闭;应用要完全自绘顶部样式时,先 jlt.setToolbarVisible(false) 隐藏自带工具栏,让页面铺满整个窗口,再用下面这组方法自行实现。全部不需要 任何 scope——它们只操作应用自己的窗口。

除 isMaximized/isFullscreen(只读查询)外,其余方法(minimize/ toggleMaximize/toggleFullscreen/startDragging/reload/reloadForce/ setToolbarVisible)限频 10 秒 30 次——脚本循环调用会让窗口不停闪烁/ 拖动,超限直接 reject。

jlt.minimize(): Promise<void> ​

最小化应用窗口。

jlt.toggleMaximize(): Promise<{ maximized: boolean }> ​

切换最大化/还原,返回切换后的状态(供自绘图标同步)。

jlt.isMaximized(): Promise<{ maximized: boolean }> ​

查询当前是否最大化。平台不推送状态变化事件(比如系统级 Aero 贴靠触发的最 大化),需要校准时主动查询即可,常见时机是窗口重新获得焦点。

jlt.toggleFullscreen(): Promise<{ fullscreen: boolean }> / jlt.isFullscreen(): Promise<{ fullscreen: boolean }> ​

切换/查询全屏状态。

jlt.startDragging(): Promise<void> ​

发起窗口拖动——在自绘顶部栏的 mousedown 事件里调用(双击切换最大化需要 应用自己判断 event.detail === 2 并调用 toggleMaximize(),跟拖动共用同一个 mousedown 处理函数,不要拆成两个事件监听,否则原生拖动会吞掉后续的 dblclick):

ts
topBar.addEventListener("mousedown", (e) => {
  if (e.button !== 0 || (e.target as HTMLElement).closest("button")) return;
  if (e.detail === 2) { jlt.toggleMaximize(); return; }
  jlt.startDragging();
});

jlt.reload(): Promise<void> ​

就地刷新页面,尽量复用引擎缓存;跨源刷新被引擎拒绝的极端情况下自动降级为 reloadForce()。

jlt.reloadForce(): Promise<void> ​

强制刷新:重新签发一次性免登码并绕过缓存整页重载(这是 v1.7.0 之前壳自带 工具栏「刷新」按钮的行为)。

jlt.setToolbarVisible(visible: boolean): Promise<void> ​

显示/隐藏壳自带的顶部工具栏。隐藏后页面铺满整个窗口,此时应用需要自行绘制 顶部栏并用本节这组方法接管窗口控制,否则用户将失去关闭/最小化/最大化等 入口(jlt.openGrants() 除外——那个面板始终由壳渲染,不受此开关影响)。

jlt.openGrants(): Promise<void> ​

唤起壳的「始终允许」授权管理面板(localFetch/browserExtract 的按 origin 授权列表),隐藏自带工具栏后仍可调用。

jlt.selectUsers(opts?): Promise<{ users: User[] }> ​

系统选人组件——弹出客户端原生的成员选择窗(带搜索),用户挑选后返回:

ts
const { users } = await jlt.selectUsers({ multiple: true, max: 20 });
// users: [{ user_id, nickname, avatar_url, title }]
参数类型说明
multipleboolean多选(默认单选:点一位即返回)
selectedstring[]打开时预选中的 user_id
titlestring选人窗口标题
maxnumber多选最多可选人数(1–100,默认 100)

用户取消时返回 { users: [] }。不需要任何能力(scope):成员由用户亲手 挑选,你的页面只会拿到被选中的那几位——用户挑选即用户授权。

jlt.openChat({ user_id }): Promise<void> ​

把用户带到 IM 里打开与该成员的单聊(桌面端聚焦主窗口;移动端直接推入聊天页); 应用窗口保持不动。适合「找 TA 催一下」这类跳转。

jlt.openChat({ group_id }): Promise<void> ​

群聊变体(v1.6.0)——同上,但打开的是某个群聊。group_id 与 user_id 二选一(同时传时以 user_id 为准)。真实权限由 IM 打开会话时自然校验(非成员 打不开),与单聊变体同一套信任模型。仅桌面端。

jlt.chooseFile(opts?): Promise<{ files }> ​

原生打开文件对话框(v1.6.0,仅桌面端)——不需要任何 scope,用户在系统 对话框里亲手选择即同意(同浏览器 <input type="file"> 同一套许可模型)。

ts
const { files } = await jlt.chooseFile({ multiple: false, accept: ["png", "jpg"] });
if (files.length && !files[0].error) {
  console.log(files[0].name, files[0].mime, files[0].data_base64.length);
}
  • opts.multiple:多选(默认单选)。
  • opts.accept:扩展名白名单(不带点),仅作对话框过滤器展示,用户仍可切到 “所有文件”。
  • 单文件上限 20MB;多选累计上限 60MB——按选中顺序读取,一旦剩余预算不够就 给该文件(及之后的文件)返回 error,不会把超限的内容读进内存再返回。
  • 单文件上限 20MB,超出的条目带 error 而不是 data_base64。用户取消 → files: []。

jlt.saveFileAs(opts): Promise<{ ok }> ​

原生另存为对话框(v1.6.0,仅桌面端)——同样不需要 scope。

ts
const { ok } = await jlt.saveFileAs({ filename: "report.pdf", data: base64OrDataUrl });
// ok === false 表示用户取消了保存对话框,不是错误
  • data:纯 base64 字符串,或带 data:...;base64, 前缀的 dataURL(SDK 自动剥除前缀)。
  • 上限约 50MB。

jlt.share(opts): Promise<{ ok, user_id? }> ​

向同组织成员分享一段文本/链接(v1.6.0)——壳弹出系统选人组件让用户亲手选一位同组织 成员,消息以用户自己的身份发出(带【应用名】前缀)。需要应用被授予 im 能力( 与 /open/im/send 同一道闸门,即使发起方是用户本人也不例外)。

ts
const { ok, user_id } = await jlt.share({ title: "报销已批准", url: "https://app.example.com/x" });
// ok === false 表示用户取消了选人,不是错误

jlt.alert({ message, title?, button? }): Promise<void> ​

系统样式警告框,用户点掉按钮后 resolve。

jlt.confirm({ message, title?, okText?, cancelText? }): Promise<{ ok }> ​

系统样式确认框:

ts
const { ok } = await jlt.confirm({ message: "确认提交这份报销单?" });
if (!ok) return;

jlt.toast({ message, type?, duration? }): Promise<void> ​

轻提示,自动消失且不遮挡页面交互。type: "info" | "success" | "error", duration 毫秒(500–6000,默认 2200)。

jlt.localFetch({ url, max_chars? }): Promise<{ url, text, truncated, length }> ​

从用户电脑抓取网页正文(走用户本机网络——能访问内网、带本地代理)。 需要应用被授予本地能力(local);目标地址弹窗请用户确认(弹窗把解析后的 协议://主机:端口 单独展示,拒绝即失败),确认框可选**「始终允许」**——按 「应用 + 组织 + 能力类别」分别记忆、30 天有效,应用窗口工具栏「授权管理」可 随时撤销(授权不跨组织共用,过期自动重新询问)。 不跟随重定向——目标返回 30x 直接报错,请自己拿规范地址来;URL 含 user@host 形式会被拒绝。仅桌面端。

jlt.browserExtract({ url, max_chars? }): Promise<{ url, text, truncated, length }> ​

无头浏览器打开页面并提取正文——JS 渲染的页面(SPA、动态表格)用这个。 隔离实例:临时浏览器配置、不带用户任何登录态、用完即杀,跟你日常浏览器 完全隔离;导航后校验最终地址与确认的地址同源,被重定向到其它站点会中止。 同样需要 local 能力;确认策略同 localFetch(首访确认,可「始终允许」—— 但与 localFetch 的授权分开记忆)。仅桌面端。

尊重确认弹窗

本地能力的授权模型是「组织管理员开 scope + 用户确认(可选始终允许,按类别 记忆且 30 天过期)」双层设计——不要高频调用把用户淹没在弹窗里;批量抓取应 合并为少量请求,或在你的后端做缓存。「始终允许」完全由用户主动选择,应用 无法绕过确认。

jlt.agent({ messages, model?, on? }): Promise<{ content, files, stopped }> ​

完整 AI 能力:把对话交给客户端内置 Agent 执行——Agent 拥有与客户端 AI 对话一致的能力:该用户已启用的技能、系统工具(文件/Shell/代码/ 浏览器)、本地数据库与服务端工具。模型自行决定调用什么工具,页面只负责 展示过程与结果。

ts
const result = await jlt.agent({
  messages: [{ role: "user", content: "总结 ~/Downloads 里最近一周的文件" }],
  model: "deepseek-chat", // 可省略,用客户端默认
  on: {
    // 全部可选;工具卡片建议按 calls[].id 建卡、tool_done 按 call_id 回填
    delta: ({ delta }) => {},                        // 正文增量
    tool_start: ({ calls }) => {},                   // 工具开始(含本机工具)
    tool_done: ({ results }) => {},                  // 结束(runtime: local/cloud)
    local_tool_start: ({ tool }) => {},              // 即将在用户电脑上执行
    local_tool_result: ({ tool, ok }) => {},
    files: ({ files }) => {},                        // 生成的文件(累计)
    subtask_start: (e) => {}, subtask_done: (e) => {},
    truncated: () => {},                             // 本轮工具迭代达上限
  },
});
// result: { content, files: [{ name, url?, size?, id? }], stopped }

files 分两类:云端工具产物带 url(https 下载直链);本机工具产物只带 不透明 id——本地路径不进页面,调 jlt.revealFile 让 桌面壳在系统文件管理器里高亮显示该文件(文件不出本机)。

jlt.revealFile({ file_id }): Promise<{ ok }> ​

在系统文件管理器里高亮显示 Agent 生成的本机文件(macOS 访达 -R / Windows 资源管理器 /select)。file_id 来自 jlt.agent 结果或 files 事件里 条目的 id。

  • 仅桌面端;路径只存在壳的 id 表里,页面拿不到任何本地路径或文件内容;
  • 只能翻出本窗口 Agent 真实产出过的文件,限频 10 次/分钟;
  • 应用窗口重开后 id 失效(文件还在原处,让 Agent 重新生成即可重新拿到 id)。

要求与边界:

  • 需要应用同时被授予 ai 与 local 能力;仅桌面端;
  • 每轮开始前桌面壳弹原生确认框(应用名 + 最后一条 user 消息全文), 拒绝即失败;没有「始终允许」——记住一次等于放行后续任意任务。应用发起的 本机命令按危险度分级确认:普通命令首次弹窗可选「本轮不再询问」(按命令 签名在这一次任务内复用,任务结束即失效、不持久化);以下两类永不豁免、 每次都要单独确认:① 破坏性命令(rm -rf/sudo/crontab/git push --force 等); ② 外发 / 读凭证 / 执行远端代码(curl/wget/scp/ssh 等外发命令, .ssh//.aws//.env/.pem/shell 历史/浏览器 Cookie 库等凭证路径,以及 … | sh/eval/base64 -d)。三方应用不沿用用户为自己 Agent 设的任何自动放行策略 (含全局静默);
  • 本机工具走白名单:只开放"每次调用都经用户确认"或"根本不碰用户数据"的 能力(当前:shell_exec、原生另存为、文件管理器定位、系统通知)。代码执行类 能力一律在沙箱里跑:macOS 上跑在本机内核级沙箱(seatbelt,断网 + 只能读写 专用工作区,子进程也逃不出去),其它平台自动跑在云端 Docker 沙箱(无网络、无 Linux capabilities、512MB/1CPU)。两条路应用侧都无需改代码,区别只是沙箱在哪。 需要真实本机环境(读用户文件/已登录浏览器/本机技能)的任务,改用 jlt.startChat() 交回用户主窗口执行;
  • 不开放走用户登录态浏览器的 browser_* 工具(要浏览器用隔离会话 JSAPI);
  • messages 只接受 user / assistant 角色(system 会被剥离——能力与安全 约束由受信壳注入),最多 40 条、每条 32000 字;同一窗口同时只允许一轮任务;
  • 计费与用户在客户端里的 AI 对话一致(个人钱包或组织钱包),应用侧不接触额度 凭证。

jlt.agentStop(): Promise<{ stopped: boolean }> ​

停止当前运行中的 agent 轮次;已生成的内容会随该轮结果正常返回 (stopped: true)。没有运行中的轮次时静默成功。

jlt.startChat({ message }): Promise<{}>(v1.8.0,仅桌面端) ​

jlt.agent 为安全故意剔除了 browser_* 工具(不给应用借道驱动用户真实登录的 日常浏览器)。需要真实登录态浏览器能力的任务(比如登录某网站抓数据)无法通过 jlt.agent 完成,只能改用 jlt.startChat:把任务改成在用户自己主窗口里 新开一个普通对话发出去——跟用户亲手在输入框敲字发送完全等价,走客户端正常 Agent 通道(包含真实登录态的 browser_*)。

js
await jlt.startChat({ message: "请运行技能「XXX」完成采集并同步" });
// 无返回内容——新对话在主窗口打开,本窗口不感知后续过程/结果。
// 需要查看结果,请让应用自己的后端提供一个持久化的进度查询接口。
  • 只需 ai 能力(不需要 local)——执行完全发生在主窗口自己的常规 Agent 通道里,不受组织应用能力限制约束;
  • 每次调用弹窗展示完整消息文本(跟 jlt.agent 的任务全文确认同一安全 等级),无「始终允许」;
  • 无返回值、无事件回调,过程/结果不会回传到调用方页面。

不想手写事件驱动的 UI?SDK 2.0 的 mountChat(el, { channel: "auto" }) 已内置 agent 通道:工具调用/本机执行/子任务/生成文件的实时卡片、流式正文、 降级到后端中继,全部开箱即用。

隔离浏览器会话(JSAPI v1.4.1,桌面端) ​

localFetch/browserExtract 是一次性动词;browserOpen…browserClose 把同一 Playwright 引擎开放成多步会话——开页 → 快照拿 ref → 点击/输入/翻页 → 读取/ 截图 → 关闭。跑在隔离实例上(临时 profile、强制无头、无任何登录态、用完即 杀),与客户端 AI 对话里那个带登录态的浏览器是两套信任等级。

js
const s = await jlt.browserOpen({ url: "https://example.com/list" });
// → { session_id, url, origin, title, text, truncated, length }

const snap = await jlt.browserSnapshot({ session_id: s.session_id });
// snap.text 形如:- button "下一页" [ref=e12]   ← ref 就是动作的 target

await jlt.browserAct({
  session_id: s.session_id, action: "click",
  element: "下一页", target: "e12",
});

const page2 = await jlt.browserRead({ session_id: s.session_id, max_chars: 50000 });
const png = await jlt.browserScreenshot({ session_id: s.session_id });
await jlt.browserClose({ session_id: s.session_id });

browserAct 动作一览 ​

action参数说明
clickelement, target点击元素
typeelement, target, text, submit?向输入框输入(submit: true 输入后回车)
fillfields: [{name?, type?, target, value}]批量填表(textbox/checkbox/radio/combobox/slider)
presskey按键(如 Enter、ArrowDown)
waittext?, textGone?, time?等待文本出现/消失或等指定秒数
back—后退一页

target 统一用快照 ref(如 e12)或唯一 CSS 选择器。不提供任意 JS 执行 (evaluate 不对三方开放)。

会话内点到外链或被重定向到未确认 origin 时:

  • 读类操作(read/snapshot/screenshot)resolve { need_consent, url }——桌面壳 弹窗请用户确认;同意后该 origin 入会话白名单,重发同一调用即返回内容; 拒绝则应用可 back 或关会话。
  • act 分两条路径(2026-08-27 起):
    • 能预解析的跳转(点击链接/提交表单——壳先在页面里解析目标元素的 a[href]/form[action]):动作不执行,resolve 带 pending: true 的 { need_consent, url };用户同意后壳自动重发该动作,应用这次拿到的就是 真实结果。
    • JS 动态导航(无法预解析):守卫仍在动作之后(不带 pending,动作已 执行)——同意后壳只把 origin 入白名单、不重跑动作,响应带 consented: true。
  • 未确认 origin 的页面内容一个字节都不会返回。

限额与生命周期 ​

每窗口 2 会话 / 全局 4 / 单会话 200 动作 / 空闲 10 分钟 / 最长 30 分钟自动回收; 关闭应用窗口同样回收;截图 ≤3MB(base64 PNG)。需要 local 能力;目标地址与 每个新 origin 逐次由用户确认——这条通道没有「始终允许」:多步自动化不能被 一次授权整段放行(抓网页/无头提取的「始终允许」不适用于会话)。

降级处理 ​

不在机灵兔客户端里打开时(例如开发者直接用普通浏览器预览页面),所有调用会 在 15 秒后超时 reject。建议先判断:

ts
if (window.jlt) {
  // 在客户端里
} else {
  // 普通浏览器,降级展示
}

使用 SDK 时对应 isInJltClient()。

机灵兔开放平台 · 组织应用开发者文档