super-agent-sdk 1.0.10 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -3
- package/dist/index.cjs +4 -4
- package/dist/index.d.ts +40 -0
- package/dist/index.mjs +204 -170
- package/dist/{mammoth.browser-DiAPPO3x.cjs → mammoth.browser-BX8glWVC.cjs} +24 -24
- package/dist/{mammoth.browser-COPV53yV.js → mammoth.browser-CmEXxaIp.js} +133 -139
- package/dist/nosUploader-BkBtdrMs.js +4712 -0
- package/dist/nosUploader-KkzFK5EW.cjs +13 -0
- package/dist/widget.cjs +134 -48
- package/dist/widget.d.ts +57 -7
- package/dist/widget.mjs +10648 -1850
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -34,7 +34,9 @@ Super Agent Web SDK —— 为 `super-agent-service` 提供的一站式浏览器
|
|
|
34
34
|
- **纯 API 模式**:`createSuperAgent()` 返回轻量 SDK 实例,提供流式聊天、会话列表、历史消息、Token 刷新等能力。
|
|
35
35
|
- **嵌入式组件**:`mount()` 挂载 React 聊天组件。
|
|
36
36
|
- **两种展示模式**:`floating`(右下角气泡 + 弹窗面板)与 `fullpage`(整页 DeepSeek 风格布局)。
|
|
37
|
+
- **Markdown 渲染**:内置 `react-markdown` + GFM(表格 / 任务列表 / 删除线),Claude 风格排版,默认转义防 XSS(见 [6.3](#63-markdown-渲染ai-消息))。
|
|
37
38
|
- **会话持久化**:自动加载会话列表、`localStorage` 记住最近会话、刷新后自动恢复。
|
|
39
|
+
- **附件上传(NOS 直传)**:内置 NOS 直传(分片 + 断点续传 + 进度),📎 选文件随消息发送,沙箱 Agent 可读取文件内容(详见 [5.12](#512-createnosuploader) / [6.1](#61-mount))。
|
|
38
40
|
|
|
39
41
|
---
|
|
40
42
|
|
|
@@ -183,6 +185,8 @@ const sessionId = await sdk.createSession();
|
|
|
183
185
|
export interface ChatOptions {
|
|
184
186
|
sessionId: string; // 必填 — 先调用 createSession()
|
|
185
187
|
message: string;
|
|
188
|
+
/** 附件(≤10 个,仅沙箱模式 Agent 支持,其他模式后端返回 400)。需先经直传上传到 NOS 拿到 nosKey。 */
|
|
189
|
+
attachments?: ChatAttachment[];
|
|
186
190
|
stream?: boolean; // 默认 true
|
|
187
191
|
onMessage?: (event: ChatEvent) => void;
|
|
188
192
|
onError?: (error: Error) => void;
|
|
@@ -193,6 +197,26 @@ export interface ChatOptions {
|
|
|
193
197
|
}) => void; // messageId:后端在 done 事件返回的消息标识(存在时 SDK Widget 用其启用消息反馈,若返回可据此判断该消息可 feedback)
|
|
194
198
|
signal?: AbortSignal;
|
|
195
199
|
}
|
|
200
|
+
|
|
201
|
+
export interface ChatAttachment {
|
|
202
|
+
nosKey: string; // NOS 对象 key(必填,直传后获得)
|
|
203
|
+
filename: string; // 原始文件名(必填,模型靠扩展名选择解析方式)
|
|
204
|
+
size?: number; // 字节数(后端落地时校验)
|
|
205
|
+
mime?: string; // MIME 类型,如 "application/pdf"
|
|
206
|
+
url?: string; // 下载链接(仅前端回显;发送时 SDK 自动剥离,不进后端)
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
带附件发送(附件文件会由后端写入沙箱,Agent 可通过 `read_file` / `execute` 读取):
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
const attachment = await createNosUploader({ baseURL: "/api/e5s" })(file); // 见 5.12
|
|
214
|
+
const controller = sdk.chat({
|
|
215
|
+
sessionId,
|
|
216
|
+
message: "总结这份报告",
|
|
217
|
+
attachments: [attachment],
|
|
218
|
+
onMessage, onDone, onError,
|
|
219
|
+
});
|
|
196
220
|
```
|
|
197
221
|
|
|
198
222
|
```ts
|
|
@@ -369,7 +393,33 @@ const skills: SkillInfo[] = await sdk.listSkills();
|
|
|
369
393
|
// [{ code: "web-searcher", name: "web-searcher", displayName: "网页搜索", description: "..." }]
|
|
370
394
|
```
|
|
371
395
|
|
|
372
|
-
### 5.12
|
|
396
|
+
### 5.12 createNosUploader()
|
|
397
|
+
|
|
398
|
+
内置 NOS 直传上传器(协议对齐 `@netease-ehr/ui` Upload 的 nosUpload 路径,**零依赖**该私有包):`nosKey = MD5(name:size:ts)` → `GET {baseURL}/nos/token` → nos-js-sdk 分片直传 NOS 边缘节点(默认 4MB 分片 + localStorage 断点续传)→ `GET {baseURL}/nos/url` 取下载地址。内置挂起防护(分片超时 120s + 无进度看门狗 130s)。
|
|
399
|
+
|
|
400
|
+
```ts
|
|
401
|
+
import { createNosUploader } from "super-agent-sdk";
|
|
402
|
+
|
|
403
|
+
export interface NosUploaderConfig {
|
|
404
|
+
baseURL: string; // 业务网关(与业务系统接口网关一致,如 "/api/e5s",同源相对路径)
|
|
405
|
+
headers?: Record<string, string>; // 网关鉴权头(token/url 两个 GET 携带)
|
|
406
|
+
trunkSize?: number; // 分片大小,默认 4MB
|
|
407
|
+
onProgress?: (percent: number) => void; // 上传进度 0-100
|
|
408
|
+
chunkTimeoutMs?: number; // 单分片超时,默认 120s
|
|
409
|
+
stallTimeoutMs?: number; // 无进度看门狗,默认 130s
|
|
410
|
+
fetchNosUrl?: boolean; // 上传后调 /nos/url 取下载地址,默认 true(失败降级不影响结果)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const upload = createNosUploader({
|
|
414
|
+
baseURL: "/api/e5s",
|
|
415
|
+
onProgress: (p) => console.log(`${p}%`),
|
|
416
|
+
});
|
|
417
|
+
const attachment = await upload(file); // → { nosKey, filename, size, mime, url }
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
> Widget 场景无需手动调用——`mount({ nosUpload: { baseURL } })` 一行开启(见 [6.1](#61-mount))。
|
|
421
|
+
|
|
422
|
+
### 5.13 后端接口对照
|
|
373
423
|
|
|
374
424
|
SDK 方法 → 后端接口的完整映射:
|
|
375
425
|
|
|
@@ -379,7 +429,7 @@ SDK 方法 → 后端接口的完整映射:
|
|
|
379
429
|
| --------------------- | -------------------------------------------- | --------------------------------------- | ----------------------------------------------- |
|
|
380
430
|
| `getToken()` | `POST {gateway}/v1/token` | `{ botId }` | `{ token, appId }` |
|
|
381
431
|
| `createSession()` | `POST /chat/sessions` | `{ botId }` | `{ sessionId }` |
|
|
382
|
-
| `chat()` | `POST /chat` | `{ botId, sessionId, message, stream }` | SSE 流,`done`/`stop` 事件含 `sessionId` |
|
|
432
|
+
| `chat()` | `POST /chat` | `{ botId, sessionId, message, stream, attachments? }` | SSE 流,`done`/`stop` 事件含 `sessionId` |
|
|
383
433
|
| `listConversations()` | `GET /chat/conversations` | 查询参数 `botId`、`page`、`size` | `{ items: [{ sessionId, botId, ... }], total }` |
|
|
384
434
|
| `feedback()` | `POST /chat/messages/{messageId}/feedback` | `{ type, reason?, remark? }` | `null` |
|
|
385
435
|
| `cancelFeedback()` | `DELETE /chat/messages/{messageId}/feedback` | 无 body | `null` |
|
|
@@ -418,6 +468,10 @@ export interface MountOptions {
|
|
|
418
468
|
logo?: string; // floating 首页居中 Logo(URL),默认 SDK 内置 Logo
|
|
419
469
|
triggerLogo?: string; // floating 触发按钮 Logo(URL),默认 SDK 内置 Logo
|
|
420
470
|
showTimestamp?: boolean; // 显示消息时间戳,默认 false 不显示
|
|
471
|
+
/** 附件上传器(完全自定义,优先于 nosUpload)。不配置则输入区无附件按钮。 */
|
|
472
|
+
attachmentUploader?: (file: File) => Promise<ChatAttachment>;
|
|
473
|
+
/** 内置 NOS 直传(推荐):配置即用,📎 按钮 + chip 进度 + 断点续传全内置 */
|
|
474
|
+
nosUpload?: { baseURL: string; headers?: Record<string, string>; trunkSize?: number };
|
|
421
475
|
}
|
|
422
476
|
|
|
423
477
|
export interface CapabilityItem {
|
|
@@ -466,6 +520,31 @@ widget.destroy(); // 卸载并移除 DOM
|
|
|
466
520
|
|
|
467
521
|
> 挂载后默认是收起状态,需调用 `widget.open()` 展开。
|
|
468
522
|
|
|
523
|
+
#### 附件上传(📎 按钮)
|
|
524
|
+
|
|
525
|
+
配置 `nosUpload`(内置 NOS 直传)或 `attachmentUploader`(完全自定义,优先级更高)任一即启用,二者都不配则附件功能完全隐藏(存量接入零影响):
|
|
526
|
+
|
|
527
|
+
```ts
|
|
528
|
+
// 内置直传(推荐,一行开启)
|
|
529
|
+
mount("#chat-root", {
|
|
530
|
+
sdk,
|
|
531
|
+
nosUpload: { baseURL: "/api/e5s" }, // 业务网关
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
// 完全自定义(其他对象存储 / 自建网关)
|
|
535
|
+
mount("#chat-root", {
|
|
536
|
+
sdk,
|
|
537
|
+
attachmentUploader: async (file) => ({
|
|
538
|
+
nosKey: await uploadSomewhere(file),
|
|
539
|
+
filename: file.name,
|
|
540
|
+
size: file.size,
|
|
541
|
+
mime: file.type,
|
|
542
|
+
}),
|
|
543
|
+
});
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
内置行为:📎 按钮(输入框左下角,绝对定位)选文件(≤10 个)→ 待发送附件以 chip 托盘显示在**输入框上方**(不占输入框内部空间),实时显示上传百分比 → 上传完成显示文件大小(可点击下载)→ 随消息发送 → 沙箱 Agent 读取(仅沙箱模式 Agent 支持,其他模式后端 400)。上传中禁止发送,失败 chip 红标可移除重选;regenerate 会连同附件一起重发。
|
|
547
|
+
|
|
469
548
|
### 6.2 源码接入(monorepo alias)注意事项
|
|
470
549
|
|
|
471
550
|
Widget 内部使用 **Tailwind CSS**(utility 全部限定在 `[data-super-agent-widget]` 宿主选择器内、禁用 preflight,不影响宿主页样式)。分发包(`dist`)已内联全部样式;内置图片资产(Logo/图标/头像等)走 CDN 绝对路径(`src/widget/assets/index.ts` 的 `BASE_URL`),**npm 引入无需任何配置**(需能访问 CDN)。
|
|
@@ -488,6 +567,17 @@ export default {
|
|
|
488
567
|
|
|
489
568
|
2. 无需在宿主 config 中镜像 SDK 的 `important: '[data-super-agent-widget]'`(那会把宿主自己的 utility 也锁进 widget 容器内);宿主管线生成的 utility 为普通类,widget DOM 照常命中,动画/自定义类由 SDK 自带的 `tailwind.css`(运行时注入)保证。
|
|
490
569
|
|
|
570
|
+
### 6.3 Markdown 渲染(AI 消息)
|
|
571
|
+
|
|
572
|
+
AI 文本消息(`text` part)按 Markdown 渲染,内置 `react-markdown` + `remark-gfm`(**随 SDK 打包,宿主无需额外安装**):
|
|
573
|
+
|
|
574
|
+
- **语法**:CommonMark 全集 + GFM 扩展(表格、任务列表 `- [ ]`、删除线 `~~x~~`、自动链接)
|
|
575
|
+
- **排版**:Claude 风格,色值对齐前端设计 tokens(`frontend/src/theme/tokens.ts`)——链接品牌橙 `#d97757`(hover 加深)、行内代码 `parchment #e8e6dc` 底、代码块 `card #efede6` 底 + `border #e3e1d8` hairline 边框(浅色暖调、横向滚动)、引用块 `clay` 左点缀、表头 `parchment` 底
|
|
576
|
+
- **安全**:默认 HTML 转义 + URL 清洗(`urlTransform`),Markdown 注入不会产生 XSS
|
|
577
|
+
- **流式友好**:未闭合语法(输出中的代码块 / 表格)渐进渲染不跳版
|
|
578
|
+
|
|
579
|
+
如需完全自定义渲染,通过 `slots.TextPart` 替换(见 [8.3 组件插槽](#83-组件插槽slots));`renderMode === "html"` 的工具结果不受影响,仍走 DOMPurify 内联渲染。
|
|
580
|
+
|
|
491
581
|
---
|
|
492
582
|
|
|
493
583
|
## 7. 展示模式
|
|
@@ -778,7 +868,7 @@ function MyMessage({
|
|
|
778
868
|
```ts
|
|
779
869
|
export interface ComposerProps {
|
|
780
870
|
status: ChatStatus;
|
|
781
|
-
onSend: (content: string) => void;
|
|
871
|
+
onSend: (content: string, attachments?: ChatAttachment[]) => void;
|
|
782
872
|
onStop: () => void;
|
|
783
873
|
}
|
|
784
874
|
```
|
|
@@ -1431,6 +1521,7 @@ export type MessagePart =
|
|
|
1431
1521
|
html?: string;
|
|
1432
1522
|
}
|
|
1433
1523
|
| { type: "error"; content: string }
|
|
1524
|
+
| { type: "attachment"; attachment: ChatAttachment } // 用户消息携带的附件 chip
|
|
1434
1525
|
| {
|
|
1435
1526
|
type: "interrupt";
|
|
1436
1527
|
interrupt: InterruptEvent;
|
|
@@ -1446,6 +1537,7 @@ export type MessagePart =
|
|
|
1446
1537
|
| `tool_call` | `toolName`, `toolCallId`, `args` | 工具调用,`args` 为 JSON 字符串 |
|
|
1447
1538
|
| `tool_result` | `toolName`, `toolCallId`, `content`, `artifacts?`, `renderMode?`, `html?` | 工具返回结果 |
|
|
1448
1539
|
| `error` | `content` | 错误信息 |
|
|
1540
|
+
| `attachment` | `attachment` | 用户消息附件 chip(发送时本地构造 / 历史加载由后端映射,有 `url` 时可下载) |
|
|
1449
1541
|
| `interrupt` | `interrupt`, `resolved?`, `response?` | 中断交互卡片(HITL) |
|
|
1450
1542
|
|
|
1451
1543
|
### 11.3 ChatEvent(流式事件)
|
|
@@ -1521,6 +1613,8 @@ mount("#chat", { sdk }).open();
|
|
|
1521
1613
|
// 遇到 interrupt 时:自动渲染卡片 → 用户点击 → 自动 respondInterrupt → Agent 继续
|
|
1522
1614
|
```
|
|
1523
1615
|
|
|
1616
|
+
> interrupt 帧是流的**暂停点**(后端不发 `done`),`respondInterrupt()` 后的续事件在同一 SSE 流上返回;续流结束(或嵌套 interrupt 再次暂停)后,输入框均可正常继续发送——挂起/续流状态由 Widget 自动管理。
|
|
1617
|
+
|
|
1524
1618
|
也可通过插槽自定义中断卡片:
|
|
1525
1619
|
|
|
1526
1620
|
```ts
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function
|
|
2
|
-
`).map(
|
|
3
|
-
`))}catch{return null}switch(t.type){case"user":return{type:"user",content:t.content
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const w=require("./nosUploader-KkzFK5EW.cjs");function h(r){if(Array.isArray(r))return r.map(e=>h(e));if(r&&typeof r=="object"&&r.constructor===Object){const e={};for(const[t,n]of Object.entries(r)){const i=t.replace(/_([a-z])/g,(a,s)=>s.toUpperCase());e[i]=h(n)}return e}return r}const T="/api/agent/runtime/v1";class k{constructor(e){this.refreshing=null;const t=e.baseUrl??T;this.baseUrl=t.replace(/\/$/,""),this.tokenGateway=e.tokenGateway.replace(/\/$/,""),this.appId=e.appId??"",this.token=e.token??"",this.botId=e.botId,this.isAbsoluteUrl=/^https?:\/\//.test(this.baseUrl),this.isAbsoluteGateway=/^https?:\/\//.test(this.tokenGateway)}getBotId(){return this.botId}getAppKey(){return this.appId}setToken(e){this.token=e}getOrigin(){return typeof window<"u"?window.location.origin:""}async fetchToken(){const t=`${this.isAbsoluteGateway?this.tokenGateway:`${this.getOrigin()}${this.tokenGateway}`}/v1/token`,n=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({botId:this.botId})});if(!n.ok)throw new Error(`HTTP ${n.status}: ${n.statusText}`);const i=await n.json();if(i.code!=="200")throw new Error(i.message||`API error: ${i.code}`);const a=h(i.data);return this.token=a.token,a.appId&&!this.appId&&(this.appId=a.appId),a.token}headers(e){return{"Content-Type":"application/json","X-App-Id":this.appId,"X-Token":this.token,...e}}async handleTokenRefresh(){if(this.refreshing)return await this.refreshing,!0;try{return this.refreshing=(async()=>{await this.fetchToken()})(),await this.refreshing,!0}catch{return!1}finally{this.refreshing=null}}async request(e,t,n){if(!this.token&&!await this.handleTokenRefresh())throw new Error("SDK token not available: POST /token failed");let i=this.isAbsoluteUrl?`${this.baseUrl}${t}`:`${this.getOrigin()}${this.baseUrl}${t}`;if(n!=null&&n.params){const o=new URLSearchParams;for(const[d,l]of Object.entries(n.params))l!=null&&o.set(d,String(l));const c=o.toString();c&&(i+=`?${c}`)}const a=await fetch(i,{method:e,headers:this.headers(),body:n!=null&&n.body?JSON.stringify(n.body):void 0});if(a.status===401&&!(n!=null&&n.retry)&&await this.handleTokenRefresh())return this.request(e,t,{...n,retry:!0});if(!a.ok)throw new Error(`HTTP ${a.status}: ${a.statusText}`);const s=await a.json();if(s.code!=="200")throw new Error(s.message||`API error: ${s.code}`);return h(s.data)}get(e,t){return this.request("GET",e,{params:t})}post(e,t){return this.request("POST",e,{body:t})}patch(e,t){return this.request("PATCH",e,{body:t})}del(e){return this.request("DELETE",e)}async streamPost(e,t,n){if(!this.token&&!await this.handleTokenRefresh())throw new Error("SDK token not available: POST /token failed");const i=this.isAbsoluteUrl?`${this.baseUrl}${e}`:`${this.getOrigin()}${this.baseUrl}${e}`,a=await fetch(i,{method:"POST",headers:this.headers(),body:JSON.stringify(t),signal:n});return a.status===401&&await this.handleTokenRefresh()?fetch(i,{method:"POST",headers:this.headers(),body:JSON.stringify(t),signal:n}):a}}function u(r){return typeof r=="string"?r:Array.isArray(r)?r.map(e=>{if(typeof e=="string")return e;if(e&&typeof e=="object"){if(typeof e.text=="string")return e.text;if(e.type)return`[${e.type}${e.mime_type?": "+e.mime_type:""}]`}return""}).join(""):r==null?"":String(r)}function I(r){const e=r.split(`
|
|
2
|
+
`).map(n=>n.trim()).filter(n=>n.startsWith("data:")).map(n=>n.slice(5).trim());if(!e.length)return null;let t;try{t=JSON.parse(e.join(`
|
|
3
|
+
`))}catch{return null}switch(t.type){case"user":return{type:"user",content:u(t.content)};case"thinking":return{type:"thinking",content:u(t.content)};case"ai":return{type:"ai",content:u(t.content)};case"tool_call":return{type:"tool_call",content:t.name??"",toolName:t.name,toolCallId:t.id,args:typeof t.args=="string"?t.args:JSON.stringify(t.args??{})};case"tool_result":return{type:"tool_result",content:u(t.output),toolName:t.name,toolCallId:t.id,artifacts:Array.isArray(t.artifacts)?t.artifacts:void 0,renderMode:t.render_mode??t.renderMode,html:t.html};case"done":return{type:"done",content:u(t.content),sessionId:t.sessionId??t.session_id??"",messageId:t.messageId??t.message_id};case"stop":return{type:"stop",content:u(t.content),sessionId:t.sessionId??t.session_id??"",messageId:t.messageId??t.message_id};case"error":return{type:"error",content:t.message??"Unknown error"};case"interrupt":return{type:"interrupt",content:t.content??"",interrupt:{interruptId:t.interruptId??t.interrupt_id??"",interruptType:t.interruptType??t.interrupt_type??"confirm",content:t.content??"",options:t.options,placeholder:t.placeholder,inputType:t.inputType??t.input_type,maxLength:t.maxLength??t.max_length,required:t.required,fields:t.fields,metadata:t.metadata}};default:return null}}async function p(r,e,t){if(!r.body){t(new Error("Response body is null"));return}const n=r.body.getReader(),i=new TextDecoder;let a="";try{for(;;){const{value:s,done:o}=await n.read();if(o)break;a+=i.decode(s,{stream:!0});let c;for(;(c=a.indexOf(`
|
|
4
4
|
|
|
5
|
-
`))!==-1;){const d=a.slice(0,c);a=a.slice(c+2);const l=
|
|
5
|
+
`))!==-1;){const d=a.slice(0,c);a=a.slice(c+2);const l=I(d);l&&e(l)}}if(a.trim()){const s=I(a);s&&e(s)}}catch(s){(s==null?void 0:s.name)!=="AbortError"&&t(s instanceof Error?s:new Error(String(s)))}finally{try{n.releaseLock()}catch{}}}function b(r,e){var a;const t=new AbortController,n=e.signal?S(e.signal,t.signal):t.signal,i={botId:r.getBotId(),message:e.message,sessionId:e.sessionId,stream:e.stream!==!1,...(a=e.attachments)!=null&&a.length?{attachments:e.attachments.map(({url:s,...o})=>o)}:{}};return i.stream?r.streamPost("/chat",i,n).then(s=>{var o;if(!s.ok){(o=e.onError)==null||o.call(e,new Error(`HTTP ${s.status}`));return}return p(s,c=>{var d,l,f,g,y,m;c.type==="done"&&c.sessionId?(d=e.onDone)==null||d.call(e,{sessionId:c.sessionId,content:c.content,messageId:c.messageId}):c.type==="done"?(l=e.onDone)==null||l.call(e,{sessionId:"",content:c.content,messageId:c.messageId}):c.type==="stop"?((f=e.onMessage)==null||f.call(e,c),(g=e.onDone)==null||g.call(e,{sessionId:c.sessionId??"",content:c.content,messageId:c.messageId})):c.type==="error"?(y=e.onError)==null||y.call(e,new Error(c.content)):(m=e.onMessage)==null||m.call(e,c)},c=>{var d;return(d=e.onError)==null?void 0:d.call(e,c)})}).catch(s=>{var o;(s==null?void 0:s.name)!=="AbortError"&&((o=e.onError)==null||o.call(e,s instanceof Error?s:new Error(String(s))))}):r.post("/chat",i).then(s=>{var o;(o=e.onDone)==null||o.call(e,{sessionId:s.sessionId,content:s.content??"",messageId:s.messageId})}).catch(s=>{var o;(o=e.onError)==null||o.call(e,s instanceof Error?s:new Error(String(s)))}),t}function S(r,e){const t=new AbortController,n=()=>t.abort();return r.addEventListener("abort",n,{once:!0}),e.addEventListener("abort",n,{once:!0}),(r.aborted||e.aborted)&&t.abort(),t.signal}async function $(r){return(await r.post("/chat/sessions",{botId:r.getBotId()})).sessionId}function A(r){return{sessionId:r.threadId??r.sessionId,botId:r.botId,title:r.title,createTime:r.createTime,updateTime:r.updateTime}}async function C(r,e){const t=await r.get("/chat/conversations",{botId:r.getBotId(),page:(e==null?void 0:e.page)??1,size:(e==null?void 0:e.size)??20});return{...t,items:t.items.map(A)}}async function _(r,e,t){await r.patch(`/chat/conversations/${e}`,{title:t})}async function O(r,e){await r.del(`/chat/conversations/${e}`)}async function P(r,e){const t=await r.get(`/chat/conversations/${e}/messages`);return E(t)}function E(r){const e=[...r].sort((s,o)=>{const c=new Date(s.createTime??0).getTime()||0,d=new Date(o.createTime??0).getTime()||0;return c!==d?c-d:(s.seq??0)-(o.seq??0)}),t=new Map;for(const s of e)s.role==="tool"&&s.toolCallId&&t.set(s.toolCallId,s.content||"");const n=new Set,i=[],a=(s,o,c)=>{const d=i[i.length-1];(d==null?void 0:d.role)==="assistant"?(d.parts.push(...s),d.id=o):i.push({id:o,role:"assistant",parts:[...s],timestamp:c})};for(const s of e){if(s.role==="system")continue;const o=String(s.id??s.messageId??`msg_${Date.now()}`),c=new Date(s.createTime??"").getTime()||Date.now();if(s.role==="human"){const d=(s.attachments??[]).map(l=>({type:"attachment",attachment:{nosKey:l.nosKey??l.attachmentId??"",filename:l.filename??"file",size:l.size??void 0,mime:l.mime??void 0,url:l.url??void 0}}));i.push({id:o,role:"user",parts:[...d,{type:"text",content:u(s.content)}],timestamp:c});continue}if(s.role==="ai"){const d=U(s,t,n);d.length>0&&a(d,o,c);continue}if(s.role==="tool"){if(s.toolCallId&&n.has(s.toolCallId))continue;a([N(s)],o,c)}}return i}function U(r,e,t){const n=[];r.reasoning&&n.push({type:"thinking",content:r.reasoning}),r.content&&n.push({type:"text",content:u(r.content)});for(const i of r.toolCalls??[])i.is_interrupt?(t.add(i.id),n.push(x(i,e))):n.push(v(i));return n}function x(r,e){const t=e.has(r.id),n=typeof r.args=="object"?r.args:{};return{type:"interrupt",interrupt:{interruptId:r.id??"",interruptType:n.interruptType??"input",content:n.content??"",options:n.options,placeholder:n.placeholder,inputType:n.inputType,maxLength:n.maxLength,required:n.required,fields:n.fields,metadata:n.metadata},resolved:t,response:t?{action:"submit",value:q(e.get(r.id))}:void 0}}function v(r){let e;try{e=typeof r.args=="string"?r.args:JSON.stringify(r.args??{})}catch{e=String(r.args??"{}")}return{type:"tool_call",toolName:r.name??"",toolCallId:r.id??"",args:e}}function N(r){var t,n,i;const e=Array.isArray(r.artifacts)?r.artifacts:void 0;return{type:"tool_result",toolName:r.toolName??"",toolCallId:r.toolCallId??"",content:u(r.content),artifacts:e,renderMode:((t=r.metadata)==null?void 0:t.renderMode)??((n=r.metadata)==null?void 0:n.render_mode),html:(i=r.metadata)==null?void 0:i.html}}function q(r){if(r!=null)try{return JSON.parse(r)}catch{return r}}async function D(r,e,t,n,i,a){const s=await r.streamPost(`/chat/interrupt/${e}/respond`,{sessionId:t,action:n.action,value:n.value});if(!s.ok){const o=new Error(`HTTP ${s.status}`);throw a==null||a(o),o}i&&await p(s,o=>{o.type==="error"?a==null||a(new Error(o.content)):i(o)},o=>a==null?void 0:a(o))}async function L(r){return r.get(`/chat/bots/${r.getBotId()}/skills`)}async function G(r,e){await r.post("/chat/stop",{sessionId:e})}function M(r){const e=new k(r);return{botId:e.getBotId(),getToken:()=>e.fetchToken(),createSession:()=>$(e),chat:n=>b(e,n),listConversations:n=>C(e,n),renameConversation:(n,i)=>_(e,n,i),deleteConversation:n=>O(e,n),getMessages:n=>P(e,n),respondInterrupt:(n,i,a,s,o)=>D(e,n,i,a,s,o),setToken:n=>e.setToken(n),feedback:async(n,i,a)=>{const s={type:i};i==="dislike"&&((a==null?void 0:a.reason)!=null&&(s.reason=a.reason),a!=null&&a.remark&&(s.remark=a.remark)),await e.post(`/chat/messages/${n}/feedback`,s)},cancelFeedback:async n=>{await e.del(`/chat/messages/${n}/feedback`)},listSkills:()=>L(e),stopGeneration:n=>G(e,n)}}exports.createNosUploader=w.createNosUploader;exports.createSuperAgent=M;
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,15 @@ export declare interface ArtifactFile {
|
|
|
14
14
|
summary?: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/** 附件描述:前端直传 NOS(如 @netease-ehr/ui Upload 的 nosUpload onSuccess 结果)提取。 */
|
|
18
|
+
export declare interface ChatAttachment {
|
|
19
|
+
nosKey: string;
|
|
20
|
+
filename: string;
|
|
21
|
+
size?: number;
|
|
22
|
+
mime?: string;
|
|
23
|
+
url?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
17
26
|
export declare interface ChatEvent {
|
|
18
27
|
type: "user" | "thinking" | "ai" | "tool_call" | "tool_result" | "done" | "stop" | "error" | "interrupt";
|
|
19
28
|
content: string;
|
|
@@ -31,6 +40,8 @@ export declare interface ChatEvent {
|
|
|
31
40
|
export declare interface ChatOptions {
|
|
32
41
|
sessionId: string;
|
|
33
42
|
message: string;
|
|
43
|
+
/** 附件(仅沙箱模式 Agent 支持,其他模式后端返回 400)。需先经直传组件传到 NOS 拿到 nosKey。 */
|
|
44
|
+
attachments?: ChatAttachment[];
|
|
34
45
|
stream?: boolean;
|
|
35
46
|
onMessage?: (event: ChatEvent) => void;
|
|
36
47
|
onError?: (error: Error) => void;
|
|
@@ -50,6 +61,15 @@ export declare interface Conversation {
|
|
|
50
61
|
updateTime: string;
|
|
51
62
|
}
|
|
52
63
|
|
|
64
|
+
/**
|
|
65
|
+
* 创建 NOS 直传上传器(attachmentUploader 形态:File → Promise<ChatAttachment>)。
|
|
66
|
+
*
|
|
67
|
+
* 与 @netease-ehr/ui Upload nosUpload 的行为对齐:
|
|
68
|
+
* - nosKey 生成算法一致(MD5(name:size:timestamp))—— 与原组件共存在同一桶内无冲突
|
|
69
|
+
* - 断点续传:nos-js-sdk 经 localStorage 记录 progress/context,同文件重传自动续传
|
|
70
|
+
*/
|
|
71
|
+
export declare function createNosUploader(config: NosUploaderConfig): (file: File) => Promise<ChatAttachment>;
|
|
72
|
+
|
|
53
73
|
export declare function createSuperAgent(config: SDKConfig): SuperAgentSDK;
|
|
54
74
|
|
|
55
75
|
export declare interface FormField {
|
|
@@ -125,6 +145,9 @@ export declare type MessagePart = {
|
|
|
125
145
|
} | {
|
|
126
146
|
type: "error";
|
|
127
147
|
content: string;
|
|
148
|
+
} | {
|
|
149
|
+
type: "attachment";
|
|
150
|
+
attachment: ChatAttachment;
|
|
128
151
|
} | {
|
|
129
152
|
type: "interrupt";
|
|
130
153
|
interrupt: InterruptEvent;
|
|
@@ -132,6 +155,23 @@ export declare type MessagePart = {
|
|
|
132
155
|
response?: InterruptResponse;
|
|
133
156
|
};
|
|
134
157
|
|
|
158
|
+
export declare interface NosUploaderConfig {
|
|
159
|
+
/** 业务网关(通常与业务系统接口网关保持一致,如 "/api/e5s") */
|
|
160
|
+
baseURL: string;
|
|
161
|
+
/** 网关自定义请求头(鉴权等,token/url GET 携带) */
|
|
162
|
+
headers?: Record<string, string>;
|
|
163
|
+
/** 分片大小(默认 4MB,合法值 (0, 4MB]) */
|
|
164
|
+
trunkSize?: number;
|
|
165
|
+
/** 上传进度回调(0-100) */
|
|
166
|
+
onProgress?: (percent: number) => void;
|
|
167
|
+
/** 单分片 XHR 超时(默认 120s;nos-js-sdk 默认 50s,慢环境 commit 易超时静默死亡) */
|
|
168
|
+
chunkTimeoutMs?: number;
|
|
169
|
+
/** 无进度看门狗(默认 130s:超过此时长无任何进度事件且未完成 → 显式报错) */
|
|
170
|
+
stallTimeoutMs?: number;
|
|
171
|
+
/** 上传完成后调 /nos/url 取下载地址(默认 true,对齐原组件;失败降级不影响上传结果) */
|
|
172
|
+
fetchNosUrl?: boolean;
|
|
173
|
+
}
|
|
174
|
+
|
|
135
175
|
export declare interface SDKConfig {
|
|
136
176
|
botId: number;
|
|
137
177
|
tokenGateway: string;
|