lsagent-sdk
v0.1.1
Published
浏览器 JS SDK——网页连接用户本机的 lsagent AI 运行时(ws://127.0.0.1:37700/ws):建任务、流式事件、文件上传、模型配置引导
Readme
lsagent-sdk — 让你的网页使用用户本机的 AI
浏览器 JS SDK。你的网页通过它连接跑在用户自己电脑上的 AI 运行时 lsagent:用户在页面上传提单 / 发票 / 客户消息, AI 在用户本机解析成结构化字段回填表单;也能让 AI 读用户的邮箱。 业务系统零后端改造——文件不用传到服务器,模型密钥不用你代管。
你的网页 ── lsagent-sdk(WebSocket,首次系统确认一次)──> 用户电脑上的 lsagent ──> 模型 API / 本机文件 / 邮箱
│
只监听 ws://127.0.0.1:37700/ws典型场景:补料 / 单证 AI 导入、客户消息抽取建单、发票识别、邮件内容提取等—— 一切"AI 处理用户自己电脑上的东西"的需求。
与 lsagent 配合使用
lsagent(npm 包)与 lsagent-sdk 是一对,各装一端:
| 端 | 安装 | 角色 |
| --- | --- | --- |
| 用户的电脑 | npm i -g lsagent,运行 lsagent | AI 运行时:连模型 API、读本机文件、收发邮件、执行任务;只监听 ws://127.0.0.1:37700/ws |
| 你的网页 | npm i lsagent-sdk | 遥控器:WebSocket 连接 + 认证 + 任务/事件 API |
用户侧一次性准备(你的页面可以引导完成):
- 安装 Node.js ≥ 20 →
npm i -g lsagent→ 终端运行lsagent(前台常驻;开机自启自行加系统启动项)。 - 无需配对码、无需配白名单:网页不带配对码连接时,用户电脑上会弹一次系统确认框,
点"是"后你的网站即持久授权、以后无感直连(用户可用
lsagent --disallow-origin撤销)。 localhost 开发环境同样只弹一次。 - 模型配置(API key + provider):用户手改两个 JSON 文件(格式见 lsagent README),
或完全由你的网页引导完成(见下方「第 1 层」的
saveProvider(),推荐)。
连接握手时序(免码模式):
connect() ──> connecting ──> pending(用户电脑弹系统确认框,onState 可感知)
──用户点「是」──> authed(此后刷新页面 / 重启浏览器无感直连)lsagent 没在运行的判别:waitForAuthed() 超时返回 false 且 getLastError() 含
code=1006(连不上)——此时页面应出安装引导(指到
lsagent README),用户装好后再 connect() 重试。
兼容性:免码确认制需 lsagent ≥ 0.1.2(更早版本会按配对码错误拒绝免码连接);
配对码模式任意版本可用。npm i -g lsagent 默认装最新版,一般无需关心。
授权管理命令(在用户电脑执行,可写进你的用户帮助文档):
lsagent --show-code # 查看 6 位配对码(配对码模式用)
lsagent --list-origins # 查看白名单与确认授权
lsagent --allow-origin https://crm.example.com # 手动放行你的网站(免弹窗)
lsagent --disallow-origin https://crm.example.com # 撤销授权(下次连接重新弹窗确认)安装
方式一:script 标签(零构建;从本包拷 dist/lsagent-sdk.js 到你的 public/,或用 CDN)
<script src="lsagent-sdk.js"></script>
<!-- CDN:<script src="https://unpkg.com/lsagent-sdk/dist/lsagent-sdk.js"></script> -->
<script>
const client = new LsagentSDK.LsagentClient(); // 免码模式:首次弹系统确认,一次点击永久授权
</script>方式二:npm / ESM
npm i lsagent-sdkimport { LsagentClient } from "lsagent-sdk";快速上手
const client = new LsagentClient(); // 免码模式(推荐):首次连接弹系统确认框,用户点一次"是"即永久授权
client.onState((s) => {
console.log("连接状态:", s); // disconnected|connecting|connected|pending|authed
if (s === "pending") showHint("请在弹出的系统确认框上点【是】");
});
client.connect();
await client.waitForAuthed(120_000); // 首次要留时间等用户点确认;false = lsagent 未运行
// 创建并执行一个任务(firstMessage 立即开始;模型建议显式指定,见「第 1 层」)
const task = await client.createTask({
title: "整理今日邮件摘要",
provider: "deepseek", modelId: "deepseek-chat", // 用户已配置的模型
firstMessage: "把今天收到的邮件整理成摘要表格发给我",
systemPromptAppend: "输出使用简体中文。", // 可选:业务背景/输出约定
mcpServers: ["crm-tools"], // 可选:只接入业务系统自己的 MCP 工具
});
// 实时进度(事件全量广播,按 taskId 过滤)
client.onEvent((event, payload) => {
if (payload.taskId !== task.id) return;
if (event === "task.delta") appendStream(payload.text); // 流式文本
if (event === "task.status") onDone(payload.status); // idle|running|error
});
// 追问 / 控制
await client.prompt(task.id, "再按发件人分组");
await client.steer(task.id, "改成按主题分组"); // 执行中插入转向
await client.abort(task.id); // 中止
// 文件:先落到用户本机,AI 才能读(≤20MB)
const buf = await file.arrayBuffer();
const { path } = await client.request("fs.saveUpload", {
name: file.name,
bytesBase64: arrayBufferToBase64(buf), // FileReader.readAsDataURL 后取逗号后一段也行
});
// 查询
const models = await client.listModels();
const messages = await client.getMessages(task.id);完整可跑示例见 demo/(npm run demo 后开 http://127.0.0.1:4567)。
API
| 成员 | 说明 |
| --- | --- |
| new LsagentClient({ url?, pairingCode? }) | url 缺省 ws://127.0.0.1:37700/ws,远程模式填 ws://IP:37700/ws;不传 pairingCode = 免码模式(推荐,首次系统弹窗确认一次),传则走配对码模式(要求页面是 localhost 或在白名单) |
| connect() / disconnect() | 连接(幂等,断线自动指数退避重连)/ 主动断开 |
| getState() / onState(cb) | 状态机:disconnected → connecting → connected → (pending) → authed;pending = 等用户在本机点确认框 |
| getLastError() | 最近一次失败原因(如"等待用户在本机确认接入…"、"接入被拒绝(denied)"),UI 提示用 |
| waitForAuthed(timeoutMs?) | 等待配对完成;超时返回 false。免码模式首次等待用户点击,建议给足超时(如 120_000) |
| onEvent(cb) | 订阅事件(见下表),返回取消函数 |
| request(type, payload?, timeoutMs?) | 通用透传:lsagent WS 协议全开放(fs.*、email.* 等) |
| createTask(opts?) | 建任务;firstMessage 非空则立即执行 |
| prompt(taskId, text) | 追加消息(任务空闲时触发执行;执行中会被拒,改用 steer 或等待) |
| steer(taskId, text) / abort(taskId) | 执行中转向 / 中止 |
| listModels() / listTasks() / getMessages(taskId) | 查询模型 / 任务列表 / 任务消息 |
| listProviders() / saveProvider(spec) | 探测本机模型服务配置状态(hasKey)/ 新增更新模型服务(见「第 1 层」) |
createTask 选项:title、provider+modelId(缺省用全局默认模型)、cwd(本机工作目录)、
firstMessage、systemPromptAppend、skills、mcpServers(详见下节)。
模型、提示词与能力配置(集成方必读)
配置分三层,全部经 SDK 同一条连接传输(createTask 参数 + request() 透传),无需额外通道。
第 1 层:模型——密钥在用户侧,选择在任务级
| 关注点 | 谁负责 | 怎么做 |
| --- | --- | --- |
| 模型服务与 API 密钥 | 用户侧 | 配置存用户本机 ~/.lsagent/pi/(models.json + auth.json);业务系统可探测、可代填,但不该后端囤 key |
| 用哪个模型跑任务 | 业务系统(任务级指定) | createTask({ provider, modelId });建议总是显式指定(见下方「默认模型」注意) |
const models = await client.listModels(); // available=true 的才可指定(已配凭据)
const task = await client.createTask({
provider: models[0].provider, modelId: models[0].id,
firstMessage: "...",
});指定了用户未配置的模型会报错「模型不存在或已删除」。
首次配置引导(用户刚装 lsagent、还没配 key 时,全部经 SDK 同一条连接完成):
// 1. 探测:没有任何已配 key 的服务 → 页面出引导表单
const providers = await client.listProviders();
const unconfigured = providers.length === 0 || providers.every((p) => !p.hasKey);
// 2. 用户在表单里选常见服务商预设或自填,提交时落到用户本机,保存即生效:
await client.saveProvider({
name: "deepseek", // provider 标识(自填时自定义,如 "my-company")
displayName: "DeepSeek",
baseUrl: "https://api.deepseek.com/v1",
apiType: "openai-completions", // OpenAI 兼容端点用这个;Anthropic 兼容用 "anthropic-messages"
apiKey: userEnteredKey, // 写入用户本机 auth.json;留空 = 保留原 key
models: [
{ id: "deepseek-chat", name: "DeepSeek-V3", contextWindow: 131072, maxTokens: 8192 },
],
});
// 3. 复查可用性,然后显式指定模型建任务
const models = await client.listModels();常见预设速查(baseUrl + apiType):DeepSeek https://api.deepseek.com/v1 / openai-completions;
Kimi https://api.moonshot.cn/v1 / openai-completions;智谱 GLM https://open.bigmodel.cn/api/anthropic /
anthropic-messages。模型 contextWindow/maxTokens 填服务商标称值即可,影响上下文管理。
企业统一密钥(可选):业务后端托管一份公司级 provider 配置,用户配对完成后页面从业务后端
拉取并 saveProvider 推到用户本机——用户零配置。key 会落到用户本机明文文件(与用户自己配等价),
是否采用由业务方自行评估。
「默认模型」注意:全局默认模型存在用户本机 settings.json,新装用户大概率没设——
所以 SDK 建任务时总是显式传 provider + modelId,不要依赖缺省。
第 2 层:提示词——systemPromptAppend 是业务注入的唯一正道
createTask({ systemPromptAppend }) 把文本追加到系统提示之后(不替换内置工具指引),适合放:
业务背景("这是货代公司的订单审核系统")、角色约定、输出格式("结果以 JSON 返回,字段…")、
红线("不要执行删除类操作")。
const task = await client.createTask({
systemPromptAppend: [
"你是订单审核助手,操作对象是本公司的 ERP。",
"输出固定 JSON:{approved: boolean, reason: string}。",
].join("\n"),
firstMessage: orderText,
});单次业务数据(订单内容等)放 firstMessage / prompt(),不要塞进系统提示。
第 3 层:能力——Skills 与 MCP 工具
任务通过参数引用能力,能力本体落在用户本机配置文件里(用户可见、可删,透明可控):
Skills(给 AI 的操作手册/站点策略)
// 探测用户已有的
const local = await client.request("skills.local.list");
// 推送业务系统自己的 skill(首次集成时一次即可;内容 = markdown 手册)
await client.request("skills.local.save", {
name: "erp-order-guide",
content: "# ERP 订单审核指南\n1. 先查 ...\n2. ...",
origin: "业务系统名", // 可选,标记来源方便用户辨认
});
// 任务引用(按名内联全文进系统提示)
const task = await client.createTask({ skills: ["erp-order-guide"], firstMessage: "..." });MCP(业务系统提供给 AI 的工具,如查订单/改状态)
// 注册业务系统自己的 MCP Server(http 型最简单:一个 URL + 鉴权头)
await client.request("mcp.local.save", {
server: {
name: "crm-tools",
type: "http",
url: "https://crm.example.com/mcp",
headers: { Authorization: "Bearer <用户级token>" },
enabled: true,
},
origin: "业务系统名",
});
// 任务只接入指定 MCP(缺省接入用户已启用的全部)
const task = await client.createTask({ mcpServers: ["crm-tools"], firstMessage: "..." });stdio 型(command/args/env)也支持,但会启动用户本机进程,优先用 http 型。
推荐的首次集成初始化流程
用户打开业务页面 → SDK 免码连接(onState 出现 pending 时提示用户点系统确认框,waitForAuthed 等待)
→ request("skills.local.list") / request("mcp.local.list") 探测本系统已注册的能力
→ 缺则推送(skills.local.save + mcp.local.save;失败不阻塞,降级为纯 systemPromptAppend)
→ 之后每次 createTask({ systemPromptAppend, skills, mcpServers, ... })能力推送是写用户本机配置的操作,请在页面里明示,不要静默反复写入; 版本更新时先比对内容再决定是否覆盖。
事件(onEvent)
| 事件 | 载荷(均含 taskId) |
| --- | --- |
| task.status | status: idle\|running\|error,error? 错误描述 |
| task.delta | text 流式增量文本 |
| task.message | message 完整消息(role/text/usage…) |
| task.tool | phase: start\|end、name 工具名 |
| task.usage | input/output/cost 本轮用量 |
| task.deleted | 任务被删除 |
| mail.new | 新邮件(无 taskId) |
事件为全量广播(与本机其它已配对客户端一致),业务方按 taskId/事件名自行过滤。
安全模型
- 免码模式 = 系统弹窗确认制:首次连接时用户电脑弹原生确认框,点一次"是"授权即持久
(存
~/.lsagent/web-grants.json,用户可lsagent --disallow-origin <url>撤销)。 授权绑定来源 origin,浏览器无法伪造;点"否"只拒当次,同一网站连拒 3 次转静默拦截, 恶意网站无法靠反复请求轰炸用户(10 分钟内最多弹 5 次确认框)。 - 配对码 = 完全授权:持有配对码的页面可读本机文件、发邮件、执行任务,且要求页面 来源是 localhost 或用户白名单(防钓鱼页骗码直连)。只在用户明确信任的系统中使用, 建议用户输入而非硬编码进代码。
- lsagent 有防爆破:同 IP 连续错 5 次锁 10 分钟;配对码 6 位、服务只监听本机回环(远程模式除外)。
限制
- 需用户电脑开机且 lsagent 在线(
onState可感知断线,SDK 自动重连)。 - 来源白名单为精确匹配,暂不支持
*.example.com通配。 - 本机模式面向「用户在场」的场景;无人值守/服务端集中模式不在本 SDK 范围。
本地开发
npm install # 在仓库根目录(workspace)
npm run build -w sdk # 构建 dist(esbuild 产物 + tsc 类型声明)
npm run demo -w sdk # 演示页 http://127.0.0.1:4567
npm run typecheck -w sdk