@useago/sdk 1.5.6 → 1.7.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +223 -0
  2. package/README.md +59 -0
  3. package/dist/AgoClient-BHegN73y.cjs +9 -0
  4. package/dist/{AgoClient-B_R_qi2s.js → AgoClient-PWPaekcg.js} +656 -387
  5. package/dist/angular.cjs +1 -1
  6. package/dist/angular.d.cts +230 -5
  7. package/dist/angular.d.ts +230 -5
  8. package/dist/angular.js +10 -1
  9. package/dist/{createConversationSession-Dqh7NXK4.cjs → createConversationSession-BbUUukV0.cjs} +1 -1
  10. package/dist/{createConversationSession-CdtI4vrf.js → createConversationSession-BkruoOVm.js} +1 -1
  11. package/dist/{createFormCollector-BjO3oB63.cjs → createFormCollector-C1ixsjV0.cjs} +1 -1
  12. package/dist/{createFormCollector-YwaZ78wU.js → createFormCollector-CvVFNkp6.js} +1 -1
  13. package/dist/{createMockClient-DcuNlufr.js → createMockClient-BDLW6tVc.js} +5 -2
  14. package/dist/createMockClient-BSNuN3ys.cjs +1 -0
  15. package/dist/devtools.d.cts +220 -5
  16. package/dist/devtools.d.ts +220 -5
  17. package/dist/index.cjs +1 -1
  18. package/dist/index.d.cts +238 -6
  19. package/dist/index.d.ts +238 -6
  20. package/dist/index.js +111 -109
  21. package/dist/react.cjs +40 -3
  22. package/dist/react.d.cts +419 -7
  23. package/dist/react.d.ts +419 -7
  24. package/dist/react.js +1195 -743
  25. package/dist/sheetController-CBUVOroo.js +205 -0
  26. package/dist/sheetController-DY-sC13T.cjs +1 -0
  27. package/dist/testing.cjs +1 -1
  28. package/dist/testing.d.cts +220 -5
  29. package/dist/testing.d.ts +220 -5
  30. package/dist/testing.js +1 -1
  31. package/dist/vue.cjs +1 -1
  32. package/dist/vue.d.cts +235 -5
  33. package/dist/vue.d.ts +235 -5
  34. package/dist/vue.js +121 -100
  35. package/dist/widget.cjs +4 -4
  36. package/dist/widget.d.cts +328 -5
  37. package/dist/widget.d.ts +328 -5
  38. package/dist/widget.js +721 -629
  39. package/llms-full.txt +202 -14
  40. package/package.json +1 -1
  41. package/dist/AgoClient-DLB9icvv.cjs +0 -8
  42. package/dist/createMockClient-Dw-cyISN.cjs +0 -1
