@tbox.cn/app-agent-sdk-client 0.1.0
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/LICENSE +21 -0
- package/dist/auth-client-BHC_nZj8.d.ts +43 -0
- package/dist/chunk-RQWCUVXR.js +1 -0
- package/dist/client/card-preview.d.ts +51 -0
- package/dist/client/card-preview.js +1 -0
- package/dist/client/index.d.ts +727 -0
- package/dist/client/index.js +1 -0
- package/package.json +60 -0
- package/platform/code-inspect.cjs +1 -0
- package/platform/code-inspect.d.cts +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tbox.cn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
interface AuthIdentity {
|
|
2
|
+
userId: string;
|
|
3
|
+
source: 'local' | 'external' | 'guest' | 'anonymous';
|
|
4
|
+
}
|
|
5
|
+
interface AuthState {
|
|
6
|
+
token: string | null;
|
|
7
|
+
identity: AuthIdentity;
|
|
8
|
+
}
|
|
9
|
+
interface AuthClientOptions {
|
|
10
|
+
/** 登录平台(外部 provider 接入):'external' 时壳内换码登录转发外部;缺省 'alipay'(本地支付宝换码)。外部场景服务端需同步配置 AUTH_EXTERNAL_* */
|
|
11
|
+
loginPlatform?: string;
|
|
12
|
+
/**
|
|
13
|
+
* token 缓存域键(域中性):返回值变更即失效缓存 token 并重登(如 mall 部署 () => query.mallId ?? '')。
|
|
14
|
+
* 读源须与 configureHelloContext 采集同源(同一 URL 参数),保证登录域与缓存域一致。
|
|
15
|
+
*/
|
|
16
|
+
scopeKey?: () => string;
|
|
17
|
+
}
|
|
18
|
+
interface AuthClient {
|
|
19
|
+
/** 获取鉴权态(幂等,pendingPromise 单例去重) */
|
|
20
|
+
ensureAuth(): Promise<AuthState>;
|
|
21
|
+
/** 强制重新解析(登出/切换用户后、401 重登录);并发调用共享单例(P1-3 去重) */
|
|
22
|
+
refreshAuth(): Promise<AuthState>;
|
|
23
|
+
/** 清空鉴权缓存(登出/切换用户:清 token + 缓存态;lastToken 保留——同用户重登续签语义) */
|
|
24
|
+
clearAuth(): void;
|
|
25
|
+
/** 同步读取当前鉴权态(未解析完成返回 null;供 token getter 使用) */
|
|
26
|
+
getAuthState(): AuthState | null;
|
|
27
|
+
/** 解析当前 userId(历史调用方:AgentChatApp / store/chat) */
|
|
28
|
+
resolveUserId(): Promise<string>;
|
|
29
|
+
}
|
|
30
|
+
declare function createAuthClient(options?: AuthClientOptions): AuthClient;
|
|
31
|
+
/**
|
|
32
|
+
* 应用级 authClient 单例读取(单一模板统一化 W3-2:域模块消费面)。
|
|
33
|
+
*
|
|
34
|
+
* 域模块(如 mall-home 的首屏数据加载)需要与宿主应用同一 authClient 实例
|
|
35
|
+
* (实例分裂免疫——token/scope 状态单份)。应用装配(agent-runtime)先建单例,
|
|
36
|
+
* 模块渲染期(组件/effect 内)惰性调用本函数读取;未建则按缺省选项首建
|
|
37
|
+
* (同一单例语义——与 createAuthClient 收敛路径重合,选项比较规则不变)。
|
|
38
|
+
*
|
|
39
|
+
* 约束:模块勿在 import 期调用(应用装配可能尚未执行;延迟到渲染/请求期即安全)。
|
|
40
|
+
*/
|
|
41
|
+
declare function getAuthClient(): AuthClient;
|
|
42
|
+
|
|
43
|
+
export { type AuthClient as A, type AuthClientOptions as a, type AuthIdentity as b, type AuthState as c, createAuthClient as d, getAuthClient as g };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createClientLogger as De}from"@tbox.cn/app-sdk-client";import{nextKeepaliveAction as Be,KEEPALIVE_MISS_LIMIT as Oe}from"@tbox.cn/app-contracts";var I=De("WS"),p=null,R=null,q=null,T=null,W=null,F=null,D=0,X=!0,B=25e3,w=null,j=0;function At(e){e.enabled!==void 0&&(X=e.enabled),e.intervalMs!==void 0&&(B=Math.max(100,e.intervalMs))}function ne(e){W=e}function Tt(e){F=e}function oe(){return F?F():null}var $=null;function Mt(e){$=e}var K=null;function ae(e){K=e}var Ue="登录状态失效,无法连接对话服务,请重试",Ge="HELLO scope 拒绝:context.mall 形状非法(需 mallId 非空)";function qe(e,s,t=3){if(!s)return{stop:!1,nextStreak:0};let o=e+1;return o>=t?{stop:!0,nextStreak:0}:{stop:!1,nextStreak:o}}function z(e,s){if(p&&(p.readyState===WebSocket.OPEN||p.readyState===WebSocket.CONNECTING))return;R=e,q=s??q;let t=W?W():null;t&&(p=new WebSocket(t),p.onopen=()=>{I.info("connected"),T&&(clearTimeout(T),T=null),We(),q?.()},p.onmessage=o=>{j=Date.now();let d=null;try{d=JSON.parse(o.data)}catch(u){I.error("parse error",u);return}if(d?.type!=="PONG"){if(d?.type==="PING"){h({type:"PONG",...d.ts!==void 0?{ts:d.ts}:{}});return}try{R?.(d)}catch(u){I.error("parse error",u)}}},p.onclose=o=>{if(I.info("disconnected",o.code?`code=${o.code}`:""),J(),p=null,o.code===4002){I.error("HELLO scope 拒绝(商圈不在白名单),停止重连"),K?.({code:o.code,message:Ge});return}let d=o.code===4001,{stop:u,nextStreak:m}=qe(D,d);if(D=m,u){I.error("HELLO 连续鉴权失败,停止重连(请刷新页面重试)"),K?.({code:o.code,message:Ue});return}if(d){($?$().catch(y=>I.warn("auth refresh failed:",y.message)):Promise.resolve()).finally(V);return}V()},p.onerror=o=>{I.error("error",o)})}function h(e){if(!p||p.readyState!==WebSocket.OPEN){I.warn("not connected, message dropped");return}p.send(JSON.stringify(e))}function O(){return!!p&&p.readyState===WebSocket.OPEN}function xt(e,s=100){let t=Date.now()+e;return new Promise(o=>{let d=()=>{if(p&&p.readyState===WebSocket.OPEN){o(!0);return}if(Date.now()>=t){o(!1);return}setTimeout(d,s)};d()})}function ie(){T&&(clearTimeout(T),T=null),J(),R=null,D=0,p&&(p.onclose=null,p.close(),p=null)}function le(e=3e3){if(!X||!p||p.readyState!==WebSocket.OPEN)return;let s=Be(Date.now()-j,B);if(s==="dead"){Fe(`keepalive stale (idle >= ${B*Oe}ms)`,e);return}s==="ping"&&h({type:"PING",ts:Date.now()})}function We(){j=Date.now(),X&&(w&&clearInterval(w),w=setInterval(()=>le(),B))}function J(){w&&(clearInterval(w),w=null)}function Fe(e,s=3e3){I.warn(`force reconnect: ${e}`),J();let t=p;p=null,t&&(t.onclose=null,t.onmessage=null,t.onopen=null,t.onerror=null,t.close()),D=0,V(s)}typeof document<"u"&&document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&le(0)});function V(e=3e3){T||!R||(T=setTimeout(()=>{T=null,R&&z(R)},e))}function de(e){return function(t,o,d){if(e.blockAll?.(t)!==!0)switch(o.type){case"sendMessage":case"submitForm":case"moduleAction":{if(e.canAct?.(t)===!1)return;e.sendToServer(t,o,d);break}case"openLink":{if(!o.url)break;e.openUrl?.(o.url);break}case"toast":{o.content&&e.toast?.(o.content,o.tone);break}case"closeCard":{e.closeCard?.(t);break}}}}import{createClientLogger as $e}from"@tbox.cn/app-sdk-client";var Ke=$e("CARD");function ce(e,s){let t=o=>o.some(d=>d.id===s.id)?o.map(d=>d.id===s.id?s:d):[...o,s];return s.messageId?{...e,cardsByMessageId:{...e.cardsByMessageId,[s.messageId]:t(e.cardsByMessageId[s.messageId]||[])}}:{...e,orphanCards:t(e.orphanCards)}}function Ht(e,s){let t={};for(let[o,d]of Object.entries(e.cardsByMessageId))t[o]=d.filter(u=>u.id!==s);return{cardsByMessageId:t,orphanCards:e.orphanCards.filter(o=>o.id!==s)}}function ue(e,s){let t=d=>{let u=d.findIndex(S=>S.id===s.id);if(u<0)return null;let m=[...d];return m[u]={...s,messageId:d[u].messageId,isHistory:d[u].isHistory,meta:{...d[u].meta,...s.meta,createdAt:d[u].meta?.createdAt??s.meta?.createdAt??Date.now(),...d[u].meta?.businessNo!==void 0?{businessNo:d[u].meta.businessNo}:{}}},m};for(let[d,u]of Object.entries(e.cardsByMessageId)){let m=t(u);if(m)return{...e,cardsByMessageId:{...e.cardsByMessageId,[d]:m}}}let o=t(e.orphanCards);return o?{...e,orphanCards:o}:(Ke.warn(`update 未命中卡实例:${s.id}(已隐藏/清空或非本连接下发)`),e)}import{TBOX_EFFECT_EVENT_NAME as _t}from"@tbox.cn/app-contracts";import{requestPayment as Ve}from"@tbox.cn/app-sdk-client";import{getAuthCode as Xe}from"@tbox.cn/app-sdk-client";import{createClientLogger as je}from"@tbox.cn/app-sdk-client";var Y=je("tbox:effect");function pe(e){if(e===null||typeof e!="object")return!1;let s=e;return typeof s.type=="string"&&s.type.length>0}function ge(){let e=new Set;return s=>{let t=s.callback?.contextToken;return t?e.has(t)?!1:(e.add(t),!0):!0}}function me(e){return e!==null&&typeof e=="object"&&typeof e.code=="string"?String(e.code):"BRIDGE_ERROR"}function ye(e){return me(e)==="BRIDGE_UNAVAILABLE"}function ze(e){return e==="9000"?"success":e==="6001"?"cancel":"failure"}async function Je(e,s){try{let t=await s.requestPayment(e.orderInfo);return{status:ze(t.resultCode),paymentRef:e.paymentRef}}catch(t){return{status:ye(t)?"unavailable":"failure",paymentRef:e.paymentRef}}}async function Ye(e,s){try{let t=await s.getAuthCode([...e.scopes]);return typeof t.authCode!="string"||!t.authCode.trim()?{status:"failure"}:{status:"success",authCode:t.authCode}}catch(t){let o=me(t);return ye(t)?{status:"unavailable"}:o.includes("CANCEL")?{status:"cancel"}:{status:"failure"}}}function fe(e,s,t){if(e.callback)try{t({actionId:e.callback.actionId,contextToken:e.callback.contextToken,formData:{...s}})}catch(o){Y.warn("结果回传失败:",o?.message)}}function Ce(e){let s={requestPayment:e?.requestPayment??Ve,getAuthCode:e?.getAuthCode??Xe,sendModuleAction:e?.sendModuleAction??(()=>{Y.warn("未注入 sendModuleAction,结果未回传")}),onSessionCredentialsRefreshed:e?.onSessionCredentialsRefreshed};return t=>{switch(t.type){case"payment":{Je(t,s).then(o=>fe(t,o,s.sendModuleAction));return}case"authorization.alipay-user-profile":{Ye(t,s).then(o=>fe(t,o,s.sendModuleAction));return}case"session.credentials-refreshed":{s.onSessionCredentialsRefreshed?.(t.reason);return}default:Y.warn(`宿主效果类型未在客户端执行:${String(t.type)}`)}}}import{parseSkillReminders as Qe,buildSkillToolCallPayload as Ze,SKILL_NAME_PATTERN as he,SKILL_TOOL_CALL_NAME as et}from"@tbox.cn/app-contracts";function Ie(e){let s=e.query??"",t=Qe(s),o=new Map;for(let d of Array.isArray(e.skills)?e.skills:[])d?.name&&he.test(d.name)&&o.set(d.name,d);for(let d of t.skills)o.has(d)||o.set(d,{name:d});return{content:t.cleanedText,skills:[...o.values()]}}function ke(e,s){return e.filter(t=>t&&he.test(t.name)).map((t,o)=>{let{args:d,result:u}=Ze(t);return{id:`skill-${s}-${o}`,name:et,args:d,result:u,status:"done"}})}import{buildPreToolToolCallPayload as tt,PRE_TOOL_CALL_NAME as rt,PRE_TOOL_NAME_PATTERN as Se}from"@tbox.cn/app-contracts";function st(e){return(Array.isArray(e.preTools)?e.preTools:[]).filter(s=>s?.toolName&&Se.test(s.toolName))}function nt(e){let s=Ie(e);return{content:s.content,skills:s.skills,preTools:st(e)}}function ot(e,s){let t=[];return(Array.isArray(e)?e:[]).filter(o=>o&&Se.test(o.toolName)).forEach((o,d)=>{try{let{args:u,result:m}=tt(o);t.push({id:`pretool-${s}-${d}`,name:rt,args:u,result:m,status:"done"})}catch{}}),t}function at(e,s){return[...ot(e.preTools,s),...ke(e.skills,s)]}function it(e){if(!e)return;let s=new Date(e.replace(" ","T")).getTime();return Number.isNaN(s)?void 0:s}function lt(e,s){let t=e.messageId??e.id??e.outerBusinessId??`hist-${s}`,o=e.createTime??it(e.requestTime)??Date.now();return{key:t,createdAt:o}}function Ee(e){let s=0,t=u=>`${u}_${Date.now()}_${s++}`,o=[],d={};return e.forEach((u,m)=>{let{key:S,createdAt:y}=lt(u,m),E=nt(u);if(u.query){let r={id:`history-user-${S}`,role:"user",content:E.content,status:"done",createdAt:y,...u.outerBusinessId&&{requestId:u.outerBusinessId}},a=u.multiModalInputs?.imageUrls;a&&a.length>0&&(r.mediaItems=a.map(n=>({id:t("history-img"),type:"image",src:n}))),o.push(r)}(u.answer||u.content||u.cards&&u.cards.length>0)&&o.push({id:`history-asst-${S}`,role:"assistant",content:u.answer||u.content||"",status:"done",createdAt:y,...u.outerBusinessId&&{requestId:u.outerBusinessId},...E.skills.length>0||E.preTools.length>0?{toolCalls:at(E,S)}:{}}),u.cards&&u.cards.length>0&&(d[`history-asst-${S}`]=u.cards.map(r=>({...r,isHistory:!0})))}),{messages:o,cardsByMessageId:d}}var be;function dt(e){be=e}function Ae(){try{return be?.()}catch{return}}import{TBOX_CARD_EVENT_NAME as Te,TBOX_CARD_UPDATE_EVENT_NAME as ct,TBOX_EFFECT_EVENT_NAME as ut,TBOX_NOTICE_EVENT_NAME as Me}from"@tbox.cn/app-contracts";import{createClientLogger as N,setClientRunContext as Q}from"@tbox.cn/app-sdk-client";import{isInAlipayShell as ft}from"@tbox.cn/app-sdk-client";import{callJsapi as xe}from"@tbox.cn/app-sdk-client";import{resolveAlipayNavigation as pt}from"@tbox.cn/app-sdk-client";var x=N("tbox"),Re="网络连接不可用,请稍后重试",we=N("CONVERSATION"),gt=N("SESSION-UPGRADE"),mt=N("HISTORY"),ve=N("FEEDBACK");function yt(e){let s=String(e?.message||e||"");return/过期|expired|PARAM_INVALID|unauthorized|token/i.test(s)}var P=0,Z=!1,U=null,k=null,ee=!1;function Ct(e){let s=e.messageId??e.id??e.outerBusinessId;return s?`id:${s}`:`content:${e.requestTime??e.createTime??""}:${e.query??""}:${e.answer??e.content??""}`}var H;function tr(e,s,t){if(!H){x.warn("卡动作通道未注册(应用 chat store 未创建),动作已丢弃");return}H(e,s,t)}function ht(e){let s=H;return H=e,()=>{H===e&&(H=s)}}var It=0,v=e=>`${e}_${Date.now()}_${It++}`;function kt(e,s,t){s(o=>({...o,...ce({cardsByMessageId:o.cardsByMessageId,orphanCards:o.orphanCards},t)})),e.onCardEvent?.({type:"render",cardId:t.id,cardType:t.cardType,...t.messageId!==void 0?{messageId:t.messageId}:{},ts:Date.now()})}function St(e,s,t,o){let d={cardsByMessageId:t().cardsByMessageId,orphanCards:t().orphanCards},u=ue(d,o);u!==d&&(s(m=>({...m,...u})),e.onCardEvent?.({type:"update",cardId:o.id,cardType:o.cardType,...o.messageId!==void 0?{messageId:o.messageId}:{},ts:Date.now()}))}function He(e){h(e().conversationEstablished?{type:"RESUME_CONVERSATION"}:{type:"NEW_CONVERSATION"})}function sr(e,s,t){ne(e.getWsUrl),ae(r=>{s({authBlocked:r.message}),e.toast(r.message,"error")});let o=ge(),d=Ce({sendModuleAction:({actionId:r,contextToken:a,formData:n})=>{h({type:"UI_ACTION",surfaceId:"",action:{type:"moduleAction",actionId:r,contextToken:a},formData:n})},...e.onSessionCredentialsRefreshed?{onSessionCredentialsRefreshed:e.onSessionCredentialsRefreshed}:{}}),u=e.onHostEffect??d,m=r=>{let{cardsByMessageId:a,orphanCards:n}=t();return[...Object.values(a).flat(),...n].find(l=>l.id===r)},S=de({canAct:r=>{if(t().isGenerating)return!1;let a=m(r);return!(a&&a.isHistory)},sendToServer:(r,a,n)=>{if(!O()){let c=m(r);e.onCardEvent?.({type:"click",cardId:r,cardType:c?.cardType??"",ts:Date.now()}),e.toast(Re,"error");return}let{addMessage:i}=t(),l=m(r);if(e.onCardEvent?.({type:"click",cardId:r,cardType:l?.cardType??"",ts:Date.now()}),a.type==="sendMessage"){a.visible!==!1&&i({id:v("user"),role:"user",content:a.displayText??a.value,status:"done",createdAt:Date.now()}),h({type:"UI_ACTION",surfaceId:r,action:a}),y=!0;return}if(a.type==="submitForm"){h({type:"UI_ACTION",surfaceId:r,action:a,formData:n??{}}),y=!0;return}h({type:"UI_ACTION",surfaceId:r,action:a,formData:n??{}})},toast:(r,a)=>e.toast(r,a),openUrl:r=>{if(!(typeof window>"u")){if(r.startsWith("tel:")){window.location.href=r;return}if(ft()){if(r.startsWith("alipays://")||r.startsWith("alipayrisk://")){let a=pt(r,t().helloAck?.mallScope?.miniAppId);a?(x.info(`[openUrl] ${a.apiName} url=${r}`),xe(a.apiName,a.params).catch(n=>{x.warn(`[openUrl] ${a.apiName} failed url=${r}`,n),e.toast("当前无法打开该支付宝页面,请确认已在支付宝内打开","error")})):(x.warn(`[openUrl] unresolved Alipay deep link url=${r}`),e.toast("当前无法打开该支付宝页面,请确认已在支付宝内打开","error"))}else x.info(`[openUrl] ap.openURL url=${r}`),xe("ap.openURL",{url:r}).catch(a=>{x.warn(`[openUrl] ap.openURL failed url=${r}`,a),e.toast("当前无法打开该链接,请稍后再试","error")});return}window.open&&window.open(r,"_blank")}},closeCard:r=>t().hideCard(r)});ht((r,a,n)=>{t().sendUiAction(r,a,n)});let y=!1,E=()=>{if(!e.platformApi||!e.authClient)throw new Error("会话方法需要 platformApi/authClient(createCoreChatState deps 未注入)");return{platformApi:e.platformApi,authClient:e.authClient}};return{userId:"",helloAck:null,authBlocked:null,messages:[],isGenerating:!1,currentRunId:null,streamingMsgId:null,conversationEstablished:!1,cardsByMessageId:{},orphanCards:[],init:()=>{z(r=>{let a=t();switch(r.type){case"HELLO_ACK":{s({helloAck:{...r.mallScope?{mallScope:r.mallScope}:{},chatConfig:r.chatConfig,...r.toolDebug?{toolDebug:!0}:{}},authBlocked:null});break}case"RUN_STARTED":{if(!y)break;y=!1;let n=v("thinking");a.addMessage({id:n,role:"assistant",content:"",status:"thinking",createdAt:Date.now()}),s({isGenerating:!0,currentRunId:r.runId,streamingMsgId:n}),Q({runId:r.runId});break}case"CUSTOM":{if(r.name===Te){let n=r.value;kt(e,s,n),typeof window<"u"&&window.dispatchEvent(new CustomEvent(Te,{detail:n}))}else if(r.name===ct)St(e,s,t,r.value);else if(r.name===ut&&pe(r.value))o(r.value)&&u(r.value);else if(r.name===Me){let n=r.value;n?.text&&e.toast(n.text,n.level??"info"),typeof window<"u"&&n?.text&&window.dispatchEvent(new CustomEvent(Me,{detail:n}))}break}case"REASONING_START":{if(!t().isGenerating)break;if(!t().streamingMsgId){let n=v("thinking");a.addMessage({id:n,role:"assistant",content:"",status:"thinking",createdAt:Date.now()}),s({streamingMsgId:n})}break}case"REASONING_MESSAGE_CONTENT":{let{streamingMsgId:n}=t();if(!n)break;s(i=>({messages:i.messages.map(l=>l.id===n?{...l,reasoning:(l.reasoning||"")+r.delta}:l)}));break}case"REASONING_MESSAGE_START":case"REASONING_MESSAGE_END":case"REASONING_END":break;case"TOOL_CALL_START":{if(!t().isGenerating)break;let{streamingMsgId:n}=t();if(!n){let i=v("thinking");a.addMessage({id:i,role:"assistant",content:"",status:"thinking",createdAt:Date.now()}),s({streamingMsgId:i}),n=i}s(i=>({messages:i.messages.map(l=>l.id===n?{...l,toolCalls:[...l.toolCalls||[],{id:r.toolCallId,name:r.toolCallName,status:"calling"}]}:l)}));break}case"TOOL_CALL_ARGS":{let{streamingMsgId:n}=t();if(!n)break;s(i=>({messages:i.messages.map(l=>l.id===n?{...l,toolCalls:(l.toolCalls||[]).map(c=>c.id===r.toolCallId?{...c,args:(c.args||"")+r.delta}:c)}:l)}));break}case"TOOL_CALL_END":break;case"TOOL_CALL_RESULT":{let{streamingMsgId:n}=t();if(!n)break;s(i=>({messages:i.messages.map(l=>l.id===n?{...l,toolCalls:(l.toolCalls||[]).map(c=>c.id===r.toolCallId?{...c,result:r.content,status:r.isError?"error":"done",...r.isError?{error:r.errorMessage??"工具执行失败"}:{}}:c)}:l)}));break}case"TEXT_MESSAGE_START":{if(!t().isGenerating)break;let n=r.messageId,{streamingMsgId:i}=t();i?.startsWith("thinking_")?s(l=>({streamingMsgId:n,messages:l.messages.map(c=>c.id===i?{...c,id:n,status:"streaming"}:c)})):(a.addMessage({id:n,role:"assistant",content:"",status:"streaming",createdAt:Date.now()}),s({streamingMsgId:n}));break}case"TEXT_MESSAGE_CONTENT":{let{streamingMsgId:n}=t();n&&r.messageId===n&&t().appendDelta(n,r.delta);break}case"TEXT_MESSAGE_END":{let{streamingMsgId:n}=t();n&&(a.updateMessage(n,{status:"done"}),s({streamingMsgId:null}));break}case"RUN_FINISHED":{if(r.runId&&r.runId!==t().currentRunId)break;let n=r.rawEvent?.requestId;s(n?i=>{let l=[...i.messages];for(let c=l.length-1;c>=0;c--)if(l[c].role==="assistant"&&l[c].status==="done"){l[c]={...l[c],requestId:n};break}return{isGenerating:!1,currentRunId:null,messages:l}}:{isGenerating:!1,currentRunId:null});{let i=0,l=t().messages.map(c=>c.role!=="assistant"||!c.toolCalls?.some(f=>f.status==="calling")?c:(i+=c.toolCalls.filter(f=>f.status==="calling").length,{...c,toolCalls:c.toolCalls.map(f=>f.status==="calling"?{...f,status:"error",error:"工具执行未返回结果"}:f)}));i>0&&(s({messages:l}),x.warn(`RUN_FINISHED:${i} 个工具未收到终态事件,已收敛为失败态`))}Q(null);break}case"RUN_ERROR":{if(!t().isGenerating&&!t().currentRunId)break;let n=r.runId;if(n&&n!==t().currentRunId)break;let i=t().streamingMsgId,l=r.message||"请求失败,请重试",c=r.rawEvent?.requestId??n??t().currentRunId??void 0;s(f=>{let b=i?f.messages.find(g=>g.id===i):void 0,A={...b??{id:v("error"),role:"assistant",content:"",createdAt:Date.now()},status:"error",error:l,...c&&{requestId:b?.requestId??c},toolCalls:b?.toolCalls?.map(g=>g.status==="calling"?{...g,status:"error",error:"请求中断"}:g)};return{isGenerating:!1,currentRunId:null,streamingMsgId:null,messages:b?f.messages.map(g=>g.id===b.id?A:g):[...f.messages,A]}}),Q(null);break}}},()=>{let r=oe(),a=Ae();h({type:"HELLO",...r?{token:r}:{},...a?{context:a}:{}}),He(t)})},sendMessage:r=>{let{addMessage:a,isGenerating:n}=t();if(n)return;if(!O()){e.toast(Re,"error");return}let i=typeof r=="string"?r:r.text,l=typeof r=="string"?void 0:r.mediaItems,c=typeof r=="string"?void 0:r.inputMethod;a({id:v("user"),role:"user",content:i,status:"done",createdAt:Date.now(),mediaItems:l,inputMethod:c}),h({type:"SEND_MESSAGE",content:i,mediaItems:l?.filter(f=>f.fileId).map(f=>({fileId:f.fileId,type:f.type,name:f.name,mimeType:f.mimeType})),inputMethod:c}),y=!0},sendUiAction:(r,a,n)=>{S(r,a,n)},hideCard:r=>{s(a=>{let n={};for(let[i,l]of Object.entries(a.cardsByMessageId))n[i]=l.filter(c=>c.id!==r);return{cardsByMessageId:n,orphanCards:a.orphanCards.filter(i=>i.id!==r)}})},destroy:()=>ie(),clearChat:()=>{t().cancelGeneration(),y=!1,s({messages:[],currentRunId:null,streamingMsgId:null,isGenerating:!1,conversationEstablished:!1,cardsByMessageId:{},orphanCards:[]})},addMessage:r=>s(a=>({messages:[...a.messages,r]})),appendDelta:(r,a)=>s(n=>({messages:n.messages.map(i=>i.id===r?{...i,content:i.content+a}:i)})),updateMessage:(r,a)=>s(n=>({messages:n.messages.map(i=>i.id===r?{...i,...a}:i)})),setGenerating:r=>s({isGenerating:r}),cancelGeneration:()=>{y=!1;let{streamingMsgId:r,isGenerating:a,currentRunId:n}=t();if(a){if(h({type:"CANCEL_RUN",...n&&{runId:n}}),r){let i=t().messages.find(l=>l.id===r);i&&!i.content?s(l=>({messages:l.messages.filter(c=>c.id!==r)})):s(l=>({messages:l.messages.map(c=>c.id===r?{...c,status:"done",toolCalls:c.toolCalls?.map(f=>f.status==="calling"?{...f,status:"done"}:f)}:c)}))}s({isGenerating:!1,currentRunId:null,streamingMsgId:null})}},setAuthBlocked:r=>s({authBlocked:r}),setUserId:r=>{t().userId&&t().userId!==r&&s({conversationEstablished:!1}),s({userId:r})},historyMessages:[],historyHasMore:!0,isLoadingHistory:!1,historyEntryKeys:[],historyBeforeId:null,runtimeStatus:"bootstrapping",initConversation:async()=>{let{authClient:r}=E();if(U)return U;let a;U=new Promise(n=>{a=n});try{let n=t().userId,i=await r.resolveUserId();if(n&&n!==i&&(P++,s({conversationEstablished:!1}),s({historyMessages:[],historyHasMore:!0,isLoadingHistory:!1,historyEntryKeys:[],historyBeforeId:null})),s({userId:i}),!r.getAuthState()?.token){we.warn("anonymous: skip WS init, expose recoverable auth failure"),s({runtimeStatus:"failed",conversationEstablished:!1}),t().setAuthBlocked("登录失败,暂时无法对话"),e.toast("登录失败,暂时无法对话","error");return}let l=O();t().init(),s({runtimeStatus:"ready"}),l&&He(t)}catch(n){we.error("init error:",n),t().init(),s({runtimeStatus:"failed"})}finally{U=null,a()}},retryBootstrap:(r=!0)=>{let{authClient:a}=E();if(k)return k;let i=(async()=>{s({runtimeStatus:"bootstrapping"}),r&&a.clearAuth(),t().destroy(),await t().initConversation()})().finally(()=>{k===i&&(k=null)});return k=i,i},newConversation:async()=>{if(!Z){Z=!0;try{P++,t().clearChat(),s({historyMessages:[],historyHasMore:!0,isLoadingHistory:!1,historyEntryKeys:[],historyBeforeId:null}),await t().initConversation()}finally{Z=!1}}},scheduleSessionUpgrade:()=>{let{authClient:r}=E();if(k)return k;if(t().isGenerating)return ee=!0,Promise.resolve();let n=(async()=>{s({runtimeStatus:"bootstrapping"});try{r.clearAuth(),await r.ensureAuth(),t().destroy(),await t().initConversation()}catch(i){gt.error("unexpected error:",i),s({runtimeStatus:"failed"})}finally{ee=!1}})().finally(()=>{k===n&&(k=null)});return k=n,n},loadMoreHistory:async()=>{let{platformApi:r}=E(),{isLoadingHistory:a,historyHasMore:n,historyMessages:i,historyBeforeId:l}=t();if(a||!n)return;let c=P;s({isLoadingHistory:!0});let f=5,b=i.length===0?t().messages.find(g=>g.requestId)?.requestId:void 0,A=l??b;try{let{status:g,data:M}=await r.listMessages({pageSize:f,...A?{beforeId:A}:{}});if(c!==P)return;if(g===200&&M){let _=M.data||[],Pe=new Set(t().messages.map(C=>C.requestId).filter(Boolean)),te=new Set(t().historyEntryKeys),re=[],se=_.filter(C=>{let G=Ct(C);return te.has(G)?!1:(te.add(G),re.push(G),!C.outerBusinessId||!Pe.has(C.outerBusinessId))}),L=[..._].reverse().find(C=>C.outerBusinessId)?.outerBusinessId??null,Ne=se.length>0||!!L&&L!==A,{messages:_e,cardsByMessageId:Le}=Ee(se.reverse());s(C=>({historyMessages:[..._e,...C.historyMessages],historyHasMore:_.length>=f&&!!L&&Ne,isLoadingHistory:!1,historyEntryKeys:[...re,...C.historyEntryKeys],historyBeforeId:L,cardsByMessageId:{...C.cardsByMessageId,...Le}}))}else s({isLoadingHistory:!1})}catch(g){if(mt.error("load more error:",g),c!==P)return;s({isLoadingHistory:!1})}},submitFeedback:async(r,a,n,i)=>{let l=t().messages.find(g=>g.id===r),c=l?.feedbackState??"none",f=l?.requestId;if(!f){ve.warn("no requestId for message",r);return}let b=a==="cancel"?"none":a;t().updateMessage(r,{feedbackState:b});let A={like:"LIKE",dislike:"DISLIKE",cancel:"CANCEL"};try{(await n.addTag(f,A[a])).success||t().updateMessage(r,{feedbackState:c})}catch(g){if(yt(g)&&i)try{let M=await i();if(M.feedback){(await M.feedback.addTag(f,A[a])).success||t().updateMessage(r,{feedbackState:c});return}}catch(M){ve.error("retry failed:",M)}t().updateMessage(r,{feedbackState:c})}}}}function nr(e){return e.subscribe((s,t)=>{ee&&t.isGenerating&&!s.isGenerating&&e.getState().scheduleSessionUpgrade()})}export{At as a,ne as b,Tt as c,oe as d,Mt as e,ae as f,z as g,h,O as i,xt as j,ie as k,de as l,ce as m,Ht as n,pe as o,ge as p,ze as q,Je as r,Ye as s,Ce as t,Ie as u,ke as v,st as w,nt as x,ot as y,at as z,lt as A,Ee as B,dt as C,Ae as D,yt as E,tr as F,ht as G,sr as H,nr as I};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { TboxCardPayload, CardComponent, UiAction } from '@tbox.cn/app-contracts';
|
|
2
|
+
import { A as AuthClient } from '../auth-client-BHC_nZj8.js';
|
|
3
|
+
import { TboxCardPreviewCard, TboxCardPreviewSample, TboxCardPreviewCatalog } from '@tbox.cn/app-sdk-core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* dev 卡片预览页纯逻辑核(零 React / 零 DOM——node 直测):
|
|
7
|
+
* 筛选派生(模块候选 + 深链定位)、payload 组装(客户端铸造 preview token)、
|
|
8
|
+
* 单行实例动作结果应用(update 原位;card 替换来源行)。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** 单行预览实例;payload 可由 update 原位替换。 */
|
|
12
|
+
interface PreviewInstanceState {
|
|
13
|
+
card: TboxCardPreviewCard;
|
|
14
|
+
sample: TboxCardPreviewSample;
|
|
15
|
+
payload: TboxCardPayload;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
declare const EMPTY_REGISTRY_MSG = "\u5BA2\u6237\u7AEF\u672A\u88C5\u914D\u6A21\u5757\u2014\u2014\u7ECF tbox-app dev --module <\u6A21\u5757> \u88C5\u914D\u540E\u53EF\u89C1";
|
|
19
|
+
declare const EMPTY_CATALOG_MSG = "\u670D\u52A1\u7AEF\u672A\u6CE8\u518C\u5361\u7247\uFF08\u6A21\u5757\u672A\u88C5\u914D\u6216\u5168\u90E8\u88AB\u7981\u7528\uFF09";
|
|
20
|
+
declare const AUTH_ERROR_MSG = "\u9274\u6743\u5931\u8D25\uFF1Adev \u73AF\u5883\u4F9D\u8D56 dev-login\uFF08\u6D4F\u89C8\u5668\u6001\u81EA\u52A8\u767B\u5F55\uFF09\uFF1B\u751F\u4EA7\u6784\u5EFA\uFF08NODE_ENV=production\uFF09\u9884\u89C8\u9762\u4E0D\u6302\u8F7D";
|
|
21
|
+
declare const LOAD_ERROR_MSG: (status: number) => string;
|
|
22
|
+
declare const PAGE_TITLE = "tbox \u5361\u7247\u9884\u89C8";
|
|
23
|
+
declare const HISTORY_VIEW_LABEL = "isHistory \u89C6\u89D2";
|
|
24
|
+
declare const MODULE_ALL_LABEL = "\u5168\u90E8\u6A21\u5757";
|
|
25
|
+
declare function CardPreviewPage(props: {
|
|
26
|
+
registry: Record<string, CardComponent>;
|
|
27
|
+
authClient: AuthClient;
|
|
28
|
+
}): JSX.Element;
|
|
29
|
+
declare function buildPreviewRows(cards: TboxCardPreviewCard[], cardType?: string, sampleLabel?: string): Record<string, PreviewInstanceState>;
|
|
30
|
+
declare function PreviewRows(props: {
|
|
31
|
+
cards: TboxCardPreviewCard[];
|
|
32
|
+
rows: Record<string, PreviewInstanceState>;
|
|
33
|
+
registry: Record<string, CardComponent>;
|
|
34
|
+
historyView: boolean;
|
|
35
|
+
onSelectSample: (rowId: string, label: string) => void;
|
|
36
|
+
onCopyCardType: (cardType: string) => void;
|
|
37
|
+
onAction: (surfaceId: string, action: UiAction) => void;
|
|
38
|
+
}): JSX.Element;
|
|
39
|
+
/** 每个卡型的一行:标题/样本切换/质量信息 + 单个 CardRenderer。 */
|
|
40
|
+
declare function PreviewSurface(props: {
|
|
41
|
+
instance: PreviewInstanceState;
|
|
42
|
+
registry: Record<string, CardComponent>;
|
|
43
|
+
historyView: boolean;
|
|
44
|
+
samples: TboxCardPreviewCatalog['cards'][number]['samples'];
|
|
45
|
+
activeSampleLabel: string;
|
|
46
|
+
onSelectSample: (label: string) => void;
|
|
47
|
+
onCopyCardType: (cardType: string) => void;
|
|
48
|
+
onAction: (surfaceId: string, action: UiAction) => void;
|
|
49
|
+
}): JSX.Element;
|
|
50
|
+
|
|
51
|
+
export { AUTH_ERROR_MSG, CardPreviewPage, EMPTY_CATALOG_MSG, EMPTY_REGISTRY_MSG, HISTORY_VIEW_LABEL, LOAD_ERROR_MSG, MODULE_ALL_LABEL, PAGE_TITLE, PreviewRows, PreviewSurface, buildPreviewRows };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{G as j}from"../chunk-RQWCUVXR.js";import{useCallback as L,useEffect as xe,useMemo as te,useRef as P,useState as R}from"react";import{CardRenderer as ve}from"@tbox.cn/app-sdk-client";var ge="preview",J="表单提交需引擎运行,预览不支持",Y=e=>`预览:已发送「${e}」`,X=e=>`宿主效果 ×${e}(预览不执行)`;function q(e){return new URLSearchParams(e).get("history")==="true"}function K(e){return new URLSearchParams(e).get("controls")!=="false"}var W="(未声明)";function fe(e){return[...new Set(e)].sort((t,n)=>t===n?0:t===W?1:n===W?-1:t.localeCompare(n))}function U(e,t={}){let n=fe(e.cards.map(a=>a.moduleId)),i=t.module!==void 0&&n.includes(t.module)?t.module:"all",r=[...i==="all"?e.cards:e.cards.filter(a=>a.moduleId===i)].sort((a,v)=>a.cardType.localeCompare(v.cardType)),s=r.map(a=>a.cardType),p=r.find(a=>a.cardType===t.card)??r[0],x=p?.cardType??"",g=p?.samples??[],f=t.sample!==void 0&&g.some(a=>a.label===t.sample)?t.sample:"初始态";return{modules:n,moduleId:i,cardTypes:s,candidateCards:r,cardType:x,selectedCard:p,samples:g,sampleLabel:f}}function me(e,t){return`preview-${e.replace(/[^a-zA-Z0-9_-]/g,"_")}-${t}`}function G(e){return{...e.title!==void 0?{title:e.title}:{},...e.subtitle!==void 0?{subtitle:e.subtitle}:{},...e.notices!==void 0?{notices:e.notices}:{},...e.expiresAt!==void 0?{expiresAt:e.expiresAt}:{}}}function z(e,t){let n={};if(!t.noTokens)for(let i of e.actions)(!i.kind||i.kind==="moduleAction")&&(n[i.actionId]=ge);return{id:t.id,cardType:e.cardType,data:t.data,status:"ready",...e.actions.length?{actions:e.actions}:{},...Object.keys(n).length?{actionTokens:n}:{},...t.isHistory?{isHistory:!0}:{},...t.title!==void 0?{title:t.title}:{},...t.subtitle!==void 0?{subtitle:t.subtitle}:{},...t.notices!==void 0?{notices:t.notices}:{},...t.expiresAt!==void 0?{expiresAt:t.expiresAt}:{}}}function Z(e,t,n){return z(e,{id:me(e.cardType,n),data:t.data,isHistory:!!t.isHistory,noTokens:!!t.noTokens,...t.envelope??{}})}function be(e,t,n,i){if(n.ok!==!0||e.payload.id!==t||!n.card)return e;if(n.kind==="card"){let r=i(n.card.cardType),s=r?.samples[0];return!r||!s?e:{card:r,sample:s,payload:z(r,{id:n.card.id,data:n.card.data,isHistory:!1,noTokens:!1,...G(n.card)})}}return n.kind!=="update"?e:{...e,payload:z(e.card,{id:t,data:n.card.data,isHistory:!!e.sample.isHistory,noTokens:!!e.sample.noTokens,...G(n.card)})}}function Q(e,t,n,i,r){let s=e[t];if(!s||s.payload.id!==n)return e;let p=be(s,n,i,r);return p===s?e:{...e,[t]:p}}function ee(e){switch(e.type){case"moduleAction":return{type:"server"};case"openLink":return{type:"openLink",url:e.url};case"sendMessage":return{type:"sendMessage",value:e.value};case"submitForm":return{type:"submitFormUnsupported"};default:return{type:"ignored"}}}import{jsx as c,jsxs as y}from"react/jsx-runtime";var we="客户端未装配模块——经 tbox-app dev --module <模块> 装配后可见",Te="服务端未注册卡片(模块未装配或全部被禁用)",he="鉴权失败:dev 环境依赖 dev-login(浏览器态自动登录);生产构建(NODE_ENV=production)预览面不挂载",re=e=>`卡片目录拉取失败(HTTP ${e})`,Ce="tbox 卡片预览",Se="isHistory 视角",Pe="全部模块";function oe(e,t,n){let i=Math.max(0,e.samples.findIndex(s=>s.label===t)),r=e.samples[i];return r?{card:e,sample:r,payload:Z(e,r,n??i)}:null}function Ze(e){let{registry:t,authClient:n}=e,i=Object.keys(t).length===0,[r,s]=R(null),[p,x]=R(null),[g,f]=R("all"),[a,v]=R(!1),[h,C]=R({}),[k,N]=R([]),F=P(null),E=P(new Set),w=P(h);w.current=h;let V=P(r);V.current=r;let ae=P(0),se=P(0),_=te(()=>{try{let o=window.location.search,u=new URLSearchParams(o);return{module:u.get("module")??void 0,card:u.get("card")??void 0,sample:u.get("sample")??void 0,history:q(o),controls:K(o)}}catch{return{}}},[]),ce=_.controls!==!1,m=L(o=>{let u=++ae.current;N(l=>[...l,{id:u,text:o}]),setTimeout(()=>N(l=>l.filter(d=>d.id!==u)),3200)},[]);xe(()=>{let o=!1,u=j((l,d,T)=>{$.current(l,d,T)});return(async()=>{try{let l=await n.ensureAuth();F.current=l.token;let d=await fetch("/_tbox/api/cards",{headers:l.token?{Authorization:`Bearer ${l.token}`}:{}});if(o)return;if(d.status===401||d.status===403){x(he);return}if(!d.ok){x(re(d.status));return}let T=await d.json();if(o)return;s(T);let A=U(T,_);f(A.moduleId);let I=Ae(T.cards,A.cardType,A.sampleLabel);w.current=I,C(I),v(_.history===!0)}catch(l){o||x(`卡片目录拉取失败:${l.message}`)}})(),()=>{o=!0,u()}},[]);let H=L((o,u,l)=>{let d=ee(u);if(d.type==="openLink"){window.open(d.url,"_blank");return}if(d.type==="sendMessage"){m(Y(d.value));return}if(d.type==="submitFormUnsupported"){m(J);return}if(d.type==="ignored")return;let T=Object.entries(w.current).find(([,S])=>S.payload.id===o),A=T?.[1];if(!T||!A||u.type!=="moduleAction"||E.current.has(o))return;E.current.add(o);let I=F.current;fetch("/_tbox/api/card-action",{method:"POST",headers:{"Content-Type":"application/json",...I?{Authorization:`Bearer ${I}`}:{}},body:JSON.stringify({cardType:A.card.cardType,actionId:u.actionId,surfaceId:o,...l!==void 0?{input:l}:{}})}).then(async S=>{let b=await S.json().catch(()=>null);if(!S.ok||!b||b.ok===!1){m(b?.error??re(S.status));return}let pe=b,D=w.current,O=Q(D,T[0],o,pe,ue=>V.current?.cards.find(ye=>ye.cardType===ue));O!==D&&(w.current=O,C(O)),typeof b.text=="string"&&b.text&&m(b.text),Array.isArray(b.effects)&&b.effects.length>0&&m(X(b.effects.length))}).catch(S=>m(`动作请求失败:${S.message}`)).finally(()=>E.current.delete(o))},[m]),$=P(H);$.current=H;let M=te(()=>r?U(r,{module:g==="all"?void 0:g}):null,[r,g]),de=L((o,u)=>{let l=w.current[o];if(!l)return;let d=oe(l.card,u,l.card.samples.length+ ++se.current);d&&(w.current={...w.current,[o]:d},C(w.current))},[]),le=L(o=>{if(typeof navigator>"u"||!navigator.clipboard?.writeText){m("当前浏览器不支持复制");return}navigator.clipboard.writeText(o).then(()=>m(`已复制卡型 ID:${o}`)).catch(()=>m("复制失败,请手动复制"))},[m]);return y("div",{style:{minHeight:"100vh",background:"#f5f5f5",fontFamily:'-apple-system, "PingFang SC", sans-serif'},children:[ce&&y("header",{style:Ie,children:[c("strong",{style:{fontSize:14,color:"#262626"},children:Ce}),y("select",{"aria-label":"模块",style:Le,value:g,onChange:o=>f(o.target.value),children:[c("option",{value:"all",children:Pe}),(M?.modules??[]).map(o=>c("option",{value:o,children:o},o))]}),c("button",{type:"button",style:a?Ee:ie,onClick:()=>v(o=>!o),children:Se})]}),y("main",{style:{maxWidth:1200,margin:"0 auto",padding:"12px 12px 48px"},children:[p&&c("div",{style:We,children:p}),!p&&!r&&c("div",{style:B,children:"加载中…"}),r&&i&&c("div",{style:B,children:we}),r&&!i&&r.cards.length===0&&c("div",{style:B,children:Te}),r&&!i&&M&&c(Re,{cards:M.candidateCards,rows:h,registry:t,historyView:a,onSelectSample:de,onCopyCardType:le,onAction:H})]}),c("div",{style:{position:"fixed",right:16,bottom:24,display:"flex",flexDirection:"column",gap:8,zIndex:20},children:k.map(o=>c("div",{style:Ge,children:o.text},o.id))})]})}function Ae(e,t,n){let i={};for(let r of e){let s=oe(r,r.cardType===t?n:void 0);s&&(i[r.cardType]=s)}return i}function Re(e){let{cards:t,rows:n,registry:i,historyView:r,onSelectSample:s,onCopyCardType:p,onAction:x}=e;return c("div",{style:He,children:t.map(g=>{let f=n[g.cardType];return f?c(ke,{instance:f,registry:i,historyView:r,samples:f.card.samples,activeSampleLabel:f.sample.label,onSelectSample:a=>s(g.cardType,a),onCopyCardType:p,onAction:x},g.cardType):null})})}function ke(e){let{instance:t,registry:n,historyView:i,samples:r,activeSampleLabel:s,onSelectSample:p,onCopyCardType:x,onAction:g}=e,f=i?{...t.payload,isHistory:!0}:t.payload,a=Math.max(0,r.findIndex(k=>k.label===s)),v=r[a]??t.sample,h=r[a-1],C=r[a+1];return y("section",{style:_e,children:[y("header",{style:{marginBottom:8},children:[c("div",{style:{fontSize:14,fontWeight:600,color:"#262626",lineHeight:"22px"},children:t.card.displayName}),y("div",{style:Me,children:[y("span",{style:Oe,title:t.card.cardType,children:["卡片id:",t.card.cardType]}),c("button",{type:"button","aria-label":`复制卡型 ID:${t.card.cardType}`,title:"复制卡型 ID",style:ze,onClick:()=>x(t.card.cardType),children:y("span",{"aria-hidden":"true",style:Ue,children:[c("span",{style:Be}),c("span",{style:Ne})]})})]})]}),r.length>0&&y("div",{style:Ve,children:[y("div",{style:je,title:v.label,"aria-live":"polite",children:[v.issues?.length||v.envelopeError?"⚠ ":"",v.label]}),y("div",{style:$e,children:[c("button",{type:"button","aria-label":"上一个样本",title:"上一个样本",disabled:!h,onClick:()=>h&&p(h.label),style:ne(!h),children:"‹"}),y("span",{style:De,children:[a+1," / ",r.length]}),c("button",{type:"button","aria-label":"下一个样本",title:"下一个样本",disabled:!C,onClick:()=>C&&p(C.label),style:ne(!C),children:"›"})]})]}),(t.sample.issues?.length||t.sample.envelopeError)&&y("div",{style:Fe,children:[t.sample.issues?.length?`样本问题:${t.sample.issues.join(";")}`:null,t.sample.envelopeError&&t.sample.issues?.length?";":"",t.sample.envelopeError??""]}),c(ve,{card:f,registry:n,onAction:k=>g(f.id,k)})]})}var Ie={position:"sticky",top:0,zIndex:10,display:"flex",flexWrap:"wrap",alignItems:"center",gap:8,padding:"10px 16px",background:"#fff",borderBottom:"1px solid #e8e8e8"},Le={fontSize:13,padding:"4px 8px",borderRadius:8,border:"1px solid #d9d9d9",background:"#fff",color:"#262626"},ie={fontSize:13,padding:"4px 12px",borderRadius:18,border:"1px solid #d9d9d9",background:"#fff",color:"#262626",cursor:"pointer"},Ee={...ie,borderColor:"#1677ff",color:"#1677ff"},_e={background:"#fff",borderRadius:8,padding:14,boxShadow:"0 1px 4px rgba(0,0,0,0.06)"},He={display:"flex",flexDirection:"column",gap:12},Me={display:"flex",alignItems:"center",justifyContent:"space-between",gap:"var(--spacing.4, 4px)",minHeight:20,color:"var(--color.text.tertiary, #8c8c8c)",fontSize:11,lineHeight:"16px"},Oe={flex:"1 1 auto",minWidth:0,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},ze={display:"inline-flex",alignItems:"center",justifyContent:"center",flex:"0 0 auto",width:20,height:20,padding:0,border:0,borderRadius:"var(--radius.4, 4px)",background:"transparent",color:"var(--color.text.tertiary, #8c8c8c)",cursor:"pointer"},Ue={position:"relative",display:"inline-block",width:13,height:13},Be={position:"absolute",top:1,left:1,width:8,height:8,border:"1px solid currentColor",borderRadius:2,boxSizing:"border-box"},Ne={position:"absolute",right:1,bottom:1,width:8,height:8,border:"1px solid currentColor",borderRadius:2,background:"#fff",boxSizing:"border-box"},Fe={fontSize:12,lineHeight:"18px",padding:"4px 10px",borderRadius:8,background:"rgba(250,140,22,0.10)",color:"#d46b08",marginBottom:8},Ve={display:"flex",alignItems:"center",justifyContent:"space-between",gap:"var(--spacing.12, 12px)",minHeight:24,marginBottom:10},$e={display:"flex",alignItems:"center",gap:"var(--spacing.2, 2px)",flex:"0 0 auto",height:24},ne=e=>({width:24,height:24,padding:0,border:0,borderRadius:"var(--radius.4, 4px)",background:"transparent",color:e?"var(--color.text.disabled, #bfbfbf)":"var(--color.text.secondary, #404040)",fontSize:18,lineHeight:"22px",cursor:e?"not-allowed":"pointer"}),De={display:"flex",alignItems:"center",justifyContent:"center",minWidth:42,color:"var(--color.text.tertiary, #8c8c8c)",fontSize:"var(--font.size.12, 12px)",fontVariantNumeric:"tabular-nums",whiteSpace:"nowrap"},je={flex:"1 1 auto",minWidth:0,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",color:"var(--color.text.primary, #1f1f1f)",fontSize:"var(--font.size.14, 14px)",fontWeight:"var(--font.weight.medium, 500)"},We={padding:"12px 14px",borderRadius:8,background:"rgba(255,77,79,0.08)",color:"#ff4d4f",fontSize:13,lineHeight:"20px"},B={padding:"12px 14px",borderRadius:8,background:"#fff",color:"#8c8c8c",fontSize:13,lineHeight:"20px",textAlign:"center"},Ge={maxWidth:320,padding:"8px 12px",borderRadius:8,background:"rgba(38,38,38,0.88)",color:"#fff",fontSize:13,lineHeight:"18px",boxShadow:"0 4px 12px rgba(0,0,0,0.18)"};export{he as AUTH_ERROR_MSG,Ze as CardPreviewPage,Te as EMPTY_CATALOG_MSG,we as EMPTY_REGISTRY_MSG,Se as HISTORY_VIEW_LABEL,re as LOAD_ERROR_MSG,Pe as MODULE_ALL_LABEL,Ce as PAGE_TITLE,Re as PreviewRows,ke as PreviewSurface,Ae as buildPreviewRows};
|
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
export * from '@tbox.cn/app-sdk-client';
|
|
2
|
+
export { InferModuleCards } from '@tbox.cn/app-sdk-core';
|
|
3
|
+
import { TboxSessionData, HostEffect, Adapters, IASRAdapter, IFeedbackAdapter, ITTSAdapter, IUploadAdapter, HelloAckChatConfig, TboxCardPayload, UiAction, CardSnapshot, SkillRecord, PreToolRecord, HostEffectResult, UploadProgressCallback, UploadOptions, UploadResult } from '@tbox.cn/app-contracts';
|
|
4
|
+
export { Adapters, ChatUiSettings, IASRAdapter, IFeedbackAdapter, ITTSAdapter, IUploadAdapter, UploadOptions, UploadProgressCallback, UploadResult } from '@tbox.cn/app-contracts';
|
|
5
|
+
import { UseBoundStore, StoreApi } from 'zustand';
|
|
6
|
+
import { A as AuthClient } from '../auth-client-BHC_nZj8.js';
|
|
7
|
+
export { a as AuthClientOptions, b as AuthIdentity, c as AuthState, d as createAuthClient, g as getAuthClient } from '../auth-client-BHC_nZj8.js';
|
|
8
|
+
import * as react from 'react';
|
|
9
|
+
|
|
10
|
+
/** 媒体 / 消息类型(SDK 核心 chat 协议类型) */
|
|
11
|
+
type MediaType = 'image' | 'video' | 'audio' | 'file';
|
|
12
|
+
type InputMethod = 'text' | 'voice' | 'image' | 'video' | 'audio' | 'file';
|
|
13
|
+
interface MediaItem {
|
|
14
|
+
id: string;
|
|
15
|
+
type: MediaType;
|
|
16
|
+
src: string;
|
|
17
|
+
fileId?: string;
|
|
18
|
+
name?: string;
|
|
19
|
+
mimeType?: string;
|
|
20
|
+
size?: number;
|
|
21
|
+
uploadProgress?: number;
|
|
22
|
+
isUploading?: boolean;
|
|
23
|
+
uploadFailed?: boolean;
|
|
24
|
+
uploadError?: string;
|
|
25
|
+
illegal?: boolean;
|
|
26
|
+
}
|
|
27
|
+
interface ChatInputMessage {
|
|
28
|
+
text: string;
|
|
29
|
+
mediaItems?: MediaItem[];
|
|
30
|
+
inputMethod: InputMethod;
|
|
31
|
+
}
|
|
32
|
+
type MessageRole = 'user' | 'assistant' | 'system';
|
|
33
|
+
type MessageStatus = 'pending' | 'thinking' | 'streaming' | 'done' | 'error';
|
|
34
|
+
interface ToolCallInfo {
|
|
35
|
+
id: string;
|
|
36
|
+
name: string;
|
|
37
|
+
args?: string;
|
|
38
|
+
result?: string;
|
|
39
|
+
error?: string;
|
|
40
|
+
status: 'calling' | 'done' | 'error';
|
|
41
|
+
}
|
|
42
|
+
interface ChatMessage {
|
|
43
|
+
id: string;
|
|
44
|
+
role: MessageRole;
|
|
45
|
+
content: string;
|
|
46
|
+
error?: string;
|
|
47
|
+
status: MessageStatus;
|
|
48
|
+
createdAt: number;
|
|
49
|
+
reasoning?: string;
|
|
50
|
+
toolCalls?: ToolCallInfo[];
|
|
51
|
+
mediaItems?: MediaItem[];
|
|
52
|
+
inputMethod?: InputMethod;
|
|
53
|
+
feedbackState?: 'none' | 'like' | 'dislike';
|
|
54
|
+
requestId?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 平台协议客户端(client 平台轴):能力凭证 + 历史消息。
|
|
59
|
+
* 路径契约与 server 平台路由配套(app-sdk/server/conversation 单源);
|
|
60
|
+
* 组装端(模板 store/AgentChatApp)经 createPlatformApi() 消费。
|
|
61
|
+
*
|
|
62
|
+
* 会话 β 收口:createConversation 退役(会话由 WS NEW/RESUME 服务端建立,客户端零会话 id);
|
|
63
|
+
* getTboxSession/listMessages 身份恒取 token(服务端收口 query userId——伪造面协议级消灭)。
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
interface ListMessagesResult {
|
|
67
|
+
status: number;
|
|
68
|
+
data: {
|
|
69
|
+
data?: unknown[];
|
|
70
|
+
totalCount?: number;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
interface ListMessagesOptions {
|
|
74
|
+
pageSize: number;
|
|
75
|
+
beforeId?: string;
|
|
76
|
+
}
|
|
77
|
+
interface PlatformApi {
|
|
78
|
+
/**
|
|
79
|
+
* 获取平台能力凭证(006 D10:tboxSessionId,区别于 auth token)。
|
|
80
|
+
* 身份恒取 token(服务端 401/403 收口)。
|
|
81
|
+
*/
|
|
82
|
+
getTboxSession(forceRefresh?: boolean): Promise<TboxSessionData>;
|
|
83
|
+
clearTboxSession(): void;
|
|
84
|
+
/** 历史消息(服务端已恢复 isHistory 卡片;归属恒 = token 身份) */
|
|
85
|
+
listMessages(opts: ListMessagesOptions): Promise<ListMessagesResult>;
|
|
86
|
+
}
|
|
87
|
+
declare function createPlatformApi(): PlatformApi;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* SDK client 侧依赖的 app 注入接口。
|
|
91
|
+
* app 通过这些接口注入平台特定实现(toast、WS URL 解析等)。
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
interface ToastFunction {
|
|
95
|
+
(content: string, tone?: 'info' | 'success' | 'error'): void;
|
|
96
|
+
}
|
|
97
|
+
interface WsUrlResolver {
|
|
98
|
+
(): string | null;
|
|
99
|
+
}
|
|
100
|
+
interface CoreChatStoreDeps {
|
|
101
|
+
/** WS URL 解析(app 侧从 env/config 注入) */
|
|
102
|
+
getWsUrl: WsUrlResolver;
|
|
103
|
+
/** toast 函数(app 侧注入) */
|
|
104
|
+
toast: ToastFunction;
|
|
105
|
+
/**
|
|
106
|
+
* 宿主效果执行(015 P4/V1:缺省 SDK 内置 bridge-aware runner;
|
|
107
|
+
* 注入可测——store 零 jsapi import)。contextToken 去重已在 store 层完成。
|
|
108
|
+
*/
|
|
109
|
+
onHostEffect?: (effect: HostEffect) => void;
|
|
110
|
+
/**
|
|
111
|
+
* 卡片事件埋点(P2,可选注入;模板零默认装配):
|
|
112
|
+
* 打点位在 store 写入动作(upsertCard=render / updateCardData 命中=update /
|
|
113
|
+
* sendToServer=click)——WS 事件流单线触发,StrictMode 双调免疫(R1)。
|
|
114
|
+
*/
|
|
115
|
+
onCardEvent?: (event: CardEventData) => void;
|
|
116
|
+
/**
|
|
117
|
+
* 会话凭证升级通知(session.credentials-refreshed effect 宿主侧执行钩子,可选注入):
|
|
118
|
+
* 宿主执行清缓存重登 + 重连拿新 credentials(通知型无回传,幂等由宿主守卫承担)。
|
|
119
|
+
*/
|
|
120
|
+
onSessionCredentialsRefreshed?: (reason?: string) => void;
|
|
121
|
+
/**
|
|
122
|
+
* 平台 API(W2-2 会话泳道下沉:initConversation/loadMoreHistory 消费
|
|
123
|
+
* createConversation/listMessages)。可选注入——缺席时调用会话泳道方法 fail-loud
|
|
124
|
+
* (旧形态自持实现的应用不走 SDK 泳道)。
|
|
125
|
+
*/
|
|
126
|
+
platformApi?: PlatformApi;
|
|
127
|
+
/**
|
|
128
|
+
* 鉴权客户端(W2-2 会话泳道下沉:resolveUserId/getAuthState/clearAuth/ensureAuth 消费)。
|
|
129
|
+
* 可选注入——与 platformApi 成对;缺席同上 fail-loud。
|
|
130
|
+
*/
|
|
131
|
+
authClient?: AuthClient;
|
|
132
|
+
}
|
|
133
|
+
/** 卡片事件(P2 埋点:render 曝光 / update 原地更新命中 / click 卡内动作上行) */
|
|
134
|
+
interface CardEventData {
|
|
135
|
+
type: 'render' | 'update' | 'click';
|
|
136
|
+
cardId: string;
|
|
137
|
+
cardType: string;
|
|
138
|
+
messageId?: string;
|
|
139
|
+
ts: number;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
interface AdapterContextValue extends Adapters {
|
|
143
|
+
/** 刷新 session 并返回重建后的 adapters,用于重试过期操作 */
|
|
144
|
+
refreshSession?: () => Promise<AdapterContextValue>;
|
|
145
|
+
}
|
|
146
|
+
declare function AdapterProvider({ value, children, }: {
|
|
147
|
+
value: AdapterContextValue;
|
|
148
|
+
children?: React.ReactNode;
|
|
149
|
+
}): react.JSX.Element;
|
|
150
|
+
declare const useAdapters: () => AdapterContextValue;
|
|
151
|
+
declare const useASRAdapter: () => IASRAdapter | undefined;
|
|
152
|
+
declare const useTTSAdapter: () => ITTSAdapter | undefined;
|
|
153
|
+
declare const useUploadAdapter: () => IUploadAdapter | undefined;
|
|
154
|
+
declare const useFeedbackAdapter: () => IFeedbackAdapter | undefined;
|
|
155
|
+
declare const useRefreshSession: () => (() => Promise<AdapterContextValue>) | undefined;
|
|
156
|
+
|
|
157
|
+
/** 配置 context 采集(应用装配点注入;返回 undefined 则调用方不携带 context) */
|
|
158
|
+
declare function configureHelloContext(getter: (() => Record<string, unknown> | undefined) | undefined): void;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 核心 ChatStore:AG-UI 事件 → 消息流式拼接 / 工具调用 / 卡片 / reasoning 状态管理。
|
|
162
|
+
* app 侧通过 extend 注入业务方法(initConversation / loadMoreHistory / submitFeedback)。
|
|
163
|
+
*/
|
|
164
|
+
|
|
165
|
+
/** 判断错误是否为 session 过期(保守策略:宁可多重试一次)。 */
|
|
166
|
+
declare function isSessionExpiredError(err: unknown): boolean;
|
|
167
|
+
/**
|
|
168
|
+
* 模块卡组件提交动作(moduleAction/submitForm/sendMessage)。
|
|
169
|
+
* 模块卡编译期不依赖应用 store——经本函数走当前绑定的分发器
|
|
170
|
+
* (含 isGenerating/isHistory 门控)。未注册(应用未创建)时 warn 丢弃。
|
|
171
|
+
*/
|
|
172
|
+
declare function sendCardAction(surfaceId: string, action: UiAction, formData?: Record<string, unknown>): void;
|
|
173
|
+
/**
|
|
174
|
+
* 绑定卡动作通道(返回 restore):替换当前分发器并恢复**前一绑定**
|
|
175
|
+
* (可能是 undefined——应用 store 未创建时)。dev 卡片预览页挂载时接管
|
|
176
|
+
* sendCardAction、卸载时还原应用通道;guard 语义(仅当当前仍是本绑定才还原)
|
|
177
|
+
* 使嵌套绑定与 React StrictMode 双 effect 天然幂等。
|
|
178
|
+
* createCoreChatState 构造期经此注册保底绑定(P5 语义不变)。
|
|
179
|
+
*/
|
|
180
|
+
declare function bindCardActionChannel(dispatch: (surfaceId: string, action: UiAction, formData?: Record<string, unknown>) => void): () => void;
|
|
181
|
+
|
|
182
|
+
interface CoreChatState {
|
|
183
|
+
userId: string;
|
|
184
|
+
/** 011 I2:HELLO_ACK 载荷(mallScope + chatConfig;服务端下发单一真源) */
|
|
185
|
+
helloAck: HelloAckState | null;
|
|
186
|
+
/** 连接终态阻断(4001 耗尽 / 4002 / mall 匿名跳建连):非 null 时 UI 禁用对话;
|
|
187
|
+
* 恢复唯一真源 = HELLO_ACK。纯展示态,不参与发送判定(发送判定归 isWsOpen 咽喉)。 */
|
|
188
|
+
authBlocked: string | null;
|
|
189
|
+
setAuthBlocked: (msg: string | null) => void;
|
|
190
|
+
messages: ChatMessage[];
|
|
191
|
+
isGenerating: boolean;
|
|
192
|
+
currentRunId: string | null;
|
|
193
|
+
streamingMsgId: string | null;
|
|
194
|
+
/**
|
|
195
|
+
* 会话 β 锚(客户端唯一会话态):会话已建立布尔——conversationId 本体由服务端持有,
|
|
196
|
+
* 客户端零会话 id(不提供即不可伪造)。
|
|
197
|
+
* 唯二写者:bootstrap 完成路径 → true;身份变更/clearChat → false。
|
|
198
|
+
* 页面刷新 = 新 store 缺省 false → NEW;retryBootstrap 同用户不触碰 → RESUME。
|
|
199
|
+
*/
|
|
200
|
+
conversationEstablished: boolean;
|
|
201
|
+
/** 卡片:按 messageId 分桶(关联到 assistant 气泡下渲染) */
|
|
202
|
+
cardsByMessageId: Record<string, TboxCardPayload[]>;
|
|
203
|
+
/** 不挂消息的卡片(业务旁路推送,如欢迎卡) */
|
|
204
|
+
orphanCards: TboxCardPayload[];
|
|
205
|
+
init: () => void;
|
|
206
|
+
sendMessage: (input: string | ChatInputMessage) => void;
|
|
207
|
+
sendUiAction: (surfaceId: string, action: UiAction, formData?: Record<string, unknown>) => void;
|
|
208
|
+
hideCard: (cardId: string) => void;
|
|
209
|
+
/** 连接与身份拆除(unmount/登出);会话清空用 clearChat()。 */
|
|
210
|
+
destroy: () => void;
|
|
211
|
+
/** 会话重置(含取消运行中 run);连接保留——身份域归 destroy()。 */
|
|
212
|
+
clearChat: () => void;
|
|
213
|
+
appendDelta: (id: string, delta: string) => void;
|
|
214
|
+
addMessage: (msg: ChatMessage) => void;
|
|
215
|
+
updateMessage: (id: string, patch: Partial<ChatMessage>) => void;
|
|
216
|
+
setGenerating: (v: boolean) => void;
|
|
217
|
+
cancelGeneration: () => void;
|
|
218
|
+
setUserId: (id: string) => void;
|
|
219
|
+
historyMessages: ChatMessage[];
|
|
220
|
+
historyHasMore: boolean;
|
|
221
|
+
isLoadingHistory: boolean;
|
|
222
|
+
/** 已归并的平台历史条目键,用于 inclusive beforeId / 重叠页去重。 */
|
|
223
|
+
historyEntryKeys: string[];
|
|
224
|
+
/** 平台分页游标真源(outerBusinessId);不再从可选的转换后消息 requestId 反推。 */
|
|
225
|
+
historyBeforeId: string | null;
|
|
226
|
+
initConversation: () => Promise<void>;
|
|
227
|
+
/** 新建会话:清会话态(连接复用)+ 重置历史态 + create 新会话 */
|
|
228
|
+
newConversation: () => Promise<void>;
|
|
229
|
+
loadMoreHistory: () => Promise<void>;
|
|
230
|
+
submitFeedback: (messageId: string, type: 'like' | 'dislike' | 'cancel', adapter: IFeedbackAdapter, refreshSession?: () => Promise<AdapterContextValue>) => Promise<void>;
|
|
231
|
+
/** 运行时状态胶囊(bootstrapping/ready/failed,主屏展示) */
|
|
232
|
+
runtimeStatus: 'bootstrapping' | 'ready' | 'failed';
|
|
233
|
+
/** 失败后重连(清会话态 → 重走 init 流程) */
|
|
234
|
+
retryBootstrap: (refreshAuth?: boolean) => Promise<void>;
|
|
235
|
+
/**
|
|
236
|
+
* 会话凭证升级(session.credentials-refreshed effect 宿主侧执行):
|
|
237
|
+
* 清缓存冷登拿新 credentials → 幂等重连(身份变更经锚复位恒 NEW——防跨用户续接)。
|
|
238
|
+
* 守卫:in-flight 防重入;生成中排队(mid-run 重连丢流式事件),run 跃迁排水。
|
|
239
|
+
*/
|
|
240
|
+
scheduleSessionUpgrade: () => Promise<void>;
|
|
241
|
+
}
|
|
242
|
+
type CoreChatStore<T = CoreChatState> = UseBoundStore<StoreApi<T>>;
|
|
243
|
+
/** 011 I2:HELLO_ACK 状态形状(chat-store 单一消费点) */
|
|
244
|
+
interface HelloAckState {
|
|
245
|
+
mallScope?: Record<string, string>;
|
|
246
|
+
chatConfig: HelloAckChatConfig;
|
|
247
|
+
/** 018 tool-debug:工具调试视图旗标(服务端 env 单源;仅开启时在场) */
|
|
248
|
+
toolDebug?: boolean;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* 创建核心状态对象(含方法),供 app 侧在同一个 create() 中合并使用。
|
|
252
|
+
* 内部所有 set/get 闭包绑定到 app 侧 create 的同一个 store 实例。
|
|
253
|
+
*/
|
|
254
|
+
declare function createCoreChatState(deps: CoreChatStoreDeps, set: {
|
|
255
|
+
(partial: Partial<CoreChatState>): void;
|
|
256
|
+
(updater: (state: CoreChatState) => Partial<CoreChatState>): void;
|
|
257
|
+
}, get: () => CoreChatState): CoreChatState;
|
|
258
|
+
/**
|
|
259
|
+
* 凭证升级排水订阅(W2-2):生成中排队的升级在 isGenerating true→false 跃迁时执行
|
|
260
|
+
* (订阅模块级双标志;scheduleSessionUpgrade in-flight/排队守卫防重入重放)。
|
|
261
|
+
* 应用在 create() 之后调用一次(返回退订函数;mall 模板薄壳装配位)。
|
|
262
|
+
*/
|
|
263
|
+
declare function subscribeSessionUpgradeDrain(store: CoreChatStore): () => void;
|
|
264
|
+
|
|
265
|
+
/** 条目动作草稿(与 TIER1 ActionDraft 结构兼容;SDK 不依赖 contracts-mall,取其结构子集) */
|
|
266
|
+
interface ItemActionDraft {
|
|
267
|
+
kind: 'send' | 'link';
|
|
268
|
+
label: string;
|
|
269
|
+
/** send:发给服务端 AI 的完整指令(可含隐藏上下文如 merchantId);缺省用 label */
|
|
270
|
+
value?: string;
|
|
271
|
+
/** send:用户气泡显示的文本;有值时实际发送走 visible=false 隐藏,再补一条 visible=true 的 visibleValue;
|
|
272
|
+
* 缺省则 visible=true 直接展示 value/label */
|
|
273
|
+
visibleValue?: string;
|
|
274
|
+
/** link:目标 URL */
|
|
275
|
+
url?: string;
|
|
276
|
+
}
|
|
277
|
+
declare function ItemActionsRow(props: {
|
|
278
|
+
actions: readonly ItemActionDraft[];
|
|
279
|
+
cardId: string;
|
|
280
|
+
isHistory: boolean;
|
|
281
|
+
/** 域皮肤类(如 member-item-actions) */
|
|
282
|
+
className?: string;
|
|
283
|
+
/** 域 chip 类(如 member-chip) */
|
|
284
|
+
chipClassName?: string;
|
|
285
|
+
}): react.JSX.Element | null;
|
|
286
|
+
|
|
287
|
+
type EventHandler = (evt: any) => void;
|
|
288
|
+
type OpenHandler = () => void;
|
|
289
|
+
type TokenGetter = () => string | null;
|
|
290
|
+
/** 注入保活配置(默认启用 25s 探测 / 75s 判死重连;intervalMs 下限 100ms) */
|
|
291
|
+
declare function configureWsKeepalive(opts: {
|
|
292
|
+
enabled?: boolean;
|
|
293
|
+
intervalMs?: number;
|
|
294
|
+
}): void;
|
|
295
|
+
/** 注入 WS URL 解析函数(由 app 侧提供) */
|
|
296
|
+
declare function configureWsUrl(fn: () => string | null): void;
|
|
297
|
+
/** 注入鉴权 token getter(auth-client 调用;HELLO 时读取) */
|
|
298
|
+
declare function configureAuthToken(fn: TokenGetter): void;
|
|
299
|
+
/** 读取当前鉴权 token(HELLO 构造用) */
|
|
300
|
+
declare function getAuthToken(): string | null;
|
|
301
|
+
type WsAuthErrorHandler = () => Promise<unknown>;
|
|
302
|
+
declare function configureWsAuthErrorHandler(fn: WsAuthErrorHandler): void;
|
|
303
|
+
interface WsFatalClose {
|
|
304
|
+
code: number;
|
|
305
|
+
message: string;
|
|
306
|
+
}
|
|
307
|
+
declare function configureWsFatalCloseHandler(fn: ((fatal: WsFatalClose) => void) | null): void;
|
|
308
|
+
declare function connect(onMessage: EventHandler, onOpen?: OpenHandler): void;
|
|
309
|
+
declare function send(msg: object): void;
|
|
310
|
+
/** 连接真实态(严格 OPEN):发送咽喉守卫唯一信号;CONNECTING/CLOSED 均不可发 */
|
|
311
|
+
declare function isWsOpen(): boolean;
|
|
312
|
+
/**
|
|
313
|
+
* 轮询等待 socket 进入 OPEN(升级后重放守卫)。
|
|
314
|
+
* send() 非 OPEN 时静默丢弃、initConversation 不等连接建立即置 ready——重放前
|
|
315
|
+
* 必须确认可写,否则消息无声丢失。纯查询语义:永不 reject,超时 resolve(false),
|
|
316
|
+
* 放弃重放与否由调用方决策(守卫超时放弃不算静默失败的前提)。
|
|
317
|
+
*/
|
|
318
|
+
declare function waitForSocketOpen(timeoutMs: number, intervalMs?: number): Promise<boolean>;
|
|
319
|
+
declare function disconnect(): void;
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* preTool 历史记录防御解析(B1 单路径——reminder 不随 query 持久化,无 B2 数据源):
|
|
323
|
+
* 平台存储数据形态不可全信(路由层已按 length 过滤,此处再挡非数组/非法 toolId)。
|
|
324
|
+
*/
|
|
325
|
+
declare function resolveHistoryPreTools(info: {
|
|
326
|
+
preTools?: PreToolRecord[];
|
|
327
|
+
}): PreToolRecord[];
|
|
328
|
+
/** 历史消息组合解析(协议/转换层):skills + preTools 合并去重(by name / by toolName) */
|
|
329
|
+
declare function resolveHistoryMessage(info: {
|
|
330
|
+
query?: string;
|
|
331
|
+
skills?: SkillRecord[];
|
|
332
|
+
preTools?: PreToolRecord[];
|
|
333
|
+
}): {
|
|
334
|
+
content: string;
|
|
335
|
+
skills: SkillRecord[];
|
|
336
|
+
preTools: PreToolRecord[];
|
|
337
|
+
};
|
|
338
|
+
/**
|
|
339
|
+
* preTool 历史表达重建(表达层):PreToolRecord[] → ToolCallInfo[],
|
|
340
|
+
* 复用 contracts buildPreToolToolCallPayload(与实时合成同构);
|
|
341
|
+
* toolId 防御校验 + messageId 必传(调用方保证唯一,React key / 展开状态依赖)。
|
|
342
|
+
*/
|
|
343
|
+
declare function buildPreToolToolCalls(preTools: PreToolRecord[], messageId: string): ToolCallInfo[];
|
|
344
|
+
/**
|
|
345
|
+
* 历史消息组合表达(表达层,模板唯一入口):
|
|
346
|
+
* preTools 先行、skills 随后(确定性顺序,对齐实时事件序);空集 → []。
|
|
347
|
+
*/
|
|
348
|
+
declare function buildHistoryToolCalls(resolved: {
|
|
349
|
+
skills: SkillRecord[];
|
|
350
|
+
preTools: PreToolRecord[];
|
|
351
|
+
}, messageId: string): ToolCallInfo[];
|
|
352
|
+
/**
|
|
353
|
+
* 平台历史消息原始形状(/api/conversation/messages 透传 + 路由层 cards/skills/preTools 附加)。
|
|
354
|
+
* 字段漂移防御:平台实测 ID 字段为 id(messageId 不返回);时间真源为 requestTime(createTime 恒空)。
|
|
355
|
+
*/
|
|
356
|
+
interface PlatformHistoryMessage {
|
|
357
|
+
messageId?: string;
|
|
358
|
+
id?: string;
|
|
359
|
+
query?: string;
|
|
360
|
+
answer?: string;
|
|
361
|
+
content?: string;
|
|
362
|
+
outerBusinessId?: string;
|
|
363
|
+
createTime?: number;
|
|
364
|
+
requestTime?: string;
|
|
365
|
+
multiModalInputs?: Record<string, unknown>;
|
|
366
|
+
/** 服务端已恢复(migrate + isHistory=true)的卡片快照 */
|
|
367
|
+
cards?: CardSnapshot[];
|
|
368
|
+
/** 服务端已恢复的 skill 加载记录(inputs.skills) */
|
|
369
|
+
skills?: SkillRecord[];
|
|
370
|
+
/** 服务端已恢复的前置工具记录(inputs.preTools) */
|
|
371
|
+
preTools?: PreToolRecord[];
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* 历史条目归一:稳定 key(messageId → id → outerBusinessId → 序列兜底)+ 时间戳
|
|
375
|
+
* (createTime → requestTime 解析 → Date.now 兜底)。
|
|
376
|
+
* key 是消息 id / 卡片桶键 / toolCalls 重建锚的同一派生源(构造性一致)。
|
|
377
|
+
*/
|
|
378
|
+
declare function resolveHistoryMeta(info: PlatformHistoryMessage, seq: number): {
|
|
379
|
+
key: string;
|
|
380
|
+
createdAt: number;
|
|
381
|
+
};
|
|
382
|
+
/**
|
|
383
|
+
* 平台历史列表 → 消息对 + 历史卡片桶(双模板唯一转换真源;调用方负责 filter+reverse)。
|
|
384
|
+
* 语义逐条继承模板原实现:query→user 行;answer/content/纯标签卡轮→assistant 行;
|
|
385
|
+
* skills/preTools→toolCalls 挂 assistant;requestId=outerBusinessId;图片恢复 multiModalInputs.imageUrls。
|
|
386
|
+
* 同一条目的 assistant 消息 id 与卡片桶键恒等(`history-asst-${key}`)。
|
|
387
|
+
*/
|
|
388
|
+
declare function convertHistoryMessages(list: PlatformHistoryMessage[]): {
|
|
389
|
+
messages: ChatMessage[];
|
|
390
|
+
cardsByMessageId: Record<string, TboxCardPayload[]>;
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
interface CallActionResult {
|
|
394
|
+
success: boolean;
|
|
395
|
+
data?: unknown;
|
|
396
|
+
error?: string;
|
|
397
|
+
/** 幂等命中缓存结果标记 */
|
|
398
|
+
cached?: boolean;
|
|
399
|
+
}
|
|
400
|
+
interface CallActionOptions {
|
|
401
|
+
idempotencyKey?: string;
|
|
402
|
+
sessionId?: string;
|
|
403
|
+
/** API base(默认同源 '') */
|
|
404
|
+
baseUrl?: string;
|
|
405
|
+
/** 鉴权 token(006 D7:Authorization: Bearer;缺省经 configureAuthToken 注入) */
|
|
406
|
+
token?: string;
|
|
407
|
+
}
|
|
408
|
+
/** 注入鉴权 token getter(auth-client 调用;与 ws.ts 共享同一 getter 语义) */
|
|
409
|
+
declare function setActionAuthTokenGetter(fn: () => string | null): void;
|
|
410
|
+
declare function callAction(action: string, payload: unknown, opts?: CallActionOptions): Promise<CallActionResult>;
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* CardStore:客户端卡片管理机制。
|
|
414
|
+
* 提供卡片 upsert / hide / 查询能力,供 UI 组件使用。
|
|
415
|
+
*/
|
|
416
|
+
|
|
417
|
+
interface CardStoreState {
|
|
418
|
+
/** 卡片:按 messageId 分桶(关联到 assistant 气泡下渲染) */
|
|
419
|
+
cardsByMessageId: Record<string, TboxCardPayload[]>;
|
|
420
|
+
/** 不挂消息的卡片(业务旁路推送,如欢迎卡) */
|
|
421
|
+
orphanCards: TboxCardPayload[];
|
|
422
|
+
}
|
|
423
|
+
declare function upsertCard(state: CardStoreState, card: TboxCardPayload): CardStoreState;
|
|
424
|
+
declare function hideCard(state: CardStoreState, cardId: string): CardStoreState;
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* CardActionHandler:卡片纯客户端动作分发(003 §3.3 / §4.2)。
|
|
428
|
+
* - 写通道(sendMessage / submitForm / moduleAction)→ sendToServer(WS UI_ACTION)
|
|
429
|
+
* - 本地通道(openLink / toast / closeCard)→ 注入副作用
|
|
430
|
+
* 从 chat-store 的 sendUiAction 抽离独立化,app 可复用同分发语义。
|
|
431
|
+
*
|
|
432
|
+
* 门控:
|
|
433
|
+
* - canAct:写通道(sendMessage/submitForm/moduleAction)门控——生成中与历史恢复卡(isHistory=true)
|
|
434
|
+
* 均在此拦截;本地通道(openLink/toast/closeCard)不受其约束(历史卡只读浏览口径,
|
|
435
|
+
* 见 Agent Note 2026-09-02-history-card-readonly-interactions)。
|
|
436
|
+
* - blockAll:可选的应用级总闸(返回 true 丢弃一切动作);SDK chat-store 缺省不注入——
|
|
437
|
+
* 原 B2「历史卡全禁」已收窄为只禁写通道。
|
|
438
|
+
*
|
|
439
|
+
* 修复(P2 顺带,既有缺口):moduleAction 此前无分支被静默丢弃——卡按钮 token 点击链路
|
|
440
|
+
* (CardRenderer 默认按钮条/模块 sendCardAction)经 sendUiAction 全部断裂。
|
|
441
|
+
*/
|
|
442
|
+
interface CardActionHandlerDeps {
|
|
443
|
+
/** 发送到服务端(WS UI_ACTION 载荷;surfaceId 为卡片实例 id,formData 仅 submitForm 携带) */
|
|
444
|
+
sendToServer: (surfaceId: string, action: UiAction, formData?: Record<string, unknown>) => void;
|
|
445
|
+
/** 生成中门控:返回 false 时丢弃 sendMessage/submitForm */
|
|
446
|
+
canAct?: (surfaceId: string) => boolean;
|
|
447
|
+
/** 应用级总闸(可选):返回 true 时丢弃一切动作(含 openLink/toast/closeCard);SDK 缺省不注入 */
|
|
448
|
+
blockAll?: (surfaceId: string) => boolean;
|
|
449
|
+
/** 本地副作用 */
|
|
450
|
+
toast?: (content: string, tone?: 'info' | 'success' | 'error') => void;
|
|
451
|
+
openUrl?: (url: string) => void;
|
|
452
|
+
closeCard?: (cardId: string) => void;
|
|
453
|
+
}
|
|
454
|
+
type CardActionHandler = (surfaceId: string, action: UiAction, formData?: Record<string, unknown>) => void;
|
|
455
|
+
declare function createCardActionHandler(deps: CardActionHandlerDeps): CardActionHandler;
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* 宿主效果执行器(015 P4):tbox:effect CUSTOM 事件 → 宿主能力调用 → 结果回传。
|
|
459
|
+
*
|
|
460
|
+
* 分发表(E1 闭合):
|
|
461
|
+
* - payment → requestPayment(9000→success / 6001→cancel / 其他→failure / 桥不可用→unavailable)
|
|
462
|
+
* - authorization.alipay-user-profile → getAuthCode(成功→success+authCode / CANCEL→cancel / 空码→failure / 桥不可用→unavailable)
|
|
463
|
+
* - session.credentials-refreshed → onSessionCredentialsRefreshed 通知钩子(宿主清缓存重登重连;无回传)
|
|
464
|
+
* - navigation.open / share / download / 未知类型 → no-op + warn(F4:不 throw 不回传)
|
|
465
|
+
*
|
|
466
|
+
* 回传:仅 effect.callback 存在时经 sendModuleAction(UI_ACTION moduleAction 通道,零新协议),
|
|
467
|
+
* formData = HostEffectResult(服务端回调动作 inputSchema 校验)。
|
|
468
|
+
*
|
|
469
|
+
* DI 可测:deps 全注入(V1/G8——chat-store 不 import jsapi,单测零桥依赖)。
|
|
470
|
+
* 缺省 deps = bridge-aware:支付宝壳内走 jsapi;浏览器 → unavailable。
|
|
471
|
+
*/
|
|
472
|
+
|
|
473
|
+
interface HostEffectRunnerDeps {
|
|
474
|
+
/** 收银台拉起(my.tradePay) */
|
|
475
|
+
requestPayment(orderInfo: string): Promise<{
|
|
476
|
+
resultCode?: string;
|
|
477
|
+
}>;
|
|
478
|
+
/** 支付宝用户授权码获取 */
|
|
479
|
+
getAuthCode(scopes: string[]): Promise<{
|
|
480
|
+
authCode: string;
|
|
481
|
+
}>;
|
|
482
|
+
/** 结果回传(UI_ACTION moduleAction;缺省走 ws send) */
|
|
483
|
+
sendModuleAction(input: {
|
|
484
|
+
actionId: string;
|
|
485
|
+
contextToken: string;
|
|
486
|
+
formData: Record<string, unknown>;
|
|
487
|
+
}): void;
|
|
488
|
+
/**
|
|
489
|
+
* 会话凭证升级通知(session.credentials-refreshed 通知型效果宿主钩子,可选注入):
|
|
490
|
+
* 宿主执行清缓存重登 + 重连拿新 credentials;无桥交互、无结果回传,幂等由宿主守卫承担
|
|
491
|
+
* (去重器对无 contextToken 恒放行)。
|
|
492
|
+
*/
|
|
493
|
+
onSessionCredentialsRefreshed?: (reason?: string) => void;
|
|
494
|
+
}
|
|
495
|
+
/** 最小形状守卫(chat-store 侧过滤非 effect 值) */
|
|
496
|
+
declare function isHostEffect(v: unknown): v is HostEffect;
|
|
497
|
+
/**
|
|
498
|
+
* contextToken 去重器(G7:同 token 二至不执行——防历史重放/重复注入)。
|
|
499
|
+
* 纯闭包,chat-store 每实例一份。
|
|
500
|
+
*/
|
|
501
|
+
declare function createEffectTokenDeduper(): (effect: HostEffect) => boolean;
|
|
502
|
+
/** tradePay resultCode → 结果状态 */
|
|
503
|
+
declare function mapPaymentResultCode(resultCode: string | undefined): 'success' | 'cancel' | 'failure';
|
|
504
|
+
/** payment 效果执行(纯函数化核心,G8 单测锚点) */
|
|
505
|
+
declare function executePaymentEffect(effect: Extract<HostEffect, {
|
|
506
|
+
type: 'payment';
|
|
507
|
+
}>, deps: Pick<HostEffectRunnerDeps, 'requestPayment'>): Promise<HostEffectResult>;
|
|
508
|
+
/** authorization 效果执行(纯函数化核心) */
|
|
509
|
+
declare function executeAuthorizationEffect(effect: Extract<HostEffect, {
|
|
510
|
+
type: 'authorization.alipay-user-profile';
|
|
511
|
+
}>, deps: Pick<HostEffectRunnerDeps, 'getAuthCode'>): Promise<HostEffectResult>;
|
|
512
|
+
declare function createHostEffectRunner(depsOverride?: Partial<HostEffectRunnerDeps>): (effect: HostEffect) => void;
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* CardHistoryLoader:客户端历史快照加载 + 恢复(001 §8.1 / 003 §4.2)。
|
|
516
|
+
* P2 提供接口 + 内存实现与 restoreCardSnapshot(isHistory 感知)。
|
|
517
|
+
* 完整 schema 迁移(meta.migrate)在服务端 restoreCardSnapshot 执行(P3 完整持久化)。
|
|
518
|
+
* 006 评审 P2-1:`sessionId` 键语义 = 会话键(conversationId,006 D5);接口名保留兼容。
|
|
519
|
+
*/
|
|
520
|
+
interface CardHistoryLoader {
|
|
521
|
+
load(sessionId: string): Promise<TboxCardPayload[]>;
|
|
522
|
+
}
|
|
523
|
+
interface CardHistoryStore {
|
|
524
|
+
save(sessionId: string, card: TboxCardPayload): Promise<void>;
|
|
525
|
+
load(sessionId: string): Promise<CardSnapshot[]>;
|
|
526
|
+
clear(sessionId: string): Promise<void>;
|
|
527
|
+
}
|
|
528
|
+
declare class InMemoryCardHistoryLoader implements CardHistoryLoader {
|
|
529
|
+
private snapshots;
|
|
530
|
+
save(sessionId: string, card: TboxCardPayload): Promise<void>;
|
|
531
|
+
load(sessionId: string): Promise<TboxCardPayload[]>;
|
|
532
|
+
clear(sessionId: string): Promise<void>;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* 构建平台能力适配器(client 平台轴)。
|
|
537
|
+
* - 依赖 session 的适配器(asr / upload / feedback 均走同源 server 代理——
|
|
538
|
+
* 2026-09 内化收口后 C端能力统一同源收口,client 树零上游 SDK 依赖);
|
|
539
|
+
* - TTS 不依赖 session(走 server /ws/tts 代理),恒可用;
|
|
540
|
+
* - 无 session(未获取能力凭证,如 TTS-only 场景)→ 只返回 { tts }(A6);
|
|
541
|
+
* - overrides:按能力覆盖默认实现(自定义 ASR/TTS 等,UI 零改动)。
|
|
542
|
+
*/
|
|
543
|
+
declare function createPlatformAdapters(session?: TboxSessionData, overrides?: Partial<Adapters>): Adapters;
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* 流式 TTS(原生 WebSocket → server /ws/tts 代理 → PCM → WebAudio 播放)。
|
|
547
|
+
* 从原 Taro 版移植:去 Taro/小程序,连接用原生 WebSocket,播放用纯 WebAudio AudioStreamPlayer。
|
|
548
|
+
* 对外实现 ITTSAdapter(speak/pause/resume/stop/onStateChange)。
|
|
549
|
+
*/
|
|
550
|
+
|
|
551
|
+
type TTSState = 'idle' | 'loading' | 'playing' | 'paused';
|
|
552
|
+
declare class TTSAdapter implements ITTSAdapter {
|
|
553
|
+
private socket;
|
|
554
|
+
private player;
|
|
555
|
+
private _state;
|
|
556
|
+
private _canSend;
|
|
557
|
+
private pending;
|
|
558
|
+
private pendingFinish;
|
|
559
|
+
private stateCallbacks;
|
|
560
|
+
private speakResolve;
|
|
561
|
+
/** 一次性朗读整段(内部走流式 + 缓冲,canSend 前的文本会被缓存)。 */
|
|
562
|
+
speak(text: string): Promise<void>;
|
|
563
|
+
pause(): void;
|
|
564
|
+
resume(): void;
|
|
565
|
+
stop(): void;
|
|
566
|
+
onStateChange(cb: (state: TTSState) => void): () => void;
|
|
567
|
+
startStreaming(): Promise<void>;
|
|
568
|
+
/** 送一段文本;start_ack 前先缓冲,握手完成后自动 flush。 */
|
|
569
|
+
sendText(text: string): void;
|
|
570
|
+
/** 结束这轮;若还没 canSend,标记待结束,握手完成后再发。 */
|
|
571
|
+
finishStreaming(): void;
|
|
572
|
+
private flushPending;
|
|
573
|
+
private handleMessage;
|
|
574
|
+
private sendMessage;
|
|
575
|
+
private closeSocket;
|
|
576
|
+
private setState;
|
|
577
|
+
private resolveSpeak;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* 浏览器录音 + ASR(getUserMedia + MediaRecorder → webm → 同源 server 代理 /api/tbox/asr)。
|
|
582
|
+
* 注意:支付宝 web-view 里 getUserMedia/MediaRecorder 可能受限,录音改走桥(见 alipay/asr)。
|
|
583
|
+
* 2026-09 内化收口:直连 o.tbox.cn 被 CORS preflight 拦截(应用域名 ≠ 平台域名 +
|
|
584
|
+
* 自定义鉴权头)——改走同源中继,session 三头透传(upload adapter 先例同构)。
|
|
585
|
+
* 失败语义保持原直连版:静默返回 ''(不 throw,上层表现为空文本)。
|
|
586
|
+
*/
|
|
587
|
+
declare class BrowserASRAdapter implements IASRAdapter {
|
|
588
|
+
private session;
|
|
589
|
+
private mediaRecorder;
|
|
590
|
+
private mediaStream;
|
|
591
|
+
private audioChunks;
|
|
592
|
+
private cancelled;
|
|
593
|
+
constructor(session: TboxSessionData);
|
|
594
|
+
checkPermission(): Promise<boolean>;
|
|
595
|
+
startRecording(): Promise<void>;
|
|
596
|
+
stopRecording(): Promise<string>;
|
|
597
|
+
cancelRecording(): void;
|
|
598
|
+
private blobToBase64;
|
|
599
|
+
private cleanup;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* 上传适配器(web:File/Blob/dataURL → 同源 server 代理 → fileId)。
|
|
604
|
+
* 浏览器直连 o.tbox.cn 被跨域 preflight 拦截(应用域名 ≠ 平台域名 + 自定义鉴权头),
|
|
605
|
+
* 改走 /api/tbox/upload 中继(2026-09-06);session 头透传——身份语义与直连等价,
|
|
606
|
+
* 过期重试链复用 isSessionExpiredError → refreshSession 既有机制(错误文本必含 errorCode)。
|
|
607
|
+
* 文件来源差异(壳内桥 chooseImage 拿 base64 → Blob)在媒体选取层处理,本适配器只管 Blob→fileId。
|
|
608
|
+
*/
|
|
609
|
+
declare class UploadAdapter implements IUploadAdapter {
|
|
610
|
+
private session;
|
|
611
|
+
onProgress?: UploadProgressCallback;
|
|
612
|
+
constructor(session: TboxSessionData);
|
|
613
|
+
upload(file: File | string, options?: UploadOptions): Promise<UploadResult>;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* 反馈适配器(web-native,浏览器/支付宝壳通用)。
|
|
618
|
+
* 2026-09 内化收口:直连 o.tbox.cn 改同源代理 /api/tbox/feedback(CORS preflight 拦截),
|
|
619
|
+
* session 三头透传;失败语义保持原直连版 { success: false }(不 throw)。
|
|
620
|
+
*/
|
|
621
|
+
declare class FeedbackAdapter implements IFeedbackAdapter {
|
|
622
|
+
private session;
|
|
623
|
+
constructor(session: TboxSessionData);
|
|
624
|
+
addTag(requestId: string, feedback: 'LIKE' | 'DISLIKE' | 'CANCEL'): Promise<{
|
|
625
|
+
success: boolean;
|
|
626
|
+
}>;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* 支付宝壳里的录音 ASR:录音走桥(壳子原生 RecorderManager),识别走同源代理 /api/tbox/asr。
|
|
631
|
+
* 壳子 startRecord/stopRecord 返回 base64 mp3(见 agent-chat-h5-template-alipay/recorder-handler.ts)。
|
|
632
|
+
* 2026-09 内化收口:直连改同源中继(CORS preflight 拦截),session 三头透传。
|
|
633
|
+
*/
|
|
634
|
+
|
|
635
|
+
declare class AlipayASRAdapter implements IASRAdapter {
|
|
636
|
+
private session;
|
|
637
|
+
private cancelled;
|
|
638
|
+
constructor(session: TboxSessionData);
|
|
639
|
+
checkPermission(): Promise<boolean>;
|
|
640
|
+
startRecording(): Promise<void>;
|
|
641
|
+
stopRecording(): Promise<string>;
|
|
642
|
+
cancelRecording(): void;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* 壳内 ASR 降级链:优先走桥调壳子原生 RecorderManager(AlipayASRAdapter),
|
|
647
|
+
* 桥录音**只要失败**就降级到纯 H5 的 BrowserASRAdapter(getUserMedia)。
|
|
648
|
+
*
|
|
649
|
+
* 设计:不依赖壳子回传的错误码。
|
|
650
|
+
* 壳子是外部仓库、版本不可控(不同壳产的 code 不一样、甚至没有),所以不去区分
|
|
651
|
+
* 「基础设施错误 vs 权限/瞬时错误」——只认「我们这次调桥失败了」这个事实,直接试 H5。
|
|
652
|
+
* 取舍:权限被拒时会多走一次必然快速失败的 H5 尝试再报错;但支付宝 webview 里
|
|
653
|
+
* getUserMedia 通常不可用、秒失败,不卡,且最终冒泡的是 H5 引擎错误(NotAllowedError 等
|
|
654
|
+
* name 可靠),反而让上层 toast 分类更准。
|
|
655
|
+
*
|
|
656
|
+
* 注:非 sticky——每次按压都先试桥。生产环境壳子正常时降级永不触发;它只是
|
|
657
|
+
* 灰度期旧壳 / isInAlipayShell 误判等边缘场景的安全网(iOS 下 H5 getUserMedia 多半也不行)。
|
|
658
|
+
*/
|
|
659
|
+
|
|
660
|
+
declare class FallbackASRAdapter implements IASRAdapter {
|
|
661
|
+
private bridge;
|
|
662
|
+
private browser;
|
|
663
|
+
/** 本次录音实际使用的引擎,stop/cancel 必须路由到同一个 */
|
|
664
|
+
private active;
|
|
665
|
+
constructor(session: TboxSessionData);
|
|
666
|
+
checkPermission(): Promise<boolean>;
|
|
667
|
+
startRecording(): Promise<void>;
|
|
668
|
+
stopRecording(): Promise<string>;
|
|
669
|
+
cancelRecording(): void;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* 实时 PCM 流式播放器(纯 WebAudio,去掉了原 Taro/小程序 的 WAV+InnerAudio 兜底)。
|
|
674
|
+
* 收 16k/16bit 单声道 PCM(ArrayBuffer),逐块无缝排程播放。
|
|
675
|
+
*/
|
|
676
|
+
declare class AudioStreamPlayer {
|
|
677
|
+
private sampleRate;
|
|
678
|
+
private isStopped;
|
|
679
|
+
private onPlayEnd?;
|
|
680
|
+
private audioContext;
|
|
681
|
+
private nextStartTime;
|
|
682
|
+
private inputCount;
|
|
683
|
+
private endedCount;
|
|
684
|
+
private totalInputCount;
|
|
685
|
+
private fallbackTimer;
|
|
686
|
+
private playEndFired;
|
|
687
|
+
constructor(options: {
|
|
688
|
+
sampleRate: number;
|
|
689
|
+
onPlayEnd?: () => void;
|
|
690
|
+
});
|
|
691
|
+
start(): void;
|
|
692
|
+
input(pcmData: ArrayBuffer): void;
|
|
693
|
+
finish(): void;
|
|
694
|
+
stop(): void;
|
|
695
|
+
pause(): Promise<void>;
|
|
696
|
+
resume(): Promise<void>;
|
|
697
|
+
private checkPlayEnd;
|
|
698
|
+
private firePlayEnd;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* 历史消息 skill 解析(协议/转换层):
|
|
703
|
+
* - B1 优先(SkillRecord 含 description);B2 补齐缺失 name(SkillRecord {name});
|
|
704
|
+
* - content = B2 剥离 reminder 后的展示文本(无 reminder 时原样);
|
|
705
|
+
* - 无 query 无 skills → { content: '', skills: [] }(纯 assistant 消息等)。
|
|
706
|
+
*/
|
|
707
|
+
declare function resolveHistorySkills(info: {
|
|
708
|
+
query?: string;
|
|
709
|
+
skills?: SkillRecord[];
|
|
710
|
+
}): {
|
|
711
|
+
content: string;
|
|
712
|
+
skills: SkillRecord[];
|
|
713
|
+
};
|
|
714
|
+
/**
|
|
715
|
+
* 历史 skill 加载表达(表达层):
|
|
716
|
+
* - args/result 复用 contracts buildSkillToolCallPayload(与实时合成事件同构契约);
|
|
717
|
+
* - name 防御校验(平台存储数据不可全信,不合法跳过);
|
|
718
|
+
* - messageId 必传(调用方保证唯一,React key / 展开状态依赖)。
|
|
719
|
+
*/
|
|
720
|
+
declare function buildSkillToolCalls(skills: SkillRecord[], messageId: string): ToolCallInfo[];
|
|
721
|
+
|
|
722
|
+
/** 窗口 wrapper:读 + history.replaceState 剥离(幂等;无效值零副作用;SSR 安全) */
|
|
723
|
+
declare function consumeAutoSendQuery(): string | null;
|
|
724
|
+
/** 编排:等 socket OPEN → 移交 send(isGenerating/isWsOpen 判定归 sendMessage 咽喉) */
|
|
725
|
+
declare function runAutoSend(text: string, send: (text: string) => void, timeoutMs?: number): Promise<void>;
|
|
726
|
+
|
|
727
|
+
export { type AdapterContextValue, AdapterProvider, AlipayASRAdapter, AudioStreamPlayer, AuthClient, BrowserASRAdapter, type CallActionOptions, type CallActionResult, type CardActionHandler, type CardActionHandlerDeps, type CardEventData, type CardHistoryLoader, type CardHistoryStore, type CardStoreState, type ChatInputMessage, type ChatMessage, type CoreChatState, type CoreChatStore, type CoreChatStoreDeps, FallbackASRAdapter, FeedbackAdapter, type HostEffectRunnerDeps, InMemoryCardHistoryLoader, type InputMethod, type ItemActionDraft, ItemActionsRow, type ListMessagesOptions, type ListMessagesResult, type MediaItem, type MediaType, type MessageRole, type MessageStatus, type PlatformApi, type PlatformHistoryMessage, TTSAdapter, type ToastFunction, type ToolCallInfo, UploadAdapter, type WsUrlResolver, bindCardActionChannel, buildHistoryToolCalls, buildPreToolToolCalls, buildSkillToolCalls, callAction, configureHelloContext, configureWsAuthErrorHandler, configureAuthToken as configureWsAuthToken, configureWsFatalCloseHandler, configureWsKeepalive, configureWsUrl, connect, consumeAutoSendQuery, convertHistoryMessages, createCardActionHandler, createCoreChatState, createEffectTokenDeduper, createHostEffectRunner, createPlatformAdapters, createPlatformApi, disconnect, executeAuthorizationEffect, executePaymentEffect, getAuthToken, hideCard, isHostEffect, isSessionExpiredError, isWsOpen, mapPaymentResultCode, resolveHistoryMessage, resolveHistoryMeta, resolveHistoryPreTools, resolveHistorySkills, runAutoSend, send, sendCardAction, setActionAuthTokenGetter, subscribeSessionUpgradeDrain, upsertCard, useASRAdapter, useAdapters, useFeedbackAdapter, useRefreshSession, useTTSAdapter, useUploadAdapter, waitForSocketOpen };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{A as kt,B as Rt,C as Pt,D as $,E as wt,F as U,G as It,H as Et,I as _t,a as ot,b as st,c as D,d as F,e as O,f as it,g as nt,h as at,i as lt,j as L,k as pt,l as dt,m as ct,n as ut,o as ht,p as ft,q as mt,r as yt,s as gt,t as St,u as xt,v as At,w as bt,x as Tt,y as Ct,z as vt}from"../chunk-RQWCUVXR.js";export*from"@tbox.cn/app-sdk-client";import{jsx as z}from"react/jsx-runtime";function Ut(r){let{actions:t,cardId:e,isHistory:o,className:s,chipClassName:i}=r;return!t||t.length===0?null:z("div",{className:`tbox-item-actions ${s??""}`.trim(),children:t.map((n,a)=>z("button",{type:"button",className:`tbox-chip ${i??""}`.trim(),disabled:o&&n.kind==="send",onClick:()=>{n.kind==="send"?U(e,{type:"sendMessage",value:n.value??n.label,...n.visibleValue?{displayText:n.visibleValue}:{},visible:!0}):U(e,{type:"openLink",url:n.url??""})},children:n.label},a))})}import{createContext as Mt,useContext as A}from"react";import{jsx as Bt}from"react/jsx-runtime";var y=Mt({});function Ht({value:r,children:t}){return Bt(y.Provider,{value:r,children:t})}var Dt=()=>A(y),Ft=()=>A(y).asr,Ot=()=>A(y).tts,Lt=()=>A(y).upload,$t=()=>A(y).feedback,Kt=()=>A(y).refreshSession;import{createClientLogger as Nt}from"@tbox.cn/app-sdk-client";var K=Nt("ACTION"),Wt=0;function Gt(r){return`${r}_${Date.now()}_${Wt++}_${Math.random().toString(36).slice(2,8)}`}var B=null;function N(r){B=r}async function Vt(r,t,e={}){let o=e.baseUrl??"",s={action:r,payload:t,...e.sessionId&&{sessionId:e.sessionId},...e.idempotencyKey&&{idempotencyKey:e.idempotencyKey}},i=s.idempotencyKey?s:{...s,idempotencyKey:Gt(r)},n=e.token??(B?B():null),a={"Content-Type":"application/json"};n&&(a.Authorization=`Bearer ${n}`);let d;try{d=await fetch(`${o}/api/action`,{method:"POST",headers:a,body:JSON.stringify(i)})}catch(T){let E=T?.message||"网络错误";return K.warn(`POST /api/action action=${r} fetch 异常:${E}`),{success:!1,error:E}}let h;try{h=await d.json()}catch{return K.warn(`POST /api/action action=${r} 响应解析失败(HTTP ${d.status})`),{success:!1,error:`响应解析失败(HTTP ${d.status})`}}return d.status===409?(K.warn(`POST /api/action action=${r} 幂等键冲突:${h.error||"请勿重复提交"}`),{success:!1,error:h.error||"幂等键冲突:请勿重复提交",cached:!1}):d.status===200&&h.cached?{...h,cached:!0}:h}function Xt(r){return{...r,data:r.data??{},isHistory:!0,status:r.status??"ready"}}var W=class{snapshots=new Map;async save(t,e){let o=this.snapshots.get(t)??[],s=o.findIndex(n=>n.id===e.id),i={id:e.id,cardType:e.cardType,data:e.data,status:e.status,schemaVersion:e.schemaVersion??1,meta:e.meta};s>=0?o[s]=i:o.push(i),this.snapshots.set(t,o)}async load(t){return(this.snapshots.get(t)??[]).map(o=>Xt(o))}async clear(t){this.snapshots.delete(t)}};import{isInAlipayShell as zt}from"@tbox.cn/app-sdk-client";import{getAuthCode as Jt}from"@tbox.cn/app-sdk-client";import{httpPost as J,httpGet as Yt,configureAuthToken as qt,configureAuthErrorHandler as Qt}from"@tbox.cn/app-sdk-client";import{createClientLogger as jt}from"@tbox.cn/app-sdk-client";var c=jt("AUTH"),g="tbox.auth.token",Y="tbox.auth.scopeKey",M="tbox.auth.lastToken",C=null,H=null;function q(r={}){let t=r.loginPlatform??"alipay";return C?(H&&H.loginPlatform!==t&&c.warn(`createAuthClient 重复调用且选项不同(当前 ${H.loginPlatform} vs ${t}),返回首建单例(首建选项生效)`),C):(C=te({loginPlatform:t,...r.scopeKey?{scopeKey:r.scopeKey}:{}}),H={loginPlatform:t},C)}function Zt(){return C??q()}function te(r={}){let t=r.loginPlatform??"alipay",e=r.scopeKey,o=null,s=null,i=null;function n(l){try{return typeof localStorage<"u"?localStorage.getItem(l):null}catch{return null}}function a(l,x){try{typeof localStorage<"u"&&localStorage.setItem(l,x)}catch{}}function d(l){try{typeof localStorage<"u"&&localStorage.removeItem(l)}catch{}}async function h(){return o||s||(s=et().then(l=>(s=null,l),l=>{throw s=null,l}),s)}async function T(){return i||(o=null,i=h().finally(()=>{i=null}),i)}function E(){o=null,s=null,d(g)}function tt(){return o}async function et(){if(e){let p;try{p=e()}catch{p=""}n(Y)!==p&&(d(g),d(M)),a(Y,p)}let l=n(g);if(l)try{let{status:p,data:f}=await Yt("/api/auth/me",{cache:"no-store"});if(p===200&&f?.identity)return c.info("identity from cached token:",f.identity.userId),_({token:l,identity:f.identity});c.warn("cached token invalid (status=%s), re-login",p),d(g)}catch(p){c.warn("/api/auth/me failed, re-login:",p.message),d(g)}let x=n(M);if(zt()){c.info("alipay shell, starting getAuthCode login...");try{let{authCode:p}=await Jt(["auth_base"]),f=$(),{status:u,data:m}=await J("/api/auth/login",{method:"code",code:p,platform:t,...f?{context:f}:{},...x?{previousToken:x}:{}});if(u===200&&m?.token&&m?.identity)return a(g,m.token),a(M,m.token),c.info("identity via alipay login:",m.identity.userId),_({token:m.token,identity:m.identity});c.warn("alipay login failed: status=%s error=%s",u,m?.error??"(no body)")}catch(p){c.error("alipay login error:",p)}return X()}try{let p=$(),{status:f,data:u}=await J("/api/auth/login",{method:"browser",...p?{context:p}:{},...x?{previousToken:x}:{}});if(f===200&&u?.token&&u?.identity)return a(g,u.token),a(M,u.token),c.info("identity via browser login:",u.identity.userId),_({token:u.token,identity:u.identity});c.warn("browser login unavailable: status=%s error=%s(403 = 服务端 browserLoginMode 关闭或生产禁 mock)",f,u?.error??"(no body)")}catch(p){c.warn("browser login error:",p.message)}return X()}function X(){let l={userId:`anonymous_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,source:"anonymous"};return c.warn("fallback to anonymous:",l.userId),_({token:null,identity:l})}function _(l){return o=l,qt(()=>o?.token??null),D(()=>o?.token??null),N(()=>o?.token??null),Qt(()=>T()),O(()=>T()),l}async function rt(){return(await h()).identity.userId}return{ensureAuth:h,refreshAuth:T,clearAuth:E,getAuthState:tt,resolveUserId:rt}}import{httpPost as ee}from"@tbox.cn/app-sdk-client";function b(r){return{"X-Tbox-Session-Id":r.tboxSessionId,"X-Tbox-Channel":r.channel,"X-Tbox-AppId":r.appId}}var S=class{constructor(t){this.session=t}session;mediaRecorder=null;mediaStream=null;audioChunks=[];cancelled=!1;async checkPermission(){try{return(await navigator.mediaDevices.getUserMedia({audio:!0})).getTracks().forEach(e=>e.stop()),!0}catch{return!1}}async startRecording(){this.cancelled=!1;let t=await navigator.mediaDevices.getUserMedia({audio:{sampleRate:16e3,channelCount:1,echoCancellation:!0,noiseSuppression:!0}});this.mediaStream=t,this.audioChunks=[];let e=MediaRecorder.isTypeSupported("audio/webm;codecs=opus")?"audio/webm;codecs=opus":"audio/webm",o=new MediaRecorder(t,{mimeType:e});this.mediaRecorder=o,o.ondataavailable=s=>{s.data.size>0&&this.audioChunks.push(s.data)},o.start()}stopRecording(){return new Promise((t,e)=>{let o=this.mediaRecorder;if(!o||o.state==="inactive"){this.cleanup(),t("");return}o.onstop=async()=>{if(this.cancelled){this.cleanup(),t("");return}try{let s=new Blob(this.audioChunks,{type:o.mimeType}),i=await this.blobToBase64(s),{status:n,data:a}=await ee("/api/tbox/asr",{base64_audio:i,audio_format:"webm",sample_rate:"16000"},{headers:b(this.session)});t(n===200&&a?.text?a.text:"")}catch(s){e(s)}finally{this.cleanup()}},o.stop()})}cancelRecording(){this.cancelled=!0,this.mediaRecorder?.stop(),this.cleanup()}blobToBase64(t){return new Promise((e,o)=>{let s=new FileReader;s.onloadend=()=>e(s.result.split(",")[1]||""),s.onerror=o,s.readAsDataURL(t)})}cleanup(){this.mediaStream?.getTracks().forEach(t=>t.stop()),this.mediaStream=null,this.mediaRecorder=null,this.audioChunks=[]}};import{callJsapi as G}from"@tbox.cn/app-sdk-client";import{httpPost as re}from"@tbox.cn/app-sdk-client";var v=class{constructor(t){this.session=t}session;cancelled=!1;async checkPermission(){return!0}async startRecording(){this.cancelled=!1,await G("startRecord",{},"invoke")}async stopRecording(){let t=await G("stopRecord",{},"invoke");if(this.cancelled)return"";let e=t?.audioBase64;if(!e)return"";let{status:o,data:s}=await re("/api/tbox/asr",{base64_audio:e,audio_format:t.format||"mp3",sample_rate:"16000"},{headers:b(this.session)});return o===200&&s?.text?s.text:""}cancelRecording(){this.cancelled=!0,G("stopRecord",{},"invoke").catch(()=>{})}};import{createClientLogger as oe}from"@tbox.cn/app-sdk-client";var se=oe("asr"),k=class{bridge;browser;active;constructor(t){this.bridge=new v(t),this.browser=new S(t),this.active=this.bridge}async checkPermission(){return!0}async startRecording(){try{await this.bridge.startRecording(),this.active=this.bridge}catch(t){se.warn("桥录音失败,降级到 H5 getUserMedia:",t),this.bridge.cancelRecording(),await this.browser.startRecording(),this.active=this.browser}}stopRecording(){return this.active.stopRecording()}cancelRecording(){this.active.cancelRecording()}};import{httpPostBinary as ie}from"@tbox.cn/app-sdk-client";var R=class{constructor(t){this.session=t}session;onProgress;async upload(t,e){let o;typeof t=="string"?o=await(await fetch(t)).blob():o=t;let{status:s,data:i}=await ie("/api/tbox/upload",o,{headers:{"Content-Type":o.type||"application/octet-stream","X-Tbox-Session-Id":this.session.tboxSessionId,"X-Tbox-Channel":this.session.channel,"X-Tbox-AppId":this.session.appId,"X-Tbox-File-Type":e?.type||"file",...e?.name?{"X-Tbox-File-Name":encodeURIComponent(e.name)}:{}}});if(s!==200||!i?.fileId)throw new Error(`[tbox-upload] ${i?.errorCode??`HTTP_${s}`} ${i?.error??""}`.trim());return{fileId:i.fileId,url:"",mimeType:typeof i.fileType=="string"?i.fileType:void 0,safed:i.safed}}};import{httpPost as ne}from"@tbox.cn/app-sdk-client";var P=class{constructor(t){this.session=t}session;async addTag(t,e){let{status:o,data:s}=await ne("/api/tbox/feedback",{requestId:t,feedback:e},{headers:b(this.session)});return{success:o===200&&!!s?.success}}};var w=class{sampleRate;isStopped=!1;onPlayEnd;audioContext=null;nextStartTime=0;inputCount=0;endedCount=0;totalInputCount=-1;fallbackTimer=null;playEndFired=!1;constructor(t){this.sampleRate=t.sampleRate,this.onPlayEnd=t.onPlayEnd}start(){this.isStopped=!1,this.inputCount=0,this.endedCount=0,this.totalInputCount=-1,this.playEndFired=!1,!this.audioContext&&(this.audioContext=new AudioContext({sampleRate:this.sampleRate}),this.audioContext.state==="suspended"&&this.audioContext.resume().catch(()=>{}),this.nextStartTime=this.audioContext.currentTime)}input(t){if(this.isStopped||!this.audioContext)return;let e=new Int16Array(t);if(e.length===0)return;let o=new Float32Array(e.length);for(let a=0;a<e.length;a++)o[a]=e[a]/32768;let s=this.audioContext.createBuffer(1,o.length,this.sampleRate);s.getChannelData(0).set(o);let i=this.audioContext.createBufferSource();i.buffer=s,i.connect(this.audioContext.destination);let n=this.audioContext.currentTime;this.nextStartTime<n&&(this.nextStartTime=n),i.start(this.nextStartTime),this.nextStartTime+=s.duration,this.inputCount++,i.onended=()=>{this.endedCount++,this.checkPlayEnd()}}finish(){if(this.totalInputCount=this.inputCount,this.totalInputCount===0){this.firePlayEnd();return}if(this.checkPlayEnd(),this.audioContext&&!this.isStopped&&!this.playEndFired){let t=this.nextStartTime-this.audioContext.currentTime,e=Math.max(0,t*1e3)+500;this.fallbackTimer=setTimeout(()=>{this.fallbackTimer=null,!this.isStopped&&!this.playEndFired&&this.firePlayEnd()},e)}}stop(){this.isStopped=!0,this.fallbackTimer&&(clearTimeout(this.fallbackTimer),this.fallbackTimer=null),this.audioContext&&(this.audioContext.close().catch(()=>{}),this.audioContext=null)}async pause(){this.isStopped||!this.audioContext||await this.audioContext.suspend()}async resume(){this.isStopped||!this.audioContext||await this.audioContext.resume()}checkPlayEnd(){this.totalInputCount>0&&this.endedCount>=this.totalInputCount&&!this.isStopped&&this.firePlayEnd()}firePlayEnd(){this.playEndFired||(this.playEndFired=!0,this.fallbackTimer&&(clearTimeout(this.fallbackTimer),this.fallbackTimer=null),this.onPlayEnd?.())}};import{getWsUrl as ae}from"@tbox.cn/app-sdk-client";import{createClientLogger as le}from"@tbox.cn/app-sdk-client";var Q=le("TTS"),I=class{socket=null;player=null;_state="idle";_canSend=!1;pending="";pendingFinish=!1;stateCallbacks=new Set;speakResolve=null;async speak(t){return this.stop(),new Promise(e=>{this.speakResolve=e,this.startStreaming().then(()=>{this.sendText(t),this.finishStreaming()}).catch(()=>{this.setState("idle"),this.resolveSpeak()})})}pause(){this._state!=="playing"||!this.player||(this.player.pause(),this.setState("paused"))}resume(){this._state!=="paused"||!this.player||(this.player.resume(),this.setState("playing"))}stop(){this._canSend=!1,this.pending="",this.pendingFinish=!1,this.closeSocket(),this.player&&(this.player.stop(),this.player=null),this.setState("idle"),this.resolveSpeak()}onStateChange(t){return this.stateCallbacks.add(t),this._state!=="idle"&&t(this._state),()=>this.stateCallbacks.delete(t)}startStreaming(){this.setState("loading"),this._canSend=!1,this.pending="",this.pendingFinish=!1,this.player=new w({sampleRate:16e3,onPlayEnd:()=>{this.setState("idle"),this.resolveSpeak()}}),this.player.start();let t=ae("/ws/tts"),e=F(),o=e?`${t}?token=${encodeURIComponent(e)}`:t;return new Promise((s,i)=>{let n=new WebSocket(o);n.binaryType="arraybuffer",this.socket=n,n.onopen=()=>{this.sendMessage({type:"START_TTS",config:{voiceCode:"lingyue",sampleRate:16e3,volume:50,speechRate:0,pitchRate:0}}),s()},n.onmessage=a=>this.handleMessage(a.data),n.onclose=()=>{this.socket=null,this.player?.finish()},n.onerror=a=>{Q.error("socket error",a),i(a),this.stop()}})}sendText(t){t&&(this._canSend?this.sendMessage({type:"TTS_TEXT",text:t}):this.pending+=t)}finishStreaming(){this._canSend?(this.flushPending(),this.sendMessage({type:"TTS_FINISH"})):this.pendingFinish=!0}flushPending(){this.pending&&(this.sendMessage({type:"TTS_TEXT",text:this.pending}),this.pending="")}handleMessage(t){if(typeof t=="string"){try{let e=JSON.parse(t);switch(e.actionType){case"start_ack":this._canSend=!0,this.flushPending(),this.pendingFinish&&(this.pendingFinish=!1,this.sendMessage({type:"TTS_FINISH"}));break;case"completed":this.player?.finish(),this.closeSocket();break;case"failed":Q.error("server error",e.error||e),this.stop();break}}catch{if(!this.player||t.length===0)return;this._state==="loading"&&this.setState("playing");let e=new ArrayBuffer(t.length),o=new Uint8Array(e);for(let s=0;s<t.length;s++)o[s]=t.charCodeAt(s);this.player.input(e)}return}this.player&&(this._state==="loading"&&this.setState("playing"),this.player.input(t))}sendMessage(t){this.socket&&this.socket.readyState===WebSocket.OPEN&&this.socket.send(JSON.stringify(t))}closeSocket(){this.socket&&(this.socket.onclose=null,this.socket.close(),this.socket=null)}setState(t){this._state!==t&&(this._state=t,this.stateCallbacks.forEach(e=>e(t)))}resolveSpeak(){this.speakResolve&&(this.speakResolve(),this.speakResolve=null)}};import{hasAlipayBridge as pe,isInAlipayShell as de}from"@tbox.cn/app-sdk-client";function ce(r,t){let e={};return t?.tts||(e.tts=new I),r&&(e.asr=de()||pe()?new k(r):new S(r),e.upload=new R(r),e.feedback=new P(r)),t?{...e,...t}:e}import{httpGet as j}from"@tbox.cn/app-sdk-client";function ue(){let r=null;async function t(s=!1){if(r&&!s)return r;r=null;let{status:i,data:n}=await j("/api/tbox/session",{cache:"no-store"});if(i!==200)throw new Error(`[tbox-session] fetch failed: ${i}`);return r=n,r}function e(){r=null}async function o(s){let i=`/api/conversation/messages?pageSize=${s.pageSize}`;return s.beforeId&&(i+=`&beforeId=${encodeURIComponent(s.beforeId)}`),j(i,{cache:"no-store"})}return{getTboxSession:t,clearTboxSession:e,listMessages:o}}import{createClientLogger as he}from"@tbox.cn/app-sdk-client";var V=he("AUTO-SEND"),Z=["query","prompt"];function fe(r){let t=new URLSearchParams(r);for(let e of Z){let o=t.get(e)?.trim();if(o)return o}return null}function me(r){let t=new URL(r);for(let e of Z)t.searchParams.delete(e);return t.toString()}function ye(){if(typeof window>"u")return null;let r=fe(window.location.search);if(!r)return null;try{window.history.replaceState(null,"",me(window.location.href))}catch(t){V.warn("strip failed (URL retained; refresh may re-send):",t)}return r}async function ge(r,t,e=5e3){if(!await L(e)){V.warn("skipped:reason=socket-timeout");return}t(r),V.info("sent")}export{Ht as AdapterProvider,v as AlipayASRAdapter,w as AudioStreamPlayer,S as BrowserASRAdapter,k as FallbackASRAdapter,P as FeedbackAdapter,W as InMemoryCardHistoryLoader,Ut as ItemActionsRow,I as TTSAdapter,R as UploadAdapter,It as bindCardActionChannel,vt as buildHistoryToolCalls,Ct as buildPreToolToolCalls,At as buildSkillToolCalls,Vt as callAction,Pt as configureHelloContext,O as configureWsAuthErrorHandler,D as configureWsAuthToken,it as configureWsFatalCloseHandler,ot as configureWsKeepalive,st as configureWsUrl,nt as connect,ye as consumeAutoSendQuery,Rt as convertHistoryMessages,q as createAuthClient,dt as createCardActionHandler,Et as createCoreChatState,ft as createEffectTokenDeduper,St as createHostEffectRunner,ce as createPlatformAdapters,ue as createPlatformApi,pt as disconnect,gt as executeAuthorizationEffect,yt as executePaymentEffect,Zt as getAuthClient,F as getAuthToken,ut as hideCard,ht as isHostEffect,wt as isSessionExpiredError,lt as isWsOpen,mt as mapPaymentResultCode,Tt as resolveHistoryMessage,kt as resolveHistoryMeta,bt as resolveHistoryPreTools,xt as resolveHistorySkills,ge as runAutoSend,at as send,U as sendCardAction,N as setActionAuthTokenGetter,_t as subscribeSessionUpgradeDrain,ct as upsertCard,Ft as useASRAdapter,Dt as useAdapters,$t as useFeedbackAdapter,Kt as useRefreshSession,Ot as useTTSAdapter,Lt as useUploadAdapter,L as waitForSocketOpen};
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tbox.cn/app-agent-sdk-client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "tbox agent 应用框架 client 轴:chat-store 状态域、WS、卡片运行时、能力适配器(asr/tts/upload/feedback)、历史/深链自动发送。通用底座(runtime/http/logger/bridge/卡片渲染基元)在 @tbox.cn/app-sdk-client。",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/client/index.d.ts",
|
|
9
|
+
"import": "./dist/client/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./client": {
|
|
12
|
+
"types": "./dist/client/index.d.ts",
|
|
13
|
+
"import": "./dist/client/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./client/card-preview": {
|
|
16
|
+
"types": "./dist/client/card-preview.d.ts",
|
|
17
|
+
"import": "./dist/client/card-preview.js"
|
|
18
|
+
},
|
|
19
|
+
"./platform/code-inspect": {
|
|
20
|
+
"types": "./platform/code-inspect.d.cts",
|
|
21
|
+
"default": "./platform/code-inspect.cjs"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"platform",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE"
|
|
29
|
+
],
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=20.0.0"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@tbox.cn/app-contracts": "0.12.0",
|
|
38
|
+
"@tbox.cn/app-sdk-client": "0.21.0",
|
|
39
|
+
"@tbox.cn/app-sdk-core": "0.20.0",
|
|
40
|
+
"react": "18.3.1",
|
|
41
|
+
"react-dom": "18.3.1",
|
|
42
|
+
"zustand": "4.5.7"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/ws": "8.18.1",
|
|
46
|
+
"@types/node": "24.13.3",
|
|
47
|
+
"@types/react": "18.3.31",
|
|
48
|
+
"@types/react-dom": "18.3.7",
|
|
49
|
+
"tsup": "8.5.1",
|
|
50
|
+
"typescript": "5.9.3",
|
|
51
|
+
"vitest": "4.1.10",
|
|
52
|
+
"ws": "8.21.3"
|
|
53
|
+
},
|
|
54
|
+
"license": "MIT",
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsup",
|
|
57
|
+
"typecheck": "tsc --noEmit",
|
|
58
|
+
"test": "vitest run"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('@tbox.cn/app-sdk-client/platform/code-inspect');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@tbox.cn/app-sdk-client/platform/code-inspect';
|