@frontera-sdk/chat 1.52.45 → 1.52.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth.d.ts +20 -0
- package/dist/auth.js +11 -0
- package/dist/callback-page.d.ts +49 -0
- package/dist/callback-page.js +84 -0
- package/dist/chat-client.d.ts +221 -0
- package/dist/chat-client.js +5 -0
- package/dist/chat-errors.d.ts +30 -0
- package/dist/chat-errors.js +3 -0
- package/dist/chunk-CMMWTCYS.js +116 -0
- package/dist/chunk-KZEGEV3P.js +35 -0
- package/dist/chunk-LS26G2N6.js +120 -0
- package/dist/chunk-RLHHRJWS.js +371 -0
- package/dist/chunk-UCUVHMJC.js +62 -0
- package/dist/chunk-VWUR3ECR.js +115 -0
- package/dist/chunk-W25Q2DQA.js +32 -0
- package/dist/chunk-YKQ5Z5EK.js +32 -0
- package/dist/connect-popup.d.ts +150 -0
- package/dist/connect-popup.js +138 -0
- package/dist/doctor.d.ts +71 -0
- package/dist/doctor.js +3 -0
- package/dist/element.d.ts +23 -0
- package/dist/element.js +175 -0
- package/dist/frontera-chat.iife.js +27 -0
- package/dist/hooks.d.ts +88 -0
- package/dist/hooks.js +3 -0
- package/dist/provider.d.ts +20 -0
- package/dist/provider.js +13 -0
- package/dist/public-chat.d.ts +50 -0
- package/dist/public-chat.js +6 -0
- package/dist/public-token.d.ts +46 -0
- package/dist/public-token.js +3 -0
- package/dist/reply-text.d.ts +25 -0
- package/dist/reply-text.js +3 -0
- package/dist/stream.d.ts +25 -0
- package/dist/stream.js +3 -0
- package/dist/token-session.d.ts +66 -0
- package/dist/token-session.js +99 -0
- package/dist/transport.d.ts +60 -0
- package/dist/transport.js +103 -0
- package/dist/types.d.ts +361 -0
- package/dist/types.js +3 -0
- package/dist/ui-stream.d.ts +25 -0
- package/dist/ui-stream.js +26 -0
- package/package.json +70 -30
- package/src/chat-errors.ts +43 -0
- package/src/element.ts +201 -0
- package/src/frontera-chat.global.ts +13 -0
- package/src/public-chat.ts +154 -0
- package/src/public-token.ts +36 -4
- package/src/reply-text.ts +49 -0
- package/src/transport.ts +7 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";var FronteraChat=(()=>{var m=Object.defineProperty;var _=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var B=Object.prototype.hasOwnProperty;var U=(e,r,t)=>r in e?m(e,r,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[r]=t;var M=(e,r)=>{for(var t in r)m(e,t,{get:r[t],enumerable:!0})},q=(e,r,t,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of L(r))!B.call(e,o)&&o!==t&&m(e,o,{get:()=>r[o],enumerable:!(n=_(r,o))||n.enumerable});return e};var H=e=>q(m({},"__esModule",{value:!0}),e);var d=(e,r,t)=>U(e,typeof r!="symbol"?r+"":r,t);var ie={};M(ie,{CHAT_ERROR_CODES:()=>R,FronteraChatElement:()=>h,PublicChat:()=>u,chatErrorCode:()=>g,defineFronteraChat:()=>w,mintPublicChatToken:()=>x,visitorErrorWords:()=>y});var p=class extends Error{constructor(t,n){super(t);d(this,"code");d(this,"status");d(this,"details");this.name="FronteraError",this.code=n.code,this.status=n.status,this.details=n.details}},T={400:"BAD_REQUEST",401:"UNAUTHORIZED",403:"FORBIDDEN",404:"NOT_FOUND",409:"CONFLICT",429:"RATE_LIMITED"};function G(e){return typeof e=="object"&&e!==null&&e.error===!0}function f(e,r){return G(r)?new p(r.message??`request failed with ${e}`,{code:r.code??T[e]??"INTERNAL_ERROR",status:e,details:r}):new p(`request failed with ${e}`,{code:T[e]??"INTERNAL_ERROR",status:e,details:r??void 0})}var R={UNAUTHORIZED:"The key or token was not accepted: it is disabled, removed, mistyped or expired. Mint a new token; if that fails too, check the key.",FORBIDDEN:"The credential may not do this: the page is not on the key\u2019s allowed websites, or a visitor asked for something a public chat cannot do.",NOT_FOUND:"The conversation or run is not this credential\u2019s, or no longer exists.",VALIDATION_ERROR:"The request was malformed, for example an empty message or more than five messages.",RATE_LIMITED:"Too many messages or new chats in a short time. Wait the number of seconds the message names, then try again.",QUOTA_EXCEEDED:"The organization\u2019s usage limit is reached. An administrator raises it.",SERVICE_UNAVAILABLE:"The platform can\u2019t take the request right now. Try again shortly.",INTERNAL_ERROR:"Something failed on the platform. Try again; if it keeps failing, the message names what to report.",PROTOCOL_ERROR:"The response wasn\u2019t a chat stream: the API origin is likely wrong, or a proxy changed the response.",FORBIDDEN_LOCAL:"Your own `authorizeConversation` callback denied access to the conversation.",CONFIG_MISSING:"The client has no API origin or credential."};function g(e){let r=e instanceof p?e.code:null;return r&&r in R?r:"INTERNAL_ERROR"}function y(e){let r=g(e);return r==="RATE_LIMITED"&&e instanceof p?e.message:r==="UNAUTHORIZED"?"This chat stopped working. Reload the page to start again.":r==="FORBIDDEN"?"This chat isn\u2019t available on this page.":r==="QUOTA_EXCEEDED"||r==="SERVICE_UNAVAILABLE"?"The assistant is unavailable right now. Please try again later.":"The message couldn\u2019t be sent. Please try again."}function K(){return globalThis.__FRONTERA_CONFIG__??{}}function j(){return K()}function v(e){throw new p(`Frontera SDK is not configured: ${e} is missing. Pass it explicitly, or set the matching FRONTERA_* environment variable.`,{code:"CONFIG_MISSING"})}function C(e,r=typeof process>"u"?{}:process.env){let t=j(),n=e?.apiBaseUrl??t.apiBaseUrl??r.FRONTERA_API_URL;n||v("apiBaseUrl");let o=e?.credential??(r.FRONTERA_TOKEN?{kind:"apiKey",key:r.FRONTERA_TOKEN}:void 0);return o||v("credential"),{apiBaseUrl:n.replace(/\/+$/,""),orgId:e?.orgId??t.orgId??r.FRONTERA_ORG_ID,workspaceId:e?.workspaceId??t.workspaceId??r.FRONTERA_WORKSPACE_ID,credential:o}}function k(e){let t={authorization:`Bearer ${e.credential.kind==="apiKey"?e.credential.key:e.credential.token}`};return e.orgId&&(t["x-org-id"]=e.orgId),e.workspaceId&&(t["x-workspace-id"]=e.workspaceId),t}function $(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)&&e.error===!1&&"data"in e?e.data:e}function I(e){return e?Object.fromEntries(Object.entries(e).map(([r,t])=>[r.toLowerCase(),t])):{}}function O(e,r,t){let n=new URL(`${e.apiBaseUrl}${r.startsWith("/")?r:`/${r}`}`);if(t)for(let[o,s]of Object.entries(t))s!==void 0&&n.searchParams.set(o,String(s));return n.toString()}async function E(e,r,t={},n=fetch){let o={...I(t.headers),...k(e)},s=typeof FormData<"u"&&t.body instanceof FormData;t.body!==void 0&&!s&&(o["content-type"]="application/json");let a=await n(O(e,r,t.query),{method:t.method??"GET",headers:o,body:t.body===void 0?void 0:s?t.body:JSON.stringify(t.body),signal:t.signal});if(!a.ok){let c=await a.text(),i=null;if(c.length>0)try{i=JSON.parse(c)}catch{i=c}throw f(a.status,i)}return a}async function A(e,r,t={},n=fetch){let s=await(await E(e,r,t,n)).text();if(s.length===0)return null;try{return JSON.parse(s)}catch{return s}}async function F(e,r,t={},n=fetch){let o={...I(t.headers),...k(e),accept:"application/json"},s=typeof FormData<"u"&&t.body instanceof FormData;t.body!==void 0&&!s&&(o["content-type"]="application/json");let a=await n(O(e,r,t.query),{method:t.method??"GET",headers:o,body:t.body===void 0?void 0:s?t.body:JSON.stringify(t.body),signal:t.signal}),c=await a.text(),i=null;if(c.length>0)try{i=JSON.parse(c)}catch{i=c}if(!a.ok)throw f(a.status,i);return $(i)}var b=class e{constructor(r,t=fetch){d(this,"config");d(this,"fetchImpl");this.config=C(r),this.fetchImpl=t}request(r,t={}){return F(this.config,r,t,this.fetchImpl)}requestRaw(r,t={}){return E(this.config,r,t,this.fetchImpl)}requestEnvelope(r,t={}){return A(this.config,r,t,this.fetchImpl)}withCredential(r){return new e({...this.config,credential:r},this.fetchImpl)}};function W(){try{return typeof navigator>"u"?void 0:navigator.language||void 0}catch{return}}function J(e){return`frontera:visitor:${e.slice(0,12)}`}function z(){try{return typeof localStorage>"u"?null:localStorage}catch{return null}}function V(e,r){try{return e?.getItem(r)??null}catch{return null}}function Z(e,r,t){try{e?.setItem(r,t)}catch{}}async function x(e){let r=e.storage===void 0?z():e.storage,t=J(e.publishableKey),n=V(r,t),o=e.language??W(),s=await(e.fetchImpl??fetch)(`${e.apiBaseUrl}/v1/chat-tokens/public`,{method:"POST",headers:{"content-type":"application/json"},credentials:"omit",body:JSON.stringify({publishableKey:e.publishableKey,...n?{visitorPass:n}:{},...o?{language:o}:{}})}),a=await s.json().catch(()=>null);if(!s.ok||!a?.data)throw f(s.status,a);return a.data.visitorPass&&Z(r,t,a.data.visitorPass),{token:a.data.token,expiresAt:a.data.expiresAt,...a.data.openingMessage?{openingMessage:a.data.openingMessage}:{}}}async function N(e,r,t={}){let n=await e.requestRaw("/v1/chat",{method:"POST",body:r,signal:t.signal}),o=n.headers.get("x-conversation-id"),s=n.headers.get("x-workflow-run-id");if(!o||!s)throw new p("chat response is missing run identification headers",{code:"PROTOCOL_ERROR",status:n.status});return{conversationId:o,runId:s,runtime:n.headers.get("x-agent-runtime")??"pi",quotaWarning:n.headers.get("x-quota-warning")??void 0,response:n}}function P(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`m-${Date.now()}-${Math.random().toString(36).slice(2)}`}async function*Q(e){if(!e.body)return;let r=e.body.getReader(),t=new TextDecoder,n="",o=a=>{let c=a.split(`
|
|
2
|
+
`).filter(i=>i.startsWith("data:")).map(i=>i.slice(5).trimStart()).join(`
|
|
3
|
+
`);if(!c||c==="[DONE]")return null;try{let i=JSON.parse(c);return i&&typeof i=="object"?i:null}catch{return null}};for(;;){let{done:a,value:c}=await r.read();c&&(n+=t.decode(c,{stream:!0}));let i=n.indexOf(`
|
|
4
|
+
|
|
5
|
+
`);for(;i!==-1;){let l=o(n.slice(0,i));n=n.slice(i+2),l&&(yield l),i=n.indexOf(`
|
|
6
|
+
|
|
7
|
+
`)}if(a)break}let s=o(n);s&&(yield s)}var u=class{constructor(r){d(this,"options",r);d(this,"token",null);d(this,"conversationId",null);d(this,"openingMessage",null);d(this,"opened",!1)}async open(){let r=await x({...this.options,...this.options.fetchImpl?{fetchImpl:this.options.fetchImpl}:{}});return this.token=r.token,this.opened||(this.openingMessage=r.openingMessage??null),this.opened=!0,{openingMessage:this.openingMessage}}client(){return new b({apiBaseUrl:this.options.apiBaseUrl,credential:{kind:"token",token:this.token}},this.options.fetchImpl??fetch)}messagesFor(r){let t={id:P(),role:"user",parts:[{type:"text",text:r}]};return this.conversationId||!this.openingMessage?[t]:[{id:P(),role:"assistant",parts:[{type:"text",text:this.openingMessage}]},t]}async*send(r,t){yield{type:"waiting"};let n;try{this.token||await this.open();let c=()=>N(this.client(),{messages:this.messagesFor(r),...this.conversationId?{conversationId:this.conversationId}:{}},{signal:t}),i;try{i=await c()}catch(l){if(g(l)!=="UNAUTHORIZED")throw l;await this.open(),i=await c()}this.conversationId=i.conversationId,n=i.response}catch(c){yield{type:"error",error:S(c)};return}let o=new Map,s=[],a=()=>s.map(c=>o.get(c)??"").filter(Boolean).join(`
|
|
8
|
+
|
|
9
|
+
`);try{for await(let c of Q(n))if(c.type==="text-delta"&&typeof c.delta=="string"){let i=typeof c.id=="string"?c.id:"text";o.has(i)||s.push(i),o.set(i,(o.get(i)??"")+c.delta),yield{type:"text",text:a()}}else if(c.type==="error"){let i=typeof c.errorText=="string"?c.errorText:"The reply failed.";yield{type:"error",error:new p(i,{code:"INTERNAL_ERROR"})};return}}catch(c){yield{type:"error",error:S(c)};return}yield{type:"done"}}};function S(e){return e instanceof p?e:new p(e instanceof Error?e.message:String(e),{code:"INTERNAL_ERROR"})}var X=/\s?\[([^\]]*)\]\(\/cite\/[^)\s]*\)/g;function Y(e){return e.replace(X,(r,t)=>/^\s*\d+\s*$/.test(t)?"":r.startsWith(" ")?` ${t}`:t)}function ee(e){return/^(https?:\/\/|mailto:)/i.test(e.trim())?e.trim():null}var te=/\*\*([^*]+)\*\*|\[([^\]]+)\]\(([^)\s]+)\)/g;function re(e){let r=[],t=0;for(let n of e.matchAll(te)){if(n.index>t&&r.push({kind:"text",text:e.slice(t,n.index)}),n[1]!==void 0)r.push({kind:"bold",text:n[1]});else{let o=ee(n[3]);r.push(o?{kind:"link",text:n[2],href:o}:{kind:"text",text:n[2]})}t=n.index+n[0].length}return t<e.length&&r.push({kind:"text",text:e.slice(t)}),r}function D(e,r={}){return(r.showSources?e:Y(e)).split(/\n\s*\n/).map(n=>n.trim()).filter(Boolean).map(n=>n.split(`
|
|
10
|
+
`).map(o=>re(o.replace(/^\s*[-*]\s+/,"\u2022 "))))}var ne=`
|
|
11
|
+
:host { display: block; height: 100%; font: 14px/1.45 var(--frontera-chat-font, system-ui, sans-serif); color: var(--frontera-chat-text, #1b1b1f); }
|
|
12
|
+
.frame { display: flex; flex-direction: column; height: 100%; min-height: 320px; background: var(--frontera-chat-surface, #fff); border: 1px solid var(--frontera-chat-border, #e3e3e8); border-radius: var(--frontera-chat-radius, 12px); overflow: hidden; }
|
|
13
|
+
header { padding: 12px 16px; font-weight: 600; border-bottom: 1px solid var(--frontera-chat-border, #e3e3e8); }
|
|
14
|
+
.log { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 10px; }
|
|
15
|
+
.msg { max-width: 85%; padding: 8px 12px; border-radius: 12px; white-space: normal; overflow-wrap: anywhere; }
|
|
16
|
+
.msg p { margin: 0 0 8px; } .msg p:last-child { margin: 0; }
|
|
17
|
+
.agent { align-self: flex-start; background: var(--frontera-chat-agent-bubble, #f1f1f4); }
|
|
18
|
+
.visitor { align-self: flex-end; background: var(--frontera-chat-accent, #2f5bea); color: var(--frontera-chat-accent-text, #fff); }
|
|
19
|
+
.note { align-self: center; font-size: 12px; color: var(--frontera-chat-muted, #6b6b76); }
|
|
20
|
+
.typing { align-self: flex-start; color: var(--frontera-chat-muted, #6b6b76); font-size: 12px; }
|
|
21
|
+
a { color: inherit; }
|
|
22
|
+
form { display: flex; gap: 8px; padding: 12px; border-top: 1px solid var(--frontera-chat-border, #e3e3e8); }
|
|
23
|
+
textarea { flex: 1; resize: none; font: inherit; padding: 8px 10px; border: 1px solid var(--frontera-chat-border, #e3e3e8); border-radius: 8px; min-height: 20px; max-height: 120px; }
|
|
24
|
+
button { font: inherit; padding: 0 14px; border: 0; border-radius: 8px; background: var(--frontera-chat-accent, #2f5bea); color: var(--frontera-chat-accent-text, #fff); cursor: pointer; }
|
|
25
|
+
button:disabled { opacity: .5; cursor: default; }
|
|
26
|
+
`,oe=typeof HTMLElement>"u"?class{}:HTMLElement,h=class extends oe{constructor(){super(...arguments);d(this,"chat",null);d(this,"log");d(this,"input");d(this,"sendButton");d(this,"header");d(this,"busy",!1)}connectedCallback(){if(this.chat)return;let t=this.shadowRoot??this.attachShadow({mode:"open"}),n=this.ownerDocument,o=n.createElement("style");o.textContent=ne;let s=n.createElement("div");s.className="frame",this.header=n.createElement("header"),this.log=n.createElement("div"),this.log.className="log",this.log.setAttribute("role","log"),this.log.setAttribute("aria-live","polite");let a=n.createElement("form");this.input=n.createElement("textarea"),this.input.rows=1,this.input.setAttribute("aria-label","Message"),this.sendButton=n.createElement("button"),this.sendButton.type="submit",this.sendButton.textContent="Send",a.append(this.input,this.sendButton),s.append(this.header,this.log,a),t.replaceChildren(o,s),this.applyAttributes(),a.addEventListener("submit",l=>{l.preventDefault(),this.submit()}),this.input.addEventListener("keydown",l=>{l.key==="Enter"&&!l.shiftKey&&(l.preventDefault(),this.submit())});let c=this.getAttribute("api-base-url")?.replace(/\/+$/,""),i=this.getAttribute("publishable-key");if(!c||!i){this.note("This chat is missing its api-base-url or publishable-key."),this.setBusy(!0);return}this.chat=new u({apiBaseUrl:c,publishableKey:i}),this.chat.open().then(({openingMessage:l})=>{l&&this.agentBubble().replaceChildren(...this.render(l))}).catch(()=>{})}attributeChangedCallback(){this.header&&this.applyAttributes()}applyAttributes(){let t=this.getAttribute("chat-title");this.header.textContent=t??"",this.header.hidden=!t,this.input.placeholder=this.getAttribute("placeholder")??"Type a message"}setBusy(t){this.busy=t,this.sendButton.disabled=t}note(t){let n=this.ownerDocument.createElement("div");n.className="note",n.textContent=t,this.log.append(n)}agentBubble(){let t=this.ownerDocument.createElement("div");return t.className="msg agent",this.log.append(t),t}render(t){let n=this.ownerDocument;return D(t,{showSources:this.hasAttribute("show-sources")}).map(o=>{let s=n.createElement("p");return o.forEach((a,c)=>{c>0&&s.append(n.createElement("br"));for(let i of a)if(i.kind==="text")s.append(n.createTextNode(i.text));else if(i.kind==="bold"){let l=n.createElement("strong");l.textContent=i.text,s.append(l)}else{let l=n.createElement("a");l.href=i.href,l.textContent=i.text,l.target="_blank",l.rel="noopener noreferrer",s.append(l)}}),s})}async submit(){let t=this.input.value.trim();if(!t||this.busy||!this.chat)return;this.input.value="";let n=this.ownerDocument.createElement("div");n.className="msg visitor",n.textContent=t,this.log.append(n),this.setBusy(!0);let o=this.ownerDocument.createElement("div");o.className="typing",o.textContent="Typing\u2026";let s=null;try{for await(let a of this.chat.send(t))a.type==="waiting"?this.log.append(o):a.type==="text"?(o.remove(),s??(s=this.agentBubble()),s.replaceChildren(...this.render(a.text))):a.type==="error"&&(o.remove(),this.note(y(a.error)),this.dispatchEvent(new CustomEvent("frontera-chat-error",{detail:{code:a.error.code,message:a.error.message}}))),this.log.scrollTop=this.log.scrollHeight}finally{o.remove(),this.setBusy(!1),this.input.focus()}}};d(h,"observedAttributes",["chat-title","placeholder"]);function w(e="frontera-chat"){typeof customElements>"u"||customElements.get(e)||customElements.define(e,class extends h{})}w();return H(ie);})();
|
|
27
|
+
//# sourceMappingURL=frontera-chat.iife.js.map
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { UseMutationOptions, UseMutationResult, UseQueryOptions, UseQueryResult } from '@tanstack/react-query';
|
|
3
|
+
import { ChatClient } from './chat-client.js';
|
|
4
|
+
import { ListSessionsQuery, Session, CreateSessionRequest, PendingApproval, ApprovalDecision, SessionMessage, Paginated, UpdateSessionRequest } from './types.js';
|
|
5
|
+
import '@frontera-sdk/core/client';
|
|
6
|
+
import './stream.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Query-key factory, same convention as `blueprintKeys`: requests serialise
|
|
10
|
+
* into the key so structurally equal requests share a cache entry.
|
|
11
|
+
*/
|
|
12
|
+
declare const chatKeys: {
|
|
13
|
+
all: readonly ["chat"];
|
|
14
|
+
sessions: (query?: ListSessionsQuery) => readonly ["chat", "sessions", string];
|
|
15
|
+
session: (id: string) => readonly ["chat", "session", string];
|
|
16
|
+
messages: (id: string) => readonly ["chat", "messages", string];
|
|
17
|
+
followUps: (conversationId: string, messageId: string) => readonly ["chat", "follow-ups", string, string];
|
|
18
|
+
approvals: (conversationId: string) => readonly ["chat", "approvals", string];
|
|
19
|
+
};
|
|
20
|
+
declare const FronteraChatContext: react.Context<ChatClient | null>;
|
|
21
|
+
declare function useChatClient(): ChatClient;
|
|
22
|
+
type ReadOptions<TData> = Omit<UseQueryOptions<TData, Error>, 'queryKey' | 'queryFn' | 'select'>;
|
|
23
|
+
type WriteOptions<TData, TVariables> = Omit<UseMutationOptions<TData, Error, TVariables>, 'mutationFn'>;
|
|
24
|
+
/**
|
|
25
|
+
* These hooks cover PERSISTED state (sessions, history). Live streaming state
|
|
26
|
+
* belongs to `useChat` from `@ai-sdk/react` with `createChatTransport` — there
|
|
27
|
+
* is deliberately no second streaming state machine here.
|
|
28
|
+
*/
|
|
29
|
+
declare function useSessions(query?: ListSessionsQuery, options?: ReadOptions<Paginated<Session>>): UseQueryResult<Paginated<Session>, Error>;
|
|
30
|
+
declare function useSession(id: string, options?: ReadOptions<Session>): UseQueryResult<Session, Error>;
|
|
31
|
+
declare function useSessionMessages(id: string, options?: ReadOptions<SessionMessage[]>): UseQueryResult<SessionMessage[], Error>;
|
|
32
|
+
declare function useFollowUps(conversationId: string, messageId: string, options?: ReadOptions<{
|
|
33
|
+
suggestions: unknown[];
|
|
34
|
+
}>): UseQueryResult<{
|
|
35
|
+
suggestions: unknown[];
|
|
36
|
+
}, Error>;
|
|
37
|
+
declare function useCreateSession(options?: WriteOptions<Session, CreateSessionRequest>): UseMutationResult<Session, Error, CreateSessionRequest>;
|
|
38
|
+
declare function useUpdateSession(options?: WriteOptions<{
|
|
39
|
+
success: boolean;
|
|
40
|
+
}, {
|
|
41
|
+
id: string;
|
|
42
|
+
patch: UpdateSessionRequest;
|
|
43
|
+
}>): UseMutationResult<{
|
|
44
|
+
success: boolean;
|
|
45
|
+
}, Error, {
|
|
46
|
+
id: string;
|
|
47
|
+
patch: UpdateSessionRequest;
|
|
48
|
+
}>;
|
|
49
|
+
/**
|
|
50
|
+
* The parked tool calls of one conversation.
|
|
51
|
+
*
|
|
52
|
+
* Polling is on by default while the tab is visible, and that is the point: an
|
|
53
|
+
* approval appears mid-run, from the agent's side, so a query that only ran on
|
|
54
|
+
* mount would leave the user staring at a stalled conversation. Pass
|
|
55
|
+
* `refetchInterval: false` if you drive it from the stream instead.
|
|
56
|
+
*/
|
|
57
|
+
declare function usePendingApprovals(conversationId: string, options?: ReadOptions<PendingApproval[]>): UseQueryResult<PendingApproval[], Error>;
|
|
58
|
+
declare function useRespondToApproval(conversationId: string, options?: WriteOptions<{
|
|
59
|
+
id: string;
|
|
60
|
+
status: string;
|
|
61
|
+
}, {
|
|
62
|
+
approvalId: string;
|
|
63
|
+
decision: ApprovalDecision;
|
|
64
|
+
}>): UseMutationResult<{
|
|
65
|
+
id: string;
|
|
66
|
+
status: string;
|
|
67
|
+
}, Error, {
|
|
68
|
+
approvalId: string;
|
|
69
|
+
decision: ApprovalDecision;
|
|
70
|
+
}>;
|
|
71
|
+
declare function useCancelRun(options?: WriteOptions<{
|
|
72
|
+
success: true;
|
|
73
|
+
}, string>): UseMutationResult<{
|
|
74
|
+
success: true;
|
|
75
|
+
}, Error, string>;
|
|
76
|
+
declare function useSteerRun(options?: WriteOptions<{
|
|
77
|
+
success: true;
|
|
78
|
+
}, {
|
|
79
|
+
runId: string;
|
|
80
|
+
message: string;
|
|
81
|
+
}>): UseMutationResult<{
|
|
82
|
+
success: true;
|
|
83
|
+
}, Error, {
|
|
84
|
+
runId: string;
|
|
85
|
+
message: string;
|
|
86
|
+
}>;
|
|
87
|
+
|
|
88
|
+
export { FronteraChatContext, chatKeys, useCancelRun, useChatClient, useCreateSession, useFollowUps, usePendingApprovals, useRespondToApproval, useSession, useSessionMessages, useSessions, useSteerRun, useUpdateSession };
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { FronteraChatContext, chatKeys, useCancelRun, useChatClient, useCreateSession, useFollowUps, usePendingApprovals, useRespondToApproval, useSession, useSessionMessages, useSessions, useSteerRun, useUpdateSession } from './chunk-CMMWTCYS.js';
|
|
2
|
+
//# sourceMappingURL=hooks.js.map
|
|
3
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { FronteraClient } from '@frontera-sdk/core/client';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Plug chat into `createFronteraApp` — Frontera Apps only:
|
|
6
|
+
*
|
|
7
|
+
* ```tsx
|
|
8
|
+
* createFronteraApp(<App />, { providers: [blueprintProvider, chatProvider] })
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* External applications don't use this: they construct their own
|
|
12
|
+
* `FronteraClient` and either use `ChatClient` directly or wrap
|
|
13
|
+
* `FronteraChatContext.Provider` themselves. The client is rebuilt whenever
|
|
14
|
+
* the host rotates the credential, same as `blueprintProvider`.
|
|
15
|
+
*/
|
|
16
|
+
declare function chatProvider(value: {
|
|
17
|
+
client: FronteraClient;
|
|
18
|
+
}, children: ReactNode): ReactNode;
|
|
19
|
+
|
|
20
|
+
export { chatProvider };
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ChatClient } from './chunk-RLHHRJWS.js';
|
|
2
|
+
import './chunk-VWUR3ECR.js';
|
|
3
|
+
import './chunk-KZEGEV3P.js';
|
|
4
|
+
import { FronteraChatContext } from './chunk-CMMWTCYS.js';
|
|
5
|
+
import { jsx } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
function chatProvider(value, children) {
|
|
8
|
+
return /* @__PURE__ */ jsx(FronteraChatContext.Provider, { value: new ChatClient(value.client), children });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export { chatProvider };
|
|
12
|
+
//# sourceMappingURL=provider.js.map
|
|
13
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { FronteraError } from '@frontera-sdk/core/errors';
|
|
2
|
+
import { PublicChatTokenOptions } from './public-token.js';
|
|
3
|
+
import './types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A website visitor's conversation without React or the AI SDK (Forge
|
|
7
|
+
* improvement plan L4): mint the visitor's token, send a message, read the
|
|
8
|
+
* reply as it arrives. `<frontera-chat>` is built on it; use it directly for a
|
|
9
|
+
* chat of your own design.
|
|
10
|
+
*
|
|
11
|
+
* Under the Agent's reply checks the platform holds a reply until it is
|
|
12
|
+
* checked, so it can arrive in one piece after a pause: `waiting` says the
|
|
13
|
+
* Agent is working, and the text follows once it may be shown.
|
|
14
|
+
*/
|
|
15
|
+
type PublicChatEvent = {
|
|
16
|
+
type: 'waiting';
|
|
17
|
+
} | {
|
|
18
|
+
type: 'text';
|
|
19
|
+
text: string;
|
|
20
|
+
} | {
|
|
21
|
+
type: 'done';
|
|
22
|
+
} | {
|
|
23
|
+
type: 'error';
|
|
24
|
+
error: FronteraError;
|
|
25
|
+
};
|
|
26
|
+
interface PublicChatOptions extends Omit<PublicChatTokenOptions, 'fetchImpl'> {
|
|
27
|
+
/** Test seam. Defaults to the global. */
|
|
28
|
+
fetchImpl?: typeof fetch;
|
|
29
|
+
}
|
|
30
|
+
/** The JSON events of a chat's server-sent event stream, the keepalive comments and the terminator left out. */
|
|
31
|
+
declare function chatEvents(response: Response): AsyncGenerator<Record<string, unknown>>;
|
|
32
|
+
declare class PublicChat {
|
|
33
|
+
private readonly options;
|
|
34
|
+
private token;
|
|
35
|
+
private conversationId;
|
|
36
|
+
/** Shown before the first message; sent back with it, so the conversation keeps it. */
|
|
37
|
+
openingMessage: string | null;
|
|
38
|
+
private opened;
|
|
39
|
+
constructor(options: PublicChatOptions);
|
|
40
|
+
/** Mint the visitor's token, and learn the Agent's opening message. Called by `send` when needed. */
|
|
41
|
+
open(): Promise<{
|
|
42
|
+
openingMessage: string | null;
|
|
43
|
+
}>;
|
|
44
|
+
private client;
|
|
45
|
+
private messagesFor;
|
|
46
|
+
/** Send one message and read the reply: `waiting`, then the reply's text as it grows, then `done` (or `error`). */
|
|
47
|
+
send(text: string, signal?: AbortSignal): AsyncGenerator<PublicChatEvent>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { PublicChat, type PublicChatEvent, type PublicChatOptions, chatEvents };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ChatToken } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Start a chat for an anonymous visitor of a public website, from the browser.
|
|
5
|
+
*
|
|
6
|
+
* A public website key (`fpk_…`) is made to sit in page JavaScript: it reaches
|
|
7
|
+
* one Agent, only from the origins listed on it, and only to mint a short-lived
|
|
8
|
+
* token that chats and does nothing else. So, unlike the server exchange in
|
|
9
|
+
* `auth.ts`, this runs where the visitor is and needs no backend of your own.
|
|
10
|
+
*
|
|
11
|
+
* The platform names the visitor, not the page. What comes back with the first
|
|
12
|
+
* token is a visitor pass; this keeps it in `localStorage` and shows it at the
|
|
13
|
+
* next mint, so a returning visitor gets their conversations back. Storage can
|
|
14
|
+
* be unavailable (a private window, blocked site data) — the chat then still
|
|
15
|
+
* works, and each page load is simply a new visitor.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
interface PublicChatTokenOptions {
|
|
19
|
+
/** Your deployment's API origin, for example `https://api.acme.example`. */
|
|
20
|
+
apiBaseUrl: string;
|
|
21
|
+
/** The Agent's public website key, `fpk_…`. */
|
|
22
|
+
publishableKey: string;
|
|
23
|
+
/** Test seam. Defaults to the global. */
|
|
24
|
+
fetchImpl?: typeof fetch;
|
|
25
|
+
/** Test seam. Defaults to `localStorage`, when there is one. */
|
|
26
|
+
storage?: Pick<Storage, 'getItem' | 'setItem' | 'removeItem'> | null;
|
|
27
|
+
/**
|
|
28
|
+
* The visitor's language (`en-GB`, `id`), for the Agent's opening message.
|
|
29
|
+
* Defaults to the browser's (`navigator.language`).
|
|
30
|
+
*/
|
|
31
|
+
language?: string;
|
|
32
|
+
}
|
|
33
|
+
/** A visitor's token, with what the Agent says first when it has an opening message. */
|
|
34
|
+
interface PublicChatToken extends ChatToken {
|
|
35
|
+
/**
|
|
36
|
+
* The Agent's opening message in the visitor's language, to show before they
|
|
37
|
+
* type. Send it back unchanged as an `assistant` message before the
|
|
38
|
+
* visitor's first message, and the conversation keeps it as the Agent's.
|
|
39
|
+
*/
|
|
40
|
+
openingMessage?: string;
|
|
41
|
+
}
|
|
42
|
+
/** One pass per key, so two Agents on one site never share a visitor. */
|
|
43
|
+
declare function visitorPassStorageKey(publishableKey: string): string;
|
|
44
|
+
declare function mintPublicChatToken(options: PublicChatTokenOptions): Promise<PublicChatToken>;
|
|
45
|
+
|
|
46
|
+
export { type PublicChatToken, type PublicChatTokenOptions, mintPublicChatToken, visitorPassStorageKey };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An Agent's reply as a page without a Markdown renderer shows it (Forge
|
|
3
|
+
* improvement plan L4): citation links out unless the page shows sources,
|
|
4
|
+
* then paragraphs of plain text, bold and links. No HTML is ever produced
|
|
5
|
+
* from the reply; the element builds DOM nodes from these pieces.
|
|
6
|
+
*/
|
|
7
|
+
/** The reply without its citations: a numbered one goes, a named one keeps its name. */
|
|
8
|
+
declare function withoutCitations(text: string): string;
|
|
9
|
+
type ReplyPiece = {
|
|
10
|
+
kind: 'text';
|
|
11
|
+
text: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'bold';
|
|
14
|
+
text: string;
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'link';
|
|
17
|
+
text: string;
|
|
18
|
+
href: string;
|
|
19
|
+
};
|
|
20
|
+
/** Paragraphs (split on blank lines), each a list of lines, each line its pieces. */
|
|
21
|
+
declare function replyParagraphs(text: string, options?: {
|
|
22
|
+
showSources?: boolean;
|
|
23
|
+
}): ReplyPiece[][][];
|
|
24
|
+
|
|
25
|
+
export { type ReplyPiece, replyParagraphs, withoutCitations };
|
package/dist/stream.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { FronteraClient } from '@frontera-sdk/core/client';
|
|
2
|
+
import { StartChatRequest, StartChatResult } from './types.js';
|
|
3
|
+
|
|
4
|
+
interface ChatStreamOptions {
|
|
5
|
+
signal?: AbortSignal;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Start a run. The returned `response.body` is an AI SDK v6 UI-message SSE
|
|
9
|
+
* stream — hand it to `readUIMessageStream` from `ai`, or use
|
|
10
|
+
* `createChatTransport` with `useChat` instead of calling this directly.
|
|
11
|
+
*
|
|
12
|
+
* The server interleaves `: ping` SSE comment frames every 15s as keepalive;
|
|
13
|
+
* standard SSE parsers (including the AI SDK's) ignore them.
|
|
14
|
+
*/
|
|
15
|
+
declare function startChat(client: FronteraClient, request: StartChatRequest, options?: ChatStreamOptions): Promise<StartChatResult>;
|
|
16
|
+
/**
|
|
17
|
+
* Re-attach to a running (or just-finished) run's stream. `startIndex` is the
|
|
18
|
+
* journal index to replay from — pass the count of chunks already consumed to
|
|
19
|
+
* resume without duplicates.
|
|
20
|
+
*/
|
|
21
|
+
declare function resumeStream(client: FronteraClient, runId: string, options?: {
|
|
22
|
+
startIndex?: number;
|
|
23
|
+
} & ChatStreamOptions): Promise<Response>;
|
|
24
|
+
|
|
25
|
+
export { type ChatStreamOptions, resumeStream, startChat };
|
package/dist/stream.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { FronteraClient } from '@frontera-sdk/core/client';
|
|
2
|
+
import { ChatToken } from './types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A chat credential that keeps itself alive.
|
|
6
|
+
*
|
|
7
|
+
* The alternative — what an integration writes by hand — is a timer that calls
|
|
8
|
+
* their own backend, plus a 401 handler, plus the knowledge that both are
|
|
9
|
+
* needed. Every one of those is a place to be subtly wrong: refresh too late
|
|
10
|
+
* and a send fails; forget the 401 path and a laptop reopened after lunch
|
|
11
|
+
* shows a chat that quietly cannot send.
|
|
12
|
+
*
|
|
13
|
+
* So the session owns it. It renews against the platform (one signature check,
|
|
14
|
+
* no round trip to the customer's backend), falls back to `getToken` when
|
|
15
|
+
* renewal is no longer allowed, and hands the current credential to whoever
|
|
16
|
+
* asks. Nothing else in the SDK needs to know a token can expire.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
interface TokenSessionOptions {
|
|
20
|
+
apiBaseUrl: string;
|
|
21
|
+
/**
|
|
22
|
+
* Fetches a token from YOUR backend. Called once at the start, and again
|
|
23
|
+
* whenever renewal is no longer possible — a session that has run past its
|
|
24
|
+
* ceiling, or a token that expired while the tab was asleep.
|
|
25
|
+
*/
|
|
26
|
+
getToken: () => Promise<ChatToken | string>;
|
|
27
|
+
/**
|
|
28
|
+
* A token you already have — typically the one whose expiry told you which
|
|
29
|
+
* `apiBaseUrl` to use. Supplying it skips the opening fetch, so a mount does
|
|
30
|
+
* not mint twice for the same person.
|
|
31
|
+
*/
|
|
32
|
+
initialToken?: ChatToken;
|
|
33
|
+
/**
|
|
34
|
+
* Renew this long before expiry, as a fraction of the token's lifetime.
|
|
35
|
+
* 0.8 leaves a fifth of the lifetime as slack — enough for a slow network or
|
|
36
|
+
* a suspended tab to recover without a single failed request.
|
|
37
|
+
*/
|
|
38
|
+
renewAt?: number;
|
|
39
|
+
/** Called after every successful renewal. Handy for persisting the token. */
|
|
40
|
+
onToken?: (token: ChatToken) => void;
|
|
41
|
+
/**
|
|
42
|
+
* Called when the session cannot be kept alive at all — both renewal and
|
|
43
|
+
* `getToken` failed. The chat is dead until something changes, and the host
|
|
44
|
+
* is the only one that can say so on screen.
|
|
45
|
+
*/
|
|
46
|
+
onExpired?: (error: unknown) => void;
|
|
47
|
+
/** Test seam. Defaults to the global. */
|
|
48
|
+
fetchImpl?: typeof fetch;
|
|
49
|
+
}
|
|
50
|
+
interface TokenSession {
|
|
51
|
+
/** The client to hand to `ChatClient` and `createChatTransport`. */
|
|
52
|
+
getClient: () => FronteraClient;
|
|
53
|
+
/** The credential right now, mostly for diagnostics. */
|
|
54
|
+
current: () => ChatToken | null;
|
|
55
|
+
/**
|
|
56
|
+
* Force a refresh — call it when a request comes back `UNAUTHORIZED`. Safe to
|
|
57
|
+
* call concurrently: overlapping callers share one in-flight refresh rather
|
|
58
|
+
* than racing to mint several tokens for the same person.
|
|
59
|
+
*/
|
|
60
|
+
refresh: () => Promise<ChatToken>;
|
|
61
|
+
/** Stop the timer. Call it when the chat unmounts. */
|
|
62
|
+
dispose: () => void;
|
|
63
|
+
}
|
|
64
|
+
declare function createTokenSession(options: TokenSessionOptions): TokenSession;
|
|
65
|
+
|
|
66
|
+
export { type TokenSession, type TokenSessionOptions, createTokenSession };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { FronteraClient } from '@frontera-sdk/core/client';
|
|
2
|
+
import { FronteraError } from '@frontera-sdk/core/errors';
|
|
3
|
+
|
|
4
|
+
// src/token-session.ts
|
|
5
|
+
var DEFAULT_RENEW_AT = 0.8;
|
|
6
|
+
var MIN_DELAY_MS = 5e3;
|
|
7
|
+
function asToken(value) {
|
|
8
|
+
return typeof value === "string" ? (
|
|
9
|
+
// A bare string has no expiry to plan around. Treated as one hour — the
|
|
10
|
+
// platform default — so the session still refreshes rather than waiting
|
|
11
|
+
// for a failure. Return the full object from your backend to be exact.
|
|
12
|
+
{ token: value, expiresAt: new Date(Date.now() + 36e5).toISOString() }
|
|
13
|
+
) : value;
|
|
14
|
+
}
|
|
15
|
+
function createTokenSession(options) {
|
|
16
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
17
|
+
const renewAt = options.renewAt ?? DEFAULT_RENEW_AT;
|
|
18
|
+
let token = null;
|
|
19
|
+
let timer = null;
|
|
20
|
+
let inFlight = null;
|
|
21
|
+
let disposed = false;
|
|
22
|
+
const client = () => new FronteraClient(
|
|
23
|
+
{
|
|
24
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
25
|
+
credential: { kind: "token", token: token?.token ?? "" }
|
|
26
|
+
},
|
|
27
|
+
fetchImpl
|
|
28
|
+
);
|
|
29
|
+
const schedule = (current) => {
|
|
30
|
+
if (timer) clearTimeout(timer);
|
|
31
|
+
if (disposed) return;
|
|
32
|
+
const lifetime = new Date(current.expiresAt).getTime() - Date.now();
|
|
33
|
+
if (!Number.isFinite(lifetime)) return;
|
|
34
|
+
const delay = Math.max(MIN_DELAY_MS, lifetime * renewAt);
|
|
35
|
+
timer = setTimeout(() => {
|
|
36
|
+
void refresh().catch(() => {
|
|
37
|
+
});
|
|
38
|
+
}, delay);
|
|
39
|
+
timer.unref?.();
|
|
40
|
+
};
|
|
41
|
+
const renew = async () => {
|
|
42
|
+
if (!token) throw new Error("no token to renew");
|
|
43
|
+
const response = await fetchImpl(`${options.apiBaseUrl}/v1/chat-tokens/renew`, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: { authorization: `Bearer ${token.token}` }
|
|
46
|
+
});
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
throw new FronteraError(`renewal refused with ${response.status}`, {
|
|
49
|
+
code: response.status === 401 ? "UNAUTHORIZED" : "RENEWAL_FAILED",
|
|
50
|
+
status: response.status
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const body = await response.json();
|
|
54
|
+
return body.data ?? body;
|
|
55
|
+
};
|
|
56
|
+
const refresh = () => {
|
|
57
|
+
inFlight ??= (async () => {
|
|
58
|
+
try {
|
|
59
|
+
let next;
|
|
60
|
+
try {
|
|
61
|
+
next = token ? await renew() : asToken(await options.getToken());
|
|
62
|
+
} catch {
|
|
63
|
+
next = asToken(await options.getToken());
|
|
64
|
+
}
|
|
65
|
+
token = next;
|
|
66
|
+
options.onToken?.(next);
|
|
67
|
+
schedule(next);
|
|
68
|
+
return next;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
options.onExpired?.(error);
|
|
71
|
+
throw error;
|
|
72
|
+
} finally {
|
|
73
|
+
inFlight = null;
|
|
74
|
+
}
|
|
75
|
+
})();
|
|
76
|
+
return inFlight;
|
|
77
|
+
};
|
|
78
|
+
if (options.initialToken) {
|
|
79
|
+
token = options.initialToken;
|
|
80
|
+
schedule(token);
|
|
81
|
+
} else {
|
|
82
|
+
void refresh().catch(() => {
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
getClient: client,
|
|
87
|
+
current: () => token,
|
|
88
|
+
refresh,
|
|
89
|
+
dispose: () => {
|
|
90
|
+
disposed = true;
|
|
91
|
+
if (timer) clearTimeout(timer);
|
|
92
|
+
timer = null;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export { createTokenSession };
|
|
98
|
+
//# sourceMappingURL=token-session.js.map
|
|
99
|
+
//# sourceMappingURL=token-session.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ChatTransport, UIMessage } from 'ai';
|
|
2
|
+
import { FronteraClient } from '@frontera-sdk/core/client';
|
|
3
|
+
import { StartChatRequest } from './types.js';
|
|
4
|
+
|
|
5
|
+
/** The id of the Agent's opening message a chat shows before the person's first message (L4). */
|
|
6
|
+
declare const OPENING_MESSAGE_ID = "frontera-opening";
|
|
7
|
+
interface ChatTransportOptions {
|
|
8
|
+
/** Continue an existing conversation. Omit to let the server create one. */
|
|
9
|
+
conversationId?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Send only the last user message (default true). The server rehydrates the
|
|
12
|
+
* rest of the conversation from its own store, which keeps request bodies
|
|
13
|
+
* small — the platform web app does the same.
|
|
14
|
+
*/
|
|
15
|
+
lastMessageOnly?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Extra body fields merged into every send (agentName, modelMode, …).
|
|
18
|
+
*
|
|
19
|
+
* Pass a FUNCTION for anything that can change while the chat is mounted.
|
|
20
|
+
* An object is read once, at construction: `useChat` keeps the transport it
|
|
21
|
+
* was first handed, so rebuilding this one with new options never reaches
|
|
22
|
+
* the request, and the send goes out with whatever was true at first render.
|
|
23
|
+
* That is how a conversation ends up with a different agent than the one on
|
|
24
|
+
* screen.
|
|
25
|
+
*/
|
|
26
|
+
body?: Partial<Omit<StartChatRequest, 'messages' | 'conversationId'>> | (() => Partial<Omit<StartChatRequest, 'messages' | 'conversationId'>>);
|
|
27
|
+
/**
|
|
28
|
+
* Fired once per run with the identifiers from the response headers. The
|
|
29
|
+
* `runId` is what `resumeStream`/`cancelRun`/`steerRun` take, and
|
|
30
|
+
* `conversationId` is how a fresh conversation learns its id.
|
|
31
|
+
*/
|
|
32
|
+
onStart?: (info: {
|
|
33
|
+
conversationId: string;
|
|
34
|
+
runId: string;
|
|
35
|
+
runtime: string;
|
|
36
|
+
quotaWarning?: string;
|
|
37
|
+
}) => void;
|
|
38
|
+
/**
|
|
39
|
+
* Called once when a request comes back `401`, before it is retried.
|
|
40
|
+
*
|
|
41
|
+
* Pass `session.refresh` from `createTokenSession` and an expired credential
|
|
42
|
+
* stops being a visible failure: the send is replayed with the new token and
|
|
43
|
+
* the person never learns there was one. Without it, a chat left open past
|
|
44
|
+
* the token's lifetime fails on the next message with nothing on screen
|
|
45
|
+
* explaining why.
|
|
46
|
+
*/
|
|
47
|
+
onUnauthorized?: () => Promise<unknown>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* An AI SDK `ChatTransport` for `useChat` that speaks the platform's chat
|
|
51
|
+
* wire protocol: auth from the `FronteraClient`'s credential, last-message-only
|
|
52
|
+
* sends, and reconnects via the run's replay stream.
|
|
53
|
+
*
|
|
54
|
+
* Browser use requires a browser-safe credential (an end-user `cht_` token, or
|
|
55
|
+
* an `apiBaseUrl` pointing at your own proxy) — never ship a `sak_` key to a
|
|
56
|
+
* browser.
|
|
57
|
+
*/
|
|
58
|
+
declare function createChatTransport(client: FronteraClient | (() => FronteraClient), options?: ChatTransportOptions): ChatTransport<UIMessage>;
|
|
59
|
+
|
|
60
|
+
export { type ChatTransportOptions, OPENING_MESSAGE_ID, createChatTransport };
|