super-agent-sdk 1.0.9 → 1.0.11
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 +170 -6
- 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-Czw2m8xL.js} +133 -139
- package/dist/nosUploader-C94D61NK.js +4712 -0
- package/dist/nosUploader-KkzFK5EW.cjs +13 -0
- package/dist/widget.cjs +52 -28
- package/dist/widget.d.ts +126 -8
- package/dist/widget.mjs +1678 -1401
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ Super Agent Web SDK —— 为 `super-agent-service` 提供的一站式浏览器
|
|
|
35
35
|
- **嵌入式组件**:`mount()` 挂载 React 聊天组件。
|
|
36
36
|
- **两种展示模式**:`floating`(右下角气泡 + 弹窗面板)与 `fullpage`(整页 DeepSeek 风格布局)。
|
|
37
37
|
- **会话持久化**:自动加载会话列表、`localStorage` 记住最近会话、刷新后自动恢复。
|
|
38
|
+
- **附件上传(NOS 直传)**:内置 NOS 直传(分片 + 断点续传 + 进度),📎 选文件随消息发送,沙箱 Agent 可读取文件内容(详见 [5.12](#512-createnosuploader) / [6.1](#61-mount))。
|
|
38
39
|
|
|
39
40
|
---
|
|
40
41
|
|
|
@@ -183,6 +184,8 @@ const sessionId = await sdk.createSession();
|
|
|
183
184
|
export interface ChatOptions {
|
|
184
185
|
sessionId: string; // 必填 — 先调用 createSession()
|
|
185
186
|
message: string;
|
|
187
|
+
/** 附件(≤10 个,仅沙箱模式 Agent 支持,其他模式后端返回 400)。需先经直传上传到 NOS 拿到 nosKey。 */
|
|
188
|
+
attachments?: ChatAttachment[];
|
|
186
189
|
stream?: boolean; // 默认 true
|
|
187
190
|
onMessage?: (event: ChatEvent) => void;
|
|
188
191
|
onError?: (error: Error) => void;
|
|
@@ -193,6 +196,26 @@ export interface ChatOptions {
|
|
|
193
196
|
}) => void; // messageId:后端在 done 事件返回的消息标识(存在时 SDK Widget 用其启用消息反馈,若返回可据此判断该消息可 feedback)
|
|
194
197
|
signal?: AbortSignal;
|
|
195
198
|
}
|
|
199
|
+
|
|
200
|
+
export interface ChatAttachment {
|
|
201
|
+
nosKey: string; // NOS 对象 key(必填,直传后获得)
|
|
202
|
+
filename: string; // 原始文件名(必填,模型靠扩展名选择解析方式)
|
|
203
|
+
size?: number; // 字节数(后端落地时校验)
|
|
204
|
+
mime?: string; // MIME 类型,如 "application/pdf"
|
|
205
|
+
url?: string; // 下载链接(仅前端回显;发送时 SDK 自动剥离,不进后端)
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
带附件发送(附件文件会由后端写入沙箱,Agent 可通过 `read_file` / `execute` 读取):
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
const attachment = await createNosUploader({ baseURL: "/api/e5s" })(file); // 见 5.12
|
|
213
|
+
const controller = sdk.chat({
|
|
214
|
+
sessionId,
|
|
215
|
+
message: "总结这份报告",
|
|
216
|
+
attachments: [attachment],
|
|
217
|
+
onMessage, onDone, onError,
|
|
218
|
+
});
|
|
196
219
|
```
|
|
197
220
|
|
|
198
221
|
```ts
|
|
@@ -369,7 +392,33 @@ const skills: SkillInfo[] = await sdk.listSkills();
|
|
|
369
392
|
// [{ code: "web-searcher", name: "web-searcher", displayName: "网页搜索", description: "..." }]
|
|
370
393
|
```
|
|
371
394
|
|
|
372
|
-
### 5.12
|
|
395
|
+
### 5.12 createNosUploader()
|
|
396
|
+
|
|
397
|
+
内置 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)。
|
|
398
|
+
|
|
399
|
+
```ts
|
|
400
|
+
import { createNosUploader } from "super-agent-sdk";
|
|
401
|
+
|
|
402
|
+
export interface NosUploaderConfig {
|
|
403
|
+
baseURL: string; // 业务网关(与业务系统接口网关一致,如 "/api/e5s",同源相对路径)
|
|
404
|
+
headers?: Record<string, string>; // 网关鉴权头(token/url 两个 GET 携带)
|
|
405
|
+
trunkSize?: number; // 分片大小,默认 4MB
|
|
406
|
+
onProgress?: (percent: number) => void; // 上传进度 0-100
|
|
407
|
+
chunkTimeoutMs?: number; // 单分片超时,默认 120s
|
|
408
|
+
stallTimeoutMs?: number; // 无进度看门狗,默认 130s
|
|
409
|
+
fetchNosUrl?: boolean; // 上传后调 /nos/url 取下载地址,默认 true(失败降级不影响结果)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const upload = createNosUploader({
|
|
413
|
+
baseURL: "/api/e5s",
|
|
414
|
+
onProgress: (p) => console.log(`${p}%`),
|
|
415
|
+
});
|
|
416
|
+
const attachment = await upload(file); // → { nosKey, filename, size, mime, url }
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
> Widget 场景无需手动调用——`mount({ nosUpload: { baseURL } })` 一行开启(见 [6.1](#61-mount))。
|
|
420
|
+
|
|
421
|
+
### 5.13 后端接口对照
|
|
373
422
|
|
|
374
423
|
SDK 方法 → 后端接口的完整映射:
|
|
375
424
|
|
|
@@ -379,7 +428,7 @@ SDK 方法 → 后端接口的完整映射:
|
|
|
379
428
|
| --------------------- | -------------------------------------------- | --------------------------------------- | ----------------------------------------------- |
|
|
380
429
|
| `getToken()` | `POST {gateway}/v1/token` | `{ botId }` | `{ token, appId }` |
|
|
381
430
|
| `createSession()` | `POST /chat/sessions` | `{ botId }` | `{ sessionId }` |
|
|
382
|
-
| `chat()` | `POST /chat` | `{ botId, sessionId, message, stream }` | SSE 流,`done`/`stop` 事件含 `sessionId` |
|
|
431
|
+
| `chat()` | `POST /chat` | `{ botId, sessionId, message, stream, attachments? }` | SSE 流,`done`/`stop` 事件含 `sessionId` |
|
|
383
432
|
| `listConversations()` | `GET /chat/conversations` | 查询参数 `botId`、`page`、`size` | `{ items: [{ sessionId, botId, ... }], total }` |
|
|
384
433
|
| `feedback()` | `POST /chat/messages/{messageId}/feedback` | `{ type, reason?, remark? }` | `null` |
|
|
385
434
|
| `cancelFeedback()` | `DELETE /chat/messages/{messageId}/feedback` | 无 body | `null` |
|
|
@@ -418,6 +467,10 @@ export interface MountOptions {
|
|
|
418
467
|
logo?: string; // floating 首页居中 Logo(URL),默认 SDK 内置 Logo
|
|
419
468
|
triggerLogo?: string; // floating 触发按钮 Logo(URL),默认 SDK 内置 Logo
|
|
420
469
|
showTimestamp?: boolean; // 显示消息时间戳,默认 false 不显示
|
|
470
|
+
/** 附件上传器(完全自定义,优先于 nosUpload)。不配置则输入区无附件按钮。 */
|
|
471
|
+
attachmentUploader?: (file: File) => Promise<ChatAttachment>;
|
|
472
|
+
/** 内置 NOS 直传(推荐):配置即用,📎 按钮 + chip 进度 + 断点续传全内置 */
|
|
473
|
+
nosUpload?: { baseURL: string; headers?: Record<string, string>; trunkSize?: number };
|
|
421
474
|
}
|
|
422
475
|
|
|
423
476
|
export interface CapabilityItem {
|
|
@@ -430,7 +483,8 @@ export interface QuickLinkItem {
|
|
|
430
483
|
icon?: string; // 图标 URL
|
|
431
484
|
label: string; // 入口名称
|
|
432
485
|
prompt?: string; // 点击后发送的消息
|
|
433
|
-
|
|
486
|
+
action?: PanelAction; // 点击行为:整屏容器 / 外链(优先于 prompt)
|
|
487
|
+
onClick?: () => void; // 自定义点击行为(优先于 action / prompt)
|
|
434
488
|
}
|
|
435
489
|
|
|
436
490
|
export interface WidgetInstance {
|
|
@@ -465,6 +519,31 @@ widget.destroy(); // 卸载并移除 DOM
|
|
|
465
519
|
|
|
466
520
|
> 挂载后默认是收起状态,需调用 `widget.open()` 展开。
|
|
467
521
|
|
|
522
|
+
#### 附件上传(📎 按钮)
|
|
523
|
+
|
|
524
|
+
配置 `nosUpload`(内置 NOS 直传)或 `attachmentUploader`(完全自定义,优先级更高)任一即启用,二者都不配则附件功能完全隐藏(存量接入零影响):
|
|
525
|
+
|
|
526
|
+
```ts
|
|
527
|
+
// 内置直传(推荐,一行开启)
|
|
528
|
+
mount("#chat-root", {
|
|
529
|
+
sdk,
|
|
530
|
+
nosUpload: { baseURL: "/api/e5s" }, // 业务网关
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
// 完全自定义(其他对象存储 / 自建网关)
|
|
534
|
+
mount("#chat-root", {
|
|
535
|
+
sdk,
|
|
536
|
+
attachmentUploader: async (file) => ({
|
|
537
|
+
nosKey: await uploadSomewhere(file),
|
|
538
|
+
filename: file.name,
|
|
539
|
+
size: file.size,
|
|
540
|
+
mime: file.type,
|
|
541
|
+
}),
|
|
542
|
+
});
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
内置行为:📎 选文件(≤10 个)→ chip 实时显示上传百分比 → 上传完成显示文件大小(可点击下载)→ 随消息发送 → 沙箱 Agent 读取(仅沙箱模式 Agent 支持,其他模式后端 400)。上传中禁止发送,失败 chip 红标可移除重选;regenerate 会连同附件一起重发。
|
|
546
|
+
|
|
468
547
|
### 6.2 源码接入(monorepo alias)注意事项
|
|
469
548
|
|
|
470
549
|
Widget 内部使用 **Tailwind CSS**(utility 全部限定在 `[data-super-agent-widget]` 宿主选择器内、禁用 preflight,不影响宿主页样式)。分发包(`dist`)已内联全部样式;内置图片资产(Logo/图标/头像等)走 CDN 绝对路径(`src/widget/assets/index.ts` 的 `BASE_URL`),**npm 引入无需任何配置**(需能访问 CDN)。
|
|
@@ -513,10 +592,10 @@ mount("#chat-root", { sdk, mode: "fullpage", sidebarDefaultOpen: false });
|
|
|
513
592
|
浮窗窗口对齐 hr-for-help 设计,开箱具备以下交互能力:
|
|
514
593
|
|
|
515
594
|
- **标题栏拖拽**:按住顶部导航栏移动窗口(视口内钳制,按钮区域不触发)
|
|
516
|
-
-
|
|
595
|
+
- **左侧拉伸调宽**:窗口左侧 6px 手柄拖拽(右缘稳定不动),宽度范围 360 ~ 800 px,拖超过 800 自动进入大窗
|
|
517
596
|
- **大窗/小窗切换**:header 右侧「大窗」按钮切换;大窗为底部弹出的近全屏 overlay(内容区 800px 居中),回小窗时宽度重置为默认值
|
|
518
597
|
- **内容自适应高度**:聊天内容增长时窗口自动变高(默认 648px,上限 95% 视口,超出后消息区滚动),回到首页恢复默认高度
|
|
519
|
-
- **内嵌会话侧边栏**:header
|
|
598
|
+
- **内嵌会话侧边栏**:header「历史记录」按钮开合;小窗下侧边栏紧贴窗口左缘向外展开(无缝拼接为一个连续圆角窗口,主区位置不动);大窗模式自动展开(220px),大窗/小窗各自记住用户偏好
|
|
520
599
|
- **关闭即还原**:关闭窗口后几何状态(位置/宽度/大窗态/高度)全部重置
|
|
521
600
|
|
|
522
601
|
浮窗消息展示对齐 hr-for-help:AI 头像独占一行(无背景色,生成中切换动效头像)、气泡白底描边、用户气泡浅蓝右对齐;`fullpage` 模式保持横排头像 + 经典气泡,两者互不影响。
|
|
@@ -610,6 +689,8 @@ export interface Slots {
|
|
|
610
689
|
ErrorPart?: ComponentType<ErrorPartProps>;
|
|
611
690
|
InterruptCard?: ComponentType<InterruptCardProps>;
|
|
612
691
|
WelcomeScreen?: ComponentType<WelcomeScreenProps>;
|
|
692
|
+
/** 首屏底部业务定制容器 */
|
|
693
|
+
AppendContainer?: ComponentType<AppendContainerProps>;
|
|
613
694
|
}
|
|
614
695
|
```
|
|
615
696
|
|
|
@@ -627,6 +708,7 @@ export interface Slots {
|
|
|
627
708
|
| `ToolResultPart` | `ToolResultPartProps` | 工具返回卡片(pre 滚动 + 复制;`renderMode==="html"` 时 DOMPurify 清洗后直接渲染,最高 60vh 滚动) |
|
|
628
709
|
| `ErrorPart` | `ErrorPartProps` | 错误提示块 |
|
|
629
710
|
| `WelcomeScreen` | `WelcomeScreenProps` | 空会话欢迎页 |
|
|
711
|
+
| `AppendContainer`| `AppendContainerProps`| 首屏底部业务定制区,`openPanel(action)` 开整屏容器或外链(见下) |
|
|
630
712
|
|
|
631
713
|
#### Trigger
|
|
632
714
|
|
|
@@ -774,7 +856,7 @@ function MyMessage({
|
|
|
774
856
|
```ts
|
|
775
857
|
export interface ComposerProps {
|
|
776
858
|
status: ChatStatus;
|
|
777
|
-
onSend: (content: string) => void;
|
|
859
|
+
onSend: (content: string, attachments?: ChatAttachment[]) => void;
|
|
778
860
|
onStop: () => void;
|
|
779
861
|
}
|
|
780
862
|
```
|
|
@@ -965,6 +1047,86 @@ function MyErrorPart({ content, onRetry }: ErrorPartProps) {
|
|
|
965
1047
|
}
|
|
966
1048
|
```
|
|
967
1049
|
|
|
1050
|
+
#### AppendContainer(首屏业务定制 + 整屏容器)
|
|
1051
|
+
|
|
1052
|
+
首屏(无消息时)在 `WelcomeScreen` 之下预留一块业务定制区,浮窗与全屏模式共用;未配置则不产生任何 DOM。
|
|
1053
|
+
用法就一句话:**点一下,`openPanel(action)`**——可视区始终在 chatPanel 内(`link` 除外)。
|
|
1054
|
+
|
|
1055
|
+
```ts
|
|
1056
|
+
export type PanelContent = ComponentType<PageProps> | ReactElement; // 组件类型,或带参数的元素
|
|
1057
|
+
export type PanelAction =
|
|
1058
|
+
| { type: 'page'; component: PanelContent; title?: string } // 整屏容器
|
|
1059
|
+
| { type: 'link'; url: string; target?: '_blank' | '_self' }; // 外链
|
|
1060
|
+
|
|
1061
|
+
export interface AppendContainerProps { openPanel: (action: PanelAction) => void }
|
|
1062
|
+
export interface PageProps { close: () => void }
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
```tsx
|
|
1066
|
+
function ReportPage({ close }: PageProps) {
|
|
1067
|
+
return (
|
|
1068
|
+
<div style={{ padding: 16 }}>
|
|
1069
|
+
报告内容……
|
|
1070
|
+
<button onClick={close}>返回</button>
|
|
1071
|
+
</div>
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
function MyHomeBlock({ openPanel }: AppendContainerProps) {
|
|
1076
|
+
return (
|
|
1077
|
+
<>
|
|
1078
|
+
<button onClick={() => openPanel({ type: "page", component: ReportPage, title: "全景报告" })}>
|
|
1079
|
+
整屏容器
|
|
1080
|
+
</button>
|
|
1081
|
+
<button onClick={() => openPanel({ type: "link", url: "https://example.com" })}>
|
|
1082
|
+
新窗口外链
|
|
1083
|
+
</button>
|
|
1084
|
+
</>
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
mount("#app", { sdk, slots: { AppendContainer: MyHomeBlock } });
|
|
1089
|
+
```
|
|
1090
|
+
|
|
1091
|
+
要开「第 3 条记录」这类带参数的页面,直接给**元素**,SDK 用 `cloneElement` 补上 `close`:
|
|
1092
|
+
|
|
1093
|
+
```tsx
|
|
1094
|
+
// 元素形态下 close 声明为可选,才能写 <DetailPage id={3} />(运行时 SDK 必然注入)
|
|
1095
|
+
function DetailPage({ id, close }: { id: number; close?: () => void }) {
|
|
1096
|
+
return <button onClick={close}>详情 #{id} 返回</button>;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
openPanel({ type: "page", component: <DetailPage id={3} />, title: "详情 #3" });
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
给组件类型(`component: DetailPage`)时 `close` 是必填 prop,同样由 SDK 注入。
|
|
1103
|
+
|
|
1104
|
+
连定制区都不想写?首屏「快速入口」吃同一个 `action`,纯配置即可接入(`title` 缺省用 `label`):
|
|
1105
|
+
|
|
1106
|
+
```tsx
|
|
1107
|
+
mount("#app", {
|
|
1108
|
+
sdk,
|
|
1109
|
+
quickLinks: [
|
|
1110
|
+
{ label: "全景报告", action: { type: "page", component: ReportPage } },
|
|
1111
|
+
{ label: "帮助文档", action: { type: "link", url: "https://example.com/help" } },
|
|
1112
|
+
],
|
|
1113
|
+
});
|
|
1114
|
+
```
|
|
1115
|
+
|
|
1116
|
+
点击优先级:`onClick > action > prompt`。
|
|
1117
|
+
|
|
1118
|
+
整屏容器行为:
|
|
1119
|
+
|
|
1120
|
+
| 项 | 表现 |
|
|
1121
|
+
| --- | --- |
|
|
1122
|
+
| 呈现 | 替换「内容区 + 输入区」,Header 保留(小窗仍可拖拽);`link` 走 `window.open`,不占状态 |
|
|
1123
|
+
| 标题栏 | 返回箭头 + `title`(可选);`Esc` 等价返回 |
|
|
1124
|
+
| 窗口几何 | 保持打开前窗态,小窗不自动升大窗;内容在容器内滚动(业务页不写滚动容器) |
|
|
1125
|
+
| 关闭 | 返回 / `close()` / `Esc`;关闭聊天面板时一并清掉 |
|
|
1126
|
+
| 栈 | 单层不叠栈,容器内部导航由业务组件自己管 |
|
|
1127
|
+
|
|
1128
|
+
注意:浮层组件渲染在 SDK 自己的 `createRoot` 树内,拿不到宿主的 React Context / Router。组件引用直接给,不需要注册表——类型即校验。
|
|
1129
|
+
|
|
968
1130
|
### 8.4 事件钩子(EventHooks)
|
|
969
1131
|
|
|
970
1132
|
通过 `hooks` 监听组件生命周期与交互事件:
|
|
@@ -1347,6 +1509,7 @@ export type MessagePart =
|
|
|
1347
1509
|
html?: string;
|
|
1348
1510
|
}
|
|
1349
1511
|
| { type: "error"; content: string }
|
|
1512
|
+
| { type: "attachment"; attachment: ChatAttachment } // 用户消息携带的附件 chip
|
|
1350
1513
|
| {
|
|
1351
1514
|
type: "interrupt";
|
|
1352
1515
|
interrupt: InterruptEvent;
|
|
@@ -1362,6 +1525,7 @@ export type MessagePart =
|
|
|
1362
1525
|
| `tool_call` | `toolName`, `toolCallId`, `args` | 工具调用,`args` 为 JSON 字符串 |
|
|
1363
1526
|
| `tool_result` | `toolName`, `toolCallId`, `content`, `artifacts?`, `renderMode?`, `html?` | 工具返回结果 |
|
|
1364
1527
|
| `error` | `content` | 错误信息 |
|
|
1528
|
+
| `attachment` | `attachment` | 用户消息附件 chip(发送时本地构造 / 历史加载由后端映射,有 `url` 时可下载) |
|
|
1365
1529
|
| `interrupt` | `interrupt`, `resolved?`, `response?` | 中断交互卡片(HITL) |
|
|
1366
1530
|
|
|
1367
1531
|
### 11.3 ChatEvent(流式事件)
|
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;
|