super-agent-sdk 1.0.10 → 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 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 {
@@ -466,6 +519,31 @@ widget.destroy(); // 卸载并移除 DOM
466
519
 
467
520
  > 挂载后默认是收起状态,需调用 `widget.open()` 展开。
468
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
+
469
547
  ### 6.2 源码接入(monorepo alias)注意事项
470
548
 
471
549
  Widget 内部使用 **Tailwind CSS**(utility 全部限定在 `[data-super-agent-widget]` 宿主选择器内、禁用 preflight,不影响宿主页样式)。分发包(`dist`)已内联全部样式;内置图片资产(Logo/图标/头像等)走 CDN 绝对路径(`src/widget/assets/index.ts` 的 `BASE_URL`),**npm 引入无需任何配置**(需能访问 CDN)。
@@ -778,7 +856,7 @@ function MyMessage({
778
856
  ```ts
779
857
  export interface ComposerProps {
780
858
  status: ChatStatus;
781
- onSend: (content: string) => void;
859
+ onSend: (content: string, attachments?: ChatAttachment[]) => void;
782
860
  onStop: () => void;
783
861
  }
784
862
  ```
@@ -1431,6 +1509,7 @@ export type MessagePart =
1431
1509
  html?: string;
1432
1510
  }
1433
1511
  | { type: "error"; content: string }
1512
+ | { type: "attachment"; attachment: ChatAttachment } // 用户消息携带的附件 chip
1434
1513
  | {
1435
1514
  type: "interrupt";
1436
1515
  interrupt: InterruptEvent;
@@ -1446,6 +1525,7 @@ export type MessagePart =
1446
1525
  | `tool_call` | `toolName`, `toolCallId`, `args` | 工具调用,`args` 为 JSON 字符串 |
1447
1526
  | `tool_result` | `toolName`, `toolCallId`, `content`, `artifacts?`, `renderMode?`, `html?` | 工具返回结果 |
1448
1527
  | `error` | `content` | 错误信息 |
1528
+ | `attachment` | `attachment` | 用户消息附件 chip(发送时本地构造 / 历史加载由后端映射,有 `url` 时可下载) |
1449
1529
  | `interrupt` | `interrupt`, `resolved?`, `response?` | 中断交互卡片(HITL) |
1450
1530
 
1451
1531
  ### 11.3 ChatEvent(流式事件)
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function u(r){if(Array.isArray(r))return r.map(e=>u(e));if(r&&typeof r=="object"&&r.constructor===Object){const e={};for(const[t,s]of Object.entries(r)){const o=t.replace(/_([a-z])/g,(a,n)=>n.toUpperCase());e[o]=u(s)}return e}return r}const I="/api/agent/runtime/v1";class w{constructor(e){this.refreshing=null;const t=e.baseUrl??I;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`,s=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({botId:this.botId})});if(!s.ok)throw new Error(`HTTP ${s.status}: ${s.statusText}`);const o=await s.json();if(o.code!=="200")throw new Error(o.message||`API error: ${o.code}`);const a=u(o.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,s){if(!this.token&&!await this.handleTokenRefresh())throw new Error("SDK token not available: POST /token failed");let o=this.isAbsoluteUrl?`${this.baseUrl}${t}`:`${this.getOrigin()}${this.baseUrl}${t}`;if(s!=null&&s.params){const i=new URLSearchParams;for(const[d,l]of Object.entries(s.params))l!=null&&i.set(d,String(l));const c=i.toString();c&&(o+=`?${c}`)}const a=await fetch(o,{method:e,headers:this.headers(),body:s!=null&&s.body?JSON.stringify(s.body):void 0});if(a.status===401&&!(s!=null&&s.retry)&&await this.handleTokenRefresh())return this.request(e,t,{...s,retry:!0});if(!a.ok)throw new Error(`HTTP ${a.status}: ${a.statusText}`);const n=await a.json();if(n.code!=="200")throw new Error(n.message||`API error: ${n.code}`);return u(n.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,s){if(!this.token&&!await this.handleTokenRefresh())throw new Error("SDK token not available: POST /token failed");const o=this.isAbsoluteUrl?`${this.baseUrl}${e}`:`${this.getOrigin()}${this.baseUrl}${e}`,a=await fetch(o,{method:"POST",headers:this.headers(),body:JSON.stringify(t),signal:s});return a.status===401&&await this.handleTokenRefresh()?fetch(o,{method:"POST",headers:this.headers(),body:JSON.stringify(t),signal:s}):a}}function y(r){const e=r.split(`
2
- `).map(s=>s.trim()).filter(s=>s.startsWith("data:")).map(s=>s.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:t.content??""};case"thinking":return{type:"thinking",content:t.content??""};case"ai":return{type:"ai",content: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: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:t.content??"",sessionId:t.sessionId??t.session_id??"",messageId:t.messageId??t.message_id};case"stop":return{type:"stop",content: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 m(r,e,t){if(!r.body){t(new Error("Response body is null"));return}const s=r.body.getReader(),o=new TextDecoder;let a="";try{for(;;){const{value:n,done:i}=await s.read();if(i)break;a+=o.decode(n,{stream:!0});let c;for(;(c=a.indexOf(`
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=y(d);l&&e(l)}}if(a.trim()){const n=y(a);n&&e(n)}}catch(n){(n==null?void 0:n.name)!=="AbortError"&&t(n instanceof Error?n:new Error(String(n)))}finally{try{s.releaseLock()}catch{}}}function b(r,e){const t=new AbortController,s=e.signal?p(e.signal,t.signal):t.signal,o={botId:r.getBotId(),message:e.message,sessionId:e.sessionId,stream:e.stream!==!1};return o.stream?r.streamPost("/chat",o,s).then(a=>{var n;if(!a.ok){(n=e.onError)==null||n.call(e,new Error(`HTTP ${a.status}`));return}return m(a,i=>{var c,d,l,h,f,g;i.type==="done"&&i.sessionId?(c=e.onDone)==null||c.call(e,{sessionId:i.sessionId,content:i.content,messageId:i.messageId}):i.type==="done"?(d=e.onDone)==null||d.call(e,{sessionId:"",content:i.content,messageId:i.messageId}):i.type==="stop"?((l=e.onMessage)==null||l.call(e,i),(h=e.onDone)==null||h.call(e,{sessionId:i.sessionId??"",content:i.content,messageId:i.messageId})):i.type==="error"?(f=e.onError)==null||f.call(e,new Error(i.content)):(g=e.onMessage)==null||g.call(e,i)},i=>{var c;return(c=e.onError)==null?void 0:c.call(e,i)})}).catch(a=>{var n;(a==null?void 0:a.name)!=="AbortError"&&((n=e.onError)==null||n.call(e,a instanceof Error?a:new Error(String(a))))}):r.post("/chat",o).then(a=>{var n;(n=e.onDone)==null||n.call(e,{sessionId:a.sessionId,content:a.content??"",messageId:a.messageId})}).catch(a=>{var n;(n=e.onError)==null||n.call(e,a instanceof Error?a:new Error(String(a)))}),t}function p(r,e){const t=new AbortController,s=()=>t.abort();return r.addEventListener("abort",s,{once:!0}),e.addEventListener("abort",s,{once:!0}),(r.aborted||e.aborted)&&t.abort(),t.signal}async function T(r){return(await r.post("/chat/sessions",{botId:r.getBotId()})).sessionId}function k(r){return{sessionId:r.threadId??r.sessionId,botId:r.botId,title:r.title,createTime:r.createTime,updateTime:r.updateTime}}async function S(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(k)}}async function $(r,e,t){await r.patch(`/chat/conversations/${e}`,{title:t})}async function A(r,e){await r.del(`/chat/conversations/${e}`)}async function C(r,e){const t=await r.get(`/chat/conversations/${e}/messages`);return O(t)}function O(r){const e=[...r].sort((n,i)=>{const c=new Date(n.createTime??0).getTime()||0,d=new Date(i.createTime??0).getTime()||0;return c!==d?c-d:(n.seq??0)-(i.seq??0)}),t=new Map;for(const n of e)n.role==="tool"&&n.toolCallId&&t.set(n.toolCallId,n.content||"");const s=new Set,o=[],a=(n,i,c)=>{const d=o[o.length-1];(d==null?void 0:d.role)==="assistant"?(d.parts.push(...n),d.id=i):o.push({id:i,role:"assistant",parts:[...n],timestamp:c})};for(const n of e){if(n.role==="system")continue;const i=String(n.id??n.messageId??`msg_${Date.now()}`),c=new Date(n.createTime??"").getTime()||Date.now();if(n.role==="human"){o.push({id:i,role:"user",parts:[{type:"text",content:n.content??""}],timestamp:c});continue}if(n.role==="ai"){const d=P(n,t,s);d.length>0&&a(d,i,c);continue}if(n.role==="tool"){if(n.toolCallId&&s.has(n.toolCallId))continue;a([v(n)],i,c)}}return o}function P(r,e,t){const s=[];r.reasoning&&s.push({type:"thinking",content:r.reasoning}),r.content&&s.push({type:"text",content:r.content});for(const o of r.toolCalls??[])o.is_interrupt?(t.add(o.id),s.push(_(o,e))):s.push(E(o));return s}function _(r,e){const t=e.has(r.id),s=typeof r.args=="object"?r.args:{};return{type:"interrupt",interrupt:{interruptId:r.id??"",interruptType:s.interruptType??"input",content:s.content??"",options:s.options,placeholder:s.placeholder,inputType:s.inputType,maxLength:s.maxLength,required:s.required,fields:s.fields,metadata:s.metadata},resolved:t,response:t?{action:"submit",value:U(e.get(r.id))}:void 0}}function E(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 v(r){var t,s,o;const e=Array.isArray(r.artifacts)?r.artifacts:void 0;return{type:"tool_result",toolName:r.toolName??"",toolCallId:r.toolCallId??"",content:r.content??"",artifacts:e,renderMode:((t=r.metadata)==null?void 0:t.renderMode)??((s=r.metadata)==null?void 0:s.render_mode),html:(o=r.metadata)==null?void 0:o.html}}function U(r){if(r!=null)try{return JSON.parse(r)}catch{return r}}async function x(r,e,t,s,o,a){const n=await r.streamPost(`/chat/interrupt/${e}/respond`,{sessionId:t,action:s.action,value:s.value});if(!n.ok){const i=new Error(`HTTP ${n.status}`);throw a==null||a(i),i}o&&await m(n,i=>{i.type==="error"?a==null||a(new Error(i.content)):o(i)},i=>a==null?void 0:a(i))}async function D(r){return r.get(`/chat/bots/${r.getBotId()}/skills`)}async function q(r,e){await r.post("/chat/stop",{sessionId:e})}function N(r){const e=new w(r);return{botId:e.getBotId(),getToken:()=>e.fetchToken(),createSession:()=>T(e),chat:s=>b(e,s),listConversations:s=>S(e,s),renameConversation:(s,o)=>$(e,s,o),deleteConversation:s=>A(e,s),getMessages:s=>C(e,s),respondInterrupt:(s,o,a,n,i)=>x(e,s,o,a,n,i),setToken:s=>e.setToken(s),feedback:async(s,o,a)=>{const n={type:o};o==="dislike"&&((a==null?void 0:a.reason)!=null&&(n.reason=a.reason),a!=null&&a.remark&&(n.remark=a.remark)),await e.post(`/chat/messages/${s}/feedback`,n)},cancelFeedback:async s=>{await e.del(`/chat/messages/${s}/feedback`)},listSkills:()=>D(e),stopGeneration:s=>q(e,s)}}exports.createSuperAgent=N;
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;