@useago/sdk 0.3.4 → 0.4.2

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.
Files changed (41) hide show
  1. package/dist/AgoClient-D4EL6nva.cjs +6 -0
  2. package/dist/{AgoClient-DF7M2WAz.js → AgoClient-rvTz2thR.js} +239 -218
  3. package/dist/angular.cjs +1 -1
  4. package/dist/angular.d.cts +27 -0
  5. package/dist/angular.d.ts +27 -0
  6. package/dist/angular.js +1 -1
  7. package/dist/createConversationSession-D9FesBQS.js +58 -0
  8. package/dist/createConversationSession-DtaeA418.cjs +1 -0
  9. package/dist/createFormCollector-BwvkKi48.js +211 -0
  10. package/dist/createFormCollector-Wb8Rr-U2.cjs +1 -0
  11. package/dist/{createMockClient-C50gjV0b.js → createMockClient-DwdxNwEn.js} +5 -2
  12. package/dist/createMockClient-zHQ0cjUH.cjs +1 -0
  13. package/dist/devtools.cjs +5 -4
  14. package/dist/devtools.d.cts +31 -26
  15. package/dist/devtools.d.ts +31 -26
  16. package/dist/devtools.js +39 -35
  17. package/dist/helpers.cjs +1 -1
  18. package/dist/helpers.js +1 -1
  19. package/dist/index.cjs +1 -1
  20. package/dist/index.d.cts +117 -1
  21. package/dist/index.d.ts +117 -1
  22. package/dist/index.js +44 -42
  23. package/dist/react.cjs +1 -1
  24. package/dist/react.d.cts +35 -0
  25. package/dist/react.d.ts +35 -0
  26. package/dist/react.js +4 -4
  27. package/dist/vue.cjs +1 -1
  28. package/dist/vue.d.cts +27 -0
  29. package/dist/vue.d.ts +27 -0
  30. package/dist/vue.js +1 -1
  31. package/dist/widget.cjs +1 -1
  32. package/dist/widget.d.cts +114 -0
  33. package/dist/widget.d.ts +114 -0
  34. package/dist/widget.js +170 -150
  35. package/package.json +58 -15
  36. package/dist/AgoClient-bjN-z2ZQ.cjs +0 -6
  37. package/dist/createFormCollector-ClzHfaBd.js +0 -185
  38. package/dist/createFormCollector-H4Me_2uj.cjs +0 -1
  39. package/dist/createMockClient-DQvFFWnc.cjs +0 -1
  40. /package/dist/{functions-CWSRi3xv.js → functions-C1-5vadY.js} +0 -0
  41. /package/dist/{functions-BH1X5kas.cjs → functions-DsmzgHjC.cjs} +0 -0
