集成与嵌入
把 Tensor Agent 嵌入你的应用 —— 后端一次性签发 JWT,前端嵌入 iframe,会话自动续期。
集成与嵌入
把 Tensor Agent 聊天面板嵌进任何 Web 应用。你只负责在后端签一次 JWT;SDK 和 Tensor Agent 服务端会自动续期会话,你的业务代码不用操心 Token 过期。
架构
你的应用 Tensor Agent
┌─────────────┐ postMessage ┌──────────────────┐
│ 你的页面 │ ◄──────────────► │ iframe 面板 │
└──────┬──────┘ └─────────┬────────┘
│ 1. 签发 4h JWT │ 2. 每 3h 自动续期
│ (共享 S2S 密钥) │ POST /api/auth/refresh
▼ ▼
你的后端 Tensor Agent 服务端核心:一把 S2S 密钥,一种 JWT,两个系统互认。 你后端签发的 JWT 和 Tensor Agent 续签的 JWT 都用同一把 Integration S2S 密钥(HS256),双方都能校验。你不需要刷新接口、不需要 refresh token。
快速开始
1. 在控制台创建 Integration
进入 系统设置 → 集成管理,新建一个 Integration(类型选 API),保存后复制 S2S 密钥,存入你后端环境变量 TENSOR_AGENT_S2S_KEY。
2. 后端:签一次 4 小时 JWT
用 S2S 密钥签一个代表当前用户的 JWT。默认有效期 4 小时;到期前 1 小时 SDK 会自动替你续。
import { createHmac } from 'node:crypto';
function issueAgentJwt(userId, orgId) {
const now = Math.floor(Date.now() / 1000);
const header = { alg: 'HS256', typ: 'JWT' };
const payload = { sub: userId, org: orgId, iat: now, exp: now + 4 * 3600 };
const h = Buffer.from(JSON.stringify(header)).toString('base64url');
const p = Buffer.from(JSON.stringify(payload)).toString('base64url');
const sig = createHmac('sha256', process.env.TENSOR_AGENT_S2S_KEY)
.update(`${h}.${p}`)
.digest('base64url');
return `${h}.${p}.${sig}`;
}
app.get('/api/agent-token', requireAuth, (req, res) => {
res.json({ jwt: issueAgentJwt(req.user.id, req.user.orgId) });
});import base64, hmac, hashlib, json, os, time
def issue_agent_jwt(user_id: str, org_id: str) -> str:
now = int(time.time())
header = {"alg": "HS256", "typ": "JWT"}
payload = {"sub": user_id, "org": org_id, "iat": now, "exp": now + 4 * 3600}
b64 = lambda d: base64.urlsafe_b64encode(
json.dumps(d, separators=(",", ":")).encode()
).rstrip(b"=").decode()
h, p = b64(header), b64(payload)
sig = base64.urlsafe_b64encode(
hmac.new(os.environ["TENSOR_AGENT_S2S_KEY"].encode(),
f"{h}.{p}".encode(), hashlib.sha256).digest()
).rstrip(b"=").decode()
return f"{h}.{p}.{sig}"func issueAgentJWT(userID, orgID string) string {
now := time.Now().Unix()
header := map[string]string{"alg": "HS256", "typ": "JWT"}
payload := map[string]any{"sub": userID, "org": orgID, "iat": now, "exp": now + 4*3600}
h, _ := json.Marshal(header); p, _ := json.Marshal(payload)
hB := base64.RawURLEncoding.EncodeToString(h)
pB := base64.RawURLEncoding.EncodeToString(p)
mac := hmac.New(sha256.New, []byte(os.Getenv("TENSOR_AGENT_S2S_KEY")))
mac.Write([]byte(hB + "." + pB))
sig := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
return hB + "." + pB + "." + sig
}JWT claims:
| Claim | 类型 | 必填 | 说明 |
|---|---|---|---|
sub | string | ✅ | 外部系统用户 ID |
exp | number | ✅ | Unix 秒,建议 iat + 14400(4h) |
iat | number | ✅ | 签发时间 |
org | string | 组织 ID;不传则用 Integration 默认 org | |
name、email | string | 展示用 |
3. 前端:嵌入面板
选一种接入方式:
npm install @nexusgpu/agent-sdk-reactimport { TensorAgentProvider, useTensorAgent } from '@nexusgpu/agent-sdk-react';
function App() {
const [jwt, setJwt] = useState('');
useEffect(() => {
fetch('/api/agent-token').then(r => r.json()).then(d => setJwt(d.jwt));
}, []);
if (!jwt) return <Loading />;
return (
<TensorAgentProvider
jwt={jwt}
agentId="my-agent"
onDataChange={(e) => queryClient.invalidateQueries([e.resource])}
onAuthFailed={() => {
// 极少触发:续期彻底失败(比如你轮换了 S2S 密钥)。
// 重新调用你的后端拿一个新 JWT。
fetch('/api/agent-token').then(r => r.json()).then(d => setJwt(d.jwt));
}}
>
<YourApp />
</TensorAgentProvider>
);
}
function CustomerPage({ customer }) {
const { setContextPrompts, sendPrompt, open } = useTensorAgent();
useEffect(() => {
setContextPrompts([`正在查看客户 ${customer.name}`]);
}, [customer]);
return <button onClick={() => { open(); sendPrompt('分析这个客户'); }}>AI 分析</button>;
}npm install @nexusgpu/agent-sdk-vue<template>
<TensorAgentLayout
:jwt="jwt"
agent-id="my-agent"
@data-change="onDataChange"
@auth-failed="refetchJwt"
>
<router-view />
</TensorAgentLayout>
</template>
<script setup lang="ts">
import { TensorAgentLayout } from '@nexusgpu/agent-sdk-vue';
</script>npm install @nexusgpu/agent-sdk-jsimport { TensorAgent } from '@nexusgpu/agent-sdk-js';
const res = await fetch('/api/agent-token');
const { jwt } = await res.json();
const agent = TensorAgent.init({
jwt,
agentId: 'my-agent',
onDataChange: (e) => refreshList(e.resource),
onAuthFailed: async () => {
const r = await fetch('/api/agent-token');
const { jwt } = await r.json();
agent.updateToken(jwt);
},
});直接在 HTML 里引,不用打包器:
<script src="https://agent.tos.run/sdk/agent-embed.iife.js"></script>
<script>
fetch('/api/agent-token').then(r => r.json()).then(({ jwt }) => {
TensorAgent.init({ jwt, agentId: 'my-agent' });
});
</script>就这样。Token 会自动续期,你不需要写刷新代码。
事件
iframe 会通过 postMessage 告诉你的页面发生了什么:
| 事件 | 触发 | 典型用途 |
|---|---|---|
data-change | 智能体调用工具修改了业务数据 | 刷新列表、失效缓存 |
action | 智能体触发了 navigate / open-modal 之类 | 路由跳转、打开弹窗 |
auth-failed | 自动续期彻底失败(罕见) | 重新调用你的后端签 JWT,然后 updateToken() |
agent.onDataChange('orders', (e) => refreshOrders());
agent.onAnyDataChange((e) => queryClient.invalidateQueries([e.resource]));
agent.onAction((e) => {
if (e.action === 'navigate') router.push(e.data.path);
});控制 API
| 方法 | 说明 |
|---|---|
setContextPrompts(prompts) | 注入页面上下文,智能体下次回答时会参考 |
setContext(ctx) | 结构化业务上下文 |
sendPrompt(text) | 主动发送一条消息 |
updateToken(jwt) | 替换当前 JWT(auth-failed 之后调用) |
open() / close() / toggle() | 控制面板显示 |
destroy() | 卸载 iframe,恢复页面原状 |
ready | Promise<void>,iframe 加载完成 |
工作原理
- 你签发一个 4 小时 的 JWT。
- iframe 在到期前 1 小时(签发后 3h)自动调用
POST https://agent.tos.run/api/auth/refresh,拿一个新 JWT 继续用。 - 续签服务用 相同的 S2S 密钥 校验并重新签发;claims(
sub、org、name、email)保留。 - 如果用户电脑长时间休眠导致 Token 过期超过 24 小时,续期会失败,SDK 抛出
auth-failed,由你补发一个新 JWT。 - 要吊销所有会话:在 Integration 管理面板轮换 S2S 密钥。
进阶
自定义 JWT 有效期
任何 exp 都可以,SDK 会根据 exp 计算续期时点。推荐默认保持 4 小时。过短会增加续期请求;过长遇到时钟偏差风险更大。
容器模式(手动布局)
默认的 TensorAgent.init() 会自动改造页面为左右分栏。如果你已经有自己的布局:
import { TensorAgentPanel } from '@nexusgpu/agent-sdk-react';
<div style={{ display: 'flex' }}>
<MyApp />
<TensorAgentPanel open={open} onOpenChange={setOpen} jwt={jwt} resizable />
</div>跨域安全
SDK 对所有 postMessage 做 origin 校验:
- 宿主 → iframe:限定目标 origin 为 Tensor Agent 服务地址
- iframe → 宿主:限定为宿主页面 origin(SDK 自动从 URL 参数传递)
- 收到不匹配 origin 的消息一律丢弃
调试
打开 iframe 里的聊天面板,在 devtools Network 标签过滤 auth/refresh —— 正常每 3 小时一次。手动模拟续期失败:在 Integration 面板轮换 S2S 密钥,iframe 下次续期时会抛 auth-failed。
完整参考
TensorAgent.init(options)
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
jwt | string | — | 必填。你后端签发的初始 JWT |
host | string | https://agent.tos.run | Tensor Agent 服务地址 |
agentId | string | — | 指定智能体 ID |
contextPrompts | string[] | — | 初始页面上下文 |
context | BusinessContext | — | 结构化上下文 |
defaultWidth / minWidth / maxWidth | number | 440 / 320 / 800 | 面板宽度(px) |
storageKey | string | ta-panel-width | localStorage 持久化 key |
onDataChange | callback | — | 数据变更通知 |
onAction | callback | — | 操作指令通知 |
onAuthFailed | callback | — | 自动续期失败,需要补签新 JWT |
postMessage 协议
仅在你需要自己写 iframe 通信时才用得到。正常用 SDK 不需要看。
| 方向 | 类型 | Payload |
|---|---|---|
| Host → Iframe | tensor-agent:set-context-prompts | { prompts: string[] } |
| Host → Iframe | tensor-agent:set-context | BusinessContext |
| Host → Iframe | tensor-agent:send-prompt | { text: string } |
| Host → Iframe | tensor-agent:update-token | { jwt: string } |
| Iframe → Host | tensor-agent:ready | {} |
| Iframe → Host | tensor-agent:data-change | DataChangePayload |
| Iframe → Host | tensor-agent:action | ActionPayload |
| Iframe → Host | tensor-agent:auth-failed | { reason: string } |