@@ -1 +1 @@
1
- const C=require("./AgoClient-DLB9icvv.cjs");var W=/^[a-zA-Z0-9_-]{1,40}$/,A=n=>n==null||n==="";function S(n,e){if("anyOf"in n)return n.anyOf.some(c=>S(c,e));if("allOf"in n)return n.allOf.every(c=>S(c,e));const l=e[n.property];if(A(l))return!1;if(Array.isArray(n.value))return n.value.some(c=>String(l)===String(c));if(!n.op)return String(l)===String(n.value);const i=Number(l),a=Number(n.value);if(Number.isNaN(i)||Number.isNaN(a))return!1;switch(n.op){case">=":return i>=a;case"<=":return i<=a;case">":return i>a;case"<":return i<a;default:return C.logger.warn(`Unknown requiredWhen op "${n.op}"; treating as not required.`),!1}}function h(n,e){const l=n.properties??{},i=new Set;for(const c of n.required??[]){const d=l[c]?.requiredWhen;(!d||S(d,e))&&i.add(c)}for(const[c,d]of Object.entries(l))d.requiredWhen&&S(d.requiredWhen,e)&&i.add(c);const a=[...i].filter(c=>A(e[c]));return{missing:a,complete:a.length===0}}function B(n){const e={};for(const[l,i]of Object.entries(n.properties??{}))e[l]={type:i.type,...i.description!=null&&{description:i.description},...i.enum&&{enum:i.enum},...i.default!==void 0&&{default:i.default}};return{type:"object",properties:e,required:[]}}function V(n){const{name:e,description:l,schema:i}=n;if(!W.test(e))throw new Error(`Invalid form collector name "${e}": must match ${W} (it becomes the update_/submit_ function names).`);const a=typeof n.submit=="string"?n.submit?{via:"client",url:n.submit}:!1:n.submit??!1;n.autoSubmit===!0&&!a&&C.logger.warn(`FormCollector "${e}": autoSubmit is ignored because no submit target is configured.`);const c=(n.autoSubmit??!0)&&!!a,d=i.properties??{},$=t=>{const r={};for(const[o,s]of Object.entries(t)){const u=d[o];if(u)if(s==null)r[o]=s;else if(u.type==="number"||u.type==="integer"){const f=typeof s=="number"?s:Number(s);r[o]=Number.isNaN(f)?s:f}else u.type==="boolean"?r[o]=typeof s=="boolean"?s:s==="true"?!0:s==="false"?!1:!!s:r[o]=s}return r},N=n.initialValues??{},m=C.createStore({values:N,submitted:!1}),w=t=>{const r=m.get(),o={...r,values:{...r.values,...t}};return m.set(o),o};let b=null;const k=(t,r)=>{b?.emitFormEvent("form:error",{name:e,values:t,error:r})};let p=null;const I=async()=>{const{values:t}=m.get(),{complete:r,missing:o}=h(i,t);if(!r)return{ok:!1,missing:o};if(!a)return{ok:!1,error:"No submit target configured for this form collector."};const s=t;try{let u;if(a.via==="client")if("handler"in a)u=await a.handler(s);else{const f=await fetch(a.url,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify(s)});if(!f.ok){const j=`Submit failed: HTTP ${f.status}`;return k(s,j),{ok:!1,error:j}}u=await f.json().catch(()=>{})}else{if(!b)return{ok:!1,error:"Backend submit requires install(client) (or useFormCollector) first."};u=await b.submitFormCollector(e,s)}return m.set({...m.get(),submitted:!0,submitResult:u}),b?.emitFormEvent("form:submitted",{name:e,values:s,result:u}),{ok:!0,result:u}}catch(u){const f=u instanceof Error?u.message:"Submit failed";return C.logger.error(`FormCollector "${e}" submit failed:`,u),k(s,f),{ok:!1,error:f}}},F=()=>p||(p=(async()=>{try{return await I()}finally{p=null}})(),p),q=async()=>{if(!c||!a)return null;const{values:t,submitted:r}=m.get();return r||!h(i,t).complete?null:F()},O={name:`update_${e}`,description:`Fill or update fields of the "${e}" form. ${l} Pass any subset of fields; call it repeatedly to fill the form as the user provides information. Returns the current values and which required fields are still missing.`,parameters:B(i),handler:async t=>{const r=w($(t)),{missing:o}=h(i,r.values),s=await q();return s?s.ok?{ok:!0,values:r.values,missing:o,submitted:!0}:{ok:!0,values:r.values,missing:o,submitted:!1,submitError:s.error}:{ok:!0,values:r.values,missing:o}}},y=[O];a&&!c&&y.push({name:`submit_${e}`,description:`Submit the completed "${e}" form. Only call this once every required field is collected and the user has confirmed. If fields are missing it returns them instead of submitting.`,parameters:{type:"object",properties:{}},handler:()=>F()});const D="client__",_=a?`submit_${e}`:null,R=t=>{const r=t.functionName??t.toolName;if(r)return r.startsWith(D)?r.slice(8):r},x=t=>{let r={...N},o=!1;for(const s of t){const u=R(s);u===O.name?s.arguments&&(r={...r,...$(s.arguments)}):_&&u===_&&s.status?.toLowerCase()==="done"&&(o=!0)}m.set({values:r,submitted:o})},g=`form:${e}`,v=`${g}:definition`,P=()=>({name:`Form definition: ${e}`,description:`${l} Some fields are conditionally required; the requiredWhen field in the schema tells you when each becomes required. The form schema is the following: ${JSON.stringify(i)}.`,stable:!0}),T=()=>{const{values:t,submitted:r}=m.get(),{missing:o,complete:s}=h(i,t),u=a?c?" The form is submitted automatically as soon as every required field is filled, so there is no submit function to call.":` When nothing is missing and the user confirms, call submit_${e}.`:"",f=r?`The "${e}" form has been submitted.`:`You are currently collecting information from the user to fill the "${e}" form.`;return{name:`Form: ${e}`,description:`${f} The form's schema and field requirements are in the "${v}" client context. Call update_${e} with any fields the user provides; ask only for what is still missing.${u}`,data:{values:t,missing:o,complete:s,submitted:r}}},E=t=>{x((t.messages??[]).flatMap(r=>r.toolCalls??[]))};return{name:e,store:m,functions:y,contextKey:g,contextProvider:T,definitionContextKey:v,definitionContextProvider:P,install:t=>{b=t;for(const o of y)t.register(o);t.addDynamicContext(v,P),t.addDynamicContext(g,T);const r=m.subscribe(()=>t.notifyContextChanged());return t.on("conversation:loaded",E),()=>{r();for(const o of y)t.unregisterFunction(o.name);t.removeDynamicContext(g),t.removeDynamicContext(v),t.off("conversation:loaded",E),b===t&&(b=null)}},hydrate:x,submit:F,getValues:()=>m.get().values,setValues:t=>{w($(t)),q()},reset:()=>{m.set({values:N,submitted:!1})},getStatus:()=>h(i,m.get().values)}}async function L(n,e){const l=await n.getFormCollector(e.name);return V({name:l.name,description:e.description??l.description,schema:e.schema??l.schema,submit:e.submit??l.submit,initialValues:e.initialValues,autoSubmit:e.autoSubmit??l.autoSubmit})}Object.defineProperty(exports,"createFormCollector",{enumerable:!0,get:function(){return V}});Object.defineProperty(exports,"deriveFormStatus",{enumerable:!0,get:function(){return h}});Object.defineProperty(exports,"loadFormCollector",{enumerable:!0,get:function(){return L}});
1
+ const C=require("./AgoClient-BHegN73y.cjs");var W=/^[a-zA-Z0-9_-]{1,40}$/,A=n=>n==null||n==="";function S(n,e){if("anyOf"in n)return n.anyOf.some(c=>S(c,e));if("allOf"in n)return n.allOf.every(c=>S(c,e));const l=e[n.property];if(A(l))return!1;if(Array.isArray(n.value))return n.value.some(c=>String(l)===String(c));if(!n.op)return String(l)===String(n.value);const i=Number(l),a=Number(n.value);if(Number.isNaN(i)||Number.isNaN(a))return!1;switch(n.op){case">=":return i>=a;case"<=":return i<=a;case">":return i>a;case"<":return i<a;default:return C.logger.warn(`Unknown requiredWhen op "${n.op}"; treating as not required.`),!1}}function h(n,e){const l=n.properties??{},i=new Set;for(const c of n.required??[]){const d=l[c]?.requiredWhen;(!d||S(d,e))&&i.add(c)}for(const[c,d]of Object.entries(l))d.requiredWhen&&S(d.requiredWhen,e)&&i.add(c);const a=[...i].filter(c=>A(e[c]));return{missing:a,complete:a.length===0}}function B(n){const e={};for(const[l,i]of Object.entries(n.properties??{}))e[l]={type:i.type,...i.description!=null&&{description:i.description},...i.enum&&{enum:i.enum},...i.default!==void 0&&{default:i.default}};return{type:"object",properties:e,required:[]}}function V(n){const{name:e,description:l,schema:i}=n;if(!W.test(e))throw new Error(`Invalid form collector name "${e}": must match ${W} (it becomes the update_/submit_ function names).`);const a=typeof n.submit=="string"?n.submit?{via:"client",url:n.submit}:!1:n.submit??!1;n.autoSubmit===!0&&!a&&C.logger.warn(`FormCollector "${e}": autoSubmit is ignored because no submit target is configured.`);const c=(n.autoSubmit??!0)&&!!a,d=i.properties??{},$=t=>{const r={};for(const[o,s]of Object.entries(t)){const u=d[o];if(u)if(s==null)r[o]=s;else if(u.type==="number"||u.type==="integer"){const f=typeof s=="number"?s:Number(s);r[o]=Number.isNaN(f)?s:f}else u.type==="boolean"?r[o]=typeof s=="boolean"?s:s==="true"?!0:s==="false"?!1:!!s:r[o]=s}return r},N=n.initialValues??{},m=C.createStore({values:N,submitted:!1}),w=t=>{const r=m.get(),o={...r,values:{...r.values,...t}};return m.set(o),o};let b=null;const k=(t,r)=>{b?.emitFormEvent("form:error",{name:e,values:t,error:r})};let p=null;const I=async()=>{const{values:t}=m.get(),{complete:r,missing:o}=h(i,t);if(!r)return{ok:!1,missing:o};if(!a)return{ok:!1,error:"No submit target configured for this form collector."};const s=t;try{let u;if(a.via==="client")if("handler"in a)u=await a.handler(s);else{const f=await fetch(a.url,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify(s)});if(!f.ok){const j=`Submit failed: HTTP ${f.status}`;return k(s,j),{ok:!1,error:j}}u=await f.json().catch(()=>{})}else{if(!b)return{ok:!1,error:"Backend submit requires install(client) (or useFormCollector) first."};u=await b.submitFormCollector(e,s)}return m.set({...m.get(),submitted:!0,submitResult:u}),b?.emitFormEvent("form:submitted",{name:e,values:s,result:u}),{ok:!0,result:u}}catch(u){const f=u instanceof Error?u.message:"Submit failed";return C.logger.error(`FormCollector "${e}" submit failed:`,u),k(s,f),{ok:!1,error:f}}},F=()=>p||(p=(async()=>{try{return await I()}finally{p=null}})(),p),q=async()=>{if(!c||!a)return null;const{values:t,submitted:r}=m.get();return r||!h(i,t).complete?null:F()},O={name:`update_${e}`,description:`Fill or update fields of the "${e}" form. ${l} Pass any subset of fields; call it repeatedly to fill the form as the user provides information. Returns the current values and which required fields are still missing.`,parameters:B(i),handler:async t=>{const r=w($(t)),{missing:o}=h(i,r.values),s=await q();return s?s.ok?{ok:!0,values:r.values,missing:o,submitted:!0}:{ok:!0,values:r.values,missing:o,submitted:!1,submitError:s.error}:{ok:!0,values:r.values,missing:o}}},y=[O];a&&!c&&y.push({name:`submit_${e}`,description:`Submit the completed "${e}" form. Only call this once every required field is collected and the user has confirmed. If fields are missing it returns them instead of submitting.`,parameters:{type:"object",properties:{}},handler:()=>F()});const D="client__",_=a?`submit_${e}`:null,R=t=>{const r=t.functionName??t.toolName;if(r)return r.startsWith(D)?r.slice(8):r},x=t=>{let r={...N},o=!1;for(const s of t){const u=R(s);u===O.name?s.arguments&&(r={...r,...$(s.arguments)}):_&&u===_&&s.status?.toLowerCase()==="done"&&(o=!0)}m.set({values:r,submitted:o})},g=`form:${e}`,v=`${g}:definition`,P=()=>({name:`Form definition: ${e}`,description:`${l} Some fields are conditionally required; the requiredWhen field in the schema tells you when each becomes required. The form schema is the following: ${JSON.stringify(i)}.`,stable:!0}),T=()=>{const{values:t,submitted:r}=m.get(),{missing:o,complete:s}=h(i,t),u=a?c?" The form is submitted automatically as soon as every required field is filled, so there is no submit function to call.":` When nothing is missing and the user confirms, call submit_${e}.`:"",f=r?`The "${e}" form has been submitted.`:`You are currently collecting information from the user to fill the "${e}" form.`;return{name:`Form: ${e}`,description:`${f} The form's schema and field requirements are in the "${v}" client context. Call update_${e} with any fields the user provides; ask only for what is still missing.${u}`,data:{values:t,missing:o,complete:s,submitted:r}}},E=t=>{x((t.messages??[]).flatMap(r=>r.toolCalls??[]))};return{name:e,store:m,functions:y,contextKey:g,contextProvider:T,definitionContextKey:v,definitionContextProvider:P,install:t=>{b=t;for(const o of y)t.register(o);t.addDynamicContext(v,P),t.addDynamicContext(g,T);const r=m.subscribe(()=>t.notifyContextChanged());return t.on("conversation:loaded",E),()=>{r();for(const o of y)t.unregisterFunction(o.name);t.removeDynamicContext(g),t.removeDynamicContext(v),t.off("conversation:loaded",E),b===t&&(b=null)}},hydrate:x,submit:F,getValues:()=>m.get().values,setValues:t=>{w($(t)),q()},reset:()=>{m.set({values:N,submitted:!1})},getStatus:()=>h(i,m.get().values)}}async function L(n,e){const l=await n.getFormCollector(e.name);return V({name:l.name,description:e.description??l.description,schema:e.schema??l.schema,submit:e.submit??l.submit,initialValues:e.initialValues,autoSubmit:e.autoSubmit??l.autoSubmit})}Object.defineProperty(exports,"createFormCollector",{enumerable:!0,get:function(){return V}});Object.defineProperty(exports,"deriveFormStatus",{enumerable:!0,get:function(){return h}});Object.defineProperty(exports,"loadFormCollector",{enumerable:!0,get:function(){return L}});
@@ -1,4 +1,4 @@
1
- import { h as R, v as w } from "./AgoClient-B_R_qi2s.js";
1
+ import { h as R, v as w } from "./AgoClient-PWPaekcg.js";
2
2
  var j = /^[a-zA-Z0-9_-]{1,40}$/, V = (s) => s == null || s === "";