package/dist/vue.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const P=require("./rolldown-runtime-CaekUdDJ.cjs"),p=require("./AgoClient-bjN-z2ZQ.cjs");let t=require("vue");var I=Symbol("ago-client"),y={install(o,n){const e=new p.AgoClient(n);o.provide(I,e)}};function g(o){if(o)return new p.AgoClient(o);const n=(0,t.inject)(I);if(!n)throw new Error("useAgo(): no AgoClient found. Either pass config or install AgoPlugin.");return n}function E(o={}){const n=o.client??g(),e=(0,t.ref)([]),s=(0,t.ref)(o.conversationId),i=(0,t.ref)(!1),r=(0,t.ref)(null),c=a=>{s.value||(s.value=a.conversationId)},f=a=>{const u=e.value.findIndex(v=>v.id===a.messageId);u>=0&&(e.value[u]={...e.value[u],content:e.value[u].content+a.content})},m=a=>{const u=e.value.findIndex(v=>v.id===a.id);u>=0?e.value[u]=a:e.value.push(a),i.value=!1},C=a=>{r.value=new Error(a.error),i.value=!1};(0,t.onMounted)(()=>{n.on("message:start",c),n.on("message:chunk",f),n.on("message:complete",m),n.on("message:error",C)}),(0,t.onUnmounted)(()=>{n.off("message:start",c),n.off("message:chunk",f),n.off("message:complete",m),n.off("message:error",C)});async function d(a,u){i.value=!0,r.value=null;const v={id:`temp-${Date.now()}`,conversationId:s.value||"",content:a,role:"user",status:"DONE",createdAt:new Date},L={id:`temp-assistant-${Date.now()}`,conversationId:s.value||"",content:"",role:"assistant",status:"IN_PROGRESS",createdAt:new Date};e.value.push(v,L);try{const l=await n.sendMessage(a,{conversationId:s.value,files:u});l.conversationId&&!s.value&&(s.value=l.conversationId),e.value=e.value.filter(w=>!w.id.startsWith("temp-"));const A={...v,id:v.id.replace("temp-","user-"),conversationId:l.conversationId};return e.value.some(w=>w.id===l.id)?e.value.push(A):e.value.push(A,l),l}catch(l){return r.value=l instanceof Error?l:new Error("Failed to send"),i.value=!1,e.value=e.value.filter(A=>!A.id.startsWith("temp-")),null}}function h(){e.value=[],s.value=void 0,r.value=null}return{messages:e,isLoading:i,error:r,conversationId:s,sendMessage:d,clearMessages:h}}function M(o={}){const n=o.client??g(),e=o.autoLoad??!0,s=(0,t.ref)([]),i=(0,t.ref)(null),r=(0,t.ref)(!1),c=(0,t.ref)(null);async function f(){r.value=!0,c.value=null;try{s.value=await n.getConversations()}catch(d){c.value=d instanceof Error?d:new Error("Failed to load conversations")}finally{r.value=!1}}async function m(d){r.value=!0,c.value=null;try{i.value=await n.getConversation(d)}catch(h){c.value=h instanceof Error?h:new Error("Failed to load conversation")}finally{r.value=!1}}function C(){i.value=null}return(0,t.onMounted)(()=>{e&&f()}),{conversations:s,currentConversation:i,isLoading:r,error:c,selectConversation:m,startNewConversation:C,refreshConversations:f}}function N(o={}){const n=E({client:o.client,conversationId:o.conversationId}),e=M({client:o.client,autoLoad:o.autoLoad}),s=(0,t.computed)(()=>n.error.value||e.error.value);return{messages:n.messages,isLoading:n.isLoading,error:s,sendMessage:n.sendMessage,clearMessages:n.clearMessages,conversationId:n.conversationId,conversations:e.conversations,currentConversation:e.currentConversation,isConversationsLoading:e.isLoading,selectConversation:e.selectConversation,startNewConversation:e.startNewConversation,refreshConversations:e.refreshConversations}}function F(o,n){const e=g();(0,t.onMounted)(()=>{e.registerFunction(o,n.handler,{description:n.description,parameters:n.parameters})}),(0,t.onUnmounted)(()=>{e.unregisterFunction(o)})}function S(o,n){const e=g();(0,t.onMounted)(()=>{e.registerNavigationFunction(o,n)}),(0,t.onUnmounted)(()=>{e.unregisterFunction("navigateToPage")})}function _(o,n){const e=g();(0,t.onMounted)(()=>{e.on(o,n)}),(0,t.onUnmounted)(()=>{e.off(o,n)})}function D(o){const n=(0,t.shallowRef)(o.get());return(0,t.onScopeDispose)(o.subscribe(e=>{n.value=e})),n}exports.AGO_CLIENT_KEY=I;exports.AgoPlugin=y;exports.useAgo=g;exports.useAgoEvents=_;exports.useAgoFunction=F;exports.useAgoNavigation=S;exports.useAgoStore=D;exports.useChat=N;exports.useConversation=M;exports.useMessages=E;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const P=require("./rolldown-runtime-CaekUdDJ.cjs"),p=require("./AgoClient-D4EL6nva.cjs");let t=require("vue");var I=Symbol("ago-client"),y={install(o,n){const e=new p.AgoClient(n);o.provide(I,e)}};function g(o){if(o)return new p.AgoClient(o);const n=(0,t.inject)(I);if(!n)throw new Error("useAgo(): no AgoClient found. Either pass config or install AgoPlugin.");return n}function E(o={}){const n=o.client??g(),e=(0,t.ref)([]),s=(0,t.ref)(o.conversationId),i=(0,t.ref)(!1),r=(0,t.ref)(null),c=a=>{s.value||(s.value=a.conversationId)},f=a=>{const u=e.value.findIndex(v=>v.id===a.messageId);u>=0&&(e.value[u]={...e.value[u],content:e.value[u].content+a.content})},m=a=>{const u=e.value.findIndex(v=>v.id===a.id);u>=0?e.value[u]=a:e.value.push(a),i.value=!1},C=a=>{r.value=new Error(a.error),i.value=!1};(0,t.onMounted)(()=>{n.on("message:start",c),n.on("message:chunk",f),n.on("message:complete",m),n.on("message:error",C)}),(0,t.onUnmounted)(()=>{n.off("message:start",c),n.off("message:chunk",f),n.off("message:complete",m),n.off("message:error",C)});async function d(a,u){i.value=!0,r.value=null;const v={id:`temp-${Date.now()}`,conversationId:s.value||"",content:a,role:"user",status:"DONE",createdAt:new Date},L={id:`temp-assistant-${Date.now()}`,conversationId:s.value||"",content:"",role:"assistant",status:"IN_PROGRESS",createdAt:new Date};e.value.push(v,L);try{const l=await n.sendMessage(a,{conversationId:s.value,files:u});l.conversationId&&!s.value&&(s.value=l.conversationId),e.value=e.value.filter(w=>!w.id.startsWith("temp-"));const A={...v,id:v.id.replace("temp-","user-"),conversationId:l.conversationId};return e.value.some(w=>w.id===l.id)?e.value.push(A):e.value.push(A,l),l}catch(l){return r.value=l instanceof Error?l:new Error("Failed to send"),i.value=!1,e.value=e.value.filter(A=>!A.id.startsWith("temp-")),null}}function h(){e.value=[],s.value=void 0,r.value=null}return{messages:e,isLoading:i,error:r,conversationId:s,sendMessage:d,clearMessages:h}}function M(o={}){const n=o.client??g(),e=o.autoLoad??!0,s=(0,t.ref)([]),i=(0,t.ref)(null),r=(0,t.ref)(!1),c=(0,t.ref)(null);async function f(){r.value=!0,c.value=null;try{s.value=await n.getConversations()}catch(d){c.value=d instanceof Error?d:new Error("Failed to load conversations")}finally{r.value=!1}}async function m(d){r.value=!0,c.value=null;try{i.value=await n.getConversation(d)}catch(h){c.value=h instanceof Error?h:new Error("Failed to load conversation")}finally{r.value=!1}}function C(){i.value=null}return(0,t.onMounted)(()=>{e&&f()}),{conversations:s,currentConversation:i,isLoading:r,error:c,selectConversation:m,startNewConversation:C,refreshConversations:f}}function N(o={}){const n=E({client:o.client,conversationId:o.conversationId}),e=M({client:o.client,autoLoad:o.autoLoad}),s=(0,t.computed)(()=>n.error.value||e.error.value);return{messages:n.messages,isLoading:n.isLoading,error:s,sendMessage:n.sendMessage,clearMessages:n.clearMessages,conversationId:n.conversationId,conversations:e.conversations,currentConversation:e.currentConversation,isConversationsLoading:e.isLoading,selectConversation:e.selectConversation,startNewConversation:e.startNewConversation,refreshConversations:e.refreshConversations}}function F(o,n){const e=g();(0,t.onMounted)(()=>{e.registerFunction(o,n.handler,{description:n.description,parameters:n.parameters})}),(0,t.onUnmounted)(()=>{e.unregisterFunction(o)})}function S(o,n){const e=g();(0,t.onMounted)(()=>{e.registerNavigationFunction(o,n)}),(0,t.onUnmounted)(()=>{e.unregisterFunction("navigateToPage")})}function _(o,n){const e=g();(0,t.onMounted)(()=>{e.on(o,n)}),(0,t.onUnmounted)(()=>{e.off(o,n)})}function D(o){const n=(0,t.shallowRef)(o.get());return(0,t.onScopeDispose)(o.subscribe(e=>{n.value=e})),n}exports.AGO_CLIENT_KEY=I;exports.AgoPlugin=y;exports.useAgo=g;exports.useAgoEvents=_;exports.useAgoFunction=F;exports.useAgoNavigation=S;exports.useAgoStore=D;exports.useChat=N;exports.useConversation=M;exports.useMessages=E;
package/dist/vue.d.cts CHANGED
@@ -39,6 +39,12 @@ declare class AgoClient {
39
39
  * Get a specific conversation with messages
40
40
  */
41
41
  getConversation(conversationId: string): Promise<Conversation>;
42
+ /**
43
+ * Map the backend's snake_case `tool_call_data` entries onto the camelCase
44
+ * {@link ToolCallData} shape used everywhere else in the SDK. Mirrors
45
+ * `SSEHandler.parseToolCall` so persisted and streamed tool calls match.
46
+ */
47
+ private static mapPersistedToolCalls;
42
48
  /**
43
49
  * Get messages for a conversation
44
50
  */
@@ -149,6 +155,13 @@ declare class AgoClient {
149
155
  * Remove a dynamic context provider.
150
156
  */
151
157
  removeDynamicContext(key: string): boolean;
158
+ /**
159
+ * Broadcast that the client-side context changed by emitting `context:changed`
160
+ * with the fresh snapshot. The context-mutating methods call this automatically;
161
+ * stateful helpers whose context lives in a store (e.g. `createFormCollector`)
162
+ * call it when their store updates so observers like the dev panel stay live.
163
+ */
164
+ notifyContextChanged(): void;
152
165
  /**
153
166
  * Enable automatic capture of the current browser page (URL + title).
154
167
  * Injected as a dynamic context entry named `browser-page`.
@@ -194,6 +207,14 @@ declare class AgoClient {
194
207
  * SDK Events
195
208
  */
196
209
  declare interface AgoClientEvents {
210
+ /**
211
+ * The client-side context changed: a context entry or dynamic provider was
212
+ * added/removed, or a stateful helper (e.g. `createFormCollector`) updated its
213
+ * store. Carries the fresh snapshot so observers — notably the dev panel — can
214
+ * repaint without waiting for the next message. Fires for the form collector's
215
+ * initial install too, so its missing fields show from the start of a conversation.
216
+ */
217
+ "context:changed": ContextSnapshot | null;
197
218
  "message:start": {
198
219
  conversationId: string;
199
220
  messageId: string;
@@ -209,6 +230,12 @@ declare interface AgoClientEvents {
209
230
  conversationId?: string;
210
231
  messageId?: string;
211
232
  };
233
+ /**
234
+ * A full conversation was loaded from the server (e.g. after a page reload).
235
+ * Carries the messages and their persisted tool calls so stateful helpers —
236
+ * notably `createFormCollector` — can replay them to restore their state.
237
+ */
238
+ "conversation:loaded": Conversation;
212
239
  "toolCall:received": ToolCallData;
213
240
  "toolCall:form": ToolCallData;
214
241
  "function:invoke": ClientFunctionInvocation;
package/dist/vue.d.ts CHANGED
@@ -39,6 +39,12 @@ declare class AgoClient {
39
39
  * Get a specific conversation with messages
40
40
  */
41
41
  getConversation(conversationId: string): Promise<Conversation>;
42
+ /**
43
+ * Map the backend's snake_case `tool_call_data` entries onto the camelCase
44
+ * {@link ToolCallData} shape used everywhere else in the SDK. Mirrors
45
+ * `SSEHandler.parseToolCall` so persisted and streamed tool calls match.
46
+ */
47
+ private static mapPersistedToolCalls;
42
48
  /**
43
49
  * Get messages for a conversation
44
50
  */
@@ -149,6 +155,13 @@ declare class AgoClient {
149
155
  * Remove a dynamic context provider.
150
156
  */
151
157
  removeDynamicContext(key: string): boolean;
158
+ /**
159
+ * Broadcast that the client-side context changed by emitting `context:changed`
160
+ * with the fresh snapshot. The context-mutating methods call this automatically;
161
+ * stateful helpers whose context lives in a store (e.g. `createFormCollector`)
162
+ * call it when their store updates so observers like the dev panel stay live.
163
+ */
164
+ notifyContextChanged(): void;
152
165
  /**
153
166
  * Enable automatic capture of the current browser page (URL + title).
154
167
  * Injected as a dynamic context entry named `browser-page`.
@@ -194,6 +207,14 @@ declare class AgoClient {
194
207
  * SDK Events
195
208
  */
196
209
  declare interface AgoClientEvents {
210
+ /**
211
+ * The client-side context changed: a context entry or dynamic provider was
212
+ * added/removed, or a stateful helper (e.g. `createFormCollector`) updated its
213
+ * store. Carries the fresh snapshot so observers — notably the dev panel — can
214
+ * repaint without waiting for the next message. Fires for the form collector's
215
+ * initial install too, so its missing fields show from the start of a conversation.
216
+ */
217
+ "context:changed": ContextSnapshot | null;
197
218
  "message:start": {
198
219
  conversationId: string;
199
220
  messageId: string;
@@ -209,6 +230,12 @@ declare interface AgoClientEvents {
209
230
  conversationId?: string;
210
231
  messageId?: string;
211
232
  };
233
+ /**
234
+ * A full conversation was loaded from the server (e.g. after a page reload).
235
+ * Carries the messages and their persisted tool calls so stateful helpers —
236
+ * notably `createFormCollector` — can replay them to restore their state.
237
+ */
238
+ "conversation:loaded": Conversation;
212
239
  "toolCall:received": ToolCallData;
213
240
  "toolCall:form": ToolCallData;
214
241
  "function:invoke": ClientFunctionInvocation;
package/dist/vue.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as A } from "./AgoClient-DF7M2WAz.js";
1
+ import { t as A } from "./AgoClient-rvTz2thR.js";
2
2
  import { computed as y, inject as F, onMounted as g, onScopeDispose as N, onUnmounted as I, ref as c, shallowRef as D } from "vue";
3
3
  var L = /* @__PURE__ */ Symbol("ago-client"), b = { install(t, n) {
4
4
  const e = new A(n);
package/dist/widget.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const X=require("./AgoClient-bjN-z2ZQ.cjs"),Z=require("./createFormCollector-H4Me_2uj.cjs");var q='"IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';function x(s,o){Object.assign(s.style,o)}function c(s={}){const o=document.createElement("div");return x(o,s),o}var P="ago-chat-widget-keyframes";function ee(){if(typeof document>"u"||document.getElementById(P))return;const s=document.createElement("style");s.id=P,s.textContent="@keyframes ago-pulse { 0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); } 40% { opacity: 1; transform: scale(1); } }",document.head.appendChild(s)}function te(s){const o=typeof s=="string"?document.querySelector(s):s;if(!o)throw new Error(`mountChatWidget: target "${String(s)}" not found.`);return o}function ne(s,o){const{title:y="Chat",welcomeMessage:I="Hello! How can I help you today?",placeholder:R="Type a message...",allowFiles:u=!1,height:g=500,logoUrl:f,showAgentName:b=!1,forms:_,onFollowUpClick:k,onMessageSent:D,onMessageReceived:l}=o;if(!o.client&&!o.config?.baseUrl)throw new Error("mountChatWidget requires either `client` or `config` (with a baseUrl).");const i=o.client??new X.AgoClient(o.config),W=te(s);let m=o.conversationId,n=[],w=!1,v=null;const N=c({display:"flex",flexDirection:"column",height:typeof g=="number"?`${g}px`:g,border:"1px solid #dee3e8",borderRadius:"16px",overflow:"hidden",backgroundColor:"#fff",fontFamily:q,boxShadow:"rgba(15, 15, 15, 0.08) 0px 2px 16px 0px"});N.className="ago-chat-widget";const z=c({padding:"14px 16px",borderBottom:"1px solid #dee3e8",backgroundColor:"#03182f",color:"#e8f0fe",display:"flex",alignItems:"center",gap:"10px"});if(z.className="ago-chat-widget__header",f){const e=document.createElement("img");e.src=f,e.alt="Logo",x(e,{height:"24px",width:"auto"}),z.appendChild(e)}const F=document.createElement("h3");F.textContent=y,x(F,{margin:"0",fontSize:"15px",fontWeight:"600"}),z.appendChild(F);const E=c({flex:"1",overflow:"auto",padding:"16px",backgroundColor:"#fbfbfb"});E.className="ago-chat-widget__messages";const{inputRow:G,setDisabled:j,focus:K}=oe({placeholder:R,allowFiles:u,onSend:(e,t)=>{M(e,t)}});ee(),N.append(z,E,G),W.appendChild(N);const A=k!==!1,U=k===!1?void 0:k??(e=>{M(e)});function V(e){const t=e.role==="user",d=c({display:"flex",flexDirection:"column",alignItems:t?"flex-end":"flex-start",marginBottom:"16px"});if(d.className=`ago-message ago-message--${e.role}`,!t&&b&&e.agent){const a=c({fontSize:"13px",fontWeight:"500",color:"#30373e",marginBottom:"6px",padding:"0 4px"});a.className="ago-message__agent",a.textContent=e.agent.displayName||e.agent.name,d.appendChild(a)}if(!t&&e.sources&&e.sources.length>0){const a=c({display:"grid",gridTemplateColumns:"1fr 1fr",gap:"6px",marginBottom:"10px",width:"100%",maxWidth:"85%"});a.className="ago-message__sources",e.sources.forEach((p,r)=>{const C=p.url?document.createElement("a"):document.createElement("div");p.url&&C instanceof HTMLAnchorElement&&(C.href=p.url,C.target="_blank",C.rel="noopener noreferrer"),x(C,{display:"flex",alignItems:"center",gap:"8px",padding:"8px 10px",border:"1px solid #dee3e8",borderRadius:"8px",backgroundColor:"#fff",textDecoration:"none",color:"#30373e",fontSize:"12px",overflow:"hidden"});const $=c({flexShrink:"0",width:"18px",height:"18px",borderRadius:"3px",border:"1px solid #1b5fc4",color:"#1b5fc4",fontSize:"11px",fontWeight:"500",display:"flex",alignItems:"center",justifyContent:"center",backgroundColor:"#f0f4ff"});$.textContent=String(r+1);const L=c({overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"});L.textContent=p.title,L.title=p.title,C.append($,L),a.appendChild(C)}),d.appendChild(a)}const h=c({maxWidth:t?"75%":"100%",padding:t?"10px 14px":"2px 8px",borderRadius:t?"16px":"0",backgroundColor:t?"#03182f":"transparent",color:t?"#fff":"#30373e",wordBreak:"break-word",fontSize:"14px",lineHeight:"1.6",whiteSpace:"pre-wrap"});if(h.className="ago-message__content",e.content?h.textContent=e.content:e.status==="IN_PROGRESS"&&h.appendChild(Q()),d.appendChild(h),e.followUpReplies&&e.followUpReplies.length>0){const a=c({display:"flex",flexWrap:"wrap",gap:"8px",marginTop:"10px"});a.className="ago-message__followups";for(const p of e.followUpReplies){const r=document.createElement("button");r.type="button",r.className="ago-message__followup-btn",r.textContent=p,r.disabled=!A,x(r,{padding:"6px 14px",fontSize:"13px",borderRadius:"16px",border:"1px solid #dee3e8",backgroundColor:"#fff",color:"#30373e",cursor:A?"pointer":"default",transition:"border-color 0.15s"}),U&&r.addEventListener("click",()=>U(p)),r.addEventListener("mouseenter",()=>{r.style.borderColor="#1b5fc4"}),r.addEventListener("mouseleave",()=>{r.style.borderColor="#dee3e8"}),a.appendChild(r)}d.appendChild(a)}return d}function S(){if(E.replaceChildren(),n.length===0){const e=c({textAlign:"center",color:"#6b6d6f",padding:"24px 16px",fontSize:"14px",lineHeight:"1.5"});e.textContent=I,E.appendChild(e)}else for(const e of n)E.appendChild(V(e));if(v){const e=c({padding:"10px 14px",backgroundColor:"#fef2f2",color:"#dc2626",borderRadius:"12px",marginTop:"8px",fontSize:"13px",border:"1px solid #fecaca"});e.textContent=v,E.appendChild(e)}E.scrollTop=E.scrollHeight,j(w)}function Y(){for(let e=n.length-1;e>=0;e--){const t=n[e];if(t.role==="assistant"&&t.status==="IN_PROGRESS")return t}}const B=e=>{m||(m=e.conversationId)},H=e=>{const t=Y();t&&(t.content+=e.content,S())},O=e=>{const t=n.findIndex(d=>d.id===e.id||d.role==="assistant"&&d.status==="IN_PROGRESS");t>=0?n[t]=e:n.push(e),w=!1,S(),e.status==="DONE"&&l?.({id:e.id,content:e.content})},T=e=>{v=e.error,w=!1,n=n.filter(t=>!t.id.startsWith("temp-")),S()};i.on("message:start",B),i.on("message:chunk",H),i.on("message:complete",O),i.on("message:error",T);const J=(_??[]).map(e=>Z.createFormCollector(e).install(i));async function M(e,t){const d=e.trim();if(!d&&!t?.length||w)return;D?.(d),w=!0,v=null;const h=Date.now();n.push({id:`temp-user-${h}`,conversationId:m||"",content:d,role:"user",status:"DONE",createdAt:new Date}),n.push({id:`temp-assistant-${h}`,conversationId:m||"",content:"",role:"assistant",status:"IN_PROGRESS",createdAt:new Date}),S();try{const a=await i.sendMessage(d,{conversationId:m,files:t});a.conversationId&&!m&&(m=a.conversationId);const p=n.find(r=>r.id===`temp-user-${h}`);if(p&&(p.id=`user-${h}`,p.conversationId=a.conversationId),!n.some(r=>r.id===a.id)){const r=n.findIndex(C=>C.id===`temp-assistant-${h}`);r>=0?n[r]=a:n.push(a)}n=n.filter(r=>!r.id.startsWith("temp-")),w=!1,S()}catch(a){v=a instanceof Error?a.message:"Failed to send message",w=!1,n=n.filter(p=>!p.id.startsWith("temp-")),S()}}return S(),K(),{client:i,element:N,sendMessage:M,destroy(){i.off("message:start",B),i.off("message:chunk",H),i.off("message:complete",O),i.off("message:error",T),J.forEach(e=>e()),N.remove(),o.client||i.destroy()}};function Q(){const e=c({display:"flex",gap:"4px",padding:"4px 0"});for(let t=0;t<3;t++){const d=c({width:"6px",height:"6px",borderRadius:"50%",backgroundColor:"#b5bfc8",animation:"ago-pulse 1.2s ease-in-out infinite",animationDelay:`${t*.2}s`});e.appendChild(d)}return e}}function oe(s){let o=[];const y=document.createElement("form");x(y,{display:"flex",flexDirection:"column",gap:"8px",padding:"12px",borderTop:"1px solid #dee3e8",backgroundColor:"#fff"}),y.className="ago-chat-input";const I=c({display:"flex",flexWrap:"wrap",gap:"6px"}),R=c({display:"flex",gap:"8px",alignItems:"flex-end"}),u=document.createElement("textarea");u.placeholder=s.placeholder,u.rows=1,x(u,{flex:"1",resize:"none",padding:"10px 12px",border:"1px solid #dee3e8",borderRadius:"12px",fontSize:"14px",fontFamily:q,lineHeight:"1.4",maxHeight:"120px",outline:"none"});const g=document.createElement("button");g.type="submit",g.textContent="Send",x(g,{padding:"10px 16px",border:"none",borderRadius:"12px",backgroundColor:"#03182f",color:"#fff",fontSize:"14px",fontWeight:"500",cursor:"pointer"});let f=null,b=null;s.allowFiles&&(f=document.createElement("input"),f.type="file",f.multiple=!0,x(f,{display:"none"}),b=document.createElement("button"),b.type="button",b.textContent="📎",x(b,{padding:"10px 12px",border:"1px solid #dee3e8",borderRadius:"12px",backgroundColor:"#fff",cursor:"pointer",fontSize:"14px"}),b.addEventListener("click",()=>f?.click()),f.addEventListener("change",()=>{o=[...o,...Array.from(f?.files??[])],_()}));function _(){I.replaceChildren(),o.forEach((l,i)=>{const W=c({display:"flex",alignItems:"center",gap:"4px",padding:"4px 8px",backgroundColor:"#f0f4ff",border:"1px solid #dee3e8",borderRadius:"8px",fontSize:"12px"}),m=document.createElement("span");m.textContent=l.name;const n=document.createElement("button");n.type="button",n.textContent="×",x(n,{border:"none",background:"transparent",cursor:"pointer",fontSize:"14px",lineHeight:"1"}),n.addEventListener("click",()=>{o=o.filter((w,v)=>v!==i),_()}),W.append(m,n),I.appendChild(W)})}const k=()=>{const l=u.value,i=o;return u.value="",o=[],_(),{content:l,files:i}},D=()=>{const{content:l,files:i}=k();(l.trim()||i.length>0)&&s.onSend(l.trim(),i.length>0?i:void 0)};return y.addEventListener("submit",l=>{l.preventDefault(),D()}),u.addEventListener("keydown",l=>{l.key==="Enter"&&!l.shiftKey&&(l.preventDefault(),D())}),b&&R.appendChild(b),R.append(u,g),f&&y.appendChild(f),y.append(I,R),{inputRow:y,getValueAndClear:k,setDisabled:l=>{u.disabled=l,g.disabled=l,g.style.opacity=l?"0.6":"1",g.style.cursor=l?"not-allowed":"pointer"},focus:()=>u.focus()}}exports.mountChatWidget=ne;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const re=require("./AgoClient-D4EL6nva.cjs"),ae=require("./createFormCollector-Wb8Rr-U2.cjs"),ie=require("./createConversationSession-DtaeA418.cjs");var Y='"IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';function m(s,n){Object.assign(s.style,n)}function c(s={}){const n=document.createElement("div");return m(n,s),n}var V="ago-chat-widget-keyframes";function se(){if(typeof document>"u"||document.getElementById(V))return;const s=document.createElement("style");s.id=V,s.textContent="@keyframes ago-pulse { 0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); } 40% { opacity: 1; transform: scale(1); } }",document.head.appendChild(s)}function le(s){const n=typeof s=="string"?document.querySelector(s):s;if(!n)throw new Error(`mountChatWidget: target "${String(s)}" not found.`);return n}function de(s,n){const{title:v="Chat",welcomeMessage:_="Hello! How can I help you today?",placeholder:N="Type a message...",allowFiles:g=!1,height:x=500,logoUrl:f,showAgentName:b=!1,loadThreads:I=!1,forms:A,onFollowUpClick:R,onMessageSent:i,onMessageReceived:C}=n;if(!n.client&&!n.config?.baseUrl)throw new Error("mountChatWidget requires either `client` or `config` (with a baseUrl).");const l=n.client??new re.AgoClient(n.config),T=le(s),y=n.persistConversation,D=y?ie.createConversationSession(y===!0?{}:y):void 0;let u=n.conversationId??D?.getLastActiveThread()??void 0,o=[],k=!1,W=null;const F=[],z=c({display:"flex",flexDirection:"column",height:typeof x=="number"?`${x}px`:x,border:"1px solid #dee3e8",borderRadius:"16px",overflow:"hidden",backgroundColor:"#fff",fontFamily:Y,boxShadow:"rgba(15, 15, 15, 0.08) 0px 2px 16px 0px"});z.className="ago-chat-widget";const L=c({padding:"14px 16px",borderBottom:"1px solid #dee3e8",backgroundColor:"#03182f",color:"#e8f0fe",display:"flex",alignItems:"center",gap:"10px"});if(L.className="ago-chat-widget__header",f){const e=document.createElement("img");e.src=f,e.alt="Logo",m(e,{height:"24px",width:"auto"}),L.appendChild(e)}const M=document.createElement("h3");M.textContent=v,m(M,{margin:"0",fontSize:"15px",fontWeight:"600"}),L.appendChild(M);const E=c({flex:"1",overflow:"auto",padding:"16px",backgroundColor:"#fbfbfb"});E.className="ago-chat-widget__messages";const{inputRow:J,setDisabled:Q,focus:X}=ce({placeholder:N,allowFiles:g,onSend:(e,t)=>{H(e,t)}});se(),z.append(L,E,J),T.appendChild(z);const O=R!==!1,$=R===!1?void 0:R??(e=>{H(e)});function Z(e){const t=e.role==="user",d=c({display:"flex",flexDirection:"column",alignItems:t?"flex-end":"flex-start",marginBottom:"16px"});if(d.className=`ago-message ago-message--${e.role}`,!t&&b&&e.agent){const r=c({fontSize:"13px",fontWeight:"500",color:"#30373e",marginBottom:"6px",padding:"0 4px"});r.className="ago-message__agent",r.textContent=e.agent.displayName||e.agent.name,d.appendChild(r)}if(!t&&e.sources&&e.sources.length>0){const r=c({display:"grid",gridTemplateColumns:"1fr 1fr",gap:"6px",marginBottom:"10px",width:"100%",maxWidth:"85%"});r.className="ago-message__sources",e.sources.forEach((p,a)=>{const w=p.url?document.createElement("a"):document.createElement("div");p.url&&w instanceof HTMLAnchorElement&&(w.href=p.url,w.target="_blank",w.rel="noopener noreferrer"),m(w,{display:"flex",alignItems:"center",gap:"8px",padding:"8px 10px",border:"1px solid #dee3e8",borderRadius:"8px",backgroundColor:"#fff",textDecoration:"none",color:"#30373e",fontSize:"12px",overflow:"hidden"});const K=c({flexShrink:"0",width:"18px",height:"18px",borderRadius:"3px",border:"1px solid #1b5fc4",color:"#1b5fc4",fontSize:"11px",fontWeight:"500",display:"flex",alignItems:"center",justifyContent:"center",backgroundColor:"#f0f4ff"});K.textContent=String(a+1);const B=c({overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"});B.textContent=p.title,B.title=p.title,w.append(K,B),r.appendChild(w)}),d.appendChild(r)}const h=c({maxWidth:t?"75%":"100%",padding:t?"10px 14px":"2px 8px",borderRadius:t?"16px":"0",backgroundColor:t?"#03182f":"transparent",color:t?"#fff":"#30373e",wordBreak:"break-word",fontSize:"14px",lineHeight:"1.6",whiteSpace:"pre-wrap"});if(h.className="ago-message__content",e.content?h.textContent=e.content:e.status==="IN_PROGRESS"&&h.appendChild(oe()),d.appendChild(h),e.followUpReplies&&e.followUpReplies.length>0){const r=c({display:"flex",flexWrap:"wrap",gap:"8px",marginTop:"10px"});r.className="ago-message__followups";for(const p of e.followUpReplies){const a=document.createElement("button");a.type="button",a.className="ago-message__followup-btn",a.textContent=p,a.disabled=!O,m(a,{padding:"6px 14px",fontSize:"13px",borderRadius:"16px",border:"1px solid #dee3e8",backgroundColor:"#fff",color:"#30373e",cursor:O?"pointer":"default",transition:"border-color 0.15s"}),$&&a.addEventListener("click",()=>$(p)),a.addEventListener("mouseenter",()=>{a.style.borderColor="#1b5fc4"}),a.addEventListener("mouseleave",()=>{a.style.borderColor="#dee3e8"}),r.appendChild(a)}d.appendChild(r)}return d}function S(){if(E.replaceChildren(),o.length===0){const e=c({textAlign:"center",color:"#6b6d6f",padding:"24px 16px",fontSize:"14px",lineHeight:"1.5"});e.textContent=_,E.appendChild(e)}else for(const e of o)E.appendChild(Z(e));if(W){const e=c({padding:"10px 14px",backgroundColor:"#fef2f2",color:"#dc2626",borderRadius:"12px",marginTop:"8px",fontSize:"13px",border:"1px solid #fecaca"});e.textContent=W,E.appendChild(e)}E.scrollTop=E.scrollHeight,Q(k)}function ee(){for(let e=o.length-1;e>=0;e--){const t=o[e];if(t.role==="assistant"&&t.status==="IN_PROGRESS")return t}}const q=e=>{u||(u=e.conversationId)},P=e=>{const t=ee();t&&(t.content+=e.content,S())},G=e=>{const t=o.findIndex(d=>d.id===e.id||d.role==="assistant"&&d.status==="IN_PROGRESS");t>=0?o[t]=e:o.push(e),k=!1,S(),e.status==="DONE"&&C?.({id:e.id,content:e.content})},j=e=>{W=e.error,k=!1,o=o.filter(t=>!t.id.startsWith("temp-")),S()};l.on("message:start",q),l.on("message:chunk",P),l.on("message:complete",G),l.on("message:error",j);const te=(A??[]).map(e=>ae.createFormCollector(e).install(l));async function H(e,t){const d=e.trim();if(!d&&!t?.length||k)return;i?.(d),k=!0,W=null;const h=Date.now();o.push({id:`temp-user-${h}`,conversationId:u||"",content:d,role:"user",status:"DONE",createdAt:new Date}),o.push({id:`temp-assistant-${h}`,conversationId:u||"",content:"",role:"assistant",status:"IN_PROGRESS",createdAt:new Date}),S();try{const r=await l.sendMessage(d,{conversationId:u,files:t});r.conversationId&&(u||(u=r.conversationId),D?.setActiveThread(r.conversationId,r.createdAt));const p=o.find(a=>a.id===`temp-user-${h}`);if(p&&(p.id=`user-${h}`,p.conversationId=r.conversationId),!o.some(a=>a.id===r.id)){const a=o.findIndex(w=>w.id===`temp-assistant-${h}`);a>=0?o[a]=r:o.push(r)}o=o.filter(a=>!a.id.startsWith("temp-")),k=!1,S(),I&&U()}catch(r){W=r instanceof Error?r.message:"Failed to send message",k=!1,o=o.filter(p=>!p.id.startsWith("temp-")),S()}}async function U(){try{const e=await l.getConversations();F.splice(0,F.length,...e)}catch{}return F}async function ne(e){try{const t=(await l.getConversation(e))?.messages??[];t.length>0&&o.length===0&&(o=t,S())}catch{o.length===0&&(D?.clear(),u=void 0)}}return S(),X(),u&&ne(u),I&&U(),{client:l,element:z,sendMessage:H,session:D,threads:F,refreshThreads:U,destroy(){l.off("message:start",q),l.off("message:chunk",P),l.off("message:complete",G),l.off("message:error",j),te.forEach(e=>e()),z.remove(),n.client||l.destroy()}};function oe(){const e=c({display:"flex",gap:"4px",padding:"4px 0"});for(let t=0;t<3;t++){const d=c({width:"6px",height:"6px",borderRadius:"50%",backgroundColor:"#b5bfc8",animation:"ago-pulse 1.2s ease-in-out infinite",animationDelay:`${t*.2}s`});e.appendChild(d)}return e}}function ce(s){let n=[];const v=document.createElement("form");m(v,{display:"flex",flexDirection:"column",gap:"8px",padding:"12px",borderTop:"1px solid #dee3e8",backgroundColor:"#fff"}),v.className="ago-chat-input";const _=c({display:"flex",flexWrap:"wrap",gap:"6px"}),N=c({display:"flex",gap:"8px",alignItems:"flex-end"}),g=document.createElement("textarea");g.placeholder=s.placeholder,g.rows=1,m(g,{flex:"1",resize:"none",padding:"10px 12px",border:"1px solid #dee3e8",borderRadius:"12px",fontSize:"14px",fontFamily:Y,lineHeight:"1.4",maxHeight:"120px",outline:"none"});const x=document.createElement("button");x.type="submit",x.textContent="Send",m(x,{padding:"10px 16px",border:"none",borderRadius:"12px",backgroundColor:"#03182f",color:"#fff",fontSize:"14px",fontWeight:"500",cursor:"pointer"});let f=null,b=null;s.allowFiles&&(f=document.createElement("input"),f.type="file",f.multiple=!0,m(f,{display:"none"}),b=document.createElement("button"),b.type="button",b.textContent="📎",m(b,{padding:"10px 12px",border:"1px solid #dee3e8",borderRadius:"12px",backgroundColor:"#fff",cursor:"pointer",fontSize:"14px"}),b.addEventListener("click",()=>f?.click()),f.addEventListener("change",()=>{n=[...n,...Array.from(f?.files??[])],I()}));function I(){_.replaceChildren(),n.forEach((i,C)=>{const l=c({display:"flex",alignItems:"center",gap:"4px",padding:"4px 8px",backgroundColor:"#f0f4ff",border:"1px solid #dee3e8",borderRadius:"8px",fontSize:"12px"}),T=document.createElement("span");T.textContent=i.name;const y=document.createElement("button");y.type="button",y.textContent="×",m(y,{border:"none",background:"transparent",cursor:"pointer",fontSize:"14px",lineHeight:"1"}),y.addEventListener("click",()=>{n=n.filter((D,u)=>u!==C),I()}),l.append(T,y),_.appendChild(l)})}const A=()=>{const i=g.value,C=n;return g.value="",n=[],I(),{content:i,files:C}},R=()=>{const{content:i,files:C}=A();(i.trim()||C.length>0)&&s.onSend(i.trim(),C.length>0?C:void 0)};return v.addEventListener("submit",i=>{i.preventDefault(),R()}),g.addEventListener("keydown",i=>{i.key==="Enter"&&!i.shiftKey&&(i.preventDefault(),R())}),b&&N.appendChild(b),N.append(g,x),f&&v.appendChild(f),v.append(_,N),{inputRow:v,getValueAndClear:A,setDisabled:i=>{g.disabled=i,x.disabled=i,x.style.opacity=i?"0.6":"1",x.style.cursor=i?"not-allowed":"pointer"},focus:()=>g.focus()}}exports.mountChatWidget=de;
package/dist/widget.d.cts CHANGED
@@ -31,6 +31,12 @@ declare class AgoClient {
31
31
  * Get a specific conversation with messages
32
32
  */
33
33
  getConversation(conversationId: string): Promise<Conversation>;
34
+ /**
35
+ * Map the backend's snake_case `tool_call_data` entries onto the camelCase
36
+ * {@link ToolCallData} shape used everywhere else in the SDK. Mirrors
37
+ * `SSEHandler.parseToolCall` so persisted and streamed tool calls match.
38
+ */
39
+ private static mapPersistedToolCalls;
34
40
  /**
35
41
  * Get messages for a conversation
36
42
  */
@@ -141,6 +147,13 @@ declare class AgoClient {
141
147
  * Remove a dynamic context provider.
142
148
  */
143
149
  removeDynamicContext(key: string): boolean;
150
+ /**
151
+ * Broadcast that the client-side context changed by emitting `context:changed`
152
+ * with the fresh snapshot. The context-mutating methods call this automatically;
153
+ * stateful helpers whose context lives in a store (e.g. `createFormCollector`)
154
+ * call it when their store updates so observers like the dev panel stay live.
155
+ */
156
+ notifyContextChanged(): void;
144
157
  /**
145
158
  * Enable automatic capture of the current browser page (URL + title).
146
159
  * Injected as a dynamic context entry named `browser-page`.
@@ -186,6 +199,14 @@ declare class AgoClient {
186
199
  * SDK Events
187
200
  */
188
201
  declare interface AgoClientEvents {
202
+ /**
203
+ * The client-side context changed: a context entry or dynamic provider was
204
+ * added/removed, or a stateful helper (e.g. `createFormCollector`) updated its
205
+ * store. Carries the fresh snapshot so observers — notably the dev panel — can
206
+ * repaint without waiting for the next message. Fires for the form collector's
207
+ * initial install too, so its missing fields show from the start of a conversation.
208
+ */
209
+ "context:changed": ContextSnapshot | null;
189
210
  "message:start": {
190
211
  conversationId: string;
191
212
  messageId: string;
@@ -201,6 +222,12 @@ declare interface AgoClientEvents {
201
222
  conversationId?: string;
202
223
  messageId?: string;
203
224
  };
225
+ /**
226
+ * A full conversation was loaded from the server (e.g. after a page reload).
227
+ * Carries the messages and their persisted tool calls so stateful helpers —
228
+ * notably `createFormCollector` — can replay them to restore their state.
229
+ */
230
+ "conversation:loaded": Conversation;
204
231
  "toolCall:received": ToolCallData;
205
232
  "toolCall:form": ToolCallData;
206
233
  "function:invoke": ClientFunctionInvocation;
@@ -313,6 +340,20 @@ export declare interface ChatWidgetHandle {
313
340
  element: HTMLElement;
314
341
  /** Programmatically send a message (same path as the input). */
315
342
  sendMessage: (content: string, files?: File[]) => Promise<void>;
343
+ /**
344
+ * The conversation-persistence session, present only when `persistConversation`
345
+ * is set — exposes the stable `widgetId` and `session.clear()` to start a new thread.
346
+ */
347
+ session?: ConversationSession;
348
+ /**
349
+ * The visitor's conversations (threads) — the vanilla equivalent of the React/Vue
350
+ * `useConversation().conversations`. Auto-loaded on mount and refreshed after each
351
+ * turn only when `loadThreads` is set; otherwise it stays empty until you call
352
+ * {@link ChatWidgetHandle.refreshThreads}.
353
+ */
354
+ readonly threads: Conversation[];
355
+ /** Re-fetch the conversations list and update {@link ChatWidgetHandle.threads}. */
356
+ refreshThreads: () => Promise<Conversation[]>;
316
357
  /** Remove listeners, uninstall forms, and clear the DOM. */
317
358
  destroy: () => void;
318
359
  }
@@ -400,6 +441,60 @@ declare interface Conversation {
400
441
  messages?: AgoMessage[];
401
442
  }
402
443
 
444
+ export declare interface ConversationSession {
445
+ /**
446
+ * The stable widget id identifying this visitor. Generated once and persisted
447
+ * forever, so a returning visitor keeps the same id (and therefore the same
448
+ * server-side conversation history) across reloads.
449
+ */
450
+ readonly widgetId: string;
451
+ /**
452
+ * The last active thread id, decided **entirely on the front** so a reload costs
453
+ * no backend round-trip. Returns `null` when there is no cached thread, when its
454
+ * last message time is unknown, or when it has been idle longer than `ttlMs`.
455
+ */
456
+ getLastActiveThread(): string | null;
457
+ /**
458
+ * Record (and refresh) the active thread together with the time of its last
459
+ * message — call it once per turn. The `lastMessageAt` drives the sliding TTL;
460
+ * omit it and the thread won't be resumed (no time → not displayed).
461
+ */
462
+ setActiveThread(conversationId: string, lastMessageAt?: Date | number): void;
463
+ /** Forget the cached thread — e.g. on "new chat". */
464
+ clear(): void;
465
+ /** The resolved storage key holding the widget id — handy for debugging. */
466
+ readonly key: string;
467
+ }
468
+
469
+ export declare interface ConversationSessionOptions {
470
+ /**
471
+ * Storage backend. Defaults to `localStorage`, degrading to an in-memory store
472
+ * when storage is unavailable (SSR / disabled). Pass `sessionStorage` for a
473
+ * tab-scoped session, or any `{ getItem, setItem }` for a custom backend.
474
+ */
475
+ storage?: StorageLike;
476
+ /**
477
+ * Storage key for the widget id. Default `"ago_widget_id"` — the same key the
478
+ * HTTP client reads, so the visitor's id is shared across the SDK.
479
+ */
480
+ key?: string;
481
+ /**
482
+ * Explicit widget id to adopt (and persist) instead of reading or generating
483
+ * one — e.g. when the host app already has a stable visitor identifier.
484
+ */
485
+ widgetId?: string;
486
+ /** Storage key for the cached last active thread. Default `"ago_last_thread"`. */
487
+ threadKey?: string;
488
+ /**
489
+ * Maximum idle age of a thread to still resume it, in milliseconds, compared
490
+ * against the recorded time of its last message. Checked entirely on the front
491
+ * (no backend call): a thread idle longer than this — or one with no recorded
492
+ * last-message time — is not resumed (`getLastActiveThread` returns `null`).
493
+ * Default 2h. Pass `Infinity` to never expire.
494
+ */
495
+ ttlMs?: number;
496
+ }
497
+
403
498
  export declare interface CreateFormCollectorOptions<V = Record<string, unknown>> {
404
499
  /** Short identifier; becomes the `update_<name>` / `submit_<name>` function names. */
405
500
  name: string;
@@ -488,6 +583,15 @@ export declare interface MountChatWidgetOptions {
488
583
  config?: AgoConfig;
489
584
  /** Initial conversation ID to continue. */
490
585
  conversationId?: string;
586
+ /**
587
+ * Resume the visitor's last active thread across reloads. `true` enables defaults
588
+ * (localStorage, widget id under `ago_widget_id`); pass an object to set `storage`
589
+ * (e.g. `sessionStorage`), `key`, or an explicit `widgetId`. Built on
590
+ * {@link createConversationSession}: the visitor is identified by a single stable
591
+ * widget id and the backend hands back their most recently updated conversation.
592
+ * An explicit `conversationId` still wins as the initial thread. Off by default.
593
+ */
594
+ persistConversation?: boolean | Partial<ConversationSessionOptions>;
491
595
  /** Widget title shown in the header. */
492
596
  title?: string;
493
597
  /** Message shown before any conversation has started. */
@@ -502,6 +606,13 @@ export declare interface MountChatWidgetOptions {
502
606
  logoUrl?: string;
503
607
  /** Show the agent name above assistant messages. Defaults to `false`. */
504
608
  showAgentName?: boolean;
609
+ /**
610
+ * Load the visitor's conversation list into `widget.threads` on mount and refresh
611
+ * it after each turn (one `GET /conversations` per load). Off by default to avoid
612
+ * the request when the integrator doesn't need it; `widget.refreshThreads()` stays
613
+ * callable on demand regardless.
614
+ */
615
+ loadThreads?: boolean;
505
616
  /**
506
617
  * Conversational forms the agent can fill and submit during the chat. Each
507
618
  * config is installed as a {@link createFormCollector} for the lifetime of the
@@ -535,6 +646,9 @@ declare interface SendMessageOptions {
535
646
  files?: File[];
536
647
  }
537
648
 
649
+ /** The slice of the Web Storage API that {@link createStore} relies on. */
650
+ declare type StorageLike = Pick<Storage, "getItem" | "setItem">;
651
+
538
652
  /**
539
653
  * Where a completed form is sent.
540
654
  * - `"https://..."` (a bare string) — shorthand for `{ via: "client", url }`: the browser
package/dist/widget.d.ts CHANGED
@@ -31,6 +31,12 @@ declare class AgoClient {
31
31
  * Get a specific conversation with messages
32
32
  */
33
33
  getConversation(conversationId: string): Promise<Conversation>;
34
+ /**
35
+ * Map the backend's snake_case `tool_call_data` entries onto the camelCase
36
+ * {@link ToolCallData} shape used everywhere else in the SDK. Mirrors
37
+ * `SSEHandler.parseToolCall` so persisted and streamed tool calls match.
38
+ */
39
+ private static mapPersistedToolCalls;
34
40
  /**
35
41
  * Get messages for a conversation
36
42
  */
@@ -141,6 +147,13 @@ declare class AgoClient {
141
147
  * Remove a dynamic context provider.
142
148
  */
143
149
  removeDynamicContext(key: string): boolean;
150
+ /**
151
+ * Broadcast that the client-side context changed by emitting `context:changed`
152
+ * with the fresh snapshot. The context-mutating methods call this automatically;
153
+ * stateful helpers whose context lives in a store (e.g. `createFormCollector`)
154
+ * call it when their store updates so observers like the dev panel stay live.
155
+ */
156
+ notifyContextChanged(): void;
144
157
  /**
145
158
  * Enable automatic capture of the current browser page (URL + title).
146
159
  * Injected as a dynamic context entry named `browser-page`.
@@ -186,6 +199,14 @@ declare class AgoClient {
186
199
  * SDK Events
187
200
  */
188
201
  declare interface AgoClientEvents {
202
+ /**
203
+ * The client-side context changed: a context entry or dynamic provider was
204
+ * added/removed, or a stateful helper (e.g. `createFormCollector`) updated its
205
+ * store. Carries the fresh snapshot so observers — notably the dev panel — can
206
+ * repaint without waiting for the next message. Fires for the form collector's
207
+ * initial install too, so its missing fields show from the start of a conversation.
208
+ */
209
+ "context:changed": ContextSnapshot | null;
189
210
  "message:start": {
190
211
  conversationId: string;
191
212
  messageId: string;
@@ -201,6 +222,12 @@ declare interface AgoClientEvents {
201
222
  conversationId?: string;
202
223
  messageId?: string;
203
224
  };
225
+ /**
226
+ * A full conversation was loaded from the server (e.g. after a page reload).
227
+ * Carries the messages and their persisted tool calls so stateful helpers —
228
+ * notably `createFormCollector` — can replay them to restore their state.
229
+ */
230
+ "conversation:loaded": Conversation;
204
231
  "toolCall:received": ToolCallData;
205
232
  "toolCall:form": ToolCallData;
206
233
  "function:invoke": ClientFunctionInvocation;
@@ -313,6 +340,20 @@ export declare interface ChatWidgetHandle {
313
340
  element: HTMLElement;
314
341
  /** Programmatically send a message (same path as the input). */
315
342
  sendMessage: (content: string, files?: File[]) => Promise<void>;
343
+ /**
344
+ * The conversation-persistence session, present only when `persistConversation`
345
+ * is set — exposes the stable `widgetId` and `session.clear()` to start a new thread.
346
+ */
347
+ session?: ConversationSession;
348
+ /**
349
+ * The visitor's conversations (threads) — the vanilla equivalent of the React/Vue
350
+ * `useConversation().conversations`. Auto-loaded on mount and refreshed after each
351
+ * turn only when `loadThreads` is set; otherwise it stays empty until you call
352
+ * {@link ChatWidgetHandle.refreshThreads}.
353
+ */
354
+ readonly threads: Conversation[];
355
+ /** Re-fetch the conversations list and update {@link ChatWidgetHandle.threads}. */
356
+ refreshThreads: () => Promise<Conversation[]>;
316
357
  /** Remove listeners, uninstall forms, and clear the DOM. */
317
358
  destroy: () => void;
318
359
  }
@@ -400,6 +441,60 @@ declare interface Conversation {
400
441
  messages?: AgoMessage[];
401
442
  }
402
443
 
444
+ export declare interface ConversationSession {
445
+ /**
446
+ * The stable widget id identifying this visitor. Generated once and persisted
447
+ * forever, so a returning visitor keeps the same id (and therefore the same
448
+ * server-side conversation history) across reloads.
449
+ */
450
+ readonly widgetId: string;
451
+ /**
452
+ * The last active thread id, decided **entirely on the front** so a reload costs
453
+ * no backend round-trip. Returns `null` when there is no cached thread, when its
454
+ * last message time is unknown, or when it has been idle longer than `ttlMs`.
455
+ */
456
+ getLastActiveThread(): string | null;
457
+ /**
458
+ * Record (and refresh) the active thread together with the time of its last
459
+ * message — call it once per turn. The `lastMessageAt` drives the sliding TTL;
460
+ * omit it and the thread won't be resumed (no time → not displayed).
461
+ */
462
+ setActiveThread(conversationId: string, lastMessageAt?: Date | number): void;
463
+ /** Forget the cached thread — e.g. on "new chat". */
464
+ clear(): void;
465
+ /** The resolved storage key holding the widget id — handy for debugging. */
466
+ readonly key: string;
467
+ }
468
+
469
+ export declare interface ConversationSessionOptions {
470
+ /**
471
+ * Storage backend. Defaults to `localStorage`, degrading to an in-memory store
472
+ * when storage is unavailable (SSR / disabled). Pass `sessionStorage` for a
473
+ * tab-scoped session, or any `{ getItem, setItem }` for a custom backend.
474
+ */
475
+ storage?: StorageLike;
476
+ /**
477
+ * Storage key for the widget id. Default `"ago_widget_id"` — the same key the
478
+ * HTTP client reads, so the visitor's id is shared across the SDK.
479
+ */
480
+ key?: string;
481
+ /**
482
+ * Explicit widget id to adopt (and persist) instead of reading or generating
483
+ * one — e.g. when the host app already has a stable visitor identifier.
484
+ */
485
+ widgetId?: string;
486
+ /** Storage key for the cached last active thread. Default `"ago_last_thread"`. */
487
+ threadKey?: string;
488
+ /**
489
+ * Maximum idle age of a thread to still resume it, in milliseconds, compared
490
+ * against the recorded time of its last message. Checked entirely on the front
491
+ * (no backend call): a thread idle longer than this — or one with no recorded
492
+ * last-message time — is not resumed (`getLastActiveThread` returns `null`).
493
+ * Default 2h. Pass `Infinity` to never expire.
494
+ */
495
+ ttlMs?: number;
496
+ }
497
+
403
498
  export declare interface CreateFormCollectorOptions<V = Record<string, unknown>> {
404
499
  /** Short identifier; becomes the `update_<name>` / `submit_<name>` function names. */
405
500
  name: string;
@@ -488,6 +583,15 @@ export declare interface MountChatWidgetOptions {
488
583
  config?: AgoConfig;
489
584
  /** Initial conversation ID to continue. */
490
585
  conversationId?: string;
586
+ /**
587
+ * Resume the visitor's last active thread across reloads. `true` enables defaults
588
+ * (localStorage, widget id under `ago_widget_id`); pass an object to set `storage`
589
+ * (e.g. `sessionStorage`), `key`, or an explicit `widgetId`. Built on
590
+ * {@link createConversationSession}: the visitor is identified by a single stable
591
+ * widget id and the backend hands back their most recently updated conversation.
592
+ * An explicit `conversationId` still wins as the initial thread. Off by default.
593
+ */
594
+ persistConversation?: boolean | Partial<ConversationSessionOptions>;
491
595
  /** Widget title shown in the header. */
492
596
  title?: string;
493
597
  /** Message shown before any conversation has started. */
@@ -502,6 +606,13 @@ export declare interface MountChatWidgetOptions {
502
606
  logoUrl?: string;
503
607
  /** Show the agent name above assistant messages. Defaults to `false`. */
504
608
  showAgentName?: boolean;
609
+ /**
610
+ * Load the visitor's conversation list into `widget.threads` on mount and refresh
611
+ * it after each turn (one `GET /conversations` per load). Off by default to avoid
612
+ * the request when the integrator doesn't need it; `widget.refreshThreads()` stays
613
+ * callable on demand regardless.
614
+ */
615
+ loadThreads?: boolean;
505
616
  /**
506
617
  * Conversational forms the agent can fill and submit during the chat. Each
507
618
  * config is installed as a {@link createFormCollector} for the lifetime of the
@@ -535,6 +646,9 @@ declare interface SendMessageOptions {
535
646
  files?: File[];
536
647
  }
537
648
 
649
+ /** The slice of the Web Storage API that {@link createStore} relies on. */
650
+ declare type StorageLike = Pick<Storage, "getItem" | "setItem">;
651
+
538
652
  /**
539
653
  * Where a completed form is sent.
540
654
  * - `"https://..."` (a bare string) — shorthand for `{ via: "client", url }`: the browser