3
3
  function S(s, e) {
4
4
  if ("anyOf" in s) return s.anyOf.some((c) => S(c, e));
@@ -1,4 +1,4 @@
1
- function v(g = {}) {
1
+ function y(g = {}) {
2
2
  const { overrides: u = {} } = g, a = {
3
3
  id: "mock-msg-1",
4
4
  conversationId: "mock-conv-1",
@@ -13,6 +13,9 @@ function v(g = {}) {
13
13
  }, o = /* @__PURE__ */ new Map(), c = [], m = {
14
14
  sendMessage: async () => a,
15
15
  continueMessage: async () => a,
16
+ stop: async () => null,
17
+ stopMessage: async () => ({ status: "not_running" }),
18
+ isGenerating: () => !1,
16
19
  resumePendingClientFunctions: async () => null,
17
20
  registerResumeGate: () => () => {
18
21
  },
@@ -93,5 +96,5 @@ function v(g = {}) {
93
96
  }, i;
94
97
  }
95
98
  export {
96
- v as t
99
+ y as t
97
100
  };
@@ -0,0 +1 @@
1
+ function y(u={}){const{overrides:g={}}=u,a={id:"mock-msg-1",conversationId:"mock-conv-1",content:"Mock response",role:"assistant",status:"DONE",createdAt:new Date},l={id:"mock-conv-1",title:"Mock Conversation",lastMessageDate:new Date},n=new Map,r=[],m={sendMessage:async()=>a,continueMessage:async()=>a,stop:async()=>null,stopMessage:async()=>({status:"not_running"}),isGenerating:()=>!1,resumePendingClientFunctions:async()=>null,registerResumeGate:()=>()=>{},getConversations:async()=>[l],getConversation:async()=>l,getMessages:async()=>[a],submitToolCallForm:async()=>({status:"completed"}),confirmToolCall:async()=>{},rejectToolCall:async()=>{},submitFeedback:async()=>{},registerFunction:()=>{},unregisterFunction:()=>!0,getRegisteredFunctions:()=>[],executeClientFunction:async()=>{},getContextSnapshot:()=>null,notifyContextChanged:()=>{},setContext:()=>{},removeContext:()=>!0,addDynamicContext:()=>{},removeDynamicContext:()=>!0,registerNavigationFunction:()=>{},unregisterNavigationFunction:()=>{},getConfiguredAgent:()=>{},getHttp:()=>({get:async()=>({}),post:async()=>({})}),emitProactiveEvent:(e,t)=>{const o=n.get(e);o&&[...o].forEach(s=>s(t))},on:(e,t)=>{n.has(e)||n.set(e,new Set),n.get(e).add(t)},off:(e,t)=>{n.get(e)?.delete(t)},once:(e,t)=>{const o=(...s)=>{n.get(e)?.delete(o),t(...s)};n.has(e)||n.set(e,new Set),n.get(e).add(o)},waitFor:(e,t)=>new Promise((o,s)=>{let c;const d=f=>{c&&clearTimeout(c),n.get(e)?.delete(d),o(f)};n.has(e)||n.set(e,new Set),n.get(e).add(d),t?.timeout&&(c=setTimeout(()=>{n.get(e)?.delete(d),s(new Error(`waitFor("${e}") timed out after ${t.timeout}ms`))},t.timeout))}),updateConfig:()=>{},destroy:()=>{n.clear()},...g},i={};for(const[e,t]of Object.entries(m))i[e]=(...o)=>(r.push({method:e,args:o}),t(...o));return i.proactive=null,i.__calls=r,i.__callsFor=e=>r.filter(t=>t.method===e),i.__emitEvent=(e,t)=>{const o=n.get(e);o&&[...o].forEach(s=>s(t))},i}Object.defineProperty(exports,"createMockClient",{enumerable:!0,get:function(){return y}});
@@ -57,6 +57,10 @@ declare class AgoClient {
57
57
  private config;
58
58
  /** Conversations already warned about an empty reply (one warning each). */
59
59
  private warnedEmptyConversations;
60
+ /** Whether the placeholder-mode page-data warning has already been logged. */
61
+ private warnedPageDataPlaceholder;
62
+ /** `readPageData` companions this SDK registered, so cleanup only removes its own. */
63
+ private pageDataCompanions;
60
64
  /** Paused turns awaiting auto-resume, keyed by assistant message id. */
61
65
  private pendingResumes;
62
66
  /**
@@ -67,6 +71,20 @@ declare class AgoClient {
67
71
  private pendingApprovals;
68
72
  /** App-provided gate deciding WHEN a paused turn resumes (see {@link registerResumeGate}). */
69
73
  private resumeGate;
74
+ /** The turn currently streaming, or `null` when the client is idle. */
75
+ private activeTurn;
76
+ /**
77
+ * A turn that closed as `WAITING_CLIENT` and has not resumed yet. There is no
78
+ * open stream in that window, but the turn is still live for the user, so it
79
+ * stays stoppable (see {@link stop}).
80
+ */
81
+ private pausedTurn;
82
+ /**
83
+ * Turns stopped while they were paused on client functions. A resume may
84
+ * already be queued (or waiting on the resume gate) at that point; this drops
85
+ * it, so a stopped turn is never restarted.
86
+ */
87
+ private stoppedTurnIds;
70
88
  /**
71
89
  * Proactive-mode controller, set when the client was created with
72
90
  * `proactive` options or when `createAgoProactive(client, opts)` was called.
@@ -78,7 +96,53 @@ declare class AgoClient {
78
96
  * Send a message and receive a streaming response
79
97
  */
80
98
  sendMessage(content: string, options?: SendMessageOptions): Promise<AgoMessage>;
99
+ /**
100
+ * Fire the streaming request, returning `null` when the user stopped before
101
+ * the response came back. Any other failure ends the turn and propagates, so
102
+ * a real error still reaches the caller.
103
+ */
104
+ private sendRequest;
81
105
  private processSSEResponse;
106
+ /**
107
+ * Stop the turn that is generating right now: closes the stream so the UI
108
+ * stops immediately, and tells the backend to stop generating.
109
+ *
110
+ * Both halves matter. Closing the stream alone does NOT stop the agent — it
111
+ * keeps running and the full answer reappears the next time the conversation
112
+ * is loaded. The backend unwinds at its next safe point (any tool already
113
+ * running finishes, no further tool or reasoning round starts), keeps the text
114
+ * produced so far and finalizes the message as `CANCELED`.
115
+ *
116
+ * The in-flight `sendMessage` / `continueMessage` promise resolves normally
117
+ * with that partial message (it does not reject), `message:complete` fires
118
+ * with status `CANCELED`, then `message:stopped`.
119
+ *
120
+ * ```ts
121
+ * // "Stop" button
122
+ * stopButton.onclick = () => void client.stop();
123
+ * ```
124
+ *
125
+ * Returns the backend's verdict, or `null` when nothing was generating or the
126
+ * stop landed before the message had an id (the stream is still closed).
127
+ */
128
+ stop(): Promise<StopMessageResult | null>;
129
+ /**
130
+ * Ask the backend to stop a specific in-flight turn, without touching any
131
+ * local stream. Idempotent — stopping a finished turn answers
132
+ * `{ status: "not_running" }` rather than failing. Most apps want
133
+ * {@link stop} instead; use this to stop a turn started elsewhere (e.g. one
134
+ * still running after a page reload, found via `getConversation`).
135
+ */
136
+ stopMessage(messageId: string): Promise<StopMessageResult>;
137
+ /** Whether a turn is generating right now (i.e. {@link stop} has something to stop). */
138
+ isGenerating(): boolean;
139
+ /** Open a new turn, replacing any bookkeeping left by the previous one. */
140
+ private beginTurn;
141
+ private endTurn;
142
+ /** POST the stop for a turn, once, never letting the failure escape. */
143
+ private postStopForTurn;
144
+ /** The message a caller gets back when the stop beat the response. */
145
+ private buildStoppedMessage;
82
146
  private resolveClientFunctionsMode;
83
147
  /**
84
148
  * Register a gate deciding WHEN a paused turn resumes. The SDK still decides
@@ -306,13 +370,38 @@ declare class AgoClient {
306
370
  * },
307
371
  * ]);
308
372
  * ```
373
+ *
374
+ * Pass `opts.data` and the agent also gets back **what the page now shows**,
375
+ * as the result of its own call, instead of having to wait for the next
376
+ * message's context. That also registers a read-only `readPageData`
377
+ * companion. See {@link AgoPageDataSource}.
309
378
  */
310
379
  registerPageStateFunction(controls: AgoStateControl[], opts?: AgoPageStateOptions): void;
311
380
  /**
312
- * Unregister a page state function and its dynamic context provider.
313
- * Mirror of unregistering `navigateToPage`.
381
+ * Unregister a page state function, its `readPageData` companion, and its
382
+ * dynamic context provider. Mirror of unregistering `navigateToPage`.
314
383
  */
315
384
  unregisterPageStateFunction(functionName?: string): void;
385
+ /**
386
+ * Size ceiling for a page snapshot, before the envelope is accounted for.
387
+ * Read at call time, not at registration, so `updateConfig` moves it.
388
+ */
389
+ private pageDataBudget;
390
+ /** Bytes the non-`data` part of the result costs, `data` included as `null`. */
391
+ private envelopeBytes;
392
+ /**
393
+ * Wait for the page to stop reloading, then read the snapshot. `get` and
394
+ * `isLoading` are re-read from `source` on every tick so a re-render that
395
+ * swaps the closures (without re-registering) is picked up mid-wait.
396
+ */
397
+ private readPageData;
398
+ /**
399
+ * The snapshot only reaches the agent inside the same turn in pause mode,
400
+ * where the SDK submits the tool result and resumes. In placeholder mode the
401
+ * turn is already over, so a `data` source looks broken from the outside:
402
+ * say so once rather than let the integrator hunt for an SDK bug.
403
+ */
404
+ private warnIfPlaceholderMode;
316
405
  /**
317
406
  * Register or update a piece of client-side context.
318
407
  * This context is sent with every message so the AI understands the user's situation.
@@ -480,6 +569,18 @@ declare interface AgoClientEvents {
480
569
  conversationId: string;
481
570
  messageId: string;
482
571
  };
572
+ /**
573
+ * The user stopped the turn (`client.stop()`): the stream was closed and the
574
+ * backend was told to stop generating. Always fires, so this is the event to
575
+ * finalize a UI on. When there was an answer to keep, a `message:complete`
576
+ * carrying it with status `CANCELED` fires first; there is none when the stop
577
+ * landed before the backend named the message (`messageId` is then an empty
578
+ * string) or when the stopped turn was paused on client functions.
579
+ */
580
+ "message:stopped": {
581
+ conversationId: string;
582
+ messageId: string;
583
+ };
483
584
  "message:error": {
484
585
  error: string;
485
586
  /** Stable error code (see configuration.md#error-codes) when the failure was an AgoError. */
@@ -673,6 +774,80 @@ declare interface AgoMessage {
673
774
  createdAt: Date;
674
775
  }
675
776
 
777
+ /**
778
+ * What the page currently displays, returned to the agent as the *result* of
779
+ * `setPageState` instead of only as context on the next message.
780
+ *
781
+ * Declare it when the agent should be able to act on what its own change
782
+ * produced ("search for dupont" → it can say who was found).
783
+ *
784
+ * A control change usually kicks off work (a refetch, a recomputation). The SDK
785
+ * has to wait for that work before reading, and there are two ways to tell it
786
+ * when the work is done. **Prefer the first.**
787
+ *
788
+ * **1. Return a promise from `get()`** — you own the await, so the SDK is
789
+ * exact: it resolves when your work resolves, with no polling and no timing
790
+ * assumption. With TanStack Query, `ensureQueryData` gives you that promise and
791
+ * shares the single in-flight request with the UI:
792
+ *
793
+ * ```ts
794
+ * useAgoPageState(controls, {
795
+ * data: {
796
+ * description: "The users matching the current filters.",
797
+ * get: () => queryClient.ensureQueryData({
798
+ * queryKey: ["users", filters],
799
+ * queryFn: fetchUsers,
800
+ * }),
801
+ * },
802
+ * });
803
+ * ```
804
+ *
805
+ * **2. Declare `isLoading`** — for data you cannot get a promise for. The SDK
806
+ * polls it instead, which is a heuristic: the flag has to be up within ~100 ms
807
+ * of the change or the SDK concludes the page settled and reads the previous
808
+ * rows. Fine for a query library that flips `isFetching` on render, wrong for a
809
+ * debounced fetch.
810
+ *
811
+ * ```ts
812
+ * const { data: users, isFetching } = useQuery({ queryKey: ["users", filters], queryFn: fetchUsers });
813
+ *
814
+ * useAgoPageState(controls, {
815
+ * data: {
816
+ * description: "The users matching the current filters.",
817
+ * get: () => users ?? [],
818
+ * // isFetching, NOT isLoading: a background refetch must count as loading,
819
+ * // otherwise the agent reads the rows from the previous search.
820
+ * isLoading: () => isFetching,
821
+ * },
822
+ * });
823
+ * ```
824
+ */
825
+ declare interface AgoPageDataSource<T = unknown> {
826
+ /** What this data is, for the LLM: "The users matching the current filters." */
827
+ description: string;
828
+ /**
829
+ * The snapshot handed back to the agent.
830
+ *
831
+ * Return a promise and the SDK awaits it: that promise resolving *is* the
832
+ * signal that the work the change triggered has finished, so nothing is
833
+ * guessed. It is bounded by `settleTimeoutMs`.
834
+ */
835
+ get: () => T | Promise<T>;
836
+ /**
837
+ * True while the page is (re)loading this data. Only needed when `get()`
838
+ * cannot return a promise. The SDK polls it every 50 ms and treats two
839
+ * consecutive `false` readings as settled, so a flag raised more than ~100 ms
840
+ * after the change is missed and the agent reads stale rows.
841
+ */
842
+ isLoading?: () => boolean;
843
+ /**
844
+ * Size ceiling for the returned snapshot, in bytes. Defaults to the client's
845
+ * `maxFunctionResultBytes`. Over the ceiling the snapshot keeps the first
846
+ * whole items that fit; `success` and `applied` are never dropped.
847
+ */
848
+ maxResultBytes?: number;
849
+ }
850
+
676
851
  /**
677
852
  * Options for {@link AgoClient.registerPageStateFunction}.
678
853
  */
@@ -684,6 +859,23 @@ declare interface AgoPageStateOptions {
684
859
  * {@link AgoConfig.approvalPolicy} — pause mode only. Defaults to `false`.
685
860
  */
686
861
  requiresApproval?: boolean;
862
+ /**
863
+ * Declare what the page displays so `setPageState` returns it. Omit it and
864
+ * the result stays exactly `{ success, applied }`.
865
+ *
866
+ * Also registers a read-only companion function (`readPageData`, derived from
867
+ * `functionName`) that returns the same snapshot without touching the page.
868
+ *
869
+ * The snapshot travels only when the agent calls one of those functions: it
870
+ * is deliberately kept out of the `page-state:` dynamic context, which would
871
+ * put it in every message.
872
+ */
873
+ data?: AgoPageDataSource;
874
+ /**
875
+ * How long to wait for the page to finish reloading before reading the
876
+ * snapshot. Defaults to 10 000 ms; on timeout whatever is there is returned.
877
+ */
878
+ settleTimeoutMs?: number;
687
879
  }
688
880
 
689
881
  /**
@@ -1006,13 +1198,21 @@ declare class HttpClient {
1006
1198
  keepalive?: boolean;
1007
1199
  }): Promise<T>;
1008
1200
  /**
1009
- * Make a POST request and return the raw Response (for streaming)
1201
+ * Make a POST request and return the raw Response (for streaming).
1202
+ *
1203
+ * `signal` lets the caller close the stream mid-flight (see
1204
+ * `AgoClient.stop`); an abort is rethrown as-is rather than wrapped in an
1205
+ * `AgoNetworkError`, so a deliberate stop is not mistaken for a network drop.
1010
1206
  */
1011
- postStream(path: string, body?: unknown): Promise<Response>;
1207
+ postStream(path: string, body?: unknown, options?: {
1208
+ signal?: AbortSignal;
1209
+ }): Promise<Response>;
1012
1210
  /**
1013
1211
  * Make a POST request with FormData (for file uploads)
1014
1212
  */
1015
- postFormData(path: string, formData: FormData): Promise<Response>;
1213
+ postFormData(path: string, formData: FormData, options?: {
1214
+ signal?: AbortSignal;
1215
+ }): Promise<Response>;
1016
1216
  private request;
1017
1217
  private handleErrorResponse;
1018
1218
  }
@@ -1338,6 +1538,21 @@ declare interface SSEChunkData {
1338
1538
  waiting_tool_call_ids?: string[];
1339
1539
  }
1340
1540
 
1541
+ /**
1542
+ * Response of `POST /messages/{id}/stop` (see `AgoClient.stopMessage`).
1543
+ *
1544
+ * - `"stopping"` — the request was accepted; the turn unwinds at its next safe
1545
+ * point and finalizes as `CANCELED`, keeping the text produced so far.
1546
+ * - `"not_running"` — the turn had already finished. Harmless.
1547
+ * - `"not_supported"` — a background agent run, which cannot be stopped once
1548
+ * started.
1549
+ */
1550
+ declare interface StopMessageResult {
1551
+ status: "stopping" | "not_running" | "not_supported";
1552
+ /** The message's status when the stop was requested. */
1553
+ messageStatus?: MessageStatus;
1554
+ }
1555
+
1341
1556
  /** The slice of the Web Storage API that {@link createStore} relies on. */
1342
1557
  declare type StorageLike = Pick<Storage, "getItem" | "setItem">;
1343
1558
 
@@ -57,6 +57,10 @@ declare class AgoClient {
57
57
  private config;
58
58
  /** Conversations already warned about an empty reply (one warning each). */
59
59
  private warnedEmptyConversations;
60
+ /** Whether the placeholder-mode page-data warning has already been logged. */
61
+ private warnedPageDataPlaceholder;
62
+ /** `readPageData` companions this SDK registered, so cleanup only removes its own. */
63
+ private pageDataCompanions;
60
64
  /** Paused turns awaiting auto-resume, keyed by assistant message id. */
61
65
  private pendingResumes;
62
66
  /**
@@ -67,6 +71,20 @@ declare class AgoClient {
67
71
  private pendingApprovals;
68
72
  /** App-provided gate deciding WHEN a paused turn resumes (see {@link registerResumeGate}). */
69
73
  private resumeGate;
74
+ /** The turn currently streaming, or `null` when the client is idle. */
75
+ private activeTurn;
76
+ /**
77
+ * A turn that closed as `WAITING_CLIENT` and has not resumed yet. There is no
78
+ * open stream in that window, but the turn is still live for the user, so it
79
+ * stays stoppable (see {@link stop}).
80
+ */
81
+ private pausedTurn;
82
+ /**
83
+ * Turns stopped while they were paused on client functions. A resume may
84
+ * already be queued (or waiting on the resume gate) at that point; this drops
85
+ * it, so a stopped turn is never restarted.
86
+ */
87
+ private stoppedTurnIds;
70
88
  /**
71
89
  * Proactive-mode controller, set when the client was created with
72
90
  * `proactive` options or when `createAgoProactive(client, opts)` was called.
@@ -78,7 +96,53 @@ declare class AgoClient {
78
96
  * Send a message and receive a streaming response
79
97
  */
80
98
  sendMessage(content: string, options?: SendMessageOptions): Promise<AgoMessage>;
99
+ /**
100
+ * Fire the streaming request, returning `null` when the user stopped before
101
+ * the response came back. Any other failure ends the turn and propagates, so
102
+ * a real error still reaches the caller.
103
+ */
104
+ private sendRequest;
81
105
  private processSSEResponse;
106
+ /**
107
+ * Stop the turn that is generating right now: closes the stream so the UI
108
+ * stops immediately, and tells the backend to stop generating.
109
+ *
110
+ * Both halves matter. Closing the stream alone does NOT stop the agent — it
111
+ * keeps running and the full answer reappears the next time the conversation
112
+ * is loaded. The backend unwinds at its next safe point (any tool already
113
+ * running finishes, no further tool or reasoning round starts), keeps the text
114
+ * produced so far and finalizes the message as `CANCELED`.
115
+ *
116
+ * The in-flight `sendMessage` / `continueMessage` promise resolves normally
117
+ * with that partial message (it does not reject), `message:complete` fires
118
+ * with status `CANCELED`, then `message:stopped`.
119
+ *
120
+ * ```ts
121
+ * // "Stop" button
122
+ * stopButton.onclick = () => void client.stop();
123
+ * ```
124
+ *
125
+ * Returns the backend's verdict, or `null` when nothing was generating or the
126
+ * stop landed before the message had an id (the stream is still closed).
127
+ */
128
+ stop(): Promise<StopMessageResult | null>;
129
+ /**
130
+ * Ask the backend to stop a specific in-flight turn, without touching any
131
+ * local stream. Idempotent — stopping a finished turn answers
132
+ * `{ status: "not_running" }` rather than failing. Most apps want
133
+ * {@link stop} instead; use this to stop a turn started elsewhere (e.g. one
134
+ * still running after a page reload, found via `getConversation`).
135
+ */
136
+ stopMessage(messageId: string): Promise<StopMessageResult>;
137
+ /** Whether a turn is generating right now (i.e. {@link stop} has something to stop). */
138
+ isGenerating(): boolean;
139
+ /** Open a new turn, replacing any bookkeeping left by the previous one. */
140
+ private beginTurn;
141
+ private endTurn;
142
+ /** POST the stop for a turn, once, never letting the failure escape. */
143
+ private postStopForTurn;
144
+ /** The message a caller gets back when the stop beat the response. */
145
+ private buildStoppedMessage;
82
146
  private resolveClientFunctionsMode;
83
147
  /**
84
148
  * Register a gate deciding WHEN a paused turn resumes. The SDK still decides
@@ -306,13 +370,38 @@ declare class AgoClient {
306
370
  * },
307
371
  * ]);
308
372
  * ```
373
+ *
374
+ * Pass `opts.data` and the agent also gets back **what the page now shows**,
375
+ * as the result of its own call, instead of having to wait for the next
376
+ * message's context. That also registers a read-only `readPageData`
377
+ * companion. See {@link AgoPageDataSource}.
309
378
  */
310
379
  registerPageStateFunction(controls: AgoStateControl[], opts?: AgoPageStateOptions): void;
311
380
  /**
312
- * Unregister a page state function and its dynamic context provider.
313
- * Mirror of unregistering `navigateToPage`.
381
+ * Unregister a page state function, its `readPageData` companion, and its
382
+ * dynamic context provider. Mirror of unregistering `navigateToPage`.
314
383
  */
315
384
  unregisterPageStateFunction(functionName?: string): void;
385
+ /**
386
+ * Size ceiling for a page snapshot, before the envelope is accounted for.
387
+ * Read at call time, not at registration, so `updateConfig` moves it.
388
+ */
389
+ private pageDataBudget;
390
+ /** Bytes the non-`data` part of the result costs, `data` included as `null`. */
391
+ private envelopeBytes;
392
+ /**
393
+ * Wait for the page to stop reloading, then read the snapshot. `get` and
394
+ * `isLoading` are re-read from `source` on every tick so a re-render that
395
+ * swaps the closures (without re-registering) is picked up mid-wait.
396
+ */
397
+ private readPageData;
398
+ /**
399
+ * The snapshot only reaches the agent inside the same turn in pause mode,
400
+ * where the SDK submits the tool result and resumes. In placeholder mode the
401
+ * turn is already over, so a `data` source looks broken from the outside:
402
+ * say so once rather than let the integrator hunt for an SDK bug.
403
+ */
404
+ private warnIfPlaceholderMode;
316
405
  /**
317
406
  * Register or update a piece of client-side context.
318
407
  * This context is sent with every message so the AI understands the user's situation.
@@ -480,6 +569,18 @@ declare interface AgoClientEvents {
480
569
  conversationId: string;
481
570
  messageId: string;
482
571
  };
572
+ /**
573
+ * The user stopped the turn (`client.stop()`): the stream was closed and the
574
+ * backend was told to stop generating. Always fires, so this is the event to
575
+ * finalize a UI on. When there was an answer to keep, a `message:complete`
576
+ * carrying it with status `CANCELED` fires first; there is none when the stop
577
+ * landed before the backend named the message (`messageId` is then an empty
578
+ * string) or when the stopped turn was paused on client functions.
579
+ */
580
+ "message:stopped": {
581
+ conversationId: string;
582
+ messageId: string;
583
+ };
483
584
  "message:error": {
484
585
  error: string;
485
586
  /** Stable error code (see configuration.md#error-codes) when the failure was an AgoError. */
@@ -673,6 +774,80 @@ declare interface AgoMessage {
673
774
  createdAt: Date;
674
775
  }
675
776
 
777
+ /**
778
+ * What the page currently displays, returned to the agent as the *result* of
779
+ * `setPageState` instead of only as context on the next message.
780
+ *
781
+ * Declare it when the agent should be able to act on what its own change
782
+ * produced ("search for dupont" → it can say who was found).
783
+ *
784
+ * A control change usually kicks off work (a refetch, a recomputation). The SDK
785
+ * has to wait for that work before reading, and there are two ways to tell it
786
+ * when the work is done. **Prefer the first.**
787
+ *
788
+ * **1. Return a promise from `get()`** — you own the await, so the SDK is
789
+ * exact: it resolves when your work resolves, with no polling and no timing
790
+ * assumption. With TanStack Query, `ensureQueryData` gives you that promise and
791
+ * shares the single in-flight request with the UI:
792
+ *
793
+ * ```ts
794
+ * useAgoPageState(controls, {
795
+ * data: {
796
+ * description: "The users matching the current filters.",
797
+ * get: () => queryClient.ensureQueryData({
798
+ * queryKey: ["users", filters],
799
+ * queryFn: fetchUsers,
800
+ * }),
801
+ * },
802
+ * });
803
+ * ```
804
+ *
805
+ * **2. Declare `isLoading`** — for data you cannot get a promise for. The SDK
806
+ * polls it instead, which is a heuristic: the flag has to be up within ~100 ms
807
+ * of the change or the SDK concludes the page settled and reads the previous
808
+ * rows. Fine for a query library that flips `isFetching` on render, wrong for a
809
+ * debounced fetch.
810
+ *
811
+ * ```ts
812
+ * const { data: users, isFetching } = useQuery({ queryKey: ["users", filters], queryFn: fetchUsers });
813
+ *
814
+ * useAgoPageState(controls, {
815
+ * data: {
816
+ * description: "The users matching the current filters.",
817
+ * get: () => users ?? [],
818
+ * // isFetching, NOT isLoading: a background refetch must count as loading,
819
+ * // otherwise the agent reads the rows from the previous search.
820
+ * isLoading: () => isFetching,
821
+ * },
822
+ * });
823
+ * ```
824
+ */
825
+ declare interface AgoPageDataSource<T = unknown> {
826
+ /** What this data is, for the LLM: "The users matching the current filters." */
827
+ description: string;
828
+ /**
829
+ * The snapshot handed back to the agent.
830
+ *
831
+ * Return a promise and the SDK awaits it: that promise resolving *is* the
832
+ * signal that the work the change triggered has finished, so nothing is
833
+ * guessed. It is bounded by `settleTimeoutMs`.
834
+ */
835
+ get: () => T | Promise<T>;
836
+ /**
837
+ * True while the page is (re)loading this data. Only needed when `get()`
838
+ * cannot return a promise. The SDK polls it every 50 ms and treats two
839
+ * consecutive `false` readings as settled, so a flag raised more than ~100 ms
840
+ * after the change is missed and the agent reads stale rows.
841
+ */
842
+ isLoading?: () => boolean;
843
+ /**
844
+ * Size ceiling for the returned snapshot, in bytes. Defaults to the client's
845
+ * `maxFunctionResultBytes`. Over the ceiling the snapshot keeps the first
846
+ * whole items that fit; `success` and `applied` are never dropped.
847
+ */
848
+ maxResultBytes?: number;
849
+ }
850
+
676
851
  /**
677
852
  * Options for {@link AgoClient.registerPageStateFunction}.
678
853
  */
@@ -684,6 +859,23 @@ declare interface AgoPageStateOptions {
684
859
  * {@link AgoConfig.approvalPolicy} — pause mode only. Defaults to `false`.
685
860
  */
686
861
  requiresApproval?: boolean;
862
+ /**
863
+ * Declare what the page displays so `setPageState` returns it. Omit it and
864
+ * the result stays exactly `{ success, applied }`.
865
+ *
866
+ * Also registers a read-only companion function (`readPageData`, derived from
867
+ * `functionName`) that returns the same snapshot without touching the page.
868
+ *
869
+ * The snapshot travels only when the agent calls one of those functions: it
870
+ * is deliberately kept out of the `page-state:` dynamic context, which would
871
+ * put it in every message.
872
+ */
873
+ data?: AgoPageDataSource;
874
+ /**
875
+ * How long to wait for the page to finish reloading before reading the
876
+ * snapshot. Defaults to 10 000 ms; on timeout whatever is there is returned.
877
+ */
878
+ settleTimeoutMs?: number;
687
879
  }
688
880
 
689
881
  /**
@@ -1006,13 +1198,21 @@ declare class HttpClient {
1006
1198
  keepalive?: boolean;
1007
1199
  }): Promise<T>;
1008
1200
  /**
1009
- * Make a POST request and return the raw Response (for streaming)
1201
+ * Make a POST request and return the raw Response (for streaming).
1202
+ *
1203
+ * `signal` lets the caller close the stream mid-flight (see
1204
+ * `AgoClient.stop`); an abort is rethrown as-is rather than wrapped in an
1205
+ * `AgoNetworkError`, so a deliberate stop is not mistaken for a network drop.
1010
1206
  */
1011
- postStream(path: string, body?: unknown): Promise<Response>;
1207
+ postStream(path: string, body?: unknown, options?: {
1208
+ signal?: AbortSignal;
1209
+ }): Promise<Response>;
1012
1210
  /**
1013
1211
  * Make a POST request with FormData (for file uploads)
1014
1212
  */
1015
- postFormData(path: string, formData: FormData): Promise<Response>;
1213
+ postFormData(path: string, formData: FormData, options?: {
1214
+ signal?: AbortSignal;
1215
+ }): Promise<Response>;
1016
1216
  private request;
1017
1217
  private handleErrorResponse;
1018
1218
  }
@@ -1338,6 +1538,21 @@ declare interface SSEChunkData {
1338
1538
  waiting_tool_call_ids?: string[];
1339
1539
  }
1340
1540
 
1541
+ /**
1542
+ * Response of `POST /messages/{id}/stop` (see `AgoClient.stopMessage`).
1543
+ *
1544
+ * - `"stopping"` — the request was accepted; the turn unwinds at its next safe
1545
+ * point and finalizes as `CANCELED`, keeping the text produced so far.
1546
+ * - `"not_running"` — the turn had already finished. Harmless.
1547
+ * - `"not_supported"` — a background agent run, which cannot be stopped once
1548
+ * started.
1549
+ */
1550
+ declare interface StopMessageResult {
1551
+ status: "stopping" | "not_running" | "not_supported";
1552
+ /** The message's status when the stop was requested. */
1553
+ messageStatus?: MessageStatus;
1554
+ }
1555
+
1341
1556
  /** The slice of the Web Storage API that {@link createStore} relies on. */
1342
1557
  declare type StorageLike = Pick<Storage, "getItem" | "setItem">;
1343
1558