@waniwani/sdk 0.4.8-beta.2 → 0.4.8-beta.3
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/mcp/index.d.ts +26 -4
- package/dist/mcp/index.js +5 -4
- package/dist/mcp/index.js.map +1 -1
- package/package.json +1 -1
package/dist/mcp/index.d.ts
CHANGED
|
@@ -808,6 +808,8 @@ type RegisteredFlow = {
|
|
|
808
808
|
title: string;
|
|
809
809
|
description: string;
|
|
810
810
|
register: (server: McpServer) => Promise<void>;
|
|
811
|
+
/** Returns a Mermaid `flowchart TD` diagram of the flow graph. */
|
|
812
|
+
graph: () => string;
|
|
811
813
|
};
|
|
812
814
|
type FlowTokenContent = {
|
|
813
815
|
step?: string;
|
|
@@ -867,7 +869,7 @@ type FlowErrorContent = {
|
|
|
867
869
|
* .compile();
|
|
868
870
|
* ```
|
|
869
871
|
*/
|
|
870
|
-
declare class StateGraph<TState extends Record<string, unknown
|
|
872
|
+
declare class StateGraph<TState extends Record<string, unknown>, TNodes extends string = never> {
|
|
871
873
|
private nodes;
|
|
872
874
|
private edges;
|
|
873
875
|
private config;
|
|
@@ -877,20 +879,40 @@ declare class StateGraph<TState extends Record<string, unknown>> {
|
|
|
877
879
|
*
|
|
878
880
|
* The handler receives a context object with `state`, `meta`, `interrupt`, and `showWidget`.
|
|
879
881
|
*/
|
|
880
|
-
addNode(name:
|
|
882
|
+
addNode<TName extends string>(name: TName, handler: NodeHandler<TState>): StateGraph<TState, TNodes | TName>;
|
|
881
883
|
/**
|
|
882
884
|
* Add a direct edge between two nodes.
|
|
883
885
|
*
|
|
884
886
|
* Use `START` as `from` to set the entry point.
|
|
885
887
|
* Use `END` as `to` to mark a terminal node.
|
|
886
888
|
*/
|
|
887
|
-
addEdge(from:
|
|
889
|
+
addEdge(from: typeof START | TNodes, to: TNodes | typeof END): this;
|
|
888
890
|
/**
|
|
889
891
|
* Add a conditional edge from a node.
|
|
890
892
|
*
|
|
891
893
|
* The condition function receives current state and returns the name of the next node.
|
|
892
894
|
*/
|
|
893
|
-
addConditionalEdge(from:
|
|
895
|
+
addConditionalEdge(from: TNodes, condition: (state: Partial<TState>) => TNodes | typeof END | Promise<TNodes | typeof END>): this;
|
|
896
|
+
/**
|
|
897
|
+
* Generate a Mermaid `flowchart TD` diagram of the graph.
|
|
898
|
+
*
|
|
899
|
+
* Direct edges use solid arrows. Conditional edges use a dashed arrow
|
|
900
|
+
* to a placeholder since branch targets are determined at runtime.
|
|
901
|
+
*
|
|
902
|
+
* @example
|
|
903
|
+
* ```ts
|
|
904
|
+
* console.log(graph.graph());
|
|
905
|
+
* // flowchart TD
|
|
906
|
+
* // __start__((Start))
|
|
907
|
+
* // ask_name[ask_name]
|
|
908
|
+
* // greet[greet]
|
|
909
|
+
* // __end__((End))
|
|
910
|
+
* // __start__ --> ask_name
|
|
911
|
+
* // ask_name --> greet
|
|
912
|
+
* // greet --> __end__
|
|
913
|
+
* ```
|
|
914
|
+
*/
|
|
915
|
+
graph(): string;
|
|
894
916
|
/**
|
|
895
917
|
* Compile the graph into a RegisteredFlow that can be registered on an McpServer.
|
|
896
918
|
*
|
package/dist/mcp/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
function Y(){return typeof window<"u"&&"openai"in window?"openai":"mcp-apps"}function
|
|
1
|
+
function Y(){return typeof window<"u"&&"openai"in window?"openai":"mcp-apps"}function Ve(){return Y()==="openai"}function ze(){return Y()==="mcp-apps"}var R="__start__",C="__end__",de=Symbol.for("waniwani.flow.interrupt"),ue=Symbol.for("waniwani.flow.widget");function pe(e,t){let n=t?.context,r=[];for(let[o,s]of Object.entries(e))if(typeof s=="object"&&s!==null&&"question"in s){let i=s;r.push({question:i.question,field:o,suggestions:i.suggestions,context:i.context,validate:i.validate})}return{__type:de,questions:r,context:n}}function le(e,t){return{__type:ue,tool:e,...t}}function fe(e){return typeof e=="object"&&e!==null&&"__type"in e&&e.__type===de}function ge(e){return typeof e=="object"&&e!==null&&"__type"in e&&e.__type===ue}import{z as $}from"zod";var J="waniwani/client";function O(e){if(typeof e=="object"&&e!==null)return e[J]}function me(e,t){return{track(n){return e.track({...n,meta:{...t,...n.meta}})},identify(n,r){return e.identify(n,r,t)},kb:e.kb}}function A(e,t){for(let n of t){let r=e[n];if(typeof r=="string"&&r.length>0)return r}}var He=["waniwani/sessionId","openai/sessionId","sessionId","conversationId","anthropic/sessionId"],Ye=["waniwani/requestId","openai/requestId","requestId","mcp/requestId"],Je=["waniwani/traceId","openai/traceId","traceId","mcp/traceId","openai/requestId","requestId"],Xe=["waniwani/userId","openai/userId","externalUserId","userId","actorId"],Ge=["correlationId","openai/requestId"];function _(e){return e?A(e,He):void 0}function he(e){return e?A(e,Ye):void 0}function we(e){return e?A(e,Je):void 0}function ye(e){return e?A(e,Xe):void 0}function Te(e){return e?A(e,Ge):void 0}var Ze=[{key:"waniwani/sessionId",source:"chatbar"},{key:"openai/sessionId",source:"chatgpt"},{key:"anthropic/sessionId",source:"claude"}];function N(e){if(!e)return;let t=e["waniwani/source"];if(typeof t=="string"&&t.length>0)return t;for(let{key:n,source:r}of Ze){let o=e[n];if(typeof o=="string"&&o.length>0)return r}}function ke(e){let t=e._zod?.def;return t?.type==="object"&&t.shape?t.shape:null}function D(e,t){let n=t.split("."),r=e;for(let o of n){if(r==null||typeof r!="object")return;r=r[o]}return r}function Qe(e,t,n){let r=t.split("."),o=r.pop();if(!o)return;let s=e;for(let i of r)(s[i]==null||typeof s[i]!="object"||Array.isArray(s[i]))&&(s[i]={}),s=s[i];s[o]=n}function Se(e,t){let n=t.split("."),r=n.pop();if(!r)return;let o=e;for(let s of n){if(o==null||typeof o!="object")return;o=o[s]}o!=null&&typeof o=="object"&&delete o[r]}function X(e){let t={};for(let[n,r]of Object.entries(e))n.includes(".")?Qe(t,n,r):t[n]=r;return t}function M(e,t){let n={...e};for(let[r,o]of Object.entries(t))o!==null&&typeof o=="object"&&!Array.isArray(o)&&n[r]!==null&&typeof n[r]=="object"&&!Array.isArray(n[r])?n[r]=M(n[r],o):n[r]=o;return n}function G(e){return e!=null&&e!==""}async function F(e,t){return e.type==="direct"?e.to:e.condition(t)}function ve(e,t,n,r){if(e.every(c=>G(D(r,c.field))))return null;let o=e.filter(c=>!G(D(r,c.field))),s=o.length===1,i=o[0];return{content:s&&i?{status:"interrupt",question:i.question,field:i.field,...i.suggestions?{suggestions:i.suggestions}:{},...i.context||t?{context:i.context??t}:{}}:{status:"interrupt",questions:o,...t?{context:t}:{}},flowTokenContent:{step:n,state:r,...s&&i?{field:i.field}:{}}}}async function j(e,t,n,r,o,s,i){let a=e,c={...t},d=50,f=0;for(;f++<d;){if(a===C)return{content:{status:"complete"},flowTokenContent:{state:c}};let h=n.get(a);if(!h)return{content:{status:"error",error:`Unknown node: "${a}"`}};try{let p=await h({state:c,meta:s,interrupt:pe,showWidget:le,waniwani:i});if(fe(p)){for(let T of p.questions)T.validate&&o.set(`${a}:${T.field}`,T.validate);let y=ve(p.questions,p.context,a,c);if(y)return y;for(let T of p.questions){let x=o.get(`${a}:${T.field}`);if(x)try{let b=D(c,T.field),E=await x(b);E&&typeof E=="object"&&(c=M(c,E))}catch(b){let E=b instanceof Error?b.message:String(b);Se(c,T.field);let k=p.questions.map(I=>I.field===T.field?{...I,context:I.context?`ERROR: ${E}
|
|
2
2
|
|
|
3
|
-
${
|
|
4
|
-
`)}var
|
|
5
|
-
${o}`,i=e.store??new N,a=new Map;async function c(d,f,w,l){if(d.action==="start"){let p=r.get(I);if(!p)return{content:{status:"error",error:"No start edge"}};let h=X(d.stateUpdates??{}),y=await F(p,h);return j(y,h,n,r,a,w,l)}if(d.action==="continue"){if(!f)return{content:{status:"error",error:"No session ID available for continue action."}};let p=await i.get(f);if(!p)return{content:{status:"error",error:"Flow state not found. The flow may have expired."}};let h=p.state,y=p.step;if(!y)return{content:{status:"error",error:"Flow state is missing the current step. The flow may have expired."}};let u=M(h,X(d.stateUpdates??{}));if(p.widgetId){let T=r.get(y);if(!T)return{content:{status:"error",error:`No edge from step "${y}"`}};let x=await F(T,u);return j(x,u,n,r,a,w,l)}return j(y,u,n,r,a,w,l)}return{content:{status:"error",error:`Unknown action: "${d.action}"`}}}return{id:t.id,title:t.title,description:s,async register(d){d.registerTool(t.id,{title:t.title,description:s,inputSchema:tt,annotations:t.annotations},(async(f,w)=>{let l=w,p=l._meta??{},h=_(p),y=O(l),u=await c(f,h,p,y);return u.flowTokenContent&&h&&await i.set(h,u.flowTokenContent),{content:[{type:"text",text:JSON.stringify(u.content,null,2)}],_meta:p,...u.content.status==="error"?{isError:!0}:{}}}))}}}var W=class{nodes=new Map;edges=new Map;config;constructor(t){this.config=t}addNode(t,n){if(t===I||t===b)throw new Error(`"${t}" is a reserved name and cannot be used as a node name`);if(this.nodes.has(t))throw new Error(`Node "${t}" already exists`);return this.nodes.set(t,n),this}addEdge(t,n){if(this.edges.has(t))throw new Error(`Node "${t}" already has an outgoing edge. Use addConditionalEdge for branching.`);return this.edges.set(t,{type:"direct",to:n}),this}addConditionalEdge(t,n){if(this.edges.has(t))throw new Error(`Node "${t}" already has an outgoing edge.`);return this.edges.set(t,{type:"conditional",condition:n}),this}compile(t){return this.validate(),Ee({config:this.config,nodes:new Map(this.nodes),edges:new Map(this.edges),store:t?.store})}validate(){if(!this.edges.has(I))throw new Error('Flow must have an entry point. Add an edge from START: .addEdge(START, "first_node")');let t=this.edges.get(I);if(t?.type==="direct"&&t.to!==b&&!this.nodes.has(t.to))throw new Error(`START edge references non-existent node: "${t.to}"`);for(let[n,r]of this.edges){if(n!==I&&!this.nodes.has(n))throw new Error(`Edge from non-existent node: "${n}"`);if(r.type==="direct"&&r.to!==b&&!this.nodes.has(r.to))throw new Error(`Edge from "${n}" references non-existent node: "${r.to}"`)}for(let[n]of this.nodes)if(!this.edges.has(n))throw new Error(`Node "${n}" has no outgoing edge. Add one with .addEdge("${n}", ...) or .addConditionalEdge("${n}", ...)`)}};function Ie(e){return new W(e)}function Ce(e){let t=e.content;return JSON.parse(t[0]?.text??"")}async function be(e,t){let n=t?.stateStore,r=[],o=`test-session-${Math.random().toString(36).slice(2,10)}`,s={registerTool:(...d)=>{r.push(d)}};await e.register(s);let i=r[0]?.[2];if(!i)throw new Error(`Flow "${e.id}" did not register a handler`);let a={_meta:{sessionId:o}};async function c(d){return{...d,decodedState:n?await n.get(o):null}}return{async start(d){let f=await i({action:"start",...d?{stateUpdates:d}:{}},a);return c(Ce(f))},async continueWith(d){let f=await i({action:"continue",...d?{stateUpdates:d}:{}},a);return c(Ce(f))},async lastState(){return n?n.get(o):null}}}var B="text/html+skybridge",$="text/html;profile=mcp-app",_e=async(e,t)=>{let n=e.endsWith("/")?e.slice(0,-1):e;return await(await fetch(`${n}${t}`)).text()};function Pe(e){return{"openai/widgetDescription":e.description,"openai/widgetPrefersBorder":e.prefersBorder,"openai/widgetDomain":e.widgetDomain,...e.widgetCSP&&{"openai/widgetCSP":e.widgetCSP}}}function Me(e){let t=e.widgetCSP?{connectDomains:e.widgetCSP.connect_domains,resourceDomains:e.widgetCSP.resource_domains,frameDomains:e.widgetCSP.frame_domains,redirectDomains:e.widgetCSP.redirect_domains}:void 0;return{ui:{...t&&{csp:t},...e.widgetDomain&&{domain:e.widgetDomain},...e.prefersBorder!==void 0&&{prefersBorder:e.prefersBorder}}}}function G(e){return{...e.openaiTemplateUri&&{"openai/outputTemplate":e.openaiTemplateUri},"openai/toolInvocation/invoking":e.invoking,"openai/toolInvocation/invoked":e.invoked,"openai/widgetAccessible":!0,"openai/resultCanProduceWidget":!0,...e.mcpTemplateUri&&{ui:{resourceUri:e.mcpTemplateUri,...e.autoHeight&&{autoHeight:!0}}},...e.mcpTemplateUri&&{"ui/resourceUri":e.mcpTemplateUri}}}function Fe(e){let{id:t,title:n,description:r,baseUrl:o,htmlPath:s,widgetDomain:i,prefersBorder:a=!0,autoHeight:c=!0}=e,d=e.widgetCSP??{connect_domains:[o],resource_domains:[o]};if(process.env.NODE_ENV==="development")try{let{hostname:u}=new URL(o);(u==="localhost"||u==="127.0.0.1")&&(d={...d,connect_domains:[...d.connect_domains||[],`ws://${u}:*`,`wss://${u}:*`],resource_domains:[...d.resource_domains||[],`http://${u}:*`]})}catch{}let f=`ui://widgets/apps-sdk/${t}.html`,w=`ui://widgets/ext-apps/${t}.html`,l=null,p=()=>(l||(l=_e(o,s)),l),h=r;async function y(u){let T=await p();u.registerResource(`${t}-openai-widget`,f,{title:n,description:h,mimeType:B,_meta:{"openai/widgetDescription":h,"openai/widgetPrefersBorder":a}},async x=>({contents:[{uri:x.href,mimeType:B,text:T,_meta:Pe({description:h,prefersBorder:a,widgetDomain:i,widgetCSP:d})}]})),u.registerResource(`${t}-mcp-widget`,w,{title:n,description:h,mimeType:$,_meta:{ui:{prefersBorder:a}}},async x=>({contents:[{uri:x.href,mimeType:$,text:T,_meta:Me({description:h,prefersBorder:a,widgetDomain:i,widgetCSP:d})}]}))}return{id:t,title:n,description:r,openaiUri:f,mcpUri:w,autoHeight:c,register:y}}function nt(e,t){let{resource:n,description:r,inputSchema:o,annotations:s,autoInjectResultText:i=!0}=e,a=e.id??n?.id,c=e.title??n?.title;if(!a)throw new Error("createTool: `id` is required when no resource is provided");if(!c)throw new Error("createTool: `title` is required when no resource is provided");let d=n?G({openaiTemplateUri:n.openaiUri,mcpTemplateUri:n.mcpUri,invoking:e.invoking??"Loading...",invoked:e.invoked??"Loaded",autoHeight:n.autoHeight}):void 0;return{id:a,title:c,description:r,async register(f){f.registerTool(a,{title:c,description:r,inputSchema:o,annotations:s,...d&&{_meta:d}},(async(w,l)=>{let p=l,h=p._meta??{},y=O(p),u=await t(w,{extra:{_meta:h},waniwani:y});return n&&u.data?{content:[{type:"text",text:u.text}],structuredContent:u.data,_meta:{...d,...h,...i===!1?{"waniwani/autoInjectResultText":!1}:{}}}:{content:[{type:"text",text:u.text}],...u.data?{structuredContent:u.data}:{},...i===!1?{_meta:{"waniwani/autoInjectResultText":!1}}:{}}}))}}}async function rt(e,t){await Promise.all(t.map(n=>n.register(e)))}var q=class extends Error{constructor(n,r){super(n);this.status=r;this.name="WaniWaniError"}};var ot="@waniwani/sdk";function We(e){let{baseUrl:t,apiKey:n}=e;function r(){if(!n)throw new Error("WANIWANI_API_KEY is not set");return n}async function o(s,i,a){let c=r(),d=`${t.replace(/\/$/,"")}${i}`,f={Authorization:`Bearer ${c}`,"X-WaniWani-SDK":ot},w={method:s,headers:f};a!==void 0&&(f["Content-Type"]="application/json",w.body=JSON.stringify(a));let l=await fetch(d,w);if(!l.ok){let h=await l.text().catch(()=>"");throw new q(h||`KB API error: HTTP ${l.status}`,l.status)}return(await l.json()).data}return{async ingest(s){return o("POST","/api/mcp/kb/ingest",{files:s})},async search(s,i){return o("POST","/api/mcp/kb/search",{query:s,...i})},async sources(){return o("GET","/api/mcp/kb/sources")}}}var it="@waniwani/sdk";function V(e,t={}){let n=t.now??(()=>new Date),r=t.generateId??Ae,o=ct(e),s=L(e.meta),i=L(e.metadata),a=dt(e,s),c=v(e.eventId)??r(),d=ut(e.timestamp,n),f=v(e.source)??D(s)??t.source??it,w=Q(e)?{...e}:void 0,l={...i};return Object.keys(s).length>0&&(l.meta=s),w&&(l.rawLegacy=w),{id:c,type:"mcp.event",name:o,source:f,timestamp:d,correlation:a,properties:st(e,o),metadata:l,rawLegacy:w}}function Ae(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?`evt_${crypto.randomUUID()}`:`evt_${Math.random().toString(36).slice(2,10)}_${Date.now().toString(36)}`}function st(e,t){if(!Q(e))return L(e.properties);let n=at(e,t),r=L(e.properties);return{...n,...r}}function at(e,t){switch(t){case"tool.called":{let n={};return v(e.toolName)&&(n.name=e.toolName),v(e.toolType)&&(n.type=e.toolType),n}case"quote.succeeded":{let n={};return typeof e.quoteAmount=="number"&&(n.amount=e.quoteAmount),v(e.quoteCurrency)&&(n.currency=e.quoteCurrency),n}case"link.clicked":{let n={};return v(e.linkUrl)&&(n.url=e.linkUrl),n}case"purchase.completed":{let n={};return typeof e.purchaseAmount=="number"&&(n.amount=e.purchaseAmount),v(e.purchaseCurrency)&&(n.currency=e.purchaseCurrency),n}default:return{}}}function ct(e){return Q(e)?e.eventType:e.event}function dt(e,t){let n=v(e.requestId)??we(t),r=v(e.sessionId)??_(t),o=v(e.traceId)??he(t),s=v(e.externalUserId)??ye(t),i=v(e.correlationId)??Te(t)??n,a={};return r&&(a.sessionId=r),o&&(a.traceId=o),n&&(a.requestId=n),i&&(a.correlationId=i),s&&(a.externalUserId=s),a}function ut(e,t){if(e instanceof Date)return e.toISOString();if(typeof e=="string"){let n=new Date(e);if(!Number.isNaN(n.getTime()))return n.toISOString()}return t().toISOString()}function L(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function v(e){if(typeof e=="string"&&e.trim().length!==0)return e}function Q(e){return"eventType"in e}var pt="/api/mcp/events/v2/batch";var De="@waniwani/sdk",lt=new Set([401,403]),ft=new Set([408,425,429,500,502,503,504]);function Ue(e){return new ee(e)}var ee=class{endpointUrl;flushIntervalMs;maxBatchSize;maxBufferSize;maxRetries;retryBaseDelayMs;retryMaxDelayMs;shutdownTimeoutMs;sdkVersion;fetchFn;logger;now;sleep;apiKey;buffer=[];flushTimer;flushScheduled=!1;flushScheduledTimer;flushInFlight;inFlightCount=0;isStopped=!1;isShuttingDown=!1;constructor(t){this.endpointUrl=wt(t.baseUrl,t.endpointPath??pt),this.flushIntervalMs=t.flushIntervalMs??1e3,this.maxBatchSize=t.maxBatchSize??20,this.maxBufferSize=t.maxBufferSize??1e3,this.maxRetries=t.maxRetries??3,this.retryBaseDelayMs=t.retryBaseDelayMs??200,this.retryMaxDelayMs=t.retryMaxDelayMs??2e3,this.shutdownTimeoutMs=t.shutdownTimeoutMs??2e3,this.fetchFn=t.fetchFn??fetch,this.logger=t.logger??console,this.now=t.now??(()=>new Date),this.sleep=t.sleep??(n=>new Promise(r=>setTimeout(r,n))),this.apiKey=t.apiKey,this.sdkVersion=t.sdkVersion,this.flushIntervalMs>0&&(this.flushTimer=setInterval(()=>{this.flush()},this.flushIntervalMs))}enqueue(t){if(this.isStopped||this.isShuttingDown){this.logger.warn("[WaniWani] Tracking transport is stopped, dropping event %s",t.id);return}if(this.buffer.length>=this.maxBufferSize){let n=this.buffer.length-this.maxBufferSize+1;this.buffer.splice(0,n),this.logger.warn("[WaniWani] Tracking buffer overflow, dropped %d oldest event(s)",n)}if(this.buffer.push(t),this.buffer.length>=this.maxBatchSize){this.flush();return}this.scheduleMicroFlush()}pendingEvents(){return this.buffer.length+this.inFlightCount}async flush(){return this.flushInFlight?this.flushInFlight:(this.flushInFlight=this.flushLoop().finally(()=>{this.flushInFlight=void 0}),this.flushInFlight)}async shutdown(t){this.isShuttingDown=!0,this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=void 0),this.flushScheduledTimer&&(clearTimeout(this.flushScheduledTimer),this.flushScheduledTimer=void 0,this.flushScheduled=!1);let n=t?.timeoutMs??this.shutdownTimeoutMs,r=this.flush();if(!Number.isFinite(n)||n<=0)return await r,this.isStopped=!0,{timedOut:!1,pendingEvents:this.pendingEvents()};let o=Symbol("shutdown-timeout");return await Promise.race([r.then(()=>"flushed"),this.sleep(n).then(()=>o)])===o?(this.isStopped=!0,{timedOut:!0,pendingEvents:this.pendingEvents()}):(this.isStopped=!0,{timedOut:!1,pendingEvents:this.pendingEvents()})}scheduleMicroFlush(){this.flushScheduled||(this.flushScheduled=!0,this.flushScheduledTimer=setTimeout(()=>{this.flushScheduledTimer=void 0,this.flushScheduled=!1,this.flush()},0))}async flushLoop(){for(;this.buffer.length>0&&!this.isStopped;){let t=this.buffer.splice(0,this.maxBatchSize);await this.sendBatchWithRetry(t)}}async sendBatchWithRetry(t){let n=0,r=t;for(;r.length>0&&!this.isStopped;){this.inFlightCount=r.length;let o=await this.sendBatchOnce(r);switch(this.inFlightCount=0,o.kind){case"success":return;case"auth":this.stopTransportForAuthFailure(o.status,r.length);return;case"permanent":this.logger.error("[WaniWani] Dropping %d event(s) after permanent failure: %s",r.length,o.reason);return;case"retryable":if(n>=this.maxRetries){this.logger.error("[WaniWani] Dropping %d event(s) after retry exhaustion: %s",r.length,o.reason);return}await this.sleep(this.backoffDelayMs(n)),n+=1;continue;case"partial":if(o.permanent.length>0&&this.logger.error("[WaniWani] Dropping %d event(s) rejected as permanent",o.permanent.length),o.retryable.length===0)return;if(n>=this.maxRetries){this.logger.error("[WaniWani] Dropping %d retryable event(s) after retry exhaustion",o.retryable.length);return}r=o.retryable,await this.sleep(this.backoffDelayMs(n)),n+=1;continue}}}async sendBatchOnce(t){let n;try{n=await this.fetchFn(this.endpointUrl,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,"X-WaniWani-SDK":De},body:JSON.stringify(this.makeBatchRequest(t))})}catch(s){return{kind:"retryable",reason:ht(s)}}if(lt.has(n.status))return{kind:"auth",status:n.status};if(ft.has(n.status))return{kind:"retryable",reason:`HTTP ${n.status}`};if(!n.ok)return{kind:"permanent",reason:`HTTP ${n.status}`};let r=await mt(n);if(!r?.rejected||r.rejected.length===0)return{kind:"success"};let o=this.classifyRejectedEvents(t,r.rejected);return o.retryable.length===0&&o.permanent.length===0?{kind:"success"}:{kind:"partial",retryable:o.retryable,permanent:o.permanent}}makeBatchRequest(t){return{sentAt:this.now().toISOString(),source:{sdk:De,version:this.sdkVersion??"0.0.0"},events:t}}classifyRejectedEvents(t,n){let r=new Map(t.map(i=>[i.id,i])),o=[],s=[];for(let i of n){let a=r.get(i.eventId);if(a){if(gt(i)){o.push(a);continue}s.push(a)}}return{retryable:o,permanent:s}}backoffDelayMs(t){let n=this.retryBaseDelayMs*2**t;return Math.min(n,this.retryMaxDelayMs)}stopTransportForAuthFailure(t,n){this.isStopped=!0;let r=this.buffer.length;this.buffer.splice(0,r),this.logger.error("[WaniWani] Auth failure (HTTP %d). Stopping tracking transport and dropping %d queued event(s)",t,n+r)}};function gt(e){if(e.retryable===!0)return!0;let t=e.code.toLowerCase();return t.includes("timeout")||t.includes("temporary")||t.includes("unavailable")||t.includes("rate_limit")||t.includes("transient")||t.includes("server")}async function mt(e){let t=await e.text();if(t)try{return JSON.parse(t)}catch{return}}function wt(e,t){let n=e.endsWith("/")?e:`${e}/`,r=t.startsWith("/")?t.slice(1):t;return`${n}${r}`}function ht(e){return e instanceof Error?e.message:String(e)}function Ne(e){let{baseUrl:t,apiKey:n,tracking:r}=e;function o(){if(!n)throw new Error("WANIWANI_API_KEY is not set");return n}let s=n?Ue({baseUrl:t,apiKey:n,endpointPath:r.endpointPath,flushIntervalMs:r.flushIntervalMs,maxBatchSize:r.maxBatchSize,maxBufferSize:r.maxBufferSize,maxRetries:r.maxRetries,retryBaseDelayMs:r.retryBaseDelayMs,retryMaxDelayMs:r.retryMaxDelayMs,shutdownTimeoutMs:r.shutdownTimeoutMs}):void 0,i={async identify(a,c,d){o();let f=V({event:"user.identified",externalUserId:a,properties:c,meta:d});return s?.enqueue(f),{eventId:f.id}},async track(a){o();let c=V(a);return s?.enqueue(c),{eventId:c.id}},async flush(){o(),await s?.flush()},async shutdown(a){return o(),await s?.shutdown({timeoutMs:a?.timeoutMs??r.shutdownTimeoutMs})??{timedOut:!1,pendingEvents:0}}};return s&&yt(i,r.shutdownTimeoutMs),i}function yt(e,t){if(typeof process>"u"||typeof process.once!="function"||typeof process.on!="function")return;let n=()=>{e.shutdown({timeoutMs:t})};process.once("beforeExit",n),process.once("SIGINT",n),process.once("SIGTERM",n)}function Oe(e){let t=e?.baseUrl??"https://app.waniwani.ai",n=e?.apiKey??process.env.WANIWANI_API_KEY,r={endpointPath:e?.tracking?.endpointPath??"/api/mcp/events/v2/batch",flushIntervalMs:e?.tracking?.flushIntervalMs??1e3,maxBatchSize:e?.tracking?.maxBatchSize??20,maxBufferSize:e?.tracking?.maxBufferSize??1e3,maxRetries:e?.tracking?.maxRetries??3,retryBaseDelayMs:e?.tracking?.retryBaseDelayMs??200,retryMaxDelayMs:e?.tracking?.retryMaxDelayMs??2e3,shutdownTimeoutMs:e?.tracking?.shutdownTimeoutMs??2e3},o={baseUrl:t,apiKey:n,tracking:r},s=Ne(o),i=We(o);return{...s,kb:i,_config:o}}function Tt(e){let t=e.event_type??"widget_click",r=t.startsWith("widget_")?t:`widget_${t}`,o={...e.metadata??{}};return e.event_name&&(o.event_name=e.event_name),{event:r,properties:o,sessionId:e.session_id,traceId:e.trace_id,externalUserId:e.user_id,eventId:e.event_id,timestamp:e.timestamp,source:e.source??"widget"}}function kt(e){let t={apiKey:e?.apiKey,baseUrl:e?.baseUrl},n;function r(){return n||(n=Oe(t)),n}return async function(s){let i;try{i=await s.json()}catch{return new Response(JSON.stringify({error:"Invalid JSON"}),{status:400,headers:{"Content-Type":"application/json"}})}if(!Array.isArray(i.events)||i.events.length===0)return new Response(JSON.stringify({error:"Missing or empty events array"}),{status:400,headers:{"Content-Type":"application/json"}});try{let a=r(),c=[];for(let d of i.events){let f=Tt(d),w=await a.track(f);c.push(w.eventId)}return await a.flush(),new Response(JSON.stringify({ok:!0,accepted:c.length}),{status:200,headers:{"Content-Type":"application/json"}})}catch(a){let c=a instanceof Error?a.message:"Unknown error";return new Response(JSON.stringify({error:c}),{status:500,headers:{"Content-Type":"application/json"}})}}}var z=class{cached=null;pending=null;config;constructor(t){this.config=t}async getToken(t,n){return this.cached&&Date.now()<this.cached.expiresAt-12e4?this.cached.token:this.pending?this.pending:(this.pending=this.mint(t,n).finally(()=>{this.pending=null}),this.pending)}async mint(t,n){let r=St(this.config.baseUrl,"/api/mcp/widget-tokens"),o={};t&&(o.sessionId=t),n&&(o.traceId=n);try{let s=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify(o)});if(!s.ok)return null;let i=await s.json(),a=i.data&&typeof i.data=="object"?i.data:i,c=new Date(a.expiresAt).getTime();return!a.token||Number.isNaN(c)?null:(this.cached={token:a.token,expiresAt:c},a.token)}catch{return null}}};function St(e,t){return`${e.endsWith("/")?e.slice(0,-1):e}${t}`}var je="waniwani/sessionId",te="waniwani/geoLocation",ne="waniwani/userLocation";function S(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}function H(e){if(!S(e))return;let t=e._meta;return S(t)?t:void 0}function re(e){if(!S(e))return;let t=e.content;return Array.isArray(t)?t.find(r=>S(r)&&r.type==="text"&&typeof r.text=="string")?.text:void 0}function vt(e,t){return typeof t=="function"?t(e)??"other":t??"other"}function oe(e,t,n,r,o,s){let i=vt(e,n.toolType),a=H(t);return console.log("[waniwani:debug] buildTrackInput meta:",JSON.stringify(a),"-> source:",D(a)),{event:"tool.called",properties:{name:e,type:i,...r??{},...s?.input!==void 0&&{input:s.input},...s?.output!==void 0&&{output:s.output}},meta:a,source:D(a),metadata:{...n.metadata??{},...o&&{clientInfo:o}}}}async function ie(e,t,n){try{await e.track(t)}catch(r){n?.(ae(r))}}async function se(e,t){try{await e.flush()}catch(n){t?.(ae(n))}}async function Ke(e,t,n,r){if(!S(e))return;S(e._meta)||(e._meta={});let o=e._meta,s=S(o.waniwani)?o.waniwani:void 0,i={...s??{},endpoint:s?.endpoint??`${n.replace(/\/$/,"")}/api/mcp/events/v2/batch`};if(t)try{let d=await t.getToken();d&&(i.token=d)}catch(d){r?.(ae(d))}let a=_(o);a&&(i.sessionId||(i.sessionId=a));let c=$e(o);c!==void 0&&(i.geoLocation||(i.geoLocation=c)),o.waniwani=i}function Be(e,t){let n=H(t);if(!n||!S(e))return;S(e._meta)||(e._meta={});let r=e._meta,o=_(n);o&&!r[je]&&(r[je]=o);let s=$e(n);s&&(r[te]||(r[te]=s),r[ne]||(r[ne]=s))}function $e(e){if(!e)return;let t=e[te]??e[ne];if(S(t)||typeof t=="string")return t}function ae(e){return e instanceof Error?e:new Error(String(e))}var qe="https://app.waniwani.ai";function xt(e,t){let n=e;if(n.__waniwaniWrapped)return n;n.__waniwaniWrapped=!0;let r=t.client,o=t.injectWidgetToken!==!1,s=null;function i(){if(s)return s;let c=r._config.apiKey;return c?(s=new z({baseUrl:r._config.baseUrl??qe,apiKey:c}),s):null}let a=e.registerTool.bind(e);return n.registerTool=((...c)=>{let[d,f,w]=c,l=typeof d=="string"&&d.trim().length>0?d:"unknown";if(typeof w!="function")return a(...c);let p=w;return a(d,f,async(y,u)=>{let T=H(u)??{},x=me(r,T);S(u)&&(u[J]=x);let C=performance.now(),R=e.server?.getClientVersion?.();try{let k=await p(y,u),P=Math.round(performance.now()-C),E=S(k)&&k.isError===!0;if(E){let ce=re(k);console.error(`[waniwani] Tool "${l}" returned error${ce?`: ${ce}`:""}`)}return await ie(r,oe(l,u,t,{durationMs:P,status:E?"error":"ok",...E&&{errorMessage:re(k)??"Unknown tool error"}},R,{input:y,output:k}),t.onError),t.flushAfterToolCall&&await se(r,t.onError),Be(k,u),o&&await Ke(k,i(),r._config.baseUrl??qe,t.onError),k}catch(k){let P=Math.round(performance.now()-C);throw await ie(r,oe(l,u,t,{durationMs:P,status:"error",errorMessage:k instanceof Error?k.message:String(k)},R,{input:y}),t.onError),t.flushAfterToolCall&&await se(r,t.onError),k}})}),n}export{b as END,I as START,W as StateGraph,Ie as createFlow,be as createFlowTestHarness,Fe as createResource,nt as createTool,kt as createTrackingRoute,Y as detectPlatform,Ve as isMCPApps,Le as isOpenAI,rt as registerTools,xt as withWaniwani};
|
|
3
|
+
${I.context}`:`ERROR: ${E}`}:I),P=ve(k,p.context,a,c);if(P)return P;break}}let u=r.get(a);if(!u)return{content:{status:"error",error:`No outgoing edge from node "${a}"`}};a=await F(u,c);continue}if(ge(p)){let y=p.field;if(y&&G(D(c,y))){let u=r.get(a);if(!u)return{content:{status:"error",error:`No outgoing edge from node "${a}"`}};a=await F(u,c);continue}return{content:{status:"widget",tool:p.tool.id,data:p.data,description:p.description,interactive:p.interactive!==!1},flowTokenContent:{step:a,state:c,field:y,widgetId:p.tool.id}}}c=M(c,p);let w=r.get(a);if(!w)return{content:{status:"error",error:`No outgoing edge from node "${a}"`}};a=await F(w,c)}catch(l){return{content:{status:"error",error:l instanceof Error?l.message:String(l)},flowTokenContent:{step:a,state:c}}}}return{content:{status:"error",error:"Flow exceeded maximum iterations (possible infinite loop)"}}}var et="@waniwani/sdk",tt="https://app.waniwani.ai",U=class{baseUrl;apiKey;constructor(t){this.baseUrl=(t?.baseUrl??process.env.WANIWANI_BASE_URL??tt).replace(/\/$/,""),this.apiKey=t?.apiKey??process.env.WANIWANI_API_KEY}async get(t){if(!this.apiKey)return null;try{return await this.request("/api/mcp/redis/get",{key:t})??null}catch{return null}}async set(t,n){if(this.apiKey)try{await this.request("/api/mcp/redis/set",{key:t,value:n})}catch{}}async delete(t){if(this.apiKey)try{await this.request("/api/mcp/redis/delete",{key:t})}catch{}}async request(t,n){let r=`${this.baseUrl}${t}`,o=await fetch(r,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json","X-WaniWani-SDK":et},body:JSON.stringify(n)});if(!o.ok){let i=await o.text().catch(()=>"");throw new Error(i||`Flow state API error: HTTP ${o.status}`)}return(await o.json()).data}};function xe(e){let t=e.description??"",n=e._zod?.def;if(n?.type==="enum"&&n.entries){let r=Object.keys(n.entries).map(o=>`"${o}"`).join(" | ");return t?`${r} \u2014 ${t}`:r}return t}function Re(e){let t=["","## FLOW EXECUTION PROTOCOL","","This tool implements a multi-step conversational flow. Follow this protocol exactly:","",'1. Call with `action: "start"` to begin. If the user\'s message already'," contains answers to likely questions, extract them into `stateUpdates`"," as `{ field: value }` pairs. The engine will auto-skip steps whose"," fields are already filled."," Only extract values the user explicitly stated \u2014 do NOT guess or invent values."];if(e.state){let n=[];for(let[r,o]of Object.entries(e.state)){let s=ke(o);if(s){let i=o.description??"",a=Object.entries(s).map(([c,d])=>{let f=xe(d);return f?`\`${r}.${c}\` (${f})`:`\`${r}.${c}\``}).join(", ");n.push(i?`\`${r}\` (${i}): ${a}`:`\`${r}\`: ${a}`)}else{let i=xe(o);n.push(i?`\`${r}\` (${i})`:`\`${r}\``)}}t.push(` Known fields: ${n.join(", ")}.`)}return t.push(" For grouped fields (shown as `group.subfield`), use dot-notation keys in `stateUpdates`:",' e.g. `{ "driver.name": "John", "driver.license": "ABC123" }`.',"2. The response JSON `status` field tells you what to do next:",' - `"interrupt"`: Pause and ask the user. Two forms:'," a. Single question: `{ question, field, context? }` \u2014 ask `question`, store answer in `field`."," b. Multi-question: `{ questions: [{question, field}, ...], context? }` \u2014 ask ALL questions"," in one conversational message, collect all answers."," `context` (if present) is hidden AI instructions \u2014 use to shape your response, do NOT show verbatim."," Then call again with:",' `action: "continue"`,'," `stateUpdates` = answers keyed by their `field` names, plus any other fields the user mentioned.",' - `"widget"`: The flow wants to show a UI widget. Call the tool named in the `tool`'," field, passing the `data` object as the tool's input."," Check the `interactive` field in the response:"," \u2022 `interactive: true` \u2014 The widget requires user interaction. After calling the display tool,"," STOP and WAIT for the user to interact with the widget. Do NOT call this flow tool again"," until the user has responded. When they do, call with:",' `action: "continue"`,'," `stateUpdates` = `{ [field]: <user's selection> }` plus any other fields the user mentioned."," \u2022 `interactive: false` \u2014 The widget is display-only. Call the display tool, then immediately",' call THIS flow tool again with `action: "continue"`. Do NOT wait for user interaction.',' - `"complete"`: The flow is done. Present the result to the user.',' - `"error"`: Something went wrong. Show the `error` message.',"","3. Do NOT invent state values. Only use `stateUpdates` for information the user explicitly provided.","4. Include only the fields the user actually answered in `stateUpdates` \u2014 do NOT guess missing ones."," If the user did not answer all pending questions, the engine will re-prompt for the remaining ones."," If the user mentioned values for other known fields, include those too \u2014"," they will be applied immediately and those steps will be auto-skipped."),t.join(`
|
|
4
|
+
`)}var nt={action:$.enum(["start","continue"]).describe('"start" to begin the flow, "continue" to resume after a pause (interrupt or widget)'),stateUpdates:$.record($.string(),$.unknown()).optional().describe("State field values to set before processing the next node. Use this to pass the user's answer (keyed by the field name from the response) and any other values the user mentioned.")};function Ee(e){let{config:t,nodes:n,edges:r}=e,o=Re(t),s=`${t.description}
|
|
5
|
+
${o}`,i=e.store??new U,a=new Map;async function c(d,f,h,l){if(d.action==="start"){let p=r.get(R);if(!p)return{content:{status:"error",error:"No start edge"}};let w=X(d.stateUpdates??{}),y=await F(p,w);return j(y,w,n,r,a,h,l)}if(d.action==="continue"){if(!f)return{content:{status:"error",error:"No session ID available for continue action."}};let p=await i.get(f);if(!p)return{content:{status:"error",error:"Flow state not found. The flow may have expired."}};let w=p.state,y=p.step;if(!y)return{content:{status:"error",error:"Flow state is missing the current step. The flow may have expired."}};let u=M(w,X(d.stateUpdates??{}));if(p.widgetId){let T=r.get(y);if(!T)return{content:{status:"error",error:`No edge from step "${y}"`}};let x=await F(T,u);return j(x,u,n,r,a,h,l)}return j(y,u,n,r,a,h,l)}return{content:{status:"error",error:`Unknown action: "${d.action}"`}}}return{id:t.id,title:t.title,description:s,graph:e.graph,async register(d){d.registerTool(t.id,{title:t.title,description:s,inputSchema:nt,annotations:t.annotations},(async(f,h)=>{let l=h,p=l._meta??{},w=_(p),y=O(l),u=await c(f,w,p,y);return u.flowTokenContent&&w&&await i.set(w,u.flowTokenContent),{content:[{type:"text",text:JSON.stringify(u.content,null,2)}],_meta:p,...u.content.status==="error"?{isError:!0}:{}}}))}}}function Ie(e,t){let n=["flowchart TD"];n.push(` ${R}((Start))`);for(let[r]of e)n.push(` ${r}[${r}]`);n.push(` ${C}((End))`);for(let[r,o]of t)o.type==="direct"?n.push(` ${r} --> ${o.to}`):n.push(` ${r} -.-> ${r}_branch([?])`);return n.join(`
|
|
6
|
+
`)}var W=class{nodes=new Map;edges=new Map;config;constructor(t){this.config=t}addNode(t,n){if(t===R||t===C)throw new Error(`"${t}" is a reserved name and cannot be used as a node name`);if(this.nodes.has(t))throw new Error(`Node "${t}" already exists`);return this.nodes.set(t,n),this}addEdge(t,n){if(this.edges.has(t))throw new Error(`Node "${t}" already has an outgoing edge. Use addConditionalEdge for branching.`);return this.edges.set(t,{type:"direct",to:n}),this}addConditionalEdge(t,n){if(this.edges.has(t))throw new Error(`Node "${t}" already has an outgoing edge.`);return this.edges.set(t,{type:"conditional",condition:n}),this}graph(){return Ie(this.nodes,this.edges)}compile(t){this.validate();let n=new Map(this.nodes),r=new Map(this.edges);return Ee({config:this.config,nodes:n,edges:r,store:t?.store,graph:()=>Ie(n,r)})}validate(){if(!this.edges.has(R))throw new Error('Flow must have an entry point. Add an edge from START: .addEdge(START, "first_node")');let t=this.edges.get(R);if(t?.type==="direct"&&t.to!==C&&!this.nodes.has(t.to))throw new Error(`START edge references non-existent node: "${t.to}"`);for(let[n,r]of this.edges){if(n!==R&&!this.nodes.has(n))throw new Error(`Edge from non-existent node: "${n}"`);if(r.type==="direct"&&r.to!==C&&!this.nodes.has(r.to))throw new Error(`Edge from "${n}" references non-existent node: "${r.to}"`)}for(let[n]of this.nodes)if(!this.edges.has(n))throw new Error(`Node "${n}" has no outgoing edge. Add one with .addEdge("${n}", ...) or .addConditionalEdge("${n}", ...)`)}};function Ce(e){return new W(e)}function be(e){let t=e.content;return JSON.parse(t[0]?.text??"")}async function _e(e,t){let n=t?.stateStore,r=[],o=`test-session-${Math.random().toString(36).slice(2,10)}`,s={registerTool:(...d)=>{r.push(d)}};await e.register(s);let i=r[0]?.[2];if(!i)throw new Error(`Flow "${e.id}" did not register a handler`);let a={_meta:{sessionId:o}};async function c(d){return{...d,decodedState:n?await n.get(o):null}}return{async start(d){let f=await i({action:"start",...d?{stateUpdates:d}:{}},a);return c(be(f))},async continueWith(d){let f=await i({action:"continue",...d?{stateUpdates:d}:{}},a);return c(be(f))},async lastState(){return n?n.get(o):null}}}var K="text/html+skybridge",B="text/html;profile=mcp-app",Pe=async(e,t)=>{let n=e.endsWith("/")?e.slice(0,-1):e;return await(await fetch(`${n}${t}`)).text()};function Me(e){return{"openai/widgetDescription":e.description,"openai/widgetPrefersBorder":e.prefersBorder,"openai/widgetDomain":e.widgetDomain,...e.widgetCSP&&{"openai/widgetCSP":e.widgetCSP}}}function Fe(e){let t=e.widgetCSP?{connectDomains:e.widgetCSP.connect_domains,resourceDomains:e.widgetCSP.resource_domains,frameDomains:e.widgetCSP.frame_domains,redirectDomains:e.widgetCSP.redirect_domains}:void 0;return{ui:{...t&&{csp:t},...e.widgetDomain&&{domain:e.widgetDomain},...e.prefersBorder!==void 0&&{prefersBorder:e.prefersBorder}}}}function Z(e){return{...e.openaiTemplateUri&&{"openai/outputTemplate":e.openaiTemplateUri},"openai/toolInvocation/invoking":e.invoking,"openai/toolInvocation/invoked":e.invoked,"openai/widgetAccessible":!0,"openai/resultCanProduceWidget":!0,...e.mcpTemplateUri&&{ui:{resourceUri:e.mcpTemplateUri,...e.autoHeight&&{autoHeight:!0}}},...e.mcpTemplateUri&&{"ui/resourceUri":e.mcpTemplateUri}}}function We(e){let{id:t,title:n,description:r,baseUrl:o,htmlPath:s,widgetDomain:i,prefersBorder:a=!0,autoHeight:c=!0}=e,d=e.widgetCSP??{connect_domains:[o],resource_domains:[o]};if(process.env.NODE_ENV==="development")try{let{hostname:u}=new URL(o);(u==="localhost"||u==="127.0.0.1")&&(d={...d,connect_domains:[...d.connect_domains||[],`ws://${u}:*`,`wss://${u}:*`],resource_domains:[...d.resource_domains||[],`http://${u}:*`]})}catch{}let f=`ui://widgets/apps-sdk/${t}.html`,h=`ui://widgets/ext-apps/${t}.html`,l=null,p=()=>(l||(l=Pe(o,s)),l),w=r;async function y(u){let T=await p();u.registerResource(`${t}-openai-widget`,f,{title:n,description:w,mimeType:K,_meta:{"openai/widgetDescription":w,"openai/widgetPrefersBorder":a}},async x=>({contents:[{uri:x.href,mimeType:K,text:T,_meta:Me({description:w,prefersBorder:a,widgetDomain:i,widgetCSP:d})}]})),u.registerResource(`${t}-mcp-widget`,h,{title:n,description:w,mimeType:B,_meta:{ui:{prefersBorder:a}}},async x=>({contents:[{uri:x.href,mimeType:B,text:T,_meta:Fe({description:w,prefersBorder:a,widgetDomain:i,widgetCSP:d})}]}))}return{id:t,title:n,description:r,openaiUri:f,mcpUri:h,autoHeight:c,register:y}}function rt(e,t){let{resource:n,description:r,inputSchema:o,annotations:s,autoInjectResultText:i=!0}=e,a=e.id??n?.id,c=e.title??n?.title;if(!a)throw new Error("createTool: `id` is required when no resource is provided");if(!c)throw new Error("createTool: `title` is required when no resource is provided");let d=n?Z({openaiTemplateUri:n.openaiUri,mcpTemplateUri:n.mcpUri,invoking:e.invoking??"Loading...",invoked:e.invoked??"Loaded",autoHeight:n.autoHeight}):void 0;return{id:a,title:c,description:r,async register(f){f.registerTool(a,{title:c,description:r,inputSchema:o,annotations:s,...d&&{_meta:d}},(async(h,l)=>{let p=l,w=p._meta??{},y=O(p),u=await t(h,{extra:{_meta:w},waniwani:y});return n&&u.data?{content:[{type:"text",text:u.text}],structuredContent:u.data,_meta:{...d,...w,...i===!1?{"waniwani/autoInjectResultText":!1}:{}}}:{content:[{type:"text",text:u.text}],...u.data?{structuredContent:u.data}:{},...i===!1?{_meta:{"waniwani/autoInjectResultText":!1}}:{}}}))}}}async function ot(e,t){await Promise.all(t.map(n=>n.register(e)))}var q=class extends Error{constructor(n,r){super(n);this.status=r;this.name="WaniWaniError"}};var it="@waniwani/sdk";function Ae(e){let{baseUrl:t,apiKey:n}=e;function r(){if(!n)throw new Error("WANIWANI_API_KEY is not set");return n}async function o(s,i,a){let c=r(),d=`${t.replace(/\/$/,"")}${i}`,f={Authorization:`Bearer ${c}`,"X-WaniWani-SDK":it},h={method:s,headers:f};a!==void 0&&(f["Content-Type"]="application/json",h.body=JSON.stringify(a));let l=await fetch(d,h);if(!l.ok){let w=await l.text().catch(()=>"");throw new q(w||`KB API error: HTTP ${l.status}`,l.status)}return(await l.json()).data}return{async ingest(s){return o("POST","/api/mcp/kb/ingest",{files:s})},async search(s,i){return o("POST","/api/mcp/kb/search",{query:s,...i})},async sources(){return o("GET","/api/mcp/kb/sources")}}}var st="@waniwani/sdk";function V(e,t={}){let n=t.now??(()=>new Date),r=t.generateId??Ne,o=dt(e),s=L(e.meta),i=L(e.metadata),a=ut(e,s),c=v(e.eventId)??r(),d=pt(e.timestamp,n),f=v(e.source)??N(s)??t.source??st,h=Q(e)?{...e}:void 0,l={...i};return Object.keys(s).length>0&&(l.meta=s),h&&(l.rawLegacy=h),{id:c,type:"mcp.event",name:o,source:f,timestamp:d,correlation:a,properties:at(e,o),metadata:l,rawLegacy:h}}function Ne(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?`evt_${crypto.randomUUID()}`:`evt_${Math.random().toString(36).slice(2,10)}_${Date.now().toString(36)}`}function at(e,t){if(!Q(e))return L(e.properties);let n=ct(e,t),r=L(e.properties);return{...n,...r}}function ct(e,t){switch(t){case"tool.called":{let n={};return v(e.toolName)&&(n.name=e.toolName),v(e.toolType)&&(n.type=e.toolType),n}case"quote.succeeded":{let n={};return typeof e.quoteAmount=="number"&&(n.amount=e.quoteAmount),v(e.quoteCurrency)&&(n.currency=e.quoteCurrency),n}case"link.clicked":{let n={};return v(e.linkUrl)&&(n.url=e.linkUrl),n}case"purchase.completed":{let n={};return typeof e.purchaseAmount=="number"&&(n.amount=e.purchaseAmount),v(e.purchaseCurrency)&&(n.currency=e.purchaseCurrency),n}default:return{}}}function dt(e){return Q(e)?e.eventType:e.event}function ut(e,t){let n=v(e.requestId)??he(t),r=v(e.sessionId)??_(t),o=v(e.traceId)??we(t),s=v(e.externalUserId)??ye(t),i=v(e.correlationId)??Te(t)??n,a={};return r&&(a.sessionId=r),o&&(a.traceId=o),n&&(a.requestId=n),i&&(a.correlationId=i),s&&(a.externalUserId=s),a}function pt(e,t){if(e instanceof Date)return e.toISOString();if(typeof e=="string"){let n=new Date(e);if(!Number.isNaN(n.getTime()))return n.toISOString()}return t().toISOString()}function L(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function v(e){if(typeof e=="string"&&e.trim().length!==0)return e}function Q(e){return"eventType"in e}var lt="/api/mcp/events/v2/batch";var De="@waniwani/sdk",ft=new Set([401,403]),gt=new Set([408,425,429,500,502,503,504]);function Ue(e){return new ee(e)}var ee=class{endpointUrl;flushIntervalMs;maxBatchSize;maxBufferSize;maxRetries;retryBaseDelayMs;retryMaxDelayMs;shutdownTimeoutMs;sdkVersion;fetchFn;logger;now;sleep;apiKey;buffer=[];flushTimer;flushScheduled=!1;flushScheduledTimer;flushInFlight;inFlightCount=0;isStopped=!1;isShuttingDown=!1;constructor(t){this.endpointUrl=wt(t.baseUrl,t.endpointPath??lt),this.flushIntervalMs=t.flushIntervalMs??1e3,this.maxBatchSize=t.maxBatchSize??20,this.maxBufferSize=t.maxBufferSize??1e3,this.maxRetries=t.maxRetries??3,this.retryBaseDelayMs=t.retryBaseDelayMs??200,this.retryMaxDelayMs=t.retryMaxDelayMs??2e3,this.shutdownTimeoutMs=t.shutdownTimeoutMs??2e3,this.fetchFn=t.fetchFn??fetch,this.logger=t.logger??console,this.now=t.now??(()=>new Date),this.sleep=t.sleep??(n=>new Promise(r=>setTimeout(r,n))),this.apiKey=t.apiKey,this.sdkVersion=t.sdkVersion,this.flushIntervalMs>0&&(this.flushTimer=setInterval(()=>{this.flush()},this.flushIntervalMs))}enqueue(t){if(this.isStopped||this.isShuttingDown){this.logger.warn("[WaniWani] Tracking transport is stopped, dropping event %s",t.id);return}if(this.buffer.length>=this.maxBufferSize){let n=this.buffer.length-this.maxBufferSize+1;this.buffer.splice(0,n),this.logger.warn("[WaniWani] Tracking buffer overflow, dropped %d oldest event(s)",n)}if(this.buffer.push(t),this.buffer.length>=this.maxBatchSize){this.flush();return}this.scheduleMicroFlush()}pendingEvents(){return this.buffer.length+this.inFlightCount}async flush(){return this.flushInFlight?this.flushInFlight:(this.flushInFlight=this.flushLoop().finally(()=>{this.flushInFlight=void 0}),this.flushInFlight)}async shutdown(t){this.isShuttingDown=!0,this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=void 0),this.flushScheduledTimer&&(clearTimeout(this.flushScheduledTimer),this.flushScheduledTimer=void 0,this.flushScheduled=!1);let n=t?.timeoutMs??this.shutdownTimeoutMs,r=this.flush();if(!Number.isFinite(n)||n<=0)return await r,this.isStopped=!0,{timedOut:!1,pendingEvents:this.pendingEvents()};let o=Symbol("shutdown-timeout");return await Promise.race([r.then(()=>"flushed"),this.sleep(n).then(()=>o)])===o?(this.isStopped=!0,{timedOut:!0,pendingEvents:this.pendingEvents()}):(this.isStopped=!0,{timedOut:!1,pendingEvents:this.pendingEvents()})}scheduleMicroFlush(){this.flushScheduled||(this.flushScheduled=!0,this.flushScheduledTimer=setTimeout(()=>{this.flushScheduledTimer=void 0,this.flushScheduled=!1,this.flush()},0))}async flushLoop(){for(;this.buffer.length>0&&!this.isStopped;){let t=this.buffer.splice(0,this.maxBatchSize);await this.sendBatchWithRetry(t)}}async sendBatchWithRetry(t){let n=0,r=t;for(;r.length>0&&!this.isStopped;){this.inFlightCount=r.length;let o=await this.sendBatchOnce(r);switch(this.inFlightCount=0,o.kind){case"success":return;case"auth":this.stopTransportForAuthFailure(o.status,r.length);return;case"permanent":this.logger.error("[WaniWani] Dropping %d event(s) after permanent failure: %s",r.length,o.reason);return;case"retryable":if(n>=this.maxRetries){this.logger.error("[WaniWani] Dropping %d event(s) after retry exhaustion: %s",r.length,o.reason);return}await this.sleep(this.backoffDelayMs(n)),n+=1;continue;case"partial":if(o.permanent.length>0&&this.logger.error("[WaniWani] Dropping %d event(s) rejected as permanent",o.permanent.length),o.retryable.length===0)return;if(n>=this.maxRetries){this.logger.error("[WaniWani] Dropping %d retryable event(s) after retry exhaustion",o.retryable.length);return}r=o.retryable,await this.sleep(this.backoffDelayMs(n)),n+=1;continue}}}async sendBatchOnce(t){let n;try{n=await this.fetchFn(this.endpointUrl,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,"X-WaniWani-SDK":De},body:JSON.stringify(this.makeBatchRequest(t))})}catch(s){return{kind:"retryable",reason:yt(s)}}if(ft.has(n.status))return{kind:"auth",status:n.status};if(gt.has(n.status))return{kind:"retryable",reason:`HTTP ${n.status}`};if(!n.ok)return{kind:"permanent",reason:`HTTP ${n.status}`};let r=await ht(n);if(!r?.rejected||r.rejected.length===0)return{kind:"success"};let o=this.classifyRejectedEvents(t,r.rejected);return o.retryable.length===0&&o.permanent.length===0?{kind:"success"}:{kind:"partial",retryable:o.retryable,permanent:o.permanent}}makeBatchRequest(t){return{sentAt:this.now().toISOString(),source:{sdk:De,version:this.sdkVersion??"0.0.0"},events:t}}classifyRejectedEvents(t,n){let r=new Map(t.map(i=>[i.id,i])),o=[],s=[];for(let i of n){let a=r.get(i.eventId);if(a){if(mt(i)){o.push(a);continue}s.push(a)}}return{retryable:o,permanent:s}}backoffDelayMs(t){let n=this.retryBaseDelayMs*2**t;return Math.min(n,this.retryMaxDelayMs)}stopTransportForAuthFailure(t,n){this.isStopped=!0;let r=this.buffer.length;this.buffer.splice(0,r),this.logger.error("[WaniWani] Auth failure (HTTP %d). Stopping tracking transport and dropping %d queued event(s)",t,n+r)}};function mt(e){if(e.retryable===!0)return!0;let t=e.code.toLowerCase();return t.includes("timeout")||t.includes("temporary")||t.includes("unavailable")||t.includes("rate_limit")||t.includes("transient")||t.includes("server")}async function ht(e){let t=await e.text();if(t)try{return JSON.parse(t)}catch{return}}function wt(e,t){let n=e.endsWith("/")?e:`${e}/`,r=t.startsWith("/")?t.slice(1):t;return`${n}${r}`}function yt(e){return e instanceof Error?e.message:String(e)}function Oe(e){let{baseUrl:t,apiKey:n,tracking:r}=e;function o(){if(!n)throw new Error("WANIWANI_API_KEY is not set");return n}let s=n?Ue({baseUrl:t,apiKey:n,endpointPath:r.endpointPath,flushIntervalMs:r.flushIntervalMs,maxBatchSize:r.maxBatchSize,maxBufferSize:r.maxBufferSize,maxRetries:r.maxRetries,retryBaseDelayMs:r.retryBaseDelayMs,retryMaxDelayMs:r.retryMaxDelayMs,shutdownTimeoutMs:r.shutdownTimeoutMs}):void 0,i={async identify(a,c,d){o();let f=V({event:"user.identified",externalUserId:a,properties:c,meta:d});return s?.enqueue(f),{eventId:f.id}},async track(a){o();let c=V(a);return s?.enqueue(c),{eventId:c.id}},async flush(){o(),await s?.flush()},async shutdown(a){return o(),await s?.shutdown({timeoutMs:a?.timeoutMs??r.shutdownTimeoutMs})??{timedOut:!1,pendingEvents:0}}};return s&&Tt(i,r.shutdownTimeoutMs),i}function Tt(e,t){if(typeof process>"u"||typeof process.once!="function"||typeof process.on!="function")return;let n=()=>{e.shutdown({timeoutMs:t})};process.once("beforeExit",n),process.once("SIGINT",n),process.once("SIGTERM",n)}function je(e){let t=e?.baseUrl??"https://app.waniwani.ai",n=e?.apiKey??process.env.WANIWANI_API_KEY,r={endpointPath:e?.tracking?.endpointPath??"/api/mcp/events/v2/batch",flushIntervalMs:e?.tracking?.flushIntervalMs??1e3,maxBatchSize:e?.tracking?.maxBatchSize??20,maxBufferSize:e?.tracking?.maxBufferSize??1e3,maxRetries:e?.tracking?.maxRetries??3,retryBaseDelayMs:e?.tracking?.retryBaseDelayMs??200,retryMaxDelayMs:e?.tracking?.retryMaxDelayMs??2e3,shutdownTimeoutMs:e?.tracking?.shutdownTimeoutMs??2e3},o={baseUrl:t,apiKey:n,tracking:r},s=Oe(o),i=Ae(o);return{...s,kb:i,_config:o}}function kt(e){let t=e.event_type??"widget_click",r=t.startsWith("widget_")?t:`widget_${t}`,o={...e.metadata??{}};return e.event_name&&(o.event_name=e.event_name),{event:r,properties:o,sessionId:e.session_id,traceId:e.trace_id,externalUserId:e.user_id,eventId:e.event_id,timestamp:e.timestamp,source:e.source??"widget"}}function St(e){let t={apiKey:e?.apiKey,baseUrl:e?.baseUrl},n;function r(){return n||(n=je(t)),n}return async function(s){let i;try{i=await s.json()}catch{return new Response(JSON.stringify({error:"Invalid JSON"}),{status:400,headers:{"Content-Type":"application/json"}})}if(!Array.isArray(i.events)||i.events.length===0)return new Response(JSON.stringify({error:"Missing or empty events array"}),{status:400,headers:{"Content-Type":"application/json"}});try{let a=r(),c=[];for(let d of i.events){let f=kt(d),h=await a.track(f);c.push(h.eventId)}return await a.flush(),new Response(JSON.stringify({ok:!0,accepted:c.length}),{status:200,headers:{"Content-Type":"application/json"}})}catch(a){let c=a instanceof Error?a.message:"Unknown error";return new Response(JSON.stringify({error:c}),{status:500,headers:{"Content-Type":"application/json"}})}}}var z=class{cached=null;pending=null;config;constructor(t){this.config=t}async getToken(t,n){return this.cached&&Date.now()<this.cached.expiresAt-12e4?this.cached.token:this.pending?this.pending:(this.pending=this.mint(t,n).finally(()=>{this.pending=null}),this.pending)}async mint(t,n){let r=vt(this.config.baseUrl,"/api/mcp/widget-tokens"),o={};t&&(o.sessionId=t),n&&(o.traceId=n);try{let s=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify(o)});if(!s.ok)return null;let i=await s.json(),a=i.data&&typeof i.data=="object"?i.data:i,c=new Date(a.expiresAt).getTime();return!a.token||Number.isNaN(c)?null:(this.cached={token:a.token,expiresAt:c},a.token)}catch{return null}}};function vt(e,t){return`${e.endsWith("/")?e.slice(0,-1):e}${t}`}var $e="waniwani/sessionId",te="waniwani/geoLocation",ne="waniwani/userLocation";function S(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}function H(e){if(!S(e))return;let t=e._meta;return S(t)?t:void 0}function re(e){if(!S(e))return;let t=e.content;return Array.isArray(t)?t.find(r=>S(r)&&r.type==="text"&&typeof r.text=="string")?.text:void 0}function xt(e,t){return typeof t=="function"?t(e)??"other":t??"other"}function oe(e,t,n,r,o,s){let i=xt(e,n.toolType),a=H(t);return console.log("[waniwani:debug] buildTrackInput meta:",JSON.stringify(a),"-> source:",N(a)),{event:"tool.called",properties:{name:e,type:i,...r??{},...s?.input!==void 0&&{input:s.input},...s?.output!==void 0&&{output:s.output}},meta:a,source:N(a),metadata:{...n.metadata??{},...o&&{clientInfo:o}}}}async function ie(e,t,n){try{await e.track(t)}catch(r){n?.(ae(r))}}async function se(e,t){try{await e.flush()}catch(n){t?.(ae(n))}}async function Ke(e,t,n,r){if(!S(e))return;S(e._meta)||(e._meta={});let o=e._meta,s=S(o.waniwani)?o.waniwani:void 0,i={...s??{},endpoint:s?.endpoint??`${n.replace(/\/$/,"")}/api/mcp/events/v2/batch`};if(t)try{let d=await t.getToken();d&&(i.token=d)}catch(d){r?.(ae(d))}let a=_(o);a&&(i.sessionId||(i.sessionId=a));let c=qe(o);c!==void 0&&(i.geoLocation||(i.geoLocation=c)),o.waniwani=i}function Be(e,t){let n=H(t);if(!n||!S(e))return;S(e._meta)||(e._meta={});let r=e._meta,o=_(n);o&&!r[$e]&&(r[$e]=o);let s=qe(n);s&&(r[te]||(r[te]=s),r[ne]||(r[ne]=s))}function qe(e){if(!e)return;let t=e[te]??e[ne];if(S(t)||typeof t=="string")return t}function ae(e){return e instanceof Error?e:new Error(String(e))}var Le="https://app.waniwani.ai";function Rt(e,t){let n=e;if(n.__waniwaniWrapped)return n;n.__waniwaniWrapped=!0;let r=t.client,o=t.injectWidgetToken!==!1,s=null;function i(){if(s)return s;let c=r._config.apiKey;return c?(s=new z({baseUrl:r._config.baseUrl??Le,apiKey:c}),s):null}let a=e.registerTool.bind(e);return n.registerTool=((...c)=>{let[d,f,h]=c,l=typeof d=="string"&&d.trim().length>0?d:"unknown";if(typeof h!="function")return a(...c);let p=h;return a(d,f,async(y,u)=>{let T=H(u)??{},x=me(r,T);S(u)&&(u[J]=x);let b=performance.now(),E=e.server?.getClientVersion?.();try{let k=await p(y,u),P=Math.round(performance.now()-b),I=S(k)&&k.isError===!0;if(I){let ce=re(k);console.error(`[waniwani] Tool "${l}" returned error${ce?`: ${ce}`:""}`)}return await ie(r,oe(l,u,t,{durationMs:P,status:I?"error":"ok",...I&&{errorMessage:re(k)??"Unknown tool error"}},E,{input:y,output:k}),t.onError),t.flushAfterToolCall&&await se(r,t.onError),Be(k,u),o&&await Ke(k,i(),r._config.baseUrl??Le,t.onError),k}catch(k){let P=Math.round(performance.now()-b);throw await ie(r,oe(l,u,t,{durationMs:P,status:"error",errorMessage:k instanceof Error?k.message:String(k)},E,{input:y}),t.onError),t.flushAfterToolCall&&await se(r,t.onError),k}})}),n}export{C as END,R as START,W as StateGraph,Ce as createFlow,_e as createFlowTestHarness,We as createResource,rt as createTool,St as createTrackingRoute,Y as detectPlatform,ze as isMCPApps,Ve as isOpenAI,ot as registerTools,Rt as withWaniwani};
|
|
6
7
|
//# sourceMappingURL=index.js.map
|
package/dist/mcp/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/mcp/react/widgets/platform.ts","../../src/mcp/server/flows/@types.ts","../../src/mcp/server/flows/compile.ts","../../src/mcp/server/scoped-client.ts","../../src/mcp/server/utils.ts","../../src/mcp/server/flows/nested.ts","../../src/mcp/server/flows/execute.ts","../../src/mcp/server/flows/flow-store.ts","../../src/mcp/server/flows/protocol.ts","../../src/mcp/server/flows/state-graph.ts","../../src/mcp/server/flows/create-flow.ts","../../src/mcp/server/flows/test-utils.ts","../../src/mcp/server/resources/meta.ts","../../src/mcp/server/resources/create-resource.ts","../../src/mcp/server/tools/create-tool.ts","../../src/error.ts","../../src/kb/client.ts","../../src/tracking/mapper.ts","../../src/tracking/transport.ts","../../src/tracking/index.ts","../../src/waniwani.ts","../../src/mcp/server/tracking-route.ts","../../src/mcp/server/widget-token.ts","../../src/mcp/server/with-waniwani/helpers.ts","../../src/mcp/server/with-waniwani/index.ts"],"sourcesContent":["/**\n * Widget platform types\n */\nexport type WidgetPlatform = \"openai\" | \"mcp-apps\";\n\n/**\n * Detects which platform the widget is running on.\n *\n * OpenAI injects a global `window.openai` object.\n * MCP Apps runs in a sandboxed iframe and uses postMessage.\n */\nexport function detectPlatform(): WidgetPlatform {\n\tif (typeof window !== \"undefined\" && \"openai\" in window) {\n\t\treturn \"openai\";\n\t}\n\treturn \"mcp-apps\";\n}\n\n/**\n * Check if running on OpenAI platform\n */\nexport function isOpenAI(): boolean {\n\treturn detectPlatform() === \"openai\";\n}\n\n/**\n * Check if running on MCP Apps platform\n */\nexport function isMCPApps(): boolean {\n\treturn detectPlatform() === \"mcp-apps\";\n}\n","import type { z } from \"zod\";\nimport type { McpServer } from \"../resources/types\";\nimport type { ScopedWaniWaniClient } from \"../scoped-client\";\nimport type { RegisteredTool } from \"../tools/types\";\nimport type { FlowStore } from \"./flow-store\";\n\nexport type { McpServer };\n\n// ============================================================================\n// Sentinel constants\n// ============================================================================\n\nexport const START = \"__start__\" as const;\nexport const END = \"__end__\" as const;\n\n// ============================================================================\n// Signal types — returned by node handlers to control flow behavior\n// ============================================================================\n\nconst INTERRUPT = Symbol.for(\"waniwani.flow.interrupt\");\nconst WIDGET = Symbol.for(\"waniwani.flow.widget\");\n\n/** A single question within an interrupt step */\nexport type InterruptQuestion = {\n\t/** Question to ask the user */\n\tquestion: string;\n\t/** State key where the answer will be stored */\n\tfield: string;\n\t/** Optional suggestions to present as options */\n\tsuggestions?: string[];\n\t/** Hidden context/instructions for this specific question (not shown to user directly) */\n\tcontext?: string;\n\t/** Validation function — runs after the user answers, before advancing to the next node */\n\t// biome-ignore lint/suspicious/noConfusingVoidType: void is needed so `async () => {}` compiles\n\tvalidate?: (value: unknown) => MaybePromise<Record<string, unknown> | void>;\n};\n\n/**\n * Interrupt signal — pauses the flow and asks the user one or more questions.\n * Single-question and multi-question interrupts use the same type.\n */\nexport type InterruptSignal = {\n\treadonly __type: typeof INTERRUPT;\n\t/** Questions to ask — ask all in one conversational message */\n\tquestions: InterruptQuestion[];\n\t/** Overall hidden context/instructions for the assistant (not shown to user directly) */\n\tcontext?: string;\n};\n\nexport type WidgetSignal = {\n\treadonly __type: typeof WIDGET;\n\t/** The display tool to delegate rendering to */\n\ttool: RegisteredTool;\n\t/** Data to pass to the display tool */\n\tdata: Record<string, unknown>;\n\t/** Description of what the widget does (for the AI's context) */\n\tdescription?: string;\n\t/**\n\t * Whether the user is expected to interact with the widget before the flow continues.\n\t * Defaults to true. Set to false for informational widgets that should render and then\n\t * immediately advance to the next flow step.\n\t */\n\tinteractive?: boolean;\n\t/**\n\t * State key this widget fills — enables auto-skip when the field is already in state.\n\t * Pass this so the engine can skip the widget step when the answer is already known.\n\t */\n\tfield?: string;\n};\n\n/**\n * Create an interrupt signal — pauses the flow and asks the user a question.\n * Used internally by the engine. Flow authors use the typed `interrupt` from the node context.\n *\n * Accepts an object where each key is a field name and the value describes the question.\n * The only reserved key is `context` (string) for overall hidden AI instructions.\n */\nexport function interrupt(\n\tfields: Record<string, unknown>,\n\tconfig?: { context?: string },\n): InterruptSignal {\n\tconst context = config?.context;\n\tconst questions: InterruptQuestion[] = [];\n\n\tfor (const [key, value] of Object.entries(fields)) {\n\t\tif (typeof value === \"object\" && value !== null && \"question\" in value) {\n\t\t\tconst q = value as {\n\t\t\t\tquestion: string;\n\t\t\t\tsuggestions?: string[];\n\t\t\t\tcontext?: string;\n\t\t\t\tvalidate?: (\n\t\t\t\t\tvalue: unknown,\n\t\t\t\t\t// biome-ignore lint/suspicious/noConfusingVoidType: void is needed so `async () => {}` compiles\n\t\t\t\t) => MaybePromise<Record<string, unknown> | void>;\n\t\t\t};\n\t\t\tquestions.push({\n\t\t\t\tquestion: q.question,\n\t\t\t\tfield: key,\n\t\t\t\tsuggestions: q.suggestions,\n\t\t\t\tcontext: q.context,\n\t\t\t\tvalidate: q.validate,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn {\n\t\t__type: INTERRUPT,\n\t\tquestions,\n\t\tcontext,\n\t};\n}\n\n/**\n * Create a widget signal — pauses the flow and delegates rendering to a display tool.\n * Used internally by the engine. Flow authors use the typed `showWidget` from the node context.\n */\nexport function showWidget(\n\ttool: RegisteredTool,\n\tconfig: {\n\t\tdata: Record<string, unknown>;\n\t\tdescription?: string;\n\t\tinteractive?: boolean;\n\t\tfield?: string;\n\t},\n): WidgetSignal {\n\treturn { __type: WIDGET, tool, ...config };\n}\n\nexport function isInterrupt(value: unknown): value is InterruptSignal {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\t\"__type\" in value &&\n\t\t(value as InterruptSignal).__type === INTERRUPT\n\t);\n}\n\nexport function isWidget(value: unknown): value is WidgetSignal {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\t\"__type\" in value &&\n\t\t(value as WidgetSignal).__type === WIDGET\n\t);\n}\n\n// ============================================================================\n// Node context & handler\n// ============================================================================\n\nexport type MaybePromise<T> = T | Promise<T>;\n\n// ============================================================================\n// Nested state utility types\n// ============================================================================\n\n/** Deep partial — allows partial updates at any nesting level (for z.object state fields). */\nexport type DeepPartial<T> = {\n\t[K in keyof T]?: T[K] extends Record<string, unknown>\n\t\t? DeepPartial<T[K]>\n\t\t: T[K];\n};\n\n/**\n * Extract known (non-index-signature) string keys from a type.\n * Zod v4's z.object() adds `[key: string]: unknown` to inferred types,\n * so we filter those out to get only the declared field names.\n */\ntype KnownStringKeys<T> = Extract<\n\tkeyof {\n\t\t[K in keyof T as string extends K\n\t\t\t? never\n\t\t\t: number extends K\n\t\t\t\t? never\n\t\t\t\t: K]: T[K];\n\t},\n\tstring\n>;\n\n/**\n * Union of all valid field paths for a state type.\n * - Flat fields produce their key: `\"email\"`\n * - `z.object()` fields produce dot-paths to sub-fields: `\"driver.name\"`, `\"driver.license\"`\n * - Arrays and general records (`z.record()`) are treated as flat fields.\n * - Only 1 level of nesting is supported.\n */\nexport type FieldPaths<TState> = {\n\t[K in Extract<keyof TState, string>]: TState[K] extends unknown[]\n\t\t? K\n\t\t: TState[K] extends Record<string, unknown>\n\t\t\t? KnownStringKeys<TState[K]> extends never\n\t\t\t\t? K\n\t\t\t\t: `${K}.${KnownStringKeys<TState[K]>}`\n\t\t\t: K;\n}[Extract<keyof TState, string>];\n\n/** Resolve a dot-path to the value type at that path in TState. */\nexport type ResolveFieldType<\n\tTState,\n\tP extends string,\n> = P extends `${infer Parent}.${infer Child}`\n\t? Parent extends keyof TState\n\t\t? Child extends keyof TState[Parent]\n\t\t\t? TState[Parent][Child]\n\t\t\t: never\n\t\t: never\n\t: P extends keyof TState\n\t\t? TState[P]\n\t\t: never;\n\n// ============================================================================\n// Typed interrupt & showWidget\n// ============================================================================\n\n/**\n * Typed interrupt function — available on the node context.\n *\n * First argument: an object where each key is a field path and each value\n * describes the question for that field. Use dot-paths for nested state fields.\n * `validate` receives the field's value typed from the Zod schema.\n *\n * Second argument (optional): config with overall hidden AI instructions.\n *\n * @example\n * ```ts\n * // Flat field\n * interrupt({ breed: { question: \"What breed is your pet?\" } })\n *\n * // Nested field (z.object in state)\n * interrupt({ \"driver.name\": { question: \"Driver's name?\" } })\n *\n * // Multiple questions with context\n * interrupt(\n * {\n * \"driver.name\": { question: \"Name?\" },\n * \"driver.license\": { question: \"License?\" },\n * },\n * { context: \"Ask both questions naturally.\" },\n * )\n * ```\n */\nexport type TypedInterrupt<TState> = (\n\tfields: {\n\t\t[P in FieldPaths<TState>]?: {\n\t\t\tquestion: string;\n\t\t\tvalidate?: (\n\t\t\t\tvalue: ResolveFieldType<TState, P>,\n\t\t\t\t// biome-ignore lint/suspicious/noConfusingVoidType: void is needed so `async () => {}` compiles\n\t\t\t) => MaybePromise<DeepPartial<TState> | void>;\n\t\t\tsuggestions?: string[];\n\t\t\tcontext?: string;\n\t\t};\n\t},\n\tconfig?: {\n\t\t/** Overall hidden context/instructions for the assistant (not shown to user directly) */\n\t\tcontext?: string;\n\t},\n) => InterruptSignal;\n\n/**\n * Typed showWidget function — available on the node context.\n * The `field` parameter accepts field paths (flat or dot-path for nested state).\n */\nexport type TypedShowWidget<TState> = (\n\ttool: RegisteredTool,\n\tconfig: {\n\t\tdata: Record<string, unknown>;\n\t\tdescription?: string;\n\t\tinteractive?: boolean;\n\t\tfield?: FieldPaths<TState>;\n\t},\n) => WidgetSignal;\n\n/**\n * Context object passed to node handlers.\n * Provides state, metadata, and typed helper functions for creating signals.\n */\nexport type NodeContext<TState> = {\n\t/** Current flow state (partial — fields are filled as the flow progresses) */\n\tstate: Partial<TState>;\n\t/** Request metadata from the MCP call */\n\tmeta?: Record<string, unknown>;\n\t/** Create an interrupt signal — pause and ask the user questions */\n\tinterrupt: TypedInterrupt<TState>;\n\t/** Create a widget signal — pause and show a UI widget */\n\tshowWidget: TypedShowWidget<TState>;\n\t/** Session-scoped WaniWani client — available when the server is wrapped with withWaniwani() */\n\twaniwani?: ScopedWaniWaniClient;\n};\n\n/**\n * Node handler — receives a context object and returns a signal or state updates.\n * The return value determines behavior:\n * - `Partial<TState>` → action node (state merged, auto-advance)\n * - `InterruptSignal` → interrupt (pause, ask user one or more questions)\n * - `WidgetSignal` → widget step (pause, show widget)\n */\nexport type NodeHandler<TState> = (\n\tctx: NodeContext<TState>,\n) => MaybePromise<DeepPartial<TState> | InterruptSignal | WidgetSignal>;\n\n/**\n * Condition function for conditional edges.\n * Receives current state, returns the name of the next node.\n */\nexport type ConditionFn<TState> = (\n\tstate: Partial<TState>,\n) => string | Promise<string>;\n\nexport type Edge<TState> =\n\t| { type: \"direct\"; to: string }\n\t| { type: \"conditional\"; condition: ConditionFn<TState> };\n\n// ============================================================================\n// Flow config & compiled output\n// ============================================================================\n\nexport type FlowConfig = {\n\t/** Unique identifier for the flow (becomes the MCP tool name) */\n\tid: string;\n\t/** Display title */\n\ttitle: string;\n\t/** Description for the AI (explains when to use this flow) */\n\tdescription: string;\n\t/**\n\t * Define the flow's state — each field the flow collects.\n\t * Keys are the field names used in `interrupt({ field })`,\n\t * values are Zod schemas with `.describe()`.\n\t *\n\t * The state definition serves two purposes:\n\t * 1. Type inference — `TState` is automatically derived, no explicit generic needed\n\t * 2. AI protocol — field names, types, and descriptions are included in the tool\n\t * description so the AI can pre-fill answers via `_meta.flow.state`\n\t *\n\t * @example\n\t * ```ts\n\t * state: {\n\t * country: z.string().describe(\"Country the business is based in\"),\n\t * status: z.enum([\"registered\", \"unregistered\"]).describe(\"Business registration status\"),\n\t * }\n\t * ```\n\t */\n\tstate: Record<string, z.ZodType>;\n\t/** Optional tool annotations */\n\tannotations?: {\n\t\treadOnlyHint?: boolean;\n\t\tidempotentHint?: boolean;\n\t\topenWorldHint?: boolean;\n\t\tdestructiveHint?: boolean;\n\t};\n};\n\n/**\n * Infer the runtime state type from a flow's state schema definition.\n *\n * @example\n * ```ts\n * const config = {\n * state: {\n * country: z.enum([\"FR\", \"DE\"]),\n * status: z.enum([\"registered\", \"unregistered\"]),\n * }\n * };\n * type MyState = InferFlowState<typeof config.state>;\n * // { country: \"FR\" | \"DE\"; status: \"registered\" | \"unregistered\" }\n * ```\n */\nexport type InferFlowState<T extends Record<string, z.ZodType>> = {\n\t[K in keyof T]: z.infer<T[K]>;\n};\n\n/**\n * A compiled flow — can be registered on an McpServer.\n */\nexport type RegisteredFlow = {\n\tid: string;\n\ttitle: string;\n\tdescription: string;\n\tregister: (server: McpServer) => Promise<void>;\n};\n\nexport interface CompileInput<TState extends Record<string, unknown>> {\n\tconfig: FlowConfig;\n\tnodes: Map<string, NodeHandler<TState>>;\n\tedges: Map<string, Edge<TState>>;\n\tstore?: FlowStore;\n}\n\nexport type FlowToolInput = {\n\taction: \"start\" | \"continue\";\n\tstateUpdates?: Record<string, unknown>;\n};\n\nexport type FlowTokenContent = {\n\tstep?: string;\n\tstate: Record<string, unknown>;\n\tfield?: string;\n\twidgetId?: string;\n};\n\nexport type InterruptQuestionData = {\n\tquestion: string;\n\tfield: string;\n\tsuggestions?: string[];\n\tcontext?: string;\n};\n\nexport type FlowInterruptContent = {\n\tstatus: \"interrupt\";\n\t/** Single-question shorthand */\n\tquestion?: string;\n\tfield?: string;\n\tsuggestions?: string[];\n\t/** Multi-question */\n\tquestions?: InterruptQuestionData[];\n\tcontext?: string;\n};\n\nexport type FlowWidgetContent = {\n\tstatus: \"widget\";\n\t/** Display tool to call */\n\ttool: string;\n\t/** Data to pass to the display tool */\n\tdata: Record<string, unknown>;\n\tdescription?: string;\n\t/** Whether the widget expects user interaction before continuing */\n\tinteractive?: boolean;\n};\n\nexport type FlowCompleteContent = {\n\tstatus: \"complete\";\n};\n\nexport type FlowErrorContent = {\n\tstatus: \"error\";\n\terror: string;\n};\n\n/** Parsed response text from a flow tool call */\nexport type FlowContent =\n\t| FlowInterruptContent\n\t| FlowWidgetContent\n\t| FlowCompleteContent\n\t| FlowErrorContent;\n\nexport type ExecutionResult = {\n\tcontent: FlowContent;\n\tflowTokenContent?: FlowTokenContent;\n};\n","import type { ToolCallback } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { RequestHandlerExtra } from \"@modelcontextprotocol/sdk/shared/protocol.js\";\nimport type {\n\tServerNotification,\n\tServerRequest,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { z } from \"zod\";\nimport type { ScopedWaniWaniClient } from \"../scoped-client\";\nimport { extractScopedClient } from \"../scoped-client\";\nimport { extractSessionId } from \"../utils\";\nimport type {\n\tCompileInput,\n\tFlowToolInput,\n\tMcpServer,\n\tRegisteredFlow,\n} from \"./@types\";\nimport { START } from \"./@types\";\nimport { executeFrom, resolveNextNode, type ValidateFn } from \"./execute\";\nimport { type FlowStore, WaniwaniFlowStore } from \"./flow-store\";\nimport { deepMerge, expandDotPaths } from \"./nested\";\nimport { buildFlowProtocol } from \"./protocol\";\n\n// ============================================================================\n// Input schema\n// ============================================================================\n\nconst inputSchema = {\n\taction: z\n\t\t.enum([\"start\", \"continue\"])\n\t\t.describe(\n\t\t\t'\"start\" to begin the flow, \"continue\" to resume after a pause (interrupt or widget)',\n\t\t),\n\tstateUpdates: z\n\t\t.record(z.string(), z.unknown())\n\t\t.optional()\n\t\t.describe(\n\t\t\t\"State field values to set before processing the next node. Use this to pass the user's answer (keyed by the field name from the response) and any other values the user mentioned.\",\n\t\t),\n};\n\n// ============================================================================\n// Compile\n// ============================================================================\n\nexport function compileFlow<TState extends Record<string, unknown>>(\n\tinput: CompileInput<TState>,\n): RegisteredFlow {\n\tconst { config, nodes, edges } = input;\n\tconst protocol = buildFlowProtocol(config);\n\tconst fullDescription = `${config.description}\\n${protocol}`;\n\n\t// Server-side state store — keyed by sessionId, backed by WaniWani API.\n\tconst store: FlowStore = input.store ?? new WaniwaniFlowStore();\n\n\t// Validator storage — populated when handlers return interrupts with validate functions.\n\t// Keyed by \"nodeName:fieldName\", persists across tool calls within the same server.\n\tconst validators = new Map<string, ValidateFn>();\n\n\tasync function handleToolCall(\n\t\targs: FlowToolInput,\n\t\tsessionId: string | undefined,\n\t\tmeta?: Record<string, unknown>,\n\t\twaniwani?: ScopedWaniWaniClient,\n\t) {\n\t\tif (args.action === \"start\") {\n\t\t\tconst startEdge = edges.get(START);\n\t\t\tif (!startEdge) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: { status: \"error\" as const, error: \"No start edge\" },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst startState = expandDotPaths(args.stateUpdates ?? {}) as TState;\n\t\t\tconst firstNode = await resolveNextNode(startEdge, startState);\n\t\t\treturn executeFrom(\n\t\t\t\tfirstNode,\n\t\t\t\tstartState,\n\t\t\t\tnodes,\n\t\t\t\tedges,\n\t\t\t\tvalidators,\n\t\t\t\tmeta,\n\t\t\t\twaniwani,\n\t\t\t);\n\t\t}\n\n\t\tif (args.action === \"continue\") {\n\t\t\tif (!sessionId) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror: \"No session ID available for continue action.\",\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst flowState = await store.get(sessionId);\n\n\t\t\tif (!flowState) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror: \"Flow state not found. The flow may have expired.\",\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst state = flowState.state as TState;\n\t\t\tconst step = flowState.step;\n\t\t\tif (!step) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror:\n\t\t\t\t\t\t\t\"Flow state is missing the current step. The flow may have expired.\",\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst updatedState = deepMerge(\n\t\t\t\tstate as Record<string, unknown>,\n\t\t\t\texpandDotPaths(args.stateUpdates ?? {}),\n\t\t\t) as TState;\n\n\t\t\t// Widget continue: advance past the widget step (don't re-show it)\n\t\t\tif (flowState.widgetId) {\n\t\t\t\tconst edge = edges.get(step);\n\t\t\t\tif (!edge) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\t\terror: `No edge from step \"${step}\"`,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst nextNode = await resolveNextNode(edge, updatedState);\n\t\t\t\treturn executeFrom(\n\t\t\t\t\tnextNode,\n\t\t\t\t\tupdatedState,\n\t\t\t\t\tnodes,\n\t\t\t\t\tedges,\n\t\t\t\t\tvalidators,\n\t\t\t\t\tmeta,\n\t\t\t\t\twaniwani,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Interrupt continue: re-execute from current step.\n\t\t\t// The handler re-runs, filters answered questions, and runs\n\t\t\t// validators if all questions are filled.\n\t\t\treturn executeFrom(\n\t\t\t\tstep,\n\t\t\t\tupdatedState,\n\t\t\t\tnodes,\n\t\t\t\tedges,\n\t\t\t\tvalidators,\n\t\t\t\tmeta,\n\t\t\t\twaniwani,\n\t\t\t);\n\t\t}\n\n\t\treturn {\n\t\t\tcontent: {\n\t\t\t\tstatus: \"error\" as const,\n\t\t\t\terror: `Unknown action: \"${args.action}\"`,\n\t\t\t},\n\t\t};\n\t}\n\n\treturn {\n\t\tid: config.id,\n\t\ttitle: config.title,\n\t\tdescription: fullDescription,\n\n\t\tasync register(server: McpServer): Promise<void> {\n\t\t\tserver.registerTool(\n\t\t\t\tconfig.id,\n\t\t\t\t{\n\t\t\t\t\ttitle: config.title,\n\t\t\t\t\tdescription: fullDescription,\n\t\t\t\t\tinputSchema,\n\t\t\t\t\tannotations: config.annotations,\n\t\t\t\t},\n\t\t\t\t(async (args: FlowToolInput, extra: unknown) => {\n\t\t\t\t\tconst requestExtra = extra as RequestHandlerExtra<\n\t\t\t\t\t\tServerRequest,\n\t\t\t\t\t\tServerNotification\n\t\t\t\t\t>;\n\t\t\t\t\tconst _meta: Record<string, unknown> = requestExtra._meta ?? {};\n\t\t\t\t\tconst sessionId = extractSessionId(_meta);\n\t\t\t\t\tconst waniwani = extractScopedClient(requestExtra);\n\n\t\t\t\t\tconst result = await handleToolCall(args, sessionId, _meta, waniwani);\n\n\t\t\t\t\t// Persist flow state under session ID\n\t\t\t\t\tif (result.flowTokenContent && sessionId) {\n\t\t\t\t\t\tawait store.set(sessionId, result.flowTokenContent);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst content = [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\t\ttext: JSON.stringify(result.content, null, 2),\n\t\t\t\t\t\t},\n\t\t\t\t\t];\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent,\n\t\t\t\t\t\t_meta,\n\t\t\t\t\t\t...(result.content.status === \"error\" ? { isError: true } : {}),\n\t\t\t\t\t};\n\t\t\t\t}) satisfies ToolCallback<typeof inputSchema>,\n\t\t\t);\n\t\t},\n\t};\n}\n","import type { KbClient } from \"../../kb/types.js\";\nimport type { TrackInput, TrackingClient } from \"../../tracking/@types.js\";\n\n/**\n * Well-known key used to attach the scoped client to the MCP `extra` object.\n * Read by `createTool` and flow compilation to surface it in handler contexts.\n */\nexport const SCOPED_CLIENT_KEY = \"waniwani/client\";\n\n/**\n * A request-scoped WaniWani client with meta pre-attached.\n *\n * Available as `context.waniwani` inside `createTool` handlers and flow nodes\n * when the server is wrapped with `withWaniwani()`.\n */\nexport interface ScopedWaniWaniClient {\n\t/** Track an event — request meta is automatically merged. */\n\ttrack(event: TrackInput): Promise<{ eventId: string }>;\n\t/** Identify a user — request meta is automatically merged. */\n\tidentify(\n\t\tuserId: string,\n\t\tproperties?: Record<string, unknown>,\n\t): Promise<{ eventId: string }>;\n\t/** Knowledge base client (no meta needed). */\n\treadonly kb: KbClient;\n}\n\n/**\n * Creates a request-scoped client that delegates to the base client\n * with request meta pre-attached to every tracking call.\n */\n/**\n * Extract the scoped client from the MCP `extra` object.\n * Returns undefined if `withWaniwani()` is not wrapping the server.\n */\nexport function extractScopedClient(\n\textra: unknown,\n): ScopedWaniWaniClient | undefined {\n\tif (typeof extra === \"object\" && extra !== null) {\n\t\treturn (extra as Record<string, unknown>)[SCOPED_CLIENT_KEY] as\n\t\t\t| ScopedWaniWaniClient\n\t\t\t| undefined;\n\t}\n\treturn undefined;\n}\n\nexport function createScopedClient(\n\tbase: Pick<TrackingClient, \"track\" | \"identify\"> & { readonly kb: KbClient },\n\tmeta: Record<string, unknown>,\n): ScopedWaniWaniClient {\n\treturn {\n\t\ttrack(event) {\n\t\t\treturn base.track({\n\t\t\t\t...event,\n\t\t\t\tmeta: { ...meta, ...event.meta },\n\t\t\t});\n\t\t},\n\t\tidentify(userId, properties) {\n\t\t\treturn base.identify(userId, properties, meta);\n\t\t},\n\t\tkb: base.kb,\n\t};\n}\n","// ============================================================================\n// Meta key extraction helpers\n// ============================================================================\n\n/** Pick the first non-empty string value from `meta` matching the given keys. */\nfunction pickFirst(\n\tmeta: Record<string, unknown>,\n\tkeys: readonly string[],\n): string | undefined {\n\tfor (const key of keys) {\n\t\tconst value = meta[key];\n\t\tif (typeof value === \"string\" && value.length > 0) {\n\t\t\treturn value;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n// --- Key lists (ordered by priority) ---\n\nconst SESSION_ID_KEYS = [\n\t\"waniwani/sessionId\",\n\t\"openai/sessionId\",\n\t\"sessionId\",\n\t\"conversationId\",\n\t\"anthropic/sessionId\",\n] as const;\n\nconst REQUEST_ID_KEYS = [\n\t\"waniwani/requestId\",\n\t\"openai/requestId\",\n\t\"requestId\",\n\t\"mcp/requestId\",\n] as const;\n\nconst TRACE_ID_KEYS = [\n\t\"waniwani/traceId\",\n\t\"openai/traceId\",\n\t\"traceId\",\n\t\"mcp/traceId\",\n\t\"openai/requestId\",\n\t\"requestId\",\n] as const;\n\nconst EXTERNAL_USER_ID_KEYS = [\n\t\"waniwani/userId\",\n\t\"openai/userId\",\n\t\"externalUserId\",\n\t\"userId\",\n\t\"actorId\",\n] as const;\n\nconst CORRELATION_ID_KEYS = [\"correlationId\", \"openai/requestId\"] as const;\n\n// --- Extractors ---\n\nexport function extractSessionId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, SESSION_ID_KEYS) : undefined;\n}\n\nexport function extractRequestId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, REQUEST_ID_KEYS) : undefined;\n}\n\nexport function extractTraceId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, TRACE_ID_KEYS) : undefined;\n}\n\nexport function extractExternalUserId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, EXTERNAL_USER_ID_KEYS) : undefined;\n}\n\nexport function extractCorrelationId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, CORRELATION_ID_KEYS) : undefined;\n}\n\nconst SOURCE_SESSION_KEYS = [\n\t{ key: \"waniwani/sessionId\", source: \"chatbar\" },\n\t{ key: \"openai/sessionId\", source: \"chatgpt\" },\n\t{ key: \"anthropic/sessionId\", source: \"claude\" },\n] as const;\n\nexport function extractSource(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\tif (!meta) {\n\t\treturn undefined;\n\t}\n\t// Explicit source set by the caller (e.g. chatbar name)\n\tconst explicit = meta[\"waniwani/source\"];\n\tif (typeof explicit === \"string\" && explicit.length > 0) {\n\t\treturn explicit;\n\t}\n\t// Derive from session ID key\n\tfor (const { key, source } of SOURCE_SESSION_KEYS) {\n\t\tconst value = meta[key];\n\t\tif (typeof value === \"string\" && value.length > 0) {\n\t\t\treturn source;\n\t\t}\n\t}\n\treturn undefined;\n}\n","import type { z } from \"zod\";\n\n// ============================================================================\n// Zod object schema detection\n// ============================================================================\n\n/** Check whether a Zod schema is a `z.object(...)`. */\nexport function isObjectSchema(schema: z.ZodType): boolean {\n\tconst def = (schema as unknown as { _zod?: { def?: { type?: string } } })._zod\n\t\t?.def;\n\treturn def?.type === \"object\";\n}\n\n/** Extract the shape of a `z.object(...)` schema, or null if not an object. */\nexport function getObjectShape(\n\tschema: z.ZodType,\n): Record<string, z.ZodType> | null {\n\tconst def = (\n\t\tschema as unknown as {\n\t\t\t_zod?: { def?: { type?: string; shape?: Record<string, z.ZodType> } };\n\t\t}\n\t)._zod?.def;\n\tif (def?.type === \"object\" && def.shape) {\n\t\treturn def.shape;\n\t}\n\treturn null;\n}\n\n// ============================================================================\n// Dot-path value access\n// ============================================================================\n\n/** Resolve a dot-path like `\"driver.name\"` to its value in a nested object. */\nexport function getNestedValue(\n\tobj: Record<string, unknown>,\n\tpath: string,\n): unknown {\n\tconst parts = path.split(\".\");\n\tlet current: unknown = obj;\n\tfor (const part of parts) {\n\t\tif (current == null || typeof current !== \"object\") {\n\t\t\treturn undefined;\n\t\t}\n\t\tcurrent = (current as Record<string, unknown>)[part];\n\t}\n\treturn current;\n}\n\n/** Set a value at a dot-path, creating intermediate objects as needed. */\nexport function setNestedValue(\n\tobj: Record<string, unknown>,\n\tpath: string,\n\tvalue: unknown,\n): void {\n\tconst parts = path.split(\".\");\n\tconst lastKey = parts.pop();\n\tif (!lastKey) {\n\t\treturn;\n\t}\n\tlet current = obj;\n\tfor (const part of parts) {\n\t\tif (\n\t\t\tcurrent[part] == null ||\n\t\t\ttypeof current[part] !== \"object\" ||\n\t\t\tArray.isArray(current[part])\n\t\t) {\n\t\t\tcurrent[part] = {};\n\t\t}\n\t\tcurrent = current[part] as Record<string, unknown>;\n\t}\n\tcurrent[lastKey] = value;\n}\n\n/** Delete a value at a dot-path. Only removes the leaf key. */\nexport function deleteNestedValue(\n\tobj: Record<string, unknown>,\n\tpath: string,\n): void {\n\tconst parts = path.split(\".\");\n\tconst lastKey = parts.pop();\n\tif (!lastKey) {\n\t\treturn;\n\t}\n\tlet current: unknown = obj;\n\tfor (const part of parts) {\n\t\tif (current == null || typeof current !== \"object\") {\n\t\t\treturn;\n\t\t}\n\t\tcurrent = (current as Record<string, unknown>)[part];\n\t}\n\tif (current != null && typeof current === \"object\") {\n\t\tdelete (current as Record<string, unknown>)[lastKey];\n\t}\n}\n\n// ============================================================================\n// State merging\n// ============================================================================\n\n/**\n * Expand dot-path keys into nested objects.\n * `{ \"driver.name\": \"John\", \"email\": \"a@b.com\" }` → `{ driver: { name: \"John\" }, email: \"a@b.com\" }`\n */\nexport function expandDotPaths(\n\tflat: Record<string, unknown>,\n): Record<string, unknown> {\n\tconst result: Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(flat)) {\n\t\tif (key.includes(\".\")) {\n\t\t\tsetNestedValue(result, key, value);\n\t\t} else {\n\t\t\tresult[key] = value;\n\t\t}\n\t}\n\treturn result;\n}\n\n/**\n * Deep-merge source into target. Preserves existing nested keys.\n * Only merges plain objects — arrays and primitives are overwritten.\n */\nexport function deepMerge(\n\ttarget: Record<string, unknown>,\n\tsource: Record<string, unknown>,\n): Record<string, unknown> {\n\tconst result = { ...target };\n\tfor (const [key, value] of Object.entries(source)) {\n\t\tif (\n\t\t\tvalue !== null &&\n\t\t\ttypeof value === \"object\" &&\n\t\t\t!Array.isArray(value) &&\n\t\t\tresult[key] !== null &&\n\t\t\ttypeof result[key] === \"object\" &&\n\t\t\t!Array.isArray(result[key])\n\t\t) {\n\t\t\tresult[key] = deepMerge(\n\t\t\t\tresult[key] as Record<string, unknown>,\n\t\t\t\tvalue as Record<string, unknown>,\n\t\t\t);\n\t\t} else {\n\t\t\tresult[key] = value;\n\t\t}\n\t}\n\treturn result;\n}\n","import type { ScopedWaniWaniClient } from \"../scoped-client\";\nimport type {\n\tEdge,\n\tExecutionResult,\n\tInterruptQuestionData,\n\tMaybePromise,\n\tNodeHandler,\n} from \"./@types\";\nimport { END, interrupt, isInterrupt, isWidget, showWidget } from \"./@types\";\nimport { deepMerge, deleteNestedValue, getNestedValue } from \"./nested\";\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Check whether a state value counts as \"filled\" (not empty/missing). */\nexport function isFilled(v: unknown): boolean {\n\treturn v !== undefined && v !== null && v !== \"\";\n}\n\nexport type ValidateFn = (\n\tvalue: unknown,\n\t// biome-ignore lint/suspicious/noConfusingVoidType: void needed for async () => {} validators\n) => MaybePromise<Record<string, unknown> | void>;\n\n// ============================================================================\n// Edge resolution\n// ============================================================================\n\nexport async function resolveNextNode<TState extends Record<string, unknown>>(\n\tedge: Edge<TState>,\n\tstate: Partial<TState>,\n): Promise<string> {\n\tif (edge.type === \"direct\") {\n\t\treturn edge.to;\n\t}\n\treturn edge.condition(state);\n}\n\n// ============================================================================\n// Interrupt result builder\n// ============================================================================\n\n/**\n * Build an interrupt ExecutionResult from a list of questions and current state.\n * Filters out already-answered questions and caches the full question list in\n * flowMeta so partial-answer continues can filter without re-executing the handler.\n *\n * Returns `null` when all questions are already filled (caller should advance).\n */\nexport function buildInterruptResult<TState extends Record<string, unknown>>(\n\tquestions: InterruptQuestionData[],\n\tcontext: string | undefined,\n\tcurrentNode: string,\n\tstate: TState,\n): ExecutionResult | null {\n\t// All filled — caller should advance to the next node\n\tif (\n\t\tquestions.every((q) =>\n\t\t\tisFilled(getNestedValue(state as Record<string, unknown>, q.field)),\n\t\t)\n\t) {\n\t\treturn null;\n\t}\n\n\t// Filter out questions whose fields are already answered\n\tconst unanswered = questions.filter(\n\t\t(q) => !isFilled(getNestedValue(state as Record<string, unknown>, q.field)),\n\t);\n\n\t// Single-question shorthand: unwrap for cleaner AI payload\n\tconst isSingle = unanswered.length === 1;\n\tconst q0 = unanswered[0];\n\tconst payload =\n\t\tisSingle && q0\n\t\t\t? {\n\t\t\t\t\tstatus: \"interrupt\" as const,\n\t\t\t\t\tquestion: q0.question,\n\t\t\t\t\tfield: q0.field,\n\t\t\t\t\t...(q0.suggestions ? { suggestions: q0.suggestions } : {}),\n\t\t\t\t\t...(q0.context || context ? { context: q0.context ?? context } : {}),\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tstatus: \"interrupt\" as const,\n\t\t\t\t\tquestions: unanswered,\n\t\t\t\t\t...(context ? { context } : {}),\n\t\t\t\t};\n\n\treturn {\n\t\tcontent: payload,\n\t\tflowTokenContent: {\n\t\t\tstep: currentNode,\n\t\t\tstate,\n\t\t\t...(isSingle && q0 ? { field: q0.field } : {}),\n\t\t},\n\t};\n}\n\n// ============================================================================\n// Execution engine\n// ============================================================================\n\nexport async function executeFrom<TState extends Record<string, unknown>>(\n\tstartNodeName: string,\n\tstartState: TState,\n\tnodes: Map<string, NodeHandler<TState>>,\n\tedges: Map<string, Edge<TState>>,\n\tvalidators: Map<string, ValidateFn>,\n\tmeta?: Record<string, unknown>,\n\twaniwani?: ScopedWaniWaniClient,\n): Promise<ExecutionResult> {\n\tlet currentNode = startNodeName;\n\tlet state = { ...startState };\n\n\t// Safety limit to prevent infinite loops\n\tconst MAX_ITERATIONS = 50;\n\tlet iterations = 0;\n\n\twhile (iterations++ < MAX_ITERATIONS) {\n\t\t// Reached END\n\t\tif (currentNode === END) {\n\t\t\treturn {\n\t\t\t\tcontent: { status: \"complete\" },\n\t\t\t\tflowTokenContent: { state },\n\t\t\t};\n\t\t}\n\n\t\tconst handler = nodes.get(currentNode);\n\t\tif (!handler) {\n\t\t\treturn {\n\t\t\t\tcontent: {\n\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\terror: `Unknown node: \"${currentNode}\"`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\ttry {\n\t\t\t// Build context object for the handler\n\t\t\tconst ctx = {\n\t\t\t\tstate,\n\t\t\t\tmeta,\n\t\t\t\tinterrupt: interrupt as NodeHandler<TState> extends never\n\t\t\t\t\t? never\n\t\t\t\t\t: typeof interrupt,\n\t\t\t\tshowWidget: showWidget as NodeHandler<TState> extends never\n\t\t\t\t\t? never\n\t\t\t\t\t: typeof showWidget,\n\t\t\t\twaniwani,\n\t\t\t};\n\t\t\tconst result = await handler(ctx as Parameters<typeof handler>[0]);\n\n\t\t\t// Interrupt signal — pause and ask the user one or more questions\n\t\t\tif (isInterrupt(result)) {\n\t\t\t\t// Extract and store any validate functions from the interrupt questions\n\t\t\t\tfor (const q of result.questions) {\n\t\t\t\t\tif (q.validate) {\n\t\t\t\t\t\tvalidators.set(`${currentNode}:${q.field}`, q.validate);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst interruptResult = buildInterruptResult(\n\t\t\t\t\tresult.questions,\n\t\t\t\t\tresult.context,\n\t\t\t\t\tcurrentNode,\n\t\t\t\t\tstate,\n\t\t\t\t);\n\n\t\t\t\tif (interruptResult) {\n\t\t\t\t\treturn interruptResult;\n\t\t\t\t}\n\n\t\t\t\t// All questions filled — run validators before advancing\n\t\t\t\tfor (const q of result.questions) {\n\t\t\t\t\tconst fn = validators.get(`${currentNode}:${q.field}`);\n\t\t\t\t\tif (fn) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst value = getNestedValue(\n\t\t\t\t\t\t\t\tstate as Record<string, unknown>,\n\t\t\t\t\t\t\t\tq.field,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tconst vResult = await fn(value);\n\t\t\t\t\t\t\tif (vResult && typeof vResult === \"object\") {\n\t\t\t\t\t\t\t\tstate = deepMerge(\n\t\t\t\t\t\t\t\t\tstate as Record<string, unknown>,\n\t\t\t\t\t\t\t\t\tvResult as Record<string, unknown>,\n\t\t\t\t\t\t\t\t) as TState;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tdeleteNestedValue(state as Record<string, unknown>, q.field);\n\t\t\t\t\t\t\tconst questionsWithError = result.questions.map((qq) =>\n\t\t\t\t\t\t\t\tqq.field === q.field\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\t...qq,\n\t\t\t\t\t\t\t\t\t\t\tcontext: qq.context\n\t\t\t\t\t\t\t\t\t\t\t\t? `ERROR: ${msg}\\n\\n${qq.context}`\n\t\t\t\t\t\t\t\t\t\t\t\t: `ERROR: ${msg}`,\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: qq,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tconst errResult = buildInterruptResult(\n\t\t\t\t\t\t\t\tquestionsWithError,\n\t\t\t\t\t\t\t\tresult.context,\n\t\t\t\t\t\t\t\tcurrentNode,\n\t\t\t\t\t\t\t\tstate,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (errResult) {\n\t\t\t\t\t\t\t\treturn errResult;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// All questions filled and validated — advance to next node\n\t\t\t\tconst edge = edges.get(currentNode);\n\t\t\t\tif (!edge) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\t\terror: `No outgoing edge from node \"${currentNode}\"`,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tcurrentNode = await resolveNextNode(edge, state);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Widget signal — delegate to display tool\n\t\t\tif (isWidget(result)) {\n\t\t\t\tconst widgetField = result.field;\n\t\t\t\tif (widgetField) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tisFilled(\n\t\t\t\t\t\t\tgetNestedValue(state as Record<string, unknown>, widgetField),\n\t\t\t\t\t\t)\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst edge = edges.get(currentNode);\n\t\t\t\t\t\tif (!edge) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\t\t\t\terror: `No outgoing edge from node \"${currentNode}\"`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentNode = await resolveNextNode(edge, state);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"widget\",\n\t\t\t\t\t\ttool: result.tool.id,\n\t\t\t\t\t\tdata: result.data,\n\t\t\t\t\t\tdescription: result.description,\n\t\t\t\t\t\tinteractive: result.interactive !== false,\n\t\t\t\t\t},\n\t\t\t\t\tflowTokenContent: {\n\t\t\t\t\t\tstep: currentNode,\n\t\t\t\t\t\tstate,\n\t\t\t\t\t\tfield: widgetField,\n\t\t\t\t\t\twidgetId: result.tool.id,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Action node — deep-merge state (preserves nested object siblings)\n\t\t\tstate = deepMerge(\n\t\t\t\tstate as Record<string, unknown>,\n\t\t\t\tresult as Record<string, unknown>,\n\t\t\t) as TState;\n\n\t\t\tconst edge = edges.get(currentNode);\n\t\t\tif (!edge) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\terror: `No outgoing edge from node \"${currentNode}\"`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\tcurrentNode = await resolveNextNode(edge, state);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\treturn {\n\t\t\t\tcontent: { status: \"error\", error: message },\n\t\t\t\tflowTokenContent: { step: currentNode, state },\n\t\t\t};\n\t\t}\n\t}\n\n\treturn {\n\t\tcontent: {\n\t\t\tstatus: \"error\",\n\t\t\terror: \"Flow exceeded maximum iterations (possible infinite loop)\",\n\t\t},\n\t};\n}\n","/**\n * Server-side flow state store.\n *\n * Flow state is stored via the WaniWani API, keyed by session ID.\n * The session ID comes from _meta (provided by the MCP client on every call),\n * so the LLM doesn't need to round-trip anything.\n *\n * Tenant isolation is handled by the API key — no manual key prefixing needed.\n *\n * The `FlowStore` interface is exported for custom implementations.\n */\n\nimport type { FlowTokenContent } from \"./@types\";\n\n// ============================================================================\n// Interface\n// ============================================================================\n\nexport interface FlowStore {\n\tget(key: string): Promise<FlowTokenContent | null>;\n\tset(key: string, value: FlowTokenContent): Promise<void>;\n\tdelete(key: string): Promise<void>;\n}\n\n// ============================================================================\n// WaniWani API implementation\n// ============================================================================\n\nconst SDK_NAME = \"@waniwani/sdk\";\nconst DEFAULT_BASE_URL = \"https://app.waniwani.ai\";\n\nexport class WaniwaniFlowStore implements FlowStore {\n\tprivate readonly baseUrl: string;\n\tprivate readonly apiKey: string | undefined;\n\n\tconstructor(options?: { baseUrl?: string; apiKey?: string }) {\n\t\tthis.baseUrl = (\n\t\t\toptions?.baseUrl ??\n\t\t\tprocess.env.WANIWANI_BASE_URL ??\n\t\t\tDEFAULT_BASE_URL\n\t\t).replace(/\\/$/, \"\");\n\t\tthis.apiKey = options?.apiKey ?? process.env.WANIWANI_API_KEY;\n\t}\n\n\tasync get(key: string): Promise<FlowTokenContent | null> {\n\t\tif (!this.apiKey) {\n\t\t\treturn null;\n\t\t}\n\t\ttry {\n\t\t\tconst data = await this.request<FlowTokenContent | null>(\n\t\t\t\t\"/api/mcp/redis/get\",\n\t\t\t\t{ key },\n\t\t\t);\n\t\t\treturn data ?? null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tasync set(key: string, value: FlowTokenContent): Promise<void> {\n\t\tif (!this.apiKey) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait this.request(\"/api/mcp/redis/set\", { key, value });\n\t\t} catch {\n\t\t\t// Non-fatal\n\t\t}\n\t}\n\n\tasync delete(key: string): Promise<void> {\n\t\tif (!this.apiKey) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait this.request(\"/api/mcp/redis/delete\", { key });\n\t\t} catch {\n\t\t\t// Non-fatal\n\t\t}\n\t}\n\n\tprivate async request<T>(path: string, body: unknown): Promise<T> {\n\t\tconst url = `${this.baseUrl}${path}`;\n\t\tconst response = await fetch(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${this.apiKey}`,\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\"X-WaniWani-SDK\": SDK_NAME,\n\t\t\t},\n\t\t\tbody: JSON.stringify(body),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst text = await response.text().catch(() => \"\");\n\t\t\tthrow new Error(text || `Flow state API error: HTTP ${response.status}`);\n\t\t}\n\n\t\tconst json = (await response.json()) as { data: T };\n\t\treturn json.data;\n\t}\n}\n","import type { z } from \"zod\";\nimport type { FlowConfig } from \"./@types\";\nimport { getObjectShape } from \"./nested\";\n\n/** Extract a human-readable label from a Zod schema for the AI protocol */\nfunction describeZodField(schema: z.ZodType): string {\n\tconst desc = schema.description ?? \"\";\n\tconst def = (\n\t\tschema as unknown as {\n\t\t\t_zod: { def: { type: string; entries?: Record<string, string> } };\n\t\t}\n\t)._zod?.def;\n\n\tif (def?.type === \"enum\" && def.entries) {\n\t\tconst vals = Object.keys(def.entries)\n\t\t\t.map((v) => `\"${v}\"`)\n\t\t\t.join(\" | \");\n\t\treturn desc ? `${vals} — ${desc}` : vals;\n\t}\n\n\treturn desc;\n}\n\nexport function buildFlowProtocol(config: FlowConfig): string {\n\tconst lines = [\n\t\t\"\",\n\t\t\"## FLOW EXECUTION PROTOCOL\",\n\t\t\"\",\n\t\t\"This tool implements a multi-step conversational flow. Follow this protocol exactly:\",\n\t\t\"\",\n\t\t'1. Call with `action: \"start\"` to begin. If the user\\'s message already',\n\t\t\" contains answers to likely questions, extract them into `stateUpdates`\",\n\t\t\" as `{ field: value }` pairs. The engine will auto-skip steps whose\",\n\t\t\" fields are already filled.\",\n\t\t\" Only extract values the user explicitly stated — do NOT guess or invent values.\",\n\t];\n\n\tif (config.state) {\n\t\tconst parts: string[] = [];\n\t\tfor (const [key, schema] of Object.entries(config.state)) {\n\t\t\tconst shape = getObjectShape(schema);\n\t\t\tif (shape) {\n\t\t\t\tconst groupDesc = schema.description ?? \"\";\n\t\t\t\tconst subFields = Object.entries(shape)\n\t\t\t\t\t.map(([subKey, subSchema]) => {\n\t\t\t\t\t\tconst info = describeZodField(subSchema);\n\t\t\t\t\t\treturn info\n\t\t\t\t\t\t\t? `\\`${key}.${subKey}\\` (${info})`\n\t\t\t\t\t\t\t: `\\`${key}.${subKey}\\``;\n\t\t\t\t\t})\n\t\t\t\t\t.join(\", \");\n\t\t\t\tparts.push(\n\t\t\t\t\tgroupDesc\n\t\t\t\t\t\t? `\\`${key}\\` (${groupDesc}): ${subFields}`\n\t\t\t\t\t\t: `\\`${key}\\`: ${subFields}`,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tconst info = describeZodField(schema);\n\t\t\t\tparts.push(info ? `\\`${key}\\` (${info})` : `\\`${key}\\``);\n\t\t\t}\n\t\t}\n\t\tlines.push(` Known fields: ${parts.join(\", \")}.`);\n\t}\n\n\tlines.push(\n\t\t\" For grouped fields (shown as `group.subfield`), use dot-notation keys in `stateUpdates`:\",\n\t\t' e.g. `{ \"driver.name\": \"John\", \"driver.license\": \"ABC123\" }`.',\n\t\t\"2. The response JSON `status` field tells you what to do next:\",\n\t\t' - `\"interrupt\"`: Pause and ask the user. Two forms:',\n\t\t\" a. Single question: `{ question, field, context? }` — ask `question`, store answer in `field`.\",\n\t\t\" b. Multi-question: `{ questions: [{question, field}, ...], context? }` — ask ALL questions\",\n\t\t\" in one conversational message, collect all answers.\",\n\t\t\" `context` (if present) is hidden AI instructions — use to shape your response, do NOT show verbatim.\",\n\t\t\" Then call again with:\",\n\t\t' `action: \"continue\"`,',\n\t\t\" `stateUpdates` = answers keyed by their `field` names, plus any other fields the user mentioned.\",\n\t\t' - `\"widget\"`: The flow wants to show a UI widget. Call the tool named in the `tool`',\n\t\t\" field, passing the `data` object as the tool's input.\",\n\t\t\" Check the `interactive` field in the response:\",\n\t\t\" • `interactive: true` — The widget requires user interaction. After calling the display tool,\",\n\t\t\" STOP and WAIT for the user to interact with the widget. Do NOT call this flow tool again\",\n\t\t\" until the user has responded. When they do, call with:\",\n\t\t' `action: \"continue\"`,',\n\t\t\" `stateUpdates` = `{ [field]: <user's selection> }` plus any other fields the user mentioned.\",\n\t\t\" • `interactive: false` — The widget is display-only. Call the display tool, then immediately\",\n\t\t' call THIS flow tool again with `action: \"continue\"`. Do NOT wait for user interaction.',\n\t\t' - `\"complete\"`: The flow is done. Present the result to the user.',\n\t\t' - `\"error\"`: Something went wrong. Show the `error` message.',\n\t\t\"\",\n\t\t\"3. Do NOT invent state values. Only use `stateUpdates` for information the user explicitly provided.\",\n\t\t\"4. Include only the fields the user actually answered in `stateUpdates` — do NOT guess missing ones.\",\n\t\t\" If the user did not answer all pending questions, the engine will re-prompt for the remaining ones.\",\n\t\t\" If the user mentioned values for other known fields, include those too —\",\n\t\t\" they will be applied immediately and those steps will be auto-skipped.\",\n\t);\n\n\treturn lines.join(\"\\n\");\n}\n","import type {\n\tConditionFn,\n\tEdge,\n\tFlowConfig,\n\tNodeHandler,\n\tRegisteredFlow,\n} from \"./@types\";\nimport { END, START } from \"./@types\";\nimport { compileFlow } from \"./compile\";\nimport type { FlowStore } from \"./flow-store\";\n\n/**\n * A LangGraph-inspired state graph builder for MCP tools.\n *\n * @example\n * ```ts\n * const flow = new StateGraph<MyState>({\n * id: \"onboarding\",\n * title: \"User Onboarding\",\n * description: \"Guides users through onboarding\",\n * })\n * .addNode(\"ask_name\", ({ interrupt }) => interrupt({ question: \"What's your name?\", field: \"name\" }))\n * .addNode(\"greet\", ({ state }) => ({ greeting: `Hello ${state.name}!` }))\n * .addEdge(START, \"ask_name\")\n * .addEdge(\"ask_name\", \"greet\")\n * .addEdge(\"greet\", END)\n * .compile();\n * ```\n */\nexport class StateGraph<TState extends Record<string, unknown>> {\n\tprivate nodes = new Map<string, NodeHandler<TState>>();\n\tprivate edges = new Map<string, Edge<TState>>();\n\tprivate config: FlowConfig;\n\n\tconstructor(config: FlowConfig) {\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * Add a node with a handler.\n\t *\n\t * The handler receives a context object with `state`, `meta`, `interrupt`, and `showWidget`.\n\t */\n\taddNode(name: string, handler: NodeHandler<TState>): this {\n\t\tif (name === START || name === END) {\n\t\t\tthrow new Error(\n\t\t\t\t`\"${name}\" is a reserved name and cannot be used as a node name`,\n\t\t\t);\n\t\t}\n\t\tif (this.nodes.has(name)) {\n\t\t\tthrow new Error(`Node \"${name}\" already exists`);\n\t\t}\n\n\t\tthis.nodes.set(name, handler);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add a direct edge between two nodes.\n\t *\n\t * Use `START` as `from` to set the entry point.\n\t * Use `END` as `to` to mark a terminal node.\n\t */\n\taddEdge(from: string, to: string): this {\n\t\tif (this.edges.has(from)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Node \"${from}\" already has an outgoing edge. Use addConditionalEdge for branching.`,\n\t\t\t);\n\t\t}\n\t\tthis.edges.set(from, { type: \"direct\", to });\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add a conditional edge from a node.\n\t *\n\t * The condition function receives current state and returns the name of the next node.\n\t */\n\taddConditionalEdge(from: string, condition: ConditionFn<TState>): this {\n\t\tif (this.edges.has(from)) {\n\t\t\tthrow new Error(`Node \"${from}\" already has an outgoing edge.`);\n\t\t}\n\t\tthis.edges.set(from, { type: \"conditional\", condition });\n\t\treturn this;\n\t}\n\n\t/**\n\t * Compile the graph into a RegisteredFlow that can be registered on an McpServer.\n\t *\n\t * Validates the graph structure and returns a registration-compatible object.\n\t */\n\tcompile(options?: { store?: FlowStore }): RegisteredFlow {\n\t\tthis.validate();\n\n\t\treturn compileFlow<TState>({\n\t\t\tconfig: this.config,\n\t\t\tnodes: new Map(this.nodes),\n\t\t\tedges: new Map(this.edges),\n\t\t\tstore: options?.store,\n\t\t});\n\t}\n\n\tprivate validate(): void {\n\t\t// Must have a START edge\n\t\tif (!this.edges.has(START)) {\n\t\t\tthrow new Error(\n\t\t\t\t'Flow must have an entry point. Add an edge from START: .addEdge(START, \"first_node\")',\n\t\t\t);\n\t\t}\n\n\t\t// START edge target must exist\n\t\tconst startEdge = this.edges.get(START);\n\t\tif (\n\t\t\tstartEdge?.type === \"direct\" &&\n\t\t\tstartEdge.to !== END &&\n\t\t\t!this.nodes.has(startEdge.to)\n\t\t) {\n\t\t\tthrow new Error(\n\t\t\t\t`START edge references non-existent node: \"${startEdge.to}\"`,\n\t\t\t);\n\t\t}\n\n\t\t// All static edge targets must reference existing nodes (or END)\n\t\tfor (const [from, edge] of this.edges) {\n\t\t\tif (from !== START && !this.nodes.has(from)) {\n\t\t\t\tthrow new Error(`Edge from non-existent node: \"${from}\"`);\n\t\t\t}\n\t\t\tif (\n\t\t\t\tedge.type === \"direct\" &&\n\t\t\t\tedge.to !== END &&\n\t\t\t\t!this.nodes.has(edge.to)\n\t\t\t) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Edge from \"${from}\" references non-existent node: \"${edge.to}\"`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Every node must have an outgoing edge\n\t\tfor (const [name] of this.nodes) {\n\t\t\tif (!this.edges.has(name)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Node \"${name}\" has no outgoing edge. Add one with .addEdge(\"${name}\", ...) or .addConditionalEdge(\"${name}\", ...)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n}\n","import type { z } from \"zod\";\nimport type { FlowConfig, InferFlowState } from \"./@types\";\nimport { StateGraph } from \"./state-graph\";\n\n/**\n * Create a new flow graph — convenience factory for `new StateGraph()`.\n *\n * The state type is automatically inferred from the `state` definition —\n * no explicit generic parameter needed.\n *\n * @example\n * ```ts\n * import { createFlow, interrupt, START, END } from \"@waniwani/sdk/mcp\";\n * import { z } from \"zod\";\n *\n * const flow = createFlow({\n * id: \"onboarding\",\n * title: \"User Onboarding\",\n * description: \"Guides users through onboarding. Use when a user wants to get started.\",\n * state: {\n * name: z.string().describe(\"The user's name\"),\n * email: z.string().describe(\"The user's email address\"),\n * },\n * })\n * .addNode(\"ask_name\", () => interrupt({ question: \"What's your name?\", field: \"name\" }))\n * .addNode(\"ask_email\", () => interrupt({ question: \"What's your email?\", field: \"email\" }))\n * .addEdge(START, \"ask_name\")\n * .addEdge(\"ask_name\", \"ask_email\")\n * .addEdge(\"ask_email\", END)\n * .compile();\n * ```\n */\nexport function createFlow<const TSchema extends Record<string, z.ZodType>>(\n\tconfig: Omit<FlowConfig, \"state\"> & { state: TSchema },\n): StateGraph<InferFlowState<TSchema>> {\n\treturn new StateGraph<InferFlowState<TSchema>>(config);\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type {\n\tFlowCompleteContent,\n\tFlowContent,\n\tFlowErrorContent,\n\tFlowInterruptContent,\n\tFlowTokenContent,\n\tFlowWidgetContent,\n\tRegisteredFlow,\n} from \"./@types\";\nimport type { FlowStore } from \"./flow-store\";\n\n// ============================================================================\n// Test harness for compiled flows\n// ============================================================================\n\ntype WithDecodedState = { decodedState: FlowTokenContent | null };\n\nexport type FlowTestResult =\n\t| (FlowInterruptContent & WithDecodedState)\n\t| (FlowWidgetContent & WithDecodedState)\n\t| (FlowCompleteContent & WithDecodedState)\n\t| (FlowErrorContent & WithDecodedState);\n\ntype Handler = (input: unknown, extra: unknown) => Promise<unknown>;\ntype RegisterToolArgs = [string, Record<string, unknown>, Handler];\n\nfunction parsePayload(result: Record<string, unknown>): FlowContent {\n\tconst content = result.content as Array<{ type: string; text?: string }>;\n\treturn JSON.parse(content[0]?.text ?? \"\") as FlowContent;\n}\n\nexport async function createFlowTestHarness(\n\tflow: RegisteredFlow,\n\toptions?: { stateStore?: FlowStore },\n) {\n\tconst store = options?.stateStore;\n\tconst registered: RegisterToolArgs[] = [];\n\tconst sessionId = `test-session-${Math.random().toString(36).slice(2, 10)}`;\n\n\tconst server = {\n\t\tregisterTool: (...args: unknown[]) => {\n\t\t\tregistered.push(args as RegisterToolArgs);\n\t\t},\n\t} as unknown as McpServer;\n\n\tawait flow.register(server);\n\n\tconst handler = registered[0]?.[2];\n\tif (!handler) {\n\t\tthrow new Error(`Flow \"${flow.id}\" did not register a handler`);\n\t}\n\n\tconst extra = { _meta: { sessionId } };\n\n\tasync function toResult(parsed: FlowContent): Promise<FlowTestResult> {\n\t\treturn {\n\t\t\t...parsed,\n\t\t\tdecodedState: store ? await store.get(sessionId) : null,\n\t\t} satisfies FlowTestResult;\n\t}\n\n\treturn {\n\t\tasync start(\n\t\t\tstateUpdates?: Record<string, unknown>,\n\t\t): Promise<FlowTestResult> {\n\t\t\tconst result = (await handler(\n\t\t\t\t{ action: \"start\", ...(stateUpdates ? { stateUpdates } : {}) },\n\t\t\t\textra,\n\t\t\t)) as Record<string, unknown>;\n\t\t\treturn toResult(parsePayload(result));\n\t\t},\n\n\t\tasync continueWith(\n\t\t\tstateUpdates?: Record<string, unknown>,\n\t\t): Promise<FlowTestResult> {\n\t\t\tconst result = (await handler(\n\t\t\t\t{\n\t\t\t\t\taction: \"continue\",\n\t\t\t\t\t...(stateUpdates ? { stateUpdates } : {}),\n\t\t\t\t},\n\t\t\t\textra,\n\t\t\t)) as Record<string, unknown>;\n\t\t\treturn toResult(parsePayload(result));\n\t\t},\n\n\t\tasync lastState(): Promise<FlowTokenContent | null> {\n\t\t\treturn store ? store.get(sessionId) : null;\n\t\t},\n\t};\n}\n","import type { WidgetCSP } from \"./types\";\n\n/**\n * MIME types for widget resources.\n * OpenAI Apps SDK uses \"text/html+skybridge\"\n * MCP Apps uses \"text/html;profile=mcp-app\"\n */\nexport const MIME_TYPE_OPENAI = \"text/html+skybridge\";\nexport const MIME_TYPE_MCP = \"text/html;profile=mcp-app\";\n\n// ---- HTML fetching ----\n\nexport const fetchHtml = async (\n\tbaseUrl: string,\n\tpath: string,\n): Promise<string> => {\n\tconst normalizedBase = baseUrl.endsWith(\"/\") ? baseUrl.slice(0, -1) : baseUrl;\n\tconst result = await fetch(`${normalizedBase}${path}`);\n\treturn await result.text();\n};\n\n// ---- OpenAI resource metadata ----\n\ninterface OpenAIResourceMeta {\n\t[key: string]: unknown;\n\t\"openai/widgetDescription\"?: string;\n\t\"openai/widgetPrefersBorder\"?: boolean;\n\t\"openai/widgetDomain\"?: string;\n\t\"openai/widgetCSP\"?: WidgetCSP;\n}\n\nexport function buildOpenAIResourceMeta(config: {\n\tdescription?: string;\n\tprefersBorder?: boolean;\n\twidgetDomain: string;\n\twidgetCSP?: WidgetCSP;\n}): OpenAIResourceMeta {\n\treturn {\n\t\t\"openai/widgetDescription\": config.description,\n\t\t\"openai/widgetPrefersBorder\": config.prefersBorder,\n\t\t\"openai/widgetDomain\": config.widgetDomain,\n\t\t...(config.widgetCSP && { \"openai/widgetCSP\": config.widgetCSP }),\n\t};\n}\n\n// ---- MCP Apps resource metadata ----\n\ninterface McpAppsResourceMeta {\n\t[key: string]: unknown;\n\tui?: {\n\t\tcsp?: {\n\t\t\tconnectDomains?: string[];\n\t\t\tresourceDomains?: string[];\n\t\t\tframeDomains?: string[];\n\t\t\tredirectDomains?: string[];\n\t\t};\n\t\tdomain?: string;\n\t\tprefersBorder?: boolean;\n\t};\n}\n\nexport function buildMcpAppsResourceMeta(config: {\n\tdescription?: string;\n\tprefersBorder?: boolean;\n\twidgetDomain?: string;\n\twidgetCSP?: WidgetCSP;\n}): McpAppsResourceMeta {\n\tconst csp = config.widgetCSP\n\t\t? {\n\t\t\t\tconnectDomains: config.widgetCSP.connect_domains,\n\t\t\t\tresourceDomains: config.widgetCSP.resource_domains,\n\t\t\t\tframeDomains: config.widgetCSP.frame_domains,\n\t\t\t\tredirectDomains: config.widgetCSP.redirect_domains,\n\t\t\t}\n\t\t: undefined;\n\n\treturn {\n\t\tui: {\n\t\t\t...(csp && { csp }),\n\t\t\t...(config.widgetDomain && { domain: config.widgetDomain }),\n\t\t\t...(config.prefersBorder !== undefined && {\n\t\t\t\tprefersBorder: config.prefersBorder,\n\t\t\t}),\n\t\t},\n\t};\n}\n\n// ---- Tool metadata (references resource URIs) ----\n\nexport function buildToolMeta(config: {\n\topenaiTemplateUri?: string;\n\tmcpTemplateUri?: string;\n\tinvoking: string;\n\tinvoked: string;\n\tautoHeight?: boolean;\n}) {\n\treturn {\n\t\t// OpenAI metadata\n\t\t...(config.openaiTemplateUri && {\n\t\t\t\"openai/outputTemplate\": config.openaiTemplateUri,\n\t\t}),\n\t\t\"openai/toolInvocation/invoking\": config.invoking,\n\t\t\"openai/toolInvocation/invoked\": config.invoked,\n\t\t\"openai/widgetAccessible\": true,\n\t\t\"openai/resultCanProduceWidget\": true,\n\t\t// MCP Apps metadata (nested)\n\t\t...(config.mcpTemplateUri && {\n\t\t\tui: {\n\t\t\t\tresourceUri: config.mcpTemplateUri,\n\t\t\t\t...(config.autoHeight && { autoHeight: true }),\n\t\t\t},\n\t\t}),\n\t\t// MCP Apps backward-compat flat key (for older hosts)\n\t\t...(config.mcpTemplateUri && {\n\t\t\t\"ui/resourceUri\": config.mcpTemplateUri,\n\t\t}),\n\t};\n}\n","import {\n\tbuildMcpAppsResourceMeta,\n\tbuildOpenAIResourceMeta,\n\tfetchHtml,\n\tMIME_TYPE_MCP,\n\tMIME_TYPE_OPENAI,\n} from \"./meta\";\nimport type { McpServer, RegisteredResource, ResourceConfig } from \"./types\";\n\n/**\n * Creates a reusable UI resource (HTML template) that can be attached\n * to tools or flow nodes.\n *\n * @example\n * ```ts\n * const pricingUI = createResource({\n * id: \"pricing_table\",\n * title: \"Pricing Table\",\n * baseUrl: \"https://my-app.com\",\n * htmlPath: \"/widgets/pricing\",\n * widgetDomain: \"my-app.com\",\n * });\n *\n * await pricingUI.register(server);\n * ```\n */\nexport function createResource(config: ResourceConfig): RegisteredResource {\n\tconst {\n\t\tid,\n\t\ttitle,\n\t\tdescription,\n\t\tbaseUrl,\n\t\thtmlPath,\n\t\twidgetDomain,\n\t\tprefersBorder = true,\n\t\tautoHeight = true,\n\t} = config;\n\n\t// Auto-generate CSP from baseUrl if not explicitly provided\n\tlet widgetCSP = config.widgetCSP ?? {\n\t\tconnect_domains: [baseUrl],\n\t\tresource_domains: [baseUrl],\n\t};\n\n\t// In development with localhost, add extra CSP domains for\n\t// Next.js dev features (WebSocket HMR, Turbopack font serving)\n\tif (process.env.NODE_ENV === \"development\") {\n\t\ttry {\n\t\t\tconst { hostname } = new URL(baseUrl);\n\t\t\tif (hostname === \"localhost\" || hostname === \"127.0.0.1\") {\n\t\t\t\twidgetCSP = {\n\t\t\t\t\t...widgetCSP,\n\t\t\t\t\tconnect_domains: [\n\t\t\t\t\t\t...(widgetCSP.connect_domains || []),\n\t\t\t\t\t\t`ws://${hostname}:*`,\n\t\t\t\t\t\t`wss://${hostname}:*`,\n\t\t\t\t\t],\n\t\t\t\t\tresource_domains: [\n\t\t\t\t\t\t...(widgetCSP.resource_domains || []),\n\t\t\t\t\t\t`http://${hostname}:*`,\n\t\t\t\t\t],\n\t\t\t\t};\n\t\t\t}\n\t\t} catch {\n\t\t\t// Invalid baseUrl — skip dev CSP additions\n\t\t}\n\t}\n\n\tconst openaiUri = `ui://widgets/apps-sdk/${id}.html`;\n\tconst mcpUri = `ui://widgets/ext-apps/${id}.html`;\n\n\t// Lazy HTML — fetched once, shared across all calls\n\tlet htmlPromise: Promise<string> | null = null;\n\tconst getHtml = () => {\n\t\tif (!htmlPromise) {\n\t\t\thtmlPromise = fetchHtml(baseUrl, htmlPath);\n\t\t}\n\t\treturn htmlPromise;\n\t};\n\n\t// Use description for UI metadata\n\tconst uiDescription = description;\n\n\tasync function register(server: McpServer): Promise<void> {\n\t\tconst html = await getHtml();\n\n\t\t// Register OpenAI Apps SDK resource\n\t\tserver.registerResource(\n\t\t\t`${id}-openai-widget`,\n\t\t\topenaiUri,\n\t\t\t{\n\t\t\t\ttitle,\n\t\t\t\tdescription: uiDescription,\n\t\t\t\tmimeType: MIME_TYPE_OPENAI,\n\t\t\t\t_meta: {\n\t\t\t\t\t\"openai/widgetDescription\": uiDescription,\n\t\t\t\t\t\"openai/widgetPrefersBorder\": prefersBorder,\n\t\t\t\t},\n\t\t\t},\n\t\t\tasync (uri) => ({\n\t\t\t\tcontents: [\n\t\t\t\t\t{\n\t\t\t\t\t\turi: uri.href,\n\t\t\t\t\t\tmimeType: MIME_TYPE_OPENAI,\n\t\t\t\t\t\ttext: html,\n\t\t\t\t\t\t_meta: buildOpenAIResourceMeta({\n\t\t\t\t\t\t\tdescription: uiDescription,\n\t\t\t\t\t\t\tprefersBorder,\n\t\t\t\t\t\t\twidgetDomain,\n\t\t\t\t\t\t\twidgetCSP,\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t}),\n\t\t);\n\n\t\t// Register MCP Apps resource\n\t\tserver.registerResource(\n\t\t\t`${id}-mcp-widget`,\n\t\t\tmcpUri,\n\t\t\t{\n\t\t\t\ttitle,\n\t\t\t\tdescription: uiDescription,\n\t\t\t\tmimeType: MIME_TYPE_MCP,\n\t\t\t\t_meta: {\n\t\t\t\t\tui: {\n\t\t\t\t\t\tprefersBorder,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tasync (uri) => ({\n\t\t\t\tcontents: [\n\t\t\t\t\t{\n\t\t\t\t\t\turi: uri.href,\n\t\t\t\t\t\tmimeType: MIME_TYPE_MCP,\n\t\t\t\t\t\ttext: html,\n\t\t\t\t\t\t_meta: buildMcpAppsResourceMeta({\n\t\t\t\t\t\t\tdescription: uiDescription,\n\t\t\t\t\t\t\tprefersBorder,\n\t\t\t\t\t\t\twidgetDomain,\n\t\t\t\t\t\t\twidgetCSP,\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t}),\n\t\t);\n\t}\n\n\treturn {\n\t\tid,\n\t\ttitle,\n\t\tdescription,\n\t\topenaiUri,\n\t\tmcpUri,\n\t\tautoHeight,\n\t\tregister,\n\t};\n}\n","import type { ToolCallback } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { ShapeOutput } from \"@modelcontextprotocol/sdk/server/zod-compat.js\";\nimport type { RequestHandlerExtra } from \"@modelcontextprotocol/sdk/shared/protocol.js\";\nimport type {\n\tServerNotification,\n\tServerRequest,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport type { z } from \"zod\";\nimport { buildToolMeta } from \"../resources/meta\";\nimport { extractScopedClient } from \"../scoped-client\";\nimport type {\n\tMcpServer,\n\tRegisteredTool,\n\tToolConfig,\n\tToolHandler,\n} from \"./types\";\n\n/**\n * Creates an MCP tool with minimal boilerplate.\n *\n * When `handler()` returns `data`, the tool includes it as MCP `structuredContent`.\n * When `config.resource` is provided, the tool also returns widget metadata.\n *\n * @example\n * ```ts\n * // Widget tool (with resource)\n * const pricingTool = createTool({\n * resource: pricingUI,\n * description: \"Show pricing comparison\",\n * inputSchema: { postalCode: z.string() },\n * }, async ({ postalCode }) => ({\n * text: \"Pricing loaded\",\n * data: { postalCode, prices: [] },\n * }));\n *\n * // Plain tool (no resource)\n * const searchTool = createTool({\n * id: \"search\",\n * title: \"Search\",\n * description: \"Search the knowledge base\",\n * inputSchema: { query: z.string() },\n * }, async ({ query }) => ({\n * text: `Results for \"${query}\"`,\n * }));\n * ```\n */\nexport function createTool<TInput extends z.ZodRawShape>(\n\tconfig: ToolConfig<TInput>,\n\thandler: ToolHandler<TInput>,\n): RegisteredTool {\n\tconst {\n\t\tresource,\n\t\tdescription,\n\t\tinputSchema,\n\t\tannotations,\n\t\tautoInjectResultText = true,\n\t} = config;\n\n\tconst id = config.id ?? resource?.id;\n\tconst title = config.title ?? resource?.title;\n\n\tif (!id) {\n\t\tthrow new Error(\n\t\t\t\"createTool: `id` is required when no resource is provided\",\n\t\t);\n\t}\n\tif (!title) {\n\t\tthrow new Error(\n\t\t\t\"createTool: `title` is required when no resource is provided\",\n\t\t);\n\t}\n\n\t// Build widget metadata only when resource is present\n\tconst toolMeta = resource\n\t\t? buildToolMeta({\n\t\t\t\topenaiTemplateUri: resource.openaiUri,\n\t\t\t\tmcpTemplateUri: resource.mcpUri,\n\t\t\t\tinvoking: config.invoking ?? \"Loading...\",\n\t\t\t\tinvoked: config.invoked ?? \"Loaded\",\n\t\t\t\tautoHeight: resource.autoHeight,\n\t\t\t})\n\t\t: undefined;\n\n\treturn {\n\t\tid,\n\t\ttitle,\n\t\tdescription,\n\n\t\tasync register(server: McpServer): Promise<void> {\n\t\t\tserver.registerTool(\n\t\t\t\tid,\n\t\t\t\t{\n\t\t\t\t\ttitle,\n\t\t\t\t\tdescription,\n\t\t\t\t\tinputSchema,\n\t\t\t\t\tannotations,\n\t\t\t\t\t...(toolMeta && { _meta: toolMeta }),\n\t\t\t\t},\n\t\t\t\t(async (args: ShapeOutput<TInput>, extra: unknown) => {\n\t\t\t\t\tconst requestExtra = extra as RequestHandlerExtra<\n\t\t\t\t\t\tServerRequest,\n\t\t\t\t\t\tServerNotification\n\t\t\t\t\t>;\n\t\t\t\t\tconst _meta: Record<string, unknown> = requestExtra._meta ?? {};\n\t\t\t\t\tconst waniwani = extractScopedClient(requestExtra);\n\n\t\t\t\t\tconst result = await handler(args, { extra: { _meta }, waniwani });\n\n\t\t\t\t\t// Widget tool: return structuredContent + widget metadata\n\t\t\t\t\tif (resource && result.data) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: result.text }],\n\t\t\t\t\t\t\tstructuredContent: result.data,\n\t\t\t\t\t\t\t_meta: {\n\t\t\t\t\t\t\t\t...toolMeta,\n\t\t\t\t\t\t\t\t..._meta,\n\t\t\t\t\t\t\t\t...(autoInjectResultText === false\n\t\t\t\t\t\t\t\t\t? { \"waniwani/autoInjectResultText\": false }\n\t\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\t// Plain tool: return text content, plus structuredContent when provided.\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: result.text }],\n\t\t\t\t\t\t...(result.data ? { structuredContent: result.data } : {}),\n\t\t\t\t\t\t...(autoInjectResultText === false\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t_meta: {\n\t\t\t\t\t\t\t\t\t\t\"waniwani/autoInjectResultText\": false,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t};\n\t\t\t\t}) as unknown as ToolCallback<TInput>,\n\t\t\t);\n\t\t},\n\t};\n}\n\n/**\n * Registers multiple tools on the server\n */\nexport async function registerTools(\n\tserver: McpServer,\n\ttools: RegisteredTool[],\n): Promise<void> {\n\tawait Promise.all(tools.map((t) => t.register(server)));\n}\n","// WaniWani SDK - Errors\n\nexport class WaniWaniError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic status: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"WaniWaniError\";\n\t}\n}\n","// KB Client — thin HTTP wrapper for knowledge base API\n\nimport { WaniWaniError } from \"../error.js\";\nimport type { InternalConfig } from \"../types.js\";\nimport type {\n\tKbClient,\n\tKbIngestFile,\n\tKbIngestResult,\n\tKbSearchOptions,\n\tKbSource,\n\tSearchResult,\n} from \"./types.js\";\n\nconst SDK_NAME = \"@waniwani/sdk\";\n\nexport function createKbClient(config: InternalConfig): KbClient {\n\tconst { baseUrl, apiKey } = config;\n\n\tfunction requireApiKey(): string {\n\t\tif (!apiKey) {\n\t\t\tthrow new Error(\"WANIWANI_API_KEY is not set\");\n\t\t}\n\t\treturn apiKey;\n\t}\n\n\tasync function request<T>(\n\t\tmethod: \"GET\" | \"POST\",\n\t\tpath: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tconst key = requireApiKey();\n\t\tconst url = `${baseUrl.replace(/\\/$/, \"\")}${path}`;\n\n\t\tconst headers: Record<string, string> = {\n\t\t\tAuthorization: `Bearer ${key}`,\n\t\t\t\"X-WaniWani-SDK\": SDK_NAME,\n\t\t};\n\n\t\tconst init: RequestInit = { method, headers };\n\n\t\tif (body !== undefined) {\n\t\t\theaders[\"Content-Type\"] = \"application/json\";\n\t\t\tinit.body = JSON.stringify(body);\n\t\t}\n\n\t\tconst response = await fetch(url, init);\n\n\t\tif (!response.ok) {\n\t\t\tconst text = await response.text().catch(() => \"\");\n\t\t\tthrow new WaniWaniError(\n\t\t\t\ttext || `KB API error: HTTP ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t);\n\t\t}\n\n\t\tconst json = (await response.json()) as { data: T };\n\t\treturn json.data;\n\t}\n\n\treturn {\n\t\tasync ingest(files: KbIngestFile[]): Promise<KbIngestResult> {\n\t\t\treturn request<KbIngestResult>(\"POST\", \"/api/mcp/kb/ingest\", {\n\t\t\t\tfiles,\n\t\t\t});\n\t\t},\n\n\t\tasync search(\n\t\t\tquery: string,\n\t\t\toptions?: KbSearchOptions,\n\t\t): Promise<SearchResult[]> {\n\t\t\treturn request<SearchResult[]>(\"POST\", \"/api/mcp/kb/search\", {\n\t\t\t\tquery,\n\t\t\t\t...options,\n\t\t\t});\n\t\t},\n\n\t\tasync sources(): Promise<KbSource[]> {\n\t\t\treturn request<KbSource[]>(\"GET\", \"/api/mcp/kb/sources\");\n\t\t},\n\t};\n}\n","import {\n\textractCorrelationId,\n\textractExternalUserId,\n\textractRequestId,\n\textractSessionId,\n\textractSource,\n\textractTraceId,\n} from \"../mcp/server/utils.js\";\nimport type { EventType, LegacyTrackEvent, TrackInput } from \"./@types.js\";\nimport type { V2CorrelationIds, V2EventEnvelope } from \"./v2-types.js\";\n\nconst DEFAULT_SOURCE = \"@waniwani/sdk\";\n\nexport interface MapTrackEventOptions {\n\tnow?: () => Date;\n\tgenerateId?: () => string;\n\tsource?: string;\n}\n\nexport function mapTrackEventToV2(\n\tinput: TrackInput,\n\toptions: MapTrackEventOptions = {},\n): V2EventEnvelope {\n\tconst now = options.now ?? (() => new Date());\n\tconst generateId = options.generateId ?? createEventId;\n\tconst eventName = resolveEventName(input);\n\tconst meta = toRecord(input.meta);\n\tconst metadata = toRecord(input.metadata);\n\tconst correlation = resolveCorrelationIds(input, meta);\n\tconst eventId = takeNonEmptyString(input.eventId) ?? generateId();\n\tconst timestamp = normalizeTimestamp(input.timestamp, now);\n\tconst source =\n\t\ttakeNonEmptyString(input.source) ??\n\t\textractSource(meta) ??\n\t\toptions.source ??\n\t\tDEFAULT_SOURCE;\n\tconst rawLegacy = isLegacyTrackEvent(input) ? { ...input } : undefined;\n\n\tconst mappedMetadata: Record<string, unknown> = {\n\t\t...metadata,\n\t};\n\tif (Object.keys(meta).length > 0) {\n\t\tmappedMetadata.meta = meta;\n\t}\n\tif (rawLegacy) {\n\t\tmappedMetadata.rawLegacy = rawLegacy;\n\t}\n\n\treturn {\n\t\tid: eventId,\n\t\ttype: \"mcp.event\",\n\t\tname: eventName,\n\t\tsource,\n\t\ttimestamp,\n\t\tcorrelation,\n\t\tproperties: mapProperties(input, eventName),\n\t\tmetadata: mappedMetadata,\n\t\trawLegacy,\n\t};\n}\n\nexport function createEventId(): string {\n\tif (\n\t\ttypeof crypto !== \"undefined\" &&\n\t\ttypeof crypto.randomUUID === \"function\"\n\t) {\n\t\treturn `evt_${crypto.randomUUID()}`;\n\t}\n\n\treturn `evt_${Math.random().toString(36).slice(2, 10)}_${Date.now().toString(36)}`;\n}\n\nfunction mapProperties(\n\tinput: TrackInput,\n\teventName: EventType,\n): Record<string, unknown> {\n\tif (!isLegacyTrackEvent(input)) {\n\t\treturn toRecord(input.properties);\n\t}\n\n\tconst legacyProperties = mapLegacyProperties(input, eventName);\n\tconst explicitProperties = toRecord(input.properties);\n\treturn {\n\t\t...legacyProperties,\n\t\t...explicitProperties,\n\t};\n}\n\nfunction mapLegacyProperties(\n\tinput: LegacyTrackEvent,\n\teventName: EventType,\n): Record<string, unknown> {\n\tswitch (eventName) {\n\t\tcase \"tool.called\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (takeNonEmptyString(input.toolName)) {\n\t\t\t\tproperties.name = input.toolName;\n\t\t\t}\n\t\t\tif (takeNonEmptyString(input.toolType)) {\n\t\t\t\tproperties.type = input.toolType;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tcase \"quote.succeeded\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (typeof input.quoteAmount === \"number\") {\n\t\t\t\tproperties.amount = input.quoteAmount;\n\t\t\t}\n\t\t\tif (takeNonEmptyString(input.quoteCurrency)) {\n\t\t\t\tproperties.currency = input.quoteCurrency;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tcase \"link.clicked\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (takeNonEmptyString(input.linkUrl)) {\n\t\t\t\tproperties.url = input.linkUrl;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tcase \"purchase.completed\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (typeof input.purchaseAmount === \"number\") {\n\t\t\t\tproperties.amount = input.purchaseAmount;\n\t\t\t}\n\t\t\tif (takeNonEmptyString(input.purchaseCurrency)) {\n\t\t\t\tproperties.currency = input.purchaseCurrency;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tdefault:\n\t\t\treturn {};\n\t}\n}\n\nfunction resolveEventName(input: TrackInput): EventType {\n\tif (isLegacyTrackEvent(input)) {\n\t\treturn input.eventType;\n\t}\n\treturn input.event;\n}\n\nfunction resolveCorrelationIds(\n\tinput: TrackInput,\n\tmeta: Record<string, unknown>,\n): V2CorrelationIds {\n\tconst requestId =\n\t\ttakeNonEmptyString(input.requestId) ?? extractRequestId(meta);\n\n\tconst sessionId =\n\t\ttakeNonEmptyString(input.sessionId) ?? extractSessionId(meta);\n\n\tconst traceId = takeNonEmptyString(input.traceId) ?? extractTraceId(meta);\n\n\tconst externalUserId =\n\t\ttakeNonEmptyString(input.externalUserId) ?? extractExternalUserId(meta);\n\n\tconst correlationId =\n\t\ttakeNonEmptyString(input.correlationId) ??\n\t\textractCorrelationId(meta) ??\n\t\trequestId;\n\n\tconst correlation: V2CorrelationIds = {};\n\tif (sessionId) {\n\t\tcorrelation.sessionId = sessionId;\n\t}\n\tif (traceId) {\n\t\tcorrelation.traceId = traceId;\n\t}\n\tif (requestId) {\n\t\tcorrelation.requestId = requestId;\n\t}\n\tif (correlationId) {\n\t\tcorrelation.correlationId = correlationId;\n\t}\n\tif (externalUserId) {\n\t\tcorrelation.externalUserId = externalUserId;\n\t}\n\treturn correlation;\n}\n\nfunction normalizeTimestamp(\n\tinput: string | Date | undefined,\n\tnow: () => Date,\n): string {\n\tif (input instanceof Date) {\n\t\treturn input.toISOString();\n\t}\n\tif (typeof input === \"string\") {\n\t\tconst date = new Date(input);\n\t\tif (!Number.isNaN(date.getTime())) {\n\t\t\treturn date.toISOString();\n\t\t}\n\t}\n\treturn now().toISOString();\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\treturn {};\n\t}\n\treturn value as Record<string, unknown>;\n}\n\nfunction takeNonEmptyString(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") {\n\t\treturn undefined;\n\t}\n\tif (value.trim().length === 0) {\n\t\treturn undefined;\n\t}\n\treturn value;\n}\n\nfunction isLegacyTrackEvent(input: TrackInput): input is LegacyTrackEvent {\n\treturn \"eventType\" in input;\n}\n","import type {\n\tTrackingShutdownOptions,\n\tTrackingShutdownResult,\n} from \"./@types.js\";\nimport type {\n\tV2BatchRejectedEvent,\n\tV2BatchRequest,\n\tV2BatchResponse,\n\tV2EventEnvelope,\n} from \"./v2-types.js\";\n\nconst DEFAULT_ENDPOINT_PATH = \"/api/mcp/events/v2/batch\";\nconst DEFAULT_FLUSH_INTERVAL_MS = 1_000;\nconst DEFAULT_MAX_BATCH_SIZE = 20;\nconst DEFAULT_MAX_BUFFER_SIZE = 1_000;\nconst DEFAULT_MAX_RETRIES = 3;\nconst DEFAULT_RETRY_BASE_DELAY_MS = 200;\nconst DEFAULT_RETRY_MAX_DELAY_MS = 2_000;\nconst DEFAULT_SHUTDOWN_TIMEOUT_MS = 2_000;\nconst SDK_NAME = \"@waniwani/sdk\";\n\nconst AUTH_FAILURE_STATUS = new Set([401, 403]);\nconst RETRYABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504]);\n\ninterface Logger {\n\twarn: (message: string, ...args: unknown[]) => void;\n\terror: (message: string, ...args: unknown[]) => void;\n}\n\nexport interface V2TransportOptions {\n\tbaseUrl: string;\n\tapiKey: string;\n\tendpointPath?: string;\n\tflushIntervalMs?: number;\n\tmaxBatchSize?: number;\n\tmaxBufferSize?: number;\n\tmaxRetries?: number;\n\tretryBaseDelayMs?: number;\n\tretryMaxDelayMs?: number;\n\tshutdownTimeoutMs?: number;\n\tsdkVersion?: string;\n\tfetchFn?: typeof fetch;\n\tlogger?: Logger;\n\tnow?: () => Date;\n\tsleep?: (delayMs: number) => Promise<void>;\n}\n\nexport interface V2BatchTransport {\n\tenqueue: (event: V2EventEnvelope) => void;\n\tflush: () => Promise<void>;\n\tshutdown: (\n\t\toptions?: TrackingShutdownOptions,\n\t) => Promise<TrackingShutdownResult>;\n\tpendingEvents: () => number;\n}\n\ntype SendBatchResult =\n\t| { kind: \"success\" }\n\t| { kind: \"retryable\"; reason: string }\n\t| { kind: \"permanent\"; reason: string }\n\t| { kind: \"auth\"; status: number }\n\t| {\n\t\t\tkind: \"partial\";\n\t\t\tretryable: V2EventEnvelope[];\n\t\t\tpermanent: V2EventEnvelope[];\n\t };\n\nexport function createV2BatchTransport(\n\toptions: V2TransportOptions,\n): V2BatchTransport {\n\treturn new BatchingV2Transport(options);\n}\n\nclass BatchingV2Transport implements V2BatchTransport {\n\tprivate readonly endpointUrl: string;\n\tprivate readonly flushIntervalMs: number;\n\tprivate readonly maxBatchSize: number;\n\tprivate readonly maxBufferSize: number;\n\tprivate readonly maxRetries: number;\n\tprivate readonly retryBaseDelayMs: number;\n\tprivate readonly retryMaxDelayMs: number;\n\tprivate readonly shutdownTimeoutMs: number;\n\tprivate readonly sdkVersion?: string;\n\tprivate readonly fetchFn: typeof fetch;\n\tprivate readonly logger: Logger;\n\tprivate readonly now: () => Date;\n\tprivate readonly sleep: (delayMs: number) => Promise<void>;\n\tprivate readonly apiKey: string;\n\n\tprivate readonly buffer: V2EventEnvelope[] = [];\n\tprivate flushTimer: ReturnType<typeof setInterval> | undefined;\n\tprivate flushScheduled = false;\n\tprivate flushScheduledTimer: ReturnType<typeof setTimeout> | undefined;\n\tprivate flushInFlight: Promise<void> | undefined;\n\tprivate inFlightCount = 0;\n\tprivate isStopped = false;\n\tprivate isShuttingDown = false;\n\n\tconstructor(options: V2TransportOptions) {\n\t\tthis.endpointUrl = joinUrl(\n\t\t\toptions.baseUrl,\n\t\t\toptions.endpointPath ?? DEFAULT_ENDPOINT_PATH,\n\t\t);\n\t\tthis.flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;\n\t\tthis.maxBatchSize = options.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;\n\t\tthis.maxBufferSize = options.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE;\n\t\tthis.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;\n\t\tthis.retryBaseDelayMs =\n\t\t\toptions.retryBaseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS;\n\t\tthis.retryMaxDelayMs =\n\t\t\toptions.retryMaxDelayMs ?? DEFAULT_RETRY_MAX_DELAY_MS;\n\t\tthis.shutdownTimeoutMs =\n\t\t\toptions.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;\n\t\tthis.fetchFn = options.fetchFn ?? fetch;\n\t\tthis.logger = options.logger ?? console;\n\t\tthis.now = options.now ?? (() => new Date());\n\t\tthis.sleep =\n\t\t\toptions.sleep ??\n\t\t\t((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)));\n\t\tthis.apiKey = options.apiKey;\n\t\tthis.sdkVersion = options.sdkVersion;\n\n\t\tif (this.flushIntervalMs > 0) {\n\t\t\tthis.flushTimer = setInterval(() => {\n\t\t\t\tvoid this.flush();\n\t\t\t}, this.flushIntervalMs);\n\t\t}\n\t}\n\n\tenqueue(event: V2EventEnvelope): void {\n\t\tif (this.isStopped || this.isShuttingDown) {\n\t\t\tthis.logger.warn(\n\t\t\t\t\"[WaniWani] Tracking transport is stopped, dropping event %s\",\n\t\t\t\tevent.id,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.buffer.length >= this.maxBufferSize) {\n\t\t\tconst dropCount = this.buffer.length - this.maxBufferSize + 1;\n\t\t\tthis.buffer.splice(0, dropCount);\n\t\t\tthis.logger.warn(\n\t\t\t\t\"[WaniWani] Tracking buffer overflow, dropped %d oldest event(s)\",\n\t\t\t\tdropCount,\n\t\t\t);\n\t\t}\n\n\t\tthis.buffer.push(event);\n\n\t\tif (this.buffer.length >= this.maxBatchSize) {\n\t\t\tvoid this.flush();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.scheduleMicroFlush();\n\t}\n\n\tpendingEvents(): number {\n\t\treturn this.buffer.length + this.inFlightCount;\n\t}\n\n\tasync flush(): Promise<void> {\n\t\tif (this.flushInFlight) {\n\t\t\treturn this.flushInFlight;\n\t\t}\n\t\tthis.flushInFlight = this.flushLoop().finally(() => {\n\t\t\tthis.flushInFlight = undefined;\n\t\t});\n\t\treturn this.flushInFlight;\n\t}\n\n\tasync shutdown(\n\t\toptions?: TrackingShutdownOptions,\n\t): Promise<TrackingShutdownResult> {\n\t\tthis.isShuttingDown = true;\n\t\tif (this.flushTimer) {\n\t\t\tclearInterval(this.flushTimer);\n\t\t\tthis.flushTimer = undefined;\n\t\t}\n\t\tif (this.flushScheduledTimer) {\n\t\t\tclearTimeout(this.flushScheduledTimer);\n\t\t\tthis.flushScheduledTimer = undefined;\n\t\t\tthis.flushScheduled = false;\n\t\t}\n\n\t\tconst timeoutMs = options?.timeoutMs ?? this.shutdownTimeoutMs;\n\t\tconst flushPromise = this.flush();\n\n\t\tif (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n\t\t\tawait flushPromise;\n\t\t\tthis.isStopped = true;\n\t\t\treturn { timedOut: false, pendingEvents: this.pendingEvents() };\n\t\t}\n\n\t\tconst timeoutSignal = Symbol(\"shutdown-timeout\");\n\t\tconst result = await Promise.race([\n\t\t\tflushPromise.then(() => \"flushed\" as const),\n\t\t\tthis.sleep(timeoutMs).then(() => timeoutSignal),\n\t\t]);\n\n\t\tif (result === timeoutSignal) {\n\t\t\tthis.isStopped = true;\n\t\t\treturn { timedOut: true, pendingEvents: this.pendingEvents() };\n\t\t}\n\n\t\tthis.isStopped = true;\n\t\treturn { timedOut: false, pendingEvents: this.pendingEvents() };\n\t}\n\n\tprivate scheduleMicroFlush(): void {\n\t\tif (this.flushScheduled) {\n\t\t\treturn;\n\t\t}\n\t\tthis.flushScheduled = true;\n\t\tthis.flushScheduledTimer = setTimeout(() => {\n\t\t\tthis.flushScheduledTimer = undefined;\n\t\t\tthis.flushScheduled = false;\n\t\t\tvoid this.flush();\n\t\t}, 0);\n\t}\n\n\tprivate async flushLoop(): Promise<void> {\n\t\twhile (this.buffer.length > 0 && !this.isStopped) {\n\t\t\tconst batch = this.buffer.splice(0, this.maxBatchSize);\n\t\t\tawait this.sendBatchWithRetry(batch);\n\t\t}\n\t}\n\n\tprivate async sendBatchWithRetry(batch: V2EventEnvelope[]): Promise<void> {\n\t\tlet attempt = 0;\n\t\tlet pendingBatch = batch;\n\n\t\twhile (pendingBatch.length > 0 && !this.isStopped) {\n\t\t\tthis.inFlightCount = pendingBatch.length;\n\t\t\tconst result = await this.sendBatchOnce(pendingBatch);\n\t\t\tthis.inFlightCount = 0;\n\n\t\t\tswitch (result.kind) {\n\t\t\t\tcase \"success\":\n\t\t\t\t\treturn;\n\t\t\t\tcase \"auth\":\n\t\t\t\t\tthis.stopTransportForAuthFailure(result.status, pendingBatch.length);\n\t\t\t\t\treturn;\n\t\t\t\tcase \"permanent\":\n\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\"[WaniWani] Dropping %d event(s) after permanent failure: %s\",\n\t\t\t\t\t\tpendingBatch.length,\n\t\t\t\t\t\tresult.reason,\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\tcase \"retryable\":\n\t\t\t\t\tif (attempt >= this.maxRetries) {\n\t\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\t\"[WaniWani] Dropping %d event(s) after retry exhaustion: %s\",\n\t\t\t\t\t\t\tpendingBatch.length,\n\t\t\t\t\t\t\tresult.reason,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tawait this.sleep(this.backoffDelayMs(attempt));\n\t\t\t\t\tattempt += 1;\n\t\t\t\t\tcontinue;\n\t\t\t\tcase \"partial\":\n\t\t\t\t\tif (result.permanent.length > 0) {\n\t\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\t\"[WaniWani] Dropping %d event(s) rejected as permanent\",\n\t\t\t\t\t\t\tresult.permanent.length,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (result.retryable.length === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (attempt >= this.maxRetries) {\n\t\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\t\"[WaniWani] Dropping %d retryable event(s) after retry exhaustion\",\n\t\t\t\t\t\t\tresult.retryable.length,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tpendingBatch = result.retryable;\n\t\t\t\t\tawait this.sleep(this.backoffDelayMs(attempt));\n\t\t\t\t\tattempt += 1;\n\t\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async sendBatchOnce(\n\t\tevents: V2EventEnvelope[],\n\t): Promise<SendBatchResult> {\n\t\tlet response: Response;\n\n\t\ttry {\n\t\t\tresponse = await this.fetchFn(this.endpointUrl, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\tAuthorization: `Bearer ${this.apiKey}`,\n\t\t\t\t\t\"X-WaniWani-SDK\": SDK_NAME,\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify(this.makeBatchRequest(events)),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tkind: \"retryable\",\n\t\t\t\treason: getErrorMessage(error),\n\t\t\t};\n\t\t}\n\n\t\tif (AUTH_FAILURE_STATUS.has(response.status)) {\n\t\t\treturn { kind: \"auth\", status: response.status };\n\t\t}\n\n\t\tif (RETRYABLE_STATUS.has(response.status)) {\n\t\t\treturn {\n\t\t\t\tkind: \"retryable\",\n\t\t\t\treason: `HTTP ${response.status}`,\n\t\t\t};\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\treturn {\n\t\t\t\tkind: \"permanent\",\n\t\t\t\treason: `HTTP ${response.status}`,\n\t\t\t};\n\t\t}\n\n\t\tconst data = await parseJsonResponse<V2BatchResponse>(response);\n\t\tif (!data?.rejected || data.rejected.length === 0) {\n\t\t\treturn { kind: \"success\" };\n\t\t}\n\n\t\tconst partial = this.classifyRejectedEvents(events, data.rejected);\n\t\tif (partial.retryable.length === 0 && partial.permanent.length === 0) {\n\t\t\treturn { kind: \"success\" };\n\t\t}\n\n\t\treturn {\n\t\t\tkind: \"partial\",\n\t\t\tretryable: partial.retryable,\n\t\t\tpermanent: partial.permanent,\n\t\t};\n\t}\n\n\tprivate makeBatchRequest(events: V2EventEnvelope[]): V2BatchRequest {\n\t\treturn {\n\t\t\tsentAt: this.now().toISOString(),\n\t\t\tsource: {\n\t\t\t\tsdk: SDK_NAME,\n\t\t\t\tversion: this.sdkVersion ?? \"0.0.0\",\n\t\t\t},\n\t\t\tevents,\n\t\t};\n\t}\n\n\tprivate classifyRejectedEvents(\n\t\tevents: V2EventEnvelope[],\n\t\trejected: V2BatchRejectedEvent[],\n\t): {\n\t\tretryable: V2EventEnvelope[];\n\t\tpermanent: V2EventEnvelope[];\n\t} {\n\t\tconst byId = new Map(events.map((event) => [event.id, event]));\n\t\tconst retryable: V2EventEnvelope[] = [];\n\t\tconst permanent: V2EventEnvelope[] = [];\n\n\t\tfor (const rejectedEvent of rejected) {\n\t\t\tconst event = byId.get(rejectedEvent.eventId);\n\t\t\tif (!event) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (isRetryableRejectedEvent(rejectedEvent)) {\n\t\t\t\tretryable.push(event);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpermanent.push(event);\n\t\t}\n\n\t\treturn { retryable, permanent };\n\t}\n\n\tprivate backoffDelayMs(attempt: number): number {\n\t\tconst rawDelay = this.retryBaseDelayMs * 2 ** attempt;\n\t\treturn Math.min(rawDelay, this.retryMaxDelayMs);\n\t}\n\n\tprivate stopTransportForAuthFailure(\n\t\tstatus: number,\n\t\trejectedCount: number,\n\t): void {\n\t\tthis.isStopped = true;\n\t\tconst buffered = this.buffer.length;\n\t\tthis.buffer.splice(0, buffered);\n\t\tthis.logger.error(\n\t\t\t\"[WaniWani] Auth failure (HTTP %d). Stopping tracking transport and dropping %d queued event(s)\",\n\t\t\tstatus,\n\t\t\trejectedCount + buffered,\n\t\t);\n\t}\n}\n\nfunction isRetryableRejectedEvent(\n\trejectedEvent: V2BatchRejectedEvent,\n): boolean {\n\tif (rejectedEvent.retryable === true) {\n\t\treturn true;\n\t}\n\tconst code = rejectedEvent.code.toLowerCase();\n\treturn (\n\t\tcode.includes(\"timeout\") ||\n\t\tcode.includes(\"temporary\") ||\n\t\tcode.includes(\"unavailable\") ||\n\t\tcode.includes(\"rate_limit\") ||\n\t\tcode.includes(\"transient\") ||\n\t\tcode.includes(\"server\")\n\t);\n}\n\nasync function parseJsonResponse<T>(\n\tresponse: Response,\n): Promise<T | undefined> {\n\tconst body = await response.text();\n\tif (!body) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\treturn JSON.parse(body) as T;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction joinUrl(baseUrl: string, endpointPath: string): string {\n\tconst normalizedBase = baseUrl.endsWith(\"/\") ? baseUrl : `${baseUrl}/`;\n\tconst normalizedPath = endpointPath.startsWith(\"/\")\n\t\t? endpointPath.slice(1)\n\t\t: endpointPath;\n\treturn `${normalizedBase}${normalizedPath}`;\n}\n\nfunction getErrorMessage(error: unknown): string {\n\tif (error instanceof Error) {\n\t\treturn error.message;\n\t}\n\treturn String(error);\n}\n","// Tracking Module\n\nimport type { InternalConfig } from \"../types.js\";\nimport type {\n\tTrackInput,\n\tTrackingClient,\n\tTrackingShutdownOptions,\n} from \"./@types.js\";\nimport { mapTrackEventToV2 } from \"./mapper.js\";\nimport { createV2BatchTransport } from \"./transport.js\";\n\n// Re-export types\nexport type {\n\tEventType,\n\tLegacyTrackEvent,\n\tLinkClickedProperties,\n\tPurchaseCompletedProperties,\n\tQuoteSucceededProperties,\n\tToolCalledProperties,\n\tTrackEvent,\n\tTrackInput,\n\tTrackingClient,\n\tTrackingConfig,\n\tTrackingShutdownOptions,\n\tTrackingShutdownResult,\n} from \"./@types.js\";\nexport { createEventId, mapTrackEventToV2 } from \"./mapper.js\";\nexport type {\n\tV2BatchRejectedEvent,\n\tV2BatchRequest,\n\tV2BatchResponse,\n\tV2CorrelationIds,\n\tV2EnvelopeType,\n\tV2EventEnvelope,\n} from \"./v2-types.js\";\n\nexport function createTrackingClient(config: InternalConfig): TrackingClient {\n\tconst { baseUrl, apiKey, tracking } = config;\n\n\tfunction requireApiKey(): string {\n\t\tif (!apiKey) {\n\t\t\tthrow new Error(\"WANIWANI_API_KEY is not set\");\n\t\t}\n\t\treturn apiKey;\n\t}\n\n\tconst transport = apiKey\n\t\t? createV2BatchTransport({\n\t\t\t\tbaseUrl,\n\t\t\t\tapiKey,\n\t\t\t\tendpointPath: tracking.endpointPath,\n\t\t\t\tflushIntervalMs: tracking.flushIntervalMs,\n\t\t\t\tmaxBatchSize: tracking.maxBatchSize,\n\t\t\t\tmaxBufferSize: tracking.maxBufferSize,\n\t\t\t\tmaxRetries: tracking.maxRetries,\n\t\t\t\tretryBaseDelayMs: tracking.retryBaseDelayMs,\n\t\t\t\tretryMaxDelayMs: tracking.retryMaxDelayMs,\n\t\t\t\tshutdownTimeoutMs: tracking.shutdownTimeoutMs,\n\t\t\t})\n\t\t: undefined;\n\n\tconst client: TrackingClient = {\n\t\tasync identify(\n\t\t\tuserId: string,\n\t\t\tproperties?: Record<string, unknown>,\n\t\t\tmeta?: Record<string, unknown>,\n\t\t): Promise<{ eventId: string }> {\n\t\t\trequireApiKey();\n\t\t\tconst mappedEvent = mapTrackEventToV2({\n\t\t\t\tevent: \"user.identified\",\n\t\t\t\texternalUserId: userId,\n\t\t\t\tproperties,\n\t\t\t\tmeta,\n\t\t\t});\n\t\t\ttransport?.enqueue(mappedEvent);\n\t\t\treturn { eventId: mappedEvent.id };\n\t\t},\n\t\tasync track(event: TrackInput): Promise<{ eventId: string }> {\n\t\t\trequireApiKey();\n\t\t\tconst mappedEvent = mapTrackEventToV2(event);\n\t\t\ttransport?.enqueue(mappedEvent);\n\t\t\treturn { eventId: mappedEvent.id };\n\t\t},\n\t\tasync flush(): Promise<void> {\n\t\t\trequireApiKey();\n\t\t\tawait transport?.flush();\n\t\t},\n\t\tasync shutdown(options?: TrackingShutdownOptions) {\n\t\t\trequireApiKey();\n\t\t\treturn (\n\t\t\t\t(await transport?.shutdown({\n\t\t\t\t\ttimeoutMs: options?.timeoutMs ?? tracking.shutdownTimeoutMs,\n\t\t\t\t})) ?? { timedOut: false, pendingEvents: 0 }\n\t\t\t);\n\t\t},\n\t};\n\n\tif (transport) {\n\t\tattachShutdownHooks(client, tracking.shutdownTimeoutMs);\n\t}\n\treturn client;\n}\n\nfunction attachShutdownHooks(\n\tclient: TrackingClient,\n\tdefaultTimeoutMs: number,\n): void {\n\tif (\n\t\ttypeof process === \"undefined\" ||\n\t\ttypeof process.once !== \"function\" ||\n\t\ttypeof process.on !== \"function\"\n\t) {\n\t\treturn;\n\t}\n\n\tconst shutdown = () => {\n\t\tvoid client.shutdown({ timeoutMs: defaultTimeoutMs });\n\t};\n\n\tprocess.once(\"beforeExit\", shutdown);\n\tprocess.once(\"SIGINT\", shutdown);\n\tprocess.once(\"SIGTERM\", shutdown);\n}\n","// WaniWani SDK - Main Entry\n\nimport { createKbClient } from \"./kb/client.js\";\nimport { createTrackingClient } from \"./tracking/index.js\";\nimport type { WaniWaniClient, WaniWaniConfig } from \"./types.js\";\n\n/**\n * Create a WaniWani SDK client\n *\n * @param config - Configuration options\n * @returns A fully typed WaniWani client\n *\n * @example\n * ```typescript\n * import { waniwani } from \"@waniwani/sdk\";\n * import { toNextJsHandler } from \"@waniwani/sdk/next-js\";\n *\n * const wani = waniwani({ apiKey: \"...\" });\n *\n * // Next.js route handler\n * export const { GET, POST } = toNextJsHandler(wani, {\n * chat: { systemPrompt: \"You are a helpful assistant.\" },\n * });\n * ```\n */\nexport function waniwani(config?: WaniWaniConfig): WaniWaniClient {\n\tconst baseUrl = config?.baseUrl ?? \"https://app.waniwani.ai\";\n\tconst apiKey = config?.apiKey ?? process.env.WANIWANI_API_KEY;\n\tconst trackingConfig = {\n\t\tendpointPath: config?.tracking?.endpointPath ?? \"/api/mcp/events/v2/batch\",\n\t\tflushIntervalMs: config?.tracking?.flushIntervalMs ?? 1_000,\n\t\tmaxBatchSize: config?.tracking?.maxBatchSize ?? 20,\n\t\tmaxBufferSize: config?.tracking?.maxBufferSize ?? 1_000,\n\t\tmaxRetries: config?.tracking?.maxRetries ?? 3,\n\t\tretryBaseDelayMs: config?.tracking?.retryBaseDelayMs ?? 200,\n\t\tretryMaxDelayMs: config?.tracking?.retryMaxDelayMs ?? 2_000,\n\t\tshutdownTimeoutMs: config?.tracking?.shutdownTimeoutMs ?? 2_000,\n\t};\n\n\tconst internalConfig = { baseUrl, apiKey, tracking: trackingConfig };\n\n\t// Compose client from modules\n\tconst trackingClient = createTrackingClient(internalConfig);\n\tconst kbClient = createKbClient(internalConfig);\n\n\treturn {\n\t\t...trackingClient,\n\t\tkb: kbClient,\n\t\t_config: internalConfig,\n\t};\n}\n","/**\n * Server-side API route handler for widget tracking events.\n *\n * Receives batched events from the `useWaniwani` React hook and forwards them\n * to the WaniWani backend using the server-side SDK.\n *\n * @example Next.js App Router\n * ```typescript\n * // app/api/waniwani/track/route.ts\n * import { createTrackingRoute } from \"@waniwani/sdk/mcp\";\n *\n * const handler = createTrackingRoute({\n * apiKey: process.env.WANIWANI_API_KEY,\n * baseUrl: process.env.WANIWANI_BASE_URL,\n * });\n *\n * export { handler as POST };\n * ```\n */\n\nimport type { EventType, TrackInput } from \"../../tracking/@types.js\";\nimport type { WaniWaniConfig } from \"../../types.js\";\nimport { waniwani } from \"../../waniwani.js\";\n\nexport interface TrackingRouteOptions {\n\t/** API key for the WaniWani backend. Defaults to WANIWANI_API_KEY env var. */\n\tapiKey?: string;\n\t/** Base URL for the WaniWani backend. Defaults to https://app.waniwani.ai. */\n\tbaseUrl?: string;\n}\n\n/** Shape of a single event from the WidgetTransport client. */\ninterface WidgetEventPayload {\n\tevent_id?: string;\n\tevent_type?: string;\n\ttimestamp?: string;\n\tsource?: string;\n\tsession_id?: string;\n\ttrace_id?: string;\n\tuser_id?: string;\n\tevent_name?: string;\n\tmetadata?: Record<string, unknown>;\n\t[key: string]: unknown;\n}\n\n/** Batch payload sent by WidgetTransport. */\ninterface BatchPayload {\n\tevents: WidgetEventPayload[];\n\tsentAt?: string;\n}\n\n/**\n * Map a WidgetEvent from the client to the SDK's TrackInput format.\n */\nfunction mapWidgetEvent(ev: WidgetEventPayload): TrackInput {\n\tconst eventType = ev.event_type ?? \"widget_click\";\n\n\t// For manual tracking methods (track, identify, step, conversion),\n\t// use \"widget_<type>\" as the event name. Auto-capture events already\n\t// have the \"widget_\" prefix.\n\tconst isAutoCapture = eventType.startsWith(\"widget_\");\n\tconst eventName: EventType = (\n\t\tisAutoCapture ? eventType : `widget_${eventType}`\n\t) as EventType;\n\n\t// Merge metadata + any extra properties from the event\n\tconst properties: Record<string, unknown> = {\n\t\t...(ev.metadata ?? {}),\n\t};\n\tif (ev.event_name) {\n\t\tproperties.event_name = ev.event_name;\n\t}\n\n\treturn {\n\t\tevent: eventName,\n\t\tproperties,\n\t\tsessionId: ev.session_id,\n\t\ttraceId: ev.trace_id,\n\t\texternalUserId: ev.user_id,\n\t\teventId: ev.event_id,\n\t\ttimestamp: ev.timestamp,\n\t\tsource: ev.source ?? \"widget\",\n\t} as TrackInput;\n}\n\n/**\n * Creates a POST handler that receives tracking events from `useWaniwani`\n * and forwards them to the WaniWani backend.\n */\nexport function createTrackingRoute(options?: TrackingRouteOptions) {\n\tconst config: WaniWaniConfig = {\n\t\tapiKey: options?.apiKey,\n\t\tbaseUrl: options?.baseUrl,\n\t};\n\n\t// Lazy singleton — created on first request\n\tlet client: ReturnType<typeof waniwani> | undefined;\n\n\tfunction getClient() {\n\t\tif (!client) {\n\t\t\tclient = waniwani(config);\n\t\t}\n\t\treturn client;\n\t}\n\n\treturn async function handler(request: Request): Promise<Response> {\n\t\tlet body: BatchPayload;\n\t\ttry {\n\t\t\tbody = (await request.json()) as BatchPayload;\n\t\t} catch {\n\t\t\treturn new Response(JSON.stringify({ error: \"Invalid JSON\" }), {\n\t\t\t\tstatus: 400,\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t});\n\t\t}\n\n\t\tif (!Array.isArray(body.events) || body.events.length === 0) {\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({ error: \"Missing or empty events array\" }),\n\t\t\t\t{\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\tconst c = getClient();\n\t\t\tconst results: string[] = [];\n\n\t\t\tfor (const ev of body.events) {\n\t\t\t\tconst trackInput = mapWidgetEvent(ev);\n\t\t\t\tconst result = await c.track(trackInput);\n\t\t\t\tresults.push(result.eventId);\n\t\t\t}\n\n\t\t\tawait c.flush();\n\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({ ok: true, accepted: results.length }),\n\t\t\t\t{\n\t\t\t\t\tstatus: 200,\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t},\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"Unknown error\";\n\t\t\treturn new Response(JSON.stringify({ error: message }), {\n\t\t\t\tstatus: 500,\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t});\n\t\t}\n\t};\n}\n","/**\n * Widget token minting — fetches short-lived JWTs from the WaniWani backend\n * so browser widgets can POST events directly.\n */\n\ninterface WidgetTokenConfig {\n\tbaseUrl: string;\n\tapiKey: string;\n}\n\ninterface WidgetTokenResult {\n\ttoken: string;\n\texpiresAt: string;\n}\n\ninterface CachedToken {\n\ttoken: string;\n\t/** Unix ms when the token expires */\n\texpiresAt: number;\n}\n\n/** Re-mint when < 2 minutes remain to avoid using nearly-expired tokens. */\nconst EXPIRY_BUFFER_MS = 2 * 60 * 1000;\n\nexport class WidgetTokenCache {\n\tprivate cached: CachedToken | null = null;\n\tprivate pending: Promise<string | null> | null = null;\n\tprivate readonly config: WidgetTokenConfig;\n\n\tconstructor(config: WidgetTokenConfig) {\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * Get a valid widget token. Returns a cached token if still fresh,\n\t * otherwise mints a new one. Returns `null` on failure.\n\t */\n\tasync getToken(sessionId?: string, traceId?: string): Promise<string | null> {\n\t\tif (this.cached && Date.now() < this.cached.expiresAt - EXPIRY_BUFFER_MS) {\n\t\t\treturn this.cached.token;\n\t\t}\n\n\t\t// Deduplicate concurrent requests\n\t\tif (this.pending) {\n\t\t\treturn this.pending;\n\t\t}\n\n\t\tthis.pending = this.mint(sessionId, traceId).finally(() => {\n\t\t\tthis.pending = null;\n\t\t});\n\n\t\treturn this.pending;\n\t}\n\n\tprivate async mint(\n\t\tsessionId?: string,\n\t\ttraceId?: string,\n\t): Promise<string | null> {\n\t\tconst url = joinUrl(this.config.baseUrl, \"/api/mcp/widget-tokens\");\n\n\t\tconst body: Record<string, string> = {};\n\t\tif (sessionId) {\n\t\t\tbody.sessionId = sessionId;\n\t\t}\n\t\tif (traceId) {\n\t\t\tbody.traceId = traceId;\n\t\t}\n\n\t\ttry {\n\t\t\tconst response = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\tAuthorization: `Bearer ${this.config.apiKey}`,\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst json = (await response.json()) as Record<string, unknown>;\n\n\t\t\t// Support both flat { token, expiresAt } and nested { data: { token, expiresAt } }\n\t\t\tconst result = (\n\t\t\t\tjson.data && typeof json.data === \"object\" ? json.data : json\n\t\t\t) as WidgetTokenResult;\n\n\t\t\tconst expiresAtMs = new Date(result.expiresAt).getTime();\n\t\t\tif (!result.token || Number.isNaN(expiresAtMs)) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tthis.cached = {\n\t\t\t\ttoken: result.token,\n\t\t\t\texpiresAt: expiresAtMs,\n\t\t\t};\n\n\t\t\treturn result.token;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n}\n\nfunction joinUrl(baseUrl: string, path: string): string {\n\tconst base = baseUrl.endsWith(\"/\") ? baseUrl.slice(0, -1) : baseUrl;\n\treturn `${base}${path}`;\n}\n","import type {\n\tToolCalledProperties,\n\tTrackInput,\n} from \"../../../tracking/index.js\";\nimport type { WaniWaniClient } from \"../../../types.js\";\nimport { extractSessionId, extractSource } from \"../utils.js\";\nimport type { WidgetTokenCache } from \"../widget-token.js\";\n\ntype UnknownRecord = Record<string, unknown>;\n\nexport type WaniwaniTracker = Pick<\n\tWaniWaniClient,\n\t\"flush\" | \"track\" | \"identify\" | \"kb\" | \"_config\"\n>;\n\nconst SESSION_ID_KEY = \"waniwani/sessionId\";\nconst GEO_LOCATION_KEY = \"waniwani/geoLocation\";\nconst LEGACY_USER_LOCATION_KEY = \"waniwani/userLocation\";\n\nexport function isRecord(value: unknown): value is UnknownRecord {\n\treturn Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nexport function extractMeta(extra: unknown): UnknownRecord | undefined {\n\tif (!isRecord(extra)) {\n\t\treturn undefined;\n\t}\n\tconst meta = extra._meta;\n\treturn isRecord(meta) ? meta : undefined;\n}\n\nexport function extractErrorText(result: unknown): string | undefined {\n\tif (!isRecord(result)) {\n\t\treturn undefined;\n\t}\n\tconst content = (result as UnknownRecord).content;\n\tif (!Array.isArray(content)) {\n\t\treturn undefined;\n\t}\n\tconst textPart = content.find(\n\t\t(c: unknown) =>\n\t\t\tisRecord(c) && c.type === \"text\" && typeof c.text === \"string\",\n\t) as UnknownRecord | undefined;\n\treturn textPart?.text as string | undefined;\n}\n\nexport function resolveToolType(\n\ttoolName: string,\n\ttoolTypeOption:\n\t\t| ToolCalledProperties[\"type\"]\n\t\t| ((toolName: string) => ToolCalledProperties[\"type\"] | undefined)\n\t\t| undefined,\n): ToolCalledProperties[\"type\"] {\n\tif (typeof toolTypeOption === \"function\") {\n\t\treturn toolTypeOption(toolName) ?? \"other\";\n\t}\n\treturn toolTypeOption ?? \"other\";\n}\n\nexport function buildTrackInput(\n\ttoolName: string,\n\textra: unknown,\n\toptions: {\n\t\ttoolType?: typeof resolveToolType extends (n: string, o: infer T) => unknown\n\t\t\t? T\n\t\t\t: never;\n\t\tmetadata?: UnknownRecord;\n\t},\n\ttiming?: { durationMs: number; status: string; errorMessage?: string },\n\tclientInfo?: { name: string; version: string },\n\tio?: { input?: unknown; output?: unknown },\n): TrackInput {\n\tconst toolType = resolveToolType(toolName, options.toolType);\n\tconst meta = extractMeta(extra);\n\tconsole.log(\n\t\t\"[waniwani:debug] buildTrackInput meta:\",\n\t\tJSON.stringify(meta),\n\t\t\"-> source:\",\n\t\textractSource(meta),\n\t);\n\n\treturn {\n\t\tevent: \"tool.called\",\n\t\tproperties: {\n\t\t\tname: toolName,\n\t\t\ttype: toolType,\n\t\t\t...(timing ?? {}),\n\t\t\t...(io?.input !== undefined && { input: io.input }),\n\t\t\t...(io?.output !== undefined && { output: io.output }),\n\t\t},\n\t\tmeta,\n\t\tsource: extractSource(meta),\n\t\tmetadata: {\n\t\t\t...(options.metadata ?? {}),\n\t\t\t...(clientInfo && { clientInfo }),\n\t\t},\n\t};\n}\n\nexport async function safeTrack(\n\ttracker: Pick<WaniWaniClient, \"track\">,\n\tinput: TrackInput,\n\tonError?: (error: Error) => void,\n): Promise<void> {\n\ttry {\n\t\tawait tracker.track(input);\n\t} catch (error) {\n\t\tonError?.(toError(error));\n\t}\n}\n\nexport async function safeFlush(\n\ttracker: Pick<WaniWaniClient, \"flush\">,\n\tonError?: (error: Error) => void,\n): Promise<void> {\n\ttry {\n\t\tawait tracker.flush();\n\t} catch (error) {\n\t\tonError?.(toError(error));\n\t}\n}\n\nexport async function injectWidgetConfig(\n\tresult: unknown,\n\tcache: WidgetTokenCache | null,\n\tbaseUrl: string,\n\tonError?: (error: Error) => void,\n): Promise<void> {\n\tif (!isRecord(result)) {\n\t\treturn;\n\t}\n\n\tif (!isRecord(result._meta)) {\n\t\t(result as UnknownRecord)._meta = {};\n\t}\n\n\tconst meta = (result as UnknownRecord)._meta as UnknownRecord;\n\tconst existingWaniwaniConfig = isRecord(meta.waniwani)\n\t\t? (meta.waniwani as UnknownRecord)\n\t\t: undefined;\n\tconst waniwaniConfig: UnknownRecord = {\n\t\t...(existingWaniwaniConfig ?? {}),\n\t\tendpoint:\n\t\t\texistingWaniwaniConfig?.endpoint ??\n\t\t\t`${baseUrl.replace(/\\/$/, \"\")}/api/mcp/events/v2/batch`,\n\t};\n\n\tif (cache) {\n\t\ttry {\n\t\t\tconst token = await cache.getToken();\n\t\t\tif (token) {\n\t\t\t\twaniwaniConfig.token = token;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tonError?.(toError(error));\n\t\t}\n\t}\n\n\tconst sessionId = extractSessionId(meta);\n\tif (sessionId) {\n\t\tif (!waniwaniConfig.sessionId) {\n\t\t\twaniwaniConfig.sessionId = sessionId;\n\t\t}\n\t}\n\n\tconst geoLocation = extractGeoLocation(meta);\n\tif (geoLocation !== undefined) {\n\t\tif (!waniwaniConfig.geoLocation) {\n\t\t\twaniwaniConfig.geoLocation = geoLocation;\n\t\t}\n\t}\n\n\tmeta.waniwani = waniwaniConfig;\n}\n\nexport function injectRequestMetadata(result: unknown, extra: unknown): void {\n\tconst requestMeta = extractMeta(extra);\n\tif (!requestMeta) {\n\t\treturn;\n\t}\n\n\tif (!isRecord(result)) {\n\t\treturn;\n\t}\n\n\tif (!isRecord(result._meta)) {\n\t\t(result as UnknownRecord)._meta = {};\n\t}\n\n\tconst resultMeta = (result as UnknownRecord)._meta as UnknownRecord;\n\tconst sessionId = extractSessionId(requestMeta);\n\tif (sessionId && !resultMeta[SESSION_ID_KEY]) {\n\t\tresultMeta[SESSION_ID_KEY] = sessionId;\n\t}\n\n\tconst geoLocation = extractGeoLocation(requestMeta);\n\tif (!geoLocation) {\n\t\treturn;\n\t}\n\n\tif (!resultMeta[GEO_LOCATION_KEY]) {\n\t\tresultMeta[GEO_LOCATION_KEY] = geoLocation;\n\t}\n\n\tif (!resultMeta[LEGACY_USER_LOCATION_KEY]) {\n\t\tresultMeta[LEGACY_USER_LOCATION_KEY] = geoLocation;\n\t}\n}\n\nfunction extractGeoLocation(\n\tmeta: UnknownRecord | undefined,\n): UnknownRecord | string | undefined {\n\tif (!meta) {\n\t\treturn undefined;\n\t}\n\n\tconst geoLocation = meta[GEO_LOCATION_KEY] ?? meta[LEGACY_USER_LOCATION_KEY];\n\tif (isRecord(geoLocation) || typeof geoLocation === \"string\") {\n\t\treturn geoLocation;\n\t}\n\n\treturn undefined;\n}\n\nfunction toError(error: unknown): Error {\n\tif (error instanceof Error) {\n\t\treturn error;\n\t}\n\treturn new Error(String(error));\n}\n","import type { ToolCalledProperties } from \"../../../tracking/index.js\";\nimport { createScopedClient, SCOPED_CLIENT_KEY } from \"../scoped-client.js\";\nimport type { McpServer } from \"../tools/types\";\nimport { WidgetTokenCache } from \"../widget-token.js\";\nimport {\n\tbuildTrackInput,\n\textractErrorText,\n\textractMeta,\n\tinjectRequestMetadata,\n\tinjectWidgetConfig,\n\tisRecord,\n\tsafeFlush,\n\tsafeTrack,\n\ttype WaniwaniTracker,\n} from \"./helpers.js\";\n\ntype UnknownRecord = Record<string, unknown>;\n\ntype WrappedServer = McpServer & {\n\t__waniwaniWrapped?: true;\n};\n\n/**\n * Options for withWaniwani().\n */\nexport type WithWaniwaniOptions = {\n\t/**\n\t * The WaniWani client instance. All tracking calls made through this client\n\t * during tool execution will automatically include session metadata.\n\t */\n\tclient: WaniwaniTracker;\n\t/**\n\t * Optional explicit tool type. Defaults to `\"other\"`.\n\t */\n\ttoolType?:\n\t\t| ToolCalledProperties[\"type\"]\n\t\t| ((toolName: string) => ToolCalledProperties[\"type\"] | undefined);\n\t/**\n\t * Optional metadata merged into every tracked event.\n\t */\n\tmetadata?: UnknownRecord;\n\t/**\n\t * Flush tracking transport after each tool call.\n\t */\n\tflushAfterToolCall?: boolean;\n\t/**\n\t * Optional error callback for non-fatal tracking errors.\n\t */\n\tonError?: (error: Error) => void;\n\t/**\n\t * Inject widget tracking config into tool response `_meta.waniwani` so browser\n\t * widgets can send events directly to the WaniWani backend.\n\t *\n\t * Always injects `endpoint`. Injects `token` when an API key is configured\n\t * and token minting succeeds.\n\t *\n\t * @default true\n\t */\n\tinjectWidgetToken?: boolean;\n};\n\nconst DEFAULT_BASE_URL = \"https://app.waniwani.ai\";\n\n/**\n * Wrap an MCP server so tool handlers automatically emit `tool.called` events.\n *\n * The wrapper intercepts `server.registerTool(...)`, tracks each invocation,\n * then forwards execution to the original tool handler.\n *\n * When `injectWidgetToken` is enabled (default), tracking config is injected\n * into tool response `_meta.waniwani` so browser widgets can post events\n * directly to the WaniWani backend without a server-side proxy.\n */\nexport function withWaniwani(\n\tserver: McpServer,\n\toptions: WithWaniwaniOptions,\n): McpServer {\n\tconst wrappedServer = server as WrappedServer;\n\tif (wrappedServer.__waniwaniWrapped) {\n\t\treturn wrappedServer;\n\t}\n\n\twrappedServer.__waniwaniWrapped = true;\n\n\tconst tracker = options.client;\n\tconst injectToken = options.injectWidgetToken !== false;\n\n\tlet tokenCache: WidgetTokenCache | null = null;\n\n\tfunction getTokenCache(): WidgetTokenCache | null {\n\t\tif (tokenCache) {\n\t\t\treturn tokenCache;\n\t\t}\n\t\tconst apiKey = tracker._config.apiKey;\n\t\tif (!apiKey) {\n\t\t\treturn null;\n\t\t}\n\t\ttokenCache = new WidgetTokenCache({\n\t\t\tbaseUrl: tracker._config.baseUrl ?? DEFAULT_BASE_URL,\n\t\t\tapiKey,\n\t\t});\n\t\treturn tokenCache;\n\t}\n\n\tconst originalRegisterTool = server.registerTool.bind(server) as (\n\t\t...args: unknown[]\n\t) => unknown;\n\n\twrappedServer.registerTool = ((...args: unknown[]) => {\n\t\tconst [toolNameRaw, config, handlerRaw] = args;\n\t\tconst toolName =\n\t\t\ttypeof toolNameRaw === \"string\" && toolNameRaw.trim().length > 0\n\t\t\t\t? toolNameRaw\n\t\t\t\t: \"unknown\";\n\n\t\tif (typeof handlerRaw !== \"function\") {\n\t\t\treturn originalRegisterTool(...args);\n\t\t}\n\n\t\tconst handler = handlerRaw as (\n\t\t\tinput: unknown,\n\t\t\textra: unknown,\n\t\t) => Promise<unknown> | unknown;\n\n\t\tconst wrappedHandler = async (input: unknown, extra: unknown) => {\n\t\t\t// Inject scoped client into extra so createTool/flows can surface it\n\t\t\tconst meta = extractMeta(extra) ?? {};\n\t\t\tconst scopedClient = createScopedClient(tracker, meta);\n\t\t\tif (isRecord(extra)) {\n\t\t\t\t(extra as UnknownRecord)[SCOPED_CLIENT_KEY] = scopedClient;\n\t\t\t}\n\n\t\t\tconst startTime = performance.now();\n\t\t\tconst clientInfo = (\n\t\t\t\tserver as {\n\t\t\t\t\tserver?: {\n\t\t\t\t\t\tgetClientVersion?: () =>\n\t\t\t\t\t\t\t| { name: string; version: string }\n\t\t\t\t\t\t\t| undefined;\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t).server?.getClientVersion?.();\n\t\t\ttry {\n\t\t\t\tconst result = await handler(input, extra);\n\t\t\t\tconst durationMs = Math.round(performance.now() - startTime);\n\n\t\t\t\tconst isErrorResult =\n\t\t\t\t\tisRecord(result) && (result as UnknownRecord).isError === true;\n\n\t\t\t\tif (isErrorResult) {\n\t\t\t\t\tconst errorText = extractErrorText(result);\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`[waniwani] Tool \"${toolName}\" returned error${errorText ? `: ${errorText}` : \"\"}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tawait safeTrack(\n\t\t\t\t\ttracker,\n\t\t\t\t\tbuildTrackInput(\n\t\t\t\t\t\ttoolName,\n\t\t\t\t\t\textra,\n\t\t\t\t\t\toptions,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdurationMs,\n\t\t\t\t\t\t\tstatus: isErrorResult ? \"error\" : \"ok\",\n\t\t\t\t\t\t\t...(isErrorResult && {\n\t\t\t\t\t\t\t\terrorMessage: extractErrorText(result) ?? \"Unknown tool error\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tclientInfo,\n\t\t\t\t\t\t{ input, output: result },\n\t\t\t\t\t),\n\t\t\t\t\toptions.onError,\n\t\t\t\t);\n\n\t\t\t\tif (options.flushAfterToolCall) {\n\t\t\t\t\tawait safeFlush(tracker, options.onError);\n\t\t\t\t}\n\n\t\t\t\tinjectRequestMetadata(result, extra);\n\n\t\t\t\tif (injectToken) {\n\t\t\t\t\tawait injectWidgetConfig(\n\t\t\t\t\t\tresult,\n\t\t\t\t\t\tgetTokenCache(),\n\t\t\t\t\t\ttracker._config.baseUrl ?? DEFAULT_BASE_URL,\n\t\t\t\t\t\toptions.onError,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\treturn result;\n\t\t\t} catch (error) {\n\t\t\t\tconst durationMs = Math.round(performance.now() - startTime);\n\n\t\t\t\tawait safeTrack(\n\t\t\t\t\ttracker,\n\t\t\t\t\tbuildTrackInput(\n\t\t\t\t\t\ttoolName,\n\t\t\t\t\t\textra,\n\t\t\t\t\t\toptions,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdurationMs,\n\t\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\t\terrorMessage:\n\t\t\t\t\t\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tclientInfo,\n\t\t\t\t\t\t{ input },\n\t\t\t\t\t),\n\t\t\t\t\toptions.onError,\n\t\t\t\t);\n\n\t\t\t\tif (options.flushAfterToolCall) {\n\t\t\t\t\tawait safeFlush(tracker, options.onError);\n\t\t\t\t}\n\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t};\n\n\t\treturn originalRegisterTool(toolNameRaw, config, wrappedHandler);\n\t}) as McpServer[\"registerTool\"];\n\n\treturn wrappedServer;\n}\n"],"mappings":"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","names":["detectPlatform","isOpenAI","isMCPApps","START","END","INTERRUPT","WIDGET","interrupt","fields","config","context","questions","key","value","q","showWidget","tool","isInterrupt","isWidget","z","SCOPED_CLIENT_KEY","extractScopedClient","extra","createScopedClient","base","meta","event","userId","properties","pickFirst","meta","keys","key","value","SESSION_ID_KEYS","REQUEST_ID_KEYS","TRACE_ID_KEYS","EXTERNAL_USER_ID_KEYS","CORRELATION_ID_KEYS","extractSessionId","extractRequestId","extractTraceId","extractExternalUserId","extractCorrelationId","SOURCE_SESSION_KEYS","extractSource","explicit","source","getObjectShape","schema","def","getNestedValue","obj","path","parts","current","part","setNestedValue","value","lastKey","deleteNestedValue","expandDotPaths","flat","result","key","deepMerge","target","source","isFilled","v","resolveNextNode","edge","state","buildInterruptResult","questions","context","currentNode","q","getNestedValue","unanswered","isSingle","q0","executeFrom","startNodeName","startState","nodes","edges","validators","meta","waniwani","MAX_ITERATIONS","iterations","END","handler","result","interrupt","showWidget","isInterrupt","interruptResult","fn","value","vResult","deepMerge","err","msg","deleteNestedValue","questionsWithError","qq","errResult","isWidget","widgetField","error","SDK_NAME","DEFAULT_BASE_URL","WaniwaniFlowStore","options","key","value","path","body","url","response","text","describeZodField","schema","desc","def","vals","v","buildFlowProtocol","config","lines","parts","key","shape","getObjectShape","groupDesc","subFields","subKey","subSchema","info","inputSchema","z","compileFlow","input","config","nodes","edges","protocol","buildFlowProtocol","fullDescription","store","WaniwaniFlowStore","validators","handleToolCall","args","sessionId","meta","waniwani","startEdge","START","startState","expandDotPaths","firstNode","resolveNextNode","executeFrom","flowState","state","step","updatedState","deepMerge","edge","nextNode","server","extra","requestExtra","_meta","extractSessionId","extractScopedClient","result","StateGraph","config","name","handler","START","END","from","to","condition","options","compileFlow","startEdge","edge","createFlow","config","StateGraph","parsePayload","result","content","createFlowTestHarness","flow","options","store","registered","sessionId","server","args","handler","extra","toResult","parsed","stateUpdates","MIME_TYPE_OPENAI","MIME_TYPE_MCP","fetchHtml","baseUrl","path","normalizedBase","buildOpenAIResourceMeta","config","buildMcpAppsResourceMeta","csp","buildToolMeta","createResource","config","id","title","description","baseUrl","htmlPath","widgetDomain","prefersBorder","autoHeight","widgetCSP","hostname","openaiUri","mcpUri","htmlPromise","getHtml","fetchHtml","uiDescription","register","server","html","MIME_TYPE_OPENAI","uri","buildOpenAIResourceMeta","MIME_TYPE_MCP","buildMcpAppsResourceMeta","createTool","config","handler","resource","description","inputSchema","annotations","autoInjectResultText","id","title","toolMeta","buildToolMeta","server","args","extra","requestExtra","_meta","waniwani","extractScopedClient","result","registerTools","tools","t","WaniWaniError","message","status","SDK_NAME","createKbClient","config","baseUrl","apiKey","requireApiKey","request","method","path","body","key","url","headers","init","response","text","WaniWaniError","files","query","options","DEFAULT_SOURCE","mapTrackEventToV2","input","options","now","generateId","createEventId","eventName","resolveEventName","meta","toRecord","metadata","correlation","resolveCorrelationIds","eventId","takeNonEmptyString","timestamp","normalizeTimestamp","source","extractSource","rawLegacy","isLegacyTrackEvent","mappedMetadata","mapProperties","legacyProperties","mapLegacyProperties","explicitProperties","properties","requestId","extractRequestId","sessionId","extractSessionId","traceId","extractTraceId","externalUserId","extractExternalUserId","correlationId","extractCorrelationId","date","value","DEFAULT_ENDPOINT_PATH","SDK_NAME","AUTH_FAILURE_STATUS","RETRYABLE_STATUS","createV2BatchTransport","options","BatchingV2Transport","joinUrl","DEFAULT_ENDPOINT_PATH","delayMs","resolve","event","dropCount","timeoutMs","flushPromise","timeoutSignal","batch","attempt","pendingBatch","result","events","response","error","getErrorMessage","data","parseJsonResponse","partial","rejected","byId","retryable","permanent","rejectedEvent","isRetryableRejectedEvent","rawDelay","status","rejectedCount","buffered","code","body","baseUrl","endpointPath","normalizedBase","normalizedPath","createTrackingClient","config","baseUrl","apiKey","tracking","requireApiKey","transport","createV2BatchTransport","client","userId","properties","meta","mappedEvent","mapTrackEventToV2","event","options","attachShutdownHooks","defaultTimeoutMs","shutdown","waniwani","config","baseUrl","apiKey","trackingConfig","internalConfig","trackingClient","createTrackingClient","kbClient","createKbClient","mapWidgetEvent","ev","eventType","eventName","properties","createTrackingRoute","options","config","client","getClient","waniwani","request","body","c","results","trackInput","result","error","message","WidgetTokenCache","config","sessionId","traceId","url","joinUrl","body","response","json","result","expiresAtMs","baseUrl","path","SESSION_ID_KEY","GEO_LOCATION_KEY","LEGACY_USER_LOCATION_KEY","isRecord","value","extractMeta","extra","meta","extractErrorText","result","content","c","resolveToolType","toolName","toolTypeOption","buildTrackInput","options","timing","clientInfo","io","toolType","extractSource","safeTrack","tracker","input","onError","error","toError","safeFlush","injectWidgetConfig","cache","baseUrl","existingWaniwaniConfig","waniwaniConfig","token","sessionId","extractSessionId","geoLocation","extractGeoLocation","injectRequestMetadata","requestMeta","resultMeta","DEFAULT_BASE_URL","withWaniwani","server","options","wrappedServer","tracker","injectToken","tokenCache","getTokenCache","apiKey","WidgetTokenCache","originalRegisterTool","args","toolNameRaw","config","handlerRaw","toolName","handler","input","extra","meta","extractMeta","scopedClient","createScopedClient","isRecord","SCOPED_CLIENT_KEY","startTime","clientInfo","result","durationMs","isErrorResult","errorText","extractErrorText","safeTrack","buildTrackInput","safeFlush","injectRequestMetadata","injectWidgetConfig","error"]}
|
|
1
|
+
{"version":3,"sources":["../../src/mcp/react/widgets/platform.ts","../../src/mcp/server/flows/@types.ts","../../src/mcp/server/flows/compile.ts","../../src/mcp/server/scoped-client.ts","../../src/mcp/server/utils.ts","../../src/mcp/server/flows/nested.ts","../../src/mcp/server/flows/execute.ts","../../src/mcp/server/flows/flow-store.ts","../../src/mcp/server/flows/protocol.ts","../../src/mcp/server/flows/state-graph.ts","../../src/mcp/server/flows/create-flow.ts","../../src/mcp/server/flows/test-utils.ts","../../src/mcp/server/resources/meta.ts","../../src/mcp/server/resources/create-resource.ts","../../src/mcp/server/tools/create-tool.ts","../../src/error.ts","../../src/kb/client.ts","../../src/tracking/mapper.ts","../../src/tracking/transport.ts","../../src/tracking/index.ts","../../src/waniwani.ts","../../src/mcp/server/tracking-route.ts","../../src/mcp/server/widget-token.ts","../../src/mcp/server/with-waniwani/helpers.ts","../../src/mcp/server/with-waniwani/index.ts"],"sourcesContent":["/**\n * Widget platform types\n */\nexport type WidgetPlatform = \"openai\" | \"mcp-apps\";\n\n/**\n * Detects which platform the widget is running on.\n *\n * OpenAI injects a global `window.openai` object.\n * MCP Apps runs in a sandboxed iframe and uses postMessage.\n */\nexport function detectPlatform(): WidgetPlatform {\n\tif (typeof window !== \"undefined\" && \"openai\" in window) {\n\t\treturn \"openai\";\n\t}\n\treturn \"mcp-apps\";\n}\n\n/**\n * Check if running on OpenAI platform\n */\nexport function isOpenAI(): boolean {\n\treturn detectPlatform() === \"openai\";\n}\n\n/**\n * Check if running on MCP Apps platform\n */\nexport function isMCPApps(): boolean {\n\treturn detectPlatform() === \"mcp-apps\";\n}\n","import type { z } from \"zod\";\nimport type { McpServer } from \"../resources/types\";\nimport type { ScopedWaniWaniClient } from \"../scoped-client\";\nimport type { RegisteredTool } from \"../tools/types\";\nimport type { FlowStore } from \"./flow-store\";\n\nexport type { McpServer };\n\n// ============================================================================\n// Sentinel constants\n// ============================================================================\n\nexport const START = \"__start__\" as const;\nexport const END = \"__end__\" as const;\n\n// ============================================================================\n// Signal types — returned by node handlers to control flow behavior\n// ============================================================================\n\nconst INTERRUPT = Symbol.for(\"waniwani.flow.interrupt\");\nconst WIDGET = Symbol.for(\"waniwani.flow.widget\");\n\n/** A single question within an interrupt step */\nexport type InterruptQuestion = {\n\t/** Question to ask the user */\n\tquestion: string;\n\t/** State key where the answer will be stored */\n\tfield: string;\n\t/** Optional suggestions to present as options */\n\tsuggestions?: string[];\n\t/** Hidden context/instructions for this specific question (not shown to user directly) */\n\tcontext?: string;\n\t/** Validation function — runs after the user answers, before advancing to the next node */\n\t// biome-ignore lint/suspicious/noConfusingVoidType: void is needed so `async () => {}` compiles\n\tvalidate?: (value: unknown) => MaybePromise<Record<string, unknown> | void>;\n};\n\n/**\n * Interrupt signal — pauses the flow and asks the user one or more questions.\n * Single-question and multi-question interrupts use the same type.\n */\nexport type InterruptSignal = {\n\treadonly __type: typeof INTERRUPT;\n\t/** Questions to ask — ask all in one conversational message */\n\tquestions: InterruptQuestion[];\n\t/** Overall hidden context/instructions for the assistant (not shown to user directly) */\n\tcontext?: string;\n};\n\nexport type WidgetSignal = {\n\treadonly __type: typeof WIDGET;\n\t/** The display tool to delegate rendering to */\n\ttool: RegisteredTool;\n\t/** Data to pass to the display tool */\n\tdata: Record<string, unknown>;\n\t/** Description of what the widget does (for the AI's context) */\n\tdescription?: string;\n\t/**\n\t * Whether the user is expected to interact with the widget before the flow continues.\n\t * Defaults to true. Set to false for informational widgets that should render and then\n\t * immediately advance to the next flow step.\n\t */\n\tinteractive?: boolean;\n\t/**\n\t * State key this widget fills — enables auto-skip when the field is already in state.\n\t * Pass this so the engine can skip the widget step when the answer is already known.\n\t */\n\tfield?: string;\n};\n\n/**\n * Create an interrupt signal — pauses the flow and asks the user a question.\n * Used internally by the engine. Flow authors use the typed `interrupt` from the node context.\n *\n * Accepts an object where each key is a field name and the value describes the question.\n * The only reserved key is `context` (string) for overall hidden AI instructions.\n */\nexport function interrupt(\n\tfields: Record<string, unknown>,\n\tconfig?: { context?: string },\n): InterruptSignal {\n\tconst context = config?.context;\n\tconst questions: InterruptQuestion[] = [];\n\n\tfor (const [key, value] of Object.entries(fields)) {\n\t\tif (typeof value === \"object\" && value !== null && \"question\" in value) {\n\t\t\tconst q = value as {\n\t\t\t\tquestion: string;\n\t\t\t\tsuggestions?: string[];\n\t\t\t\tcontext?: string;\n\t\t\t\tvalidate?: (\n\t\t\t\t\tvalue: unknown,\n\t\t\t\t\t// biome-ignore lint/suspicious/noConfusingVoidType: void is needed so `async () => {}` compiles\n\t\t\t\t) => MaybePromise<Record<string, unknown> | void>;\n\t\t\t};\n\t\t\tquestions.push({\n\t\t\t\tquestion: q.question,\n\t\t\t\tfield: key,\n\t\t\t\tsuggestions: q.suggestions,\n\t\t\t\tcontext: q.context,\n\t\t\t\tvalidate: q.validate,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn {\n\t\t__type: INTERRUPT,\n\t\tquestions,\n\t\tcontext,\n\t};\n}\n\n/**\n * Create a widget signal — pauses the flow and delegates rendering to a display tool.\n * Used internally by the engine. Flow authors use the typed `showWidget` from the node context.\n */\nexport function showWidget(\n\ttool: RegisteredTool,\n\tconfig: {\n\t\tdata: Record<string, unknown>;\n\t\tdescription?: string;\n\t\tinteractive?: boolean;\n\t\tfield?: string;\n\t},\n): WidgetSignal {\n\treturn { __type: WIDGET, tool, ...config };\n}\n\nexport function isInterrupt(value: unknown): value is InterruptSignal {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\t\"__type\" in value &&\n\t\t(value as InterruptSignal).__type === INTERRUPT\n\t);\n}\n\nexport function isWidget(value: unknown): value is WidgetSignal {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\t\"__type\" in value &&\n\t\t(value as WidgetSignal).__type === WIDGET\n\t);\n}\n\n// ============================================================================\n// Node context & handler\n// ============================================================================\n\nexport type MaybePromise<T> = T | Promise<T>;\n\n// ============================================================================\n// Nested state utility types\n// ============================================================================\n\n/** Deep partial — allows partial updates at any nesting level (for z.object state fields). */\nexport type DeepPartial<T> = {\n\t[K in keyof T]?: T[K] extends Record<string, unknown>\n\t\t? DeepPartial<T[K]>\n\t\t: T[K];\n};\n\n/**\n * Extract known (non-index-signature) string keys from a type.\n * Zod v4's z.object() adds `[key: string]: unknown` to inferred types,\n * so we filter those out to get only the declared field names.\n */\ntype KnownStringKeys<T> = Extract<\n\tkeyof {\n\t\t[K in keyof T as string extends K\n\t\t\t? never\n\t\t\t: number extends K\n\t\t\t\t? never\n\t\t\t\t: K]: T[K];\n\t},\n\tstring\n>;\n\n/**\n * Union of all valid field paths for a state type.\n * - Flat fields produce their key: `\"email\"`\n * - `z.object()` fields produce dot-paths to sub-fields: `\"driver.name\"`, `\"driver.license\"`\n * - Arrays and general records (`z.record()`) are treated as flat fields.\n * - Only 1 level of nesting is supported.\n */\nexport type FieldPaths<TState> = {\n\t[K in Extract<keyof TState, string>]: TState[K] extends unknown[]\n\t\t? K\n\t\t: TState[K] extends Record<string, unknown>\n\t\t\t? KnownStringKeys<TState[K]> extends never\n\t\t\t\t? K\n\t\t\t\t: `${K}.${KnownStringKeys<TState[K]>}`\n\t\t\t: K;\n}[Extract<keyof TState, string>];\n\n/** Resolve a dot-path to the value type at that path in TState. */\nexport type ResolveFieldType<\n\tTState,\n\tP extends string,\n> = P extends `${infer Parent}.${infer Child}`\n\t? Parent extends keyof TState\n\t\t? Child extends keyof TState[Parent]\n\t\t\t? TState[Parent][Child]\n\t\t\t: never\n\t\t: never\n\t: P extends keyof TState\n\t\t? TState[P]\n\t\t: never;\n\n// ============================================================================\n// Typed interrupt & showWidget\n// ============================================================================\n\n/**\n * Typed interrupt function — available on the node context.\n *\n * First argument: an object where each key is a field path and each value\n * describes the question for that field. Use dot-paths for nested state fields.\n * `validate` receives the field's value typed from the Zod schema.\n *\n * Second argument (optional): config with overall hidden AI instructions.\n *\n * @example\n * ```ts\n * // Flat field\n * interrupt({ breed: { question: \"What breed is your pet?\" } })\n *\n * // Nested field (z.object in state)\n * interrupt({ \"driver.name\": { question: \"Driver's name?\" } })\n *\n * // Multiple questions with context\n * interrupt(\n * {\n * \"driver.name\": { question: \"Name?\" },\n * \"driver.license\": { question: \"License?\" },\n * },\n * { context: \"Ask both questions naturally.\" },\n * )\n * ```\n */\nexport type TypedInterrupt<TState> = (\n\tfields: {\n\t\t[P in FieldPaths<TState>]?: {\n\t\t\tquestion: string;\n\t\t\tvalidate?: (\n\t\t\t\tvalue: ResolveFieldType<TState, P>,\n\t\t\t\t// biome-ignore lint/suspicious/noConfusingVoidType: void is needed so `async () => {}` compiles\n\t\t\t) => MaybePromise<DeepPartial<TState> | void>;\n\t\t\tsuggestions?: string[];\n\t\t\tcontext?: string;\n\t\t};\n\t},\n\tconfig?: {\n\t\t/** Overall hidden context/instructions for the assistant (not shown to user directly) */\n\t\tcontext?: string;\n\t},\n) => InterruptSignal;\n\n/**\n * Typed showWidget function — available on the node context.\n * The `field` parameter accepts field paths (flat or dot-path for nested state).\n */\nexport type TypedShowWidget<TState> = (\n\ttool: RegisteredTool,\n\tconfig: {\n\t\tdata: Record<string, unknown>;\n\t\tdescription?: string;\n\t\tinteractive?: boolean;\n\t\tfield?: FieldPaths<TState>;\n\t},\n) => WidgetSignal;\n\n/**\n * Context object passed to node handlers.\n * Provides state, metadata, and typed helper functions for creating signals.\n */\nexport type NodeContext<TState> = {\n\t/** Current flow state (partial — fields are filled as the flow progresses) */\n\tstate: Partial<TState>;\n\t/** Request metadata from the MCP call */\n\tmeta?: Record<string, unknown>;\n\t/** Create an interrupt signal — pause and ask the user questions */\n\tinterrupt: TypedInterrupt<TState>;\n\t/** Create a widget signal — pause and show a UI widget */\n\tshowWidget: TypedShowWidget<TState>;\n\t/** Session-scoped WaniWani client — available when the server is wrapped with withWaniwani() */\n\twaniwani?: ScopedWaniWaniClient;\n};\n\n/**\n * Node handler — receives a context object and returns a signal or state updates.\n * The return value determines behavior:\n * - `Partial<TState>` → action node (state merged, auto-advance)\n * - `InterruptSignal` → interrupt (pause, ask user one or more questions)\n * - `WidgetSignal` → widget step (pause, show widget)\n */\nexport type NodeHandler<TState> = (\n\tctx: NodeContext<TState>,\n) => MaybePromise<DeepPartial<TState> | InterruptSignal | WidgetSignal>;\n\n/**\n * Condition function for conditional edges.\n * Receives current state, returns the name of the next node.\n */\nexport type ConditionFn<TState> = (\n\tstate: Partial<TState>,\n) => string | Promise<string>;\n\nexport type Edge<TState> =\n\t| { type: \"direct\"; to: string }\n\t| { type: \"conditional\"; condition: ConditionFn<TState> };\n\n// ============================================================================\n// Flow config & compiled output\n// ============================================================================\n\nexport type FlowConfig = {\n\t/** Unique identifier for the flow (becomes the MCP tool name) */\n\tid: string;\n\t/** Display title */\n\ttitle: string;\n\t/** Description for the AI (explains when to use this flow) */\n\tdescription: string;\n\t/**\n\t * Define the flow's state — each field the flow collects.\n\t * Keys are the field names used in `interrupt({ field })`,\n\t * values are Zod schemas with `.describe()`.\n\t *\n\t * The state definition serves two purposes:\n\t * 1. Type inference — `TState` is automatically derived, no explicit generic needed\n\t * 2. AI protocol — field names, types, and descriptions are included in the tool\n\t * description so the AI can pre-fill answers via `_meta.flow.state`\n\t *\n\t * @example\n\t * ```ts\n\t * state: {\n\t * country: z.string().describe(\"Country the business is based in\"),\n\t * status: z.enum([\"registered\", \"unregistered\"]).describe(\"Business registration status\"),\n\t * }\n\t * ```\n\t */\n\tstate: Record<string, z.ZodType>;\n\t/** Optional tool annotations */\n\tannotations?: {\n\t\treadOnlyHint?: boolean;\n\t\tidempotentHint?: boolean;\n\t\topenWorldHint?: boolean;\n\t\tdestructiveHint?: boolean;\n\t};\n};\n\n/**\n * Infer the runtime state type from a flow's state schema definition.\n *\n * @example\n * ```ts\n * const config = {\n * state: {\n * country: z.enum([\"FR\", \"DE\"]),\n * status: z.enum([\"registered\", \"unregistered\"]),\n * }\n * };\n * type MyState = InferFlowState<typeof config.state>;\n * // { country: \"FR\" | \"DE\"; status: \"registered\" | \"unregistered\" }\n * ```\n */\nexport type InferFlowState<T extends Record<string, z.ZodType>> = {\n\t[K in keyof T]: z.infer<T[K]>;\n};\n\n/**\n * A compiled flow — can be registered on an McpServer.\n */\nexport type RegisteredFlow = {\n\tid: string;\n\ttitle: string;\n\tdescription: string;\n\tregister: (server: McpServer) => Promise<void>;\n\t/** Returns a Mermaid `flowchart TD` diagram of the flow graph. */\n\tgraph: () => string;\n};\n\nexport interface CompileInput<TState extends Record<string, unknown>> {\n\tconfig: FlowConfig;\n\tnodes: Map<string, NodeHandler<TState>>;\n\tedges: Map<string, Edge<TState>>;\n\tstore?: FlowStore;\n\tgraph: () => string;\n}\n\nexport type FlowToolInput = {\n\taction: \"start\" | \"continue\";\n\tstateUpdates?: Record<string, unknown>;\n};\n\nexport type FlowTokenContent = {\n\tstep?: string;\n\tstate: Record<string, unknown>;\n\tfield?: string;\n\twidgetId?: string;\n};\n\nexport type InterruptQuestionData = {\n\tquestion: string;\n\tfield: string;\n\tsuggestions?: string[];\n\tcontext?: string;\n};\n\nexport type FlowInterruptContent = {\n\tstatus: \"interrupt\";\n\t/** Single-question shorthand */\n\tquestion?: string;\n\tfield?: string;\n\tsuggestions?: string[];\n\t/** Multi-question */\n\tquestions?: InterruptQuestionData[];\n\tcontext?: string;\n};\n\nexport type FlowWidgetContent = {\n\tstatus: \"widget\";\n\t/** Display tool to call */\n\ttool: string;\n\t/** Data to pass to the display tool */\n\tdata: Record<string, unknown>;\n\tdescription?: string;\n\t/** Whether the widget expects user interaction before continuing */\n\tinteractive?: boolean;\n};\n\nexport type FlowCompleteContent = {\n\tstatus: \"complete\";\n};\n\nexport type FlowErrorContent = {\n\tstatus: \"error\";\n\terror: string;\n};\n\n/** Parsed response text from a flow tool call */\nexport type FlowContent =\n\t| FlowInterruptContent\n\t| FlowWidgetContent\n\t| FlowCompleteContent\n\t| FlowErrorContent;\n\nexport type ExecutionResult = {\n\tcontent: FlowContent;\n\tflowTokenContent?: FlowTokenContent;\n};\n","import type { ToolCallback } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { RequestHandlerExtra } from \"@modelcontextprotocol/sdk/shared/protocol.js\";\nimport type {\n\tServerNotification,\n\tServerRequest,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { z } from \"zod\";\nimport type { ScopedWaniWaniClient } from \"../scoped-client\";\nimport { extractScopedClient } from \"../scoped-client\";\nimport { extractSessionId } from \"../utils\";\nimport type {\n\tCompileInput,\n\tFlowToolInput,\n\tMcpServer,\n\tRegisteredFlow,\n} from \"./@types\";\nimport { START } from \"./@types\";\nimport { executeFrom, resolveNextNode, type ValidateFn } from \"./execute\";\nimport { type FlowStore, WaniwaniFlowStore } from \"./flow-store\";\nimport { deepMerge, expandDotPaths } from \"./nested\";\nimport { buildFlowProtocol } from \"./protocol\";\n\n// ============================================================================\n// Input schema\n// ============================================================================\n\nconst inputSchema = {\n\taction: z\n\t\t.enum([\"start\", \"continue\"])\n\t\t.describe(\n\t\t\t'\"start\" to begin the flow, \"continue\" to resume after a pause (interrupt or widget)',\n\t\t),\n\tstateUpdates: z\n\t\t.record(z.string(), z.unknown())\n\t\t.optional()\n\t\t.describe(\n\t\t\t\"State field values to set before processing the next node. Use this to pass the user's answer (keyed by the field name from the response) and any other values the user mentioned.\",\n\t\t),\n};\n\n// ============================================================================\n// Compile\n// ============================================================================\n\nexport function compileFlow<TState extends Record<string, unknown>>(\n\tinput: CompileInput<TState>,\n): RegisteredFlow {\n\tconst { config, nodes, edges } = input;\n\tconst protocol = buildFlowProtocol(config);\n\tconst fullDescription = `${config.description}\\n${protocol}`;\n\n\t// Server-side state store — keyed by sessionId, backed by WaniWani API.\n\tconst store: FlowStore = input.store ?? new WaniwaniFlowStore();\n\n\t// Validator storage — populated when handlers return interrupts with validate functions.\n\t// Keyed by \"nodeName:fieldName\", persists across tool calls within the same server.\n\tconst validators = new Map<string, ValidateFn>();\n\n\tasync function handleToolCall(\n\t\targs: FlowToolInput,\n\t\tsessionId: string | undefined,\n\t\tmeta?: Record<string, unknown>,\n\t\twaniwani?: ScopedWaniWaniClient,\n\t) {\n\t\tif (args.action === \"start\") {\n\t\t\tconst startEdge = edges.get(START);\n\t\t\tif (!startEdge) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: { status: \"error\" as const, error: \"No start edge\" },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst startState = expandDotPaths(args.stateUpdates ?? {}) as TState;\n\t\t\tconst firstNode = await resolveNextNode(startEdge, startState);\n\t\t\treturn executeFrom(\n\t\t\t\tfirstNode,\n\t\t\t\tstartState,\n\t\t\t\tnodes,\n\t\t\t\tedges,\n\t\t\t\tvalidators,\n\t\t\t\tmeta,\n\t\t\t\twaniwani,\n\t\t\t);\n\t\t}\n\n\t\tif (args.action === \"continue\") {\n\t\t\tif (!sessionId) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror: \"No session ID available for continue action.\",\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst flowState = await store.get(sessionId);\n\n\t\t\tif (!flowState) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror: \"Flow state not found. The flow may have expired.\",\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst state = flowState.state as TState;\n\t\t\tconst step = flowState.step;\n\t\t\tif (!step) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror:\n\t\t\t\t\t\t\t\"Flow state is missing the current step. The flow may have expired.\",\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst updatedState = deepMerge(\n\t\t\t\tstate as Record<string, unknown>,\n\t\t\t\texpandDotPaths(args.stateUpdates ?? {}),\n\t\t\t) as TState;\n\n\t\t\t// Widget continue: advance past the widget step (don't re-show it)\n\t\t\tif (flowState.widgetId) {\n\t\t\t\tconst edge = edges.get(step);\n\t\t\t\tif (!edge) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\t\terror: `No edge from step \"${step}\"`,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst nextNode = await resolveNextNode(edge, updatedState);\n\t\t\t\treturn executeFrom(\n\t\t\t\t\tnextNode,\n\t\t\t\t\tupdatedState,\n\t\t\t\t\tnodes,\n\t\t\t\t\tedges,\n\t\t\t\t\tvalidators,\n\t\t\t\t\tmeta,\n\t\t\t\t\twaniwani,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Interrupt continue: re-execute from current step.\n\t\t\t// The handler re-runs, filters answered questions, and runs\n\t\t\t// validators if all questions are filled.\n\t\t\treturn executeFrom(\n\t\t\t\tstep,\n\t\t\t\tupdatedState,\n\t\t\t\tnodes,\n\t\t\t\tedges,\n\t\t\t\tvalidators,\n\t\t\t\tmeta,\n\t\t\t\twaniwani,\n\t\t\t);\n\t\t}\n\n\t\treturn {\n\t\t\tcontent: {\n\t\t\t\tstatus: \"error\" as const,\n\t\t\t\terror: `Unknown action: \"${args.action}\"`,\n\t\t\t},\n\t\t};\n\t}\n\n\treturn {\n\t\tid: config.id,\n\t\ttitle: config.title,\n\t\tdescription: fullDescription,\n\t\tgraph: input.graph,\n\n\t\tasync register(server: McpServer): Promise<void> {\n\t\t\tserver.registerTool(\n\t\t\t\tconfig.id,\n\t\t\t\t{\n\t\t\t\t\ttitle: config.title,\n\t\t\t\t\tdescription: fullDescription,\n\t\t\t\t\tinputSchema,\n\t\t\t\t\tannotations: config.annotations,\n\t\t\t\t},\n\t\t\t\t(async (args: FlowToolInput, extra: unknown) => {\n\t\t\t\t\tconst requestExtra = extra as RequestHandlerExtra<\n\t\t\t\t\t\tServerRequest,\n\t\t\t\t\t\tServerNotification\n\t\t\t\t\t>;\n\t\t\t\t\tconst _meta: Record<string, unknown> = requestExtra._meta ?? {};\n\t\t\t\t\tconst sessionId = extractSessionId(_meta);\n\t\t\t\t\tconst waniwani = extractScopedClient(requestExtra);\n\n\t\t\t\t\tconst result = await handleToolCall(args, sessionId, _meta, waniwani);\n\n\t\t\t\t\t// Persist flow state under session ID\n\t\t\t\t\tif (result.flowTokenContent && sessionId) {\n\t\t\t\t\t\tawait store.set(sessionId, result.flowTokenContent);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst content = [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\t\ttext: JSON.stringify(result.content, null, 2),\n\t\t\t\t\t\t},\n\t\t\t\t\t];\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent,\n\t\t\t\t\t\t_meta,\n\t\t\t\t\t\t...(result.content.status === \"error\" ? { isError: true } : {}),\n\t\t\t\t\t};\n\t\t\t\t}) satisfies ToolCallback<typeof inputSchema>,\n\t\t\t);\n\t\t},\n\t};\n}\n","import type { KbClient } from \"../../kb/types.js\";\nimport type { TrackInput, TrackingClient } from \"../../tracking/@types.js\";\n\n/**\n * Well-known key used to attach the scoped client to the MCP `extra` object.\n * Read by `createTool` and flow compilation to surface it in handler contexts.\n */\nexport const SCOPED_CLIENT_KEY = \"waniwani/client\";\n\n/**\n * A request-scoped WaniWani client with meta pre-attached.\n *\n * Available as `context.waniwani` inside `createTool` handlers and flow nodes\n * when the server is wrapped with `withWaniwani()`.\n */\nexport interface ScopedWaniWaniClient {\n\t/** Track an event — request meta is automatically merged. */\n\ttrack(event: TrackInput): Promise<{ eventId: string }>;\n\t/** Identify a user — request meta is automatically merged. */\n\tidentify(\n\t\tuserId: string,\n\t\tproperties?: Record<string, unknown>,\n\t): Promise<{ eventId: string }>;\n\t/** Knowledge base client (no meta needed). */\n\treadonly kb: KbClient;\n}\n\n/**\n * Creates a request-scoped client that delegates to the base client\n * with request meta pre-attached to every tracking call.\n */\n/**\n * Extract the scoped client from the MCP `extra` object.\n * Returns undefined if `withWaniwani()` is not wrapping the server.\n */\nexport function extractScopedClient(\n\textra: unknown,\n): ScopedWaniWaniClient | undefined {\n\tif (typeof extra === \"object\" && extra !== null) {\n\t\treturn (extra as Record<string, unknown>)[SCOPED_CLIENT_KEY] as\n\t\t\t| ScopedWaniWaniClient\n\t\t\t| undefined;\n\t}\n\treturn undefined;\n}\n\nexport function createScopedClient(\n\tbase: Pick<TrackingClient, \"track\" | \"identify\"> & { readonly kb: KbClient },\n\tmeta: Record<string, unknown>,\n): ScopedWaniWaniClient {\n\treturn {\n\t\ttrack(event) {\n\t\t\treturn base.track({\n\t\t\t\t...event,\n\t\t\t\tmeta: { ...meta, ...event.meta },\n\t\t\t});\n\t\t},\n\t\tidentify(userId, properties) {\n\t\t\treturn base.identify(userId, properties, meta);\n\t\t},\n\t\tkb: base.kb,\n\t};\n}\n","// ============================================================================\n// Meta key extraction helpers\n// ============================================================================\n\n/** Pick the first non-empty string value from `meta` matching the given keys. */\nfunction pickFirst(\n\tmeta: Record<string, unknown>,\n\tkeys: readonly string[],\n): string | undefined {\n\tfor (const key of keys) {\n\t\tconst value = meta[key];\n\t\tif (typeof value === \"string\" && value.length > 0) {\n\t\t\treturn value;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n// --- Key lists (ordered by priority) ---\n\nconst SESSION_ID_KEYS = [\n\t\"waniwani/sessionId\",\n\t\"openai/sessionId\",\n\t\"sessionId\",\n\t\"conversationId\",\n\t\"anthropic/sessionId\",\n] as const;\n\nconst REQUEST_ID_KEYS = [\n\t\"waniwani/requestId\",\n\t\"openai/requestId\",\n\t\"requestId\",\n\t\"mcp/requestId\",\n] as const;\n\nconst TRACE_ID_KEYS = [\n\t\"waniwani/traceId\",\n\t\"openai/traceId\",\n\t\"traceId\",\n\t\"mcp/traceId\",\n\t\"openai/requestId\",\n\t\"requestId\",\n] as const;\n\nconst EXTERNAL_USER_ID_KEYS = [\n\t\"waniwani/userId\",\n\t\"openai/userId\",\n\t\"externalUserId\",\n\t\"userId\",\n\t\"actorId\",\n] as const;\n\nconst CORRELATION_ID_KEYS = [\"correlationId\", \"openai/requestId\"] as const;\n\n// --- Extractors ---\n\nexport function extractSessionId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, SESSION_ID_KEYS) : undefined;\n}\n\nexport function extractRequestId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, REQUEST_ID_KEYS) : undefined;\n}\n\nexport function extractTraceId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, TRACE_ID_KEYS) : undefined;\n}\n\nexport function extractExternalUserId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, EXTERNAL_USER_ID_KEYS) : undefined;\n}\n\nexport function extractCorrelationId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, CORRELATION_ID_KEYS) : undefined;\n}\n\nconst SOURCE_SESSION_KEYS = [\n\t{ key: \"waniwani/sessionId\", source: \"chatbar\" },\n\t{ key: \"openai/sessionId\", source: \"chatgpt\" },\n\t{ key: \"anthropic/sessionId\", source: \"claude\" },\n] as const;\n\nexport function extractSource(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\tif (!meta) {\n\t\treturn undefined;\n\t}\n\t// Explicit source set by the caller (e.g. chatbar name)\n\tconst explicit = meta[\"waniwani/source\"];\n\tif (typeof explicit === \"string\" && explicit.length > 0) {\n\t\treturn explicit;\n\t}\n\t// Derive from session ID key\n\tfor (const { key, source } of SOURCE_SESSION_KEYS) {\n\t\tconst value = meta[key];\n\t\tif (typeof value === \"string\" && value.length > 0) {\n\t\t\treturn source;\n\t\t}\n\t}\n\treturn undefined;\n}\n","import type { z } from \"zod\";\n\n// ============================================================================\n// Zod object schema detection\n// ============================================================================\n\n/** Check whether a Zod schema is a `z.object(...)`. */\nexport function isObjectSchema(schema: z.ZodType): boolean {\n\tconst def = (schema as unknown as { _zod?: { def?: { type?: string } } })._zod\n\t\t?.def;\n\treturn def?.type === \"object\";\n}\n\n/** Extract the shape of a `z.object(...)` schema, or null if not an object. */\nexport function getObjectShape(\n\tschema: z.ZodType,\n): Record<string, z.ZodType> | null {\n\tconst def = (\n\t\tschema as unknown as {\n\t\t\t_zod?: { def?: { type?: string; shape?: Record<string, z.ZodType> } };\n\t\t}\n\t)._zod?.def;\n\tif (def?.type === \"object\" && def.shape) {\n\t\treturn def.shape;\n\t}\n\treturn null;\n}\n\n// ============================================================================\n// Dot-path value access\n// ============================================================================\n\n/** Resolve a dot-path like `\"driver.name\"` to its value in a nested object. */\nexport function getNestedValue(\n\tobj: Record<string, unknown>,\n\tpath: string,\n): unknown {\n\tconst parts = path.split(\".\");\n\tlet current: unknown = obj;\n\tfor (const part of parts) {\n\t\tif (current == null || typeof current !== \"object\") {\n\t\t\treturn undefined;\n\t\t}\n\t\tcurrent = (current as Record<string, unknown>)[part];\n\t}\n\treturn current;\n}\n\n/** Set a value at a dot-path, creating intermediate objects as needed. */\nexport function setNestedValue(\n\tobj: Record<string, unknown>,\n\tpath: string,\n\tvalue: unknown,\n): void {\n\tconst parts = path.split(\".\");\n\tconst lastKey = parts.pop();\n\tif (!lastKey) {\n\t\treturn;\n\t}\n\tlet current = obj;\n\tfor (const part of parts) {\n\t\tif (\n\t\t\tcurrent[part] == null ||\n\t\t\ttypeof current[part] !== \"object\" ||\n\t\t\tArray.isArray(current[part])\n\t\t) {\n\t\t\tcurrent[part] = {};\n\t\t}\n\t\tcurrent = current[part] as Record<string, unknown>;\n\t}\n\tcurrent[lastKey] = value;\n}\n\n/** Delete a value at a dot-path. Only removes the leaf key. */\nexport function deleteNestedValue(\n\tobj: Record<string, unknown>,\n\tpath: string,\n): void {\n\tconst parts = path.split(\".\");\n\tconst lastKey = parts.pop();\n\tif (!lastKey) {\n\t\treturn;\n\t}\n\tlet current: unknown = obj;\n\tfor (const part of parts) {\n\t\tif (current == null || typeof current !== \"object\") {\n\t\t\treturn;\n\t\t}\n\t\tcurrent = (current as Record<string, unknown>)[part];\n\t}\n\tif (current != null && typeof current === \"object\") {\n\t\tdelete (current as Record<string, unknown>)[lastKey];\n\t}\n}\n\n// ============================================================================\n// State merging\n// ============================================================================\n\n/**\n * Expand dot-path keys into nested objects.\n * `{ \"driver.name\": \"John\", \"email\": \"a@b.com\" }` → `{ driver: { name: \"John\" }, email: \"a@b.com\" }`\n */\nexport function expandDotPaths(\n\tflat: Record<string, unknown>,\n): Record<string, unknown> {\n\tconst result: Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(flat)) {\n\t\tif (key.includes(\".\")) {\n\t\t\tsetNestedValue(result, key, value);\n\t\t} else {\n\t\t\tresult[key] = value;\n\t\t}\n\t}\n\treturn result;\n}\n\n/**\n * Deep-merge source into target. Preserves existing nested keys.\n * Only merges plain objects — arrays and primitives are overwritten.\n */\nexport function deepMerge(\n\ttarget: Record<string, unknown>,\n\tsource: Record<string, unknown>,\n): Record<string, unknown> {\n\tconst result = { ...target };\n\tfor (const [key, value] of Object.entries(source)) {\n\t\tif (\n\t\t\tvalue !== null &&\n\t\t\ttypeof value === \"object\" &&\n\t\t\t!Array.isArray(value) &&\n\t\t\tresult[key] !== null &&\n\t\t\ttypeof result[key] === \"object\" &&\n\t\t\t!Array.isArray(result[key])\n\t\t) {\n\t\t\tresult[key] = deepMerge(\n\t\t\t\tresult[key] as Record<string, unknown>,\n\t\t\t\tvalue as Record<string, unknown>,\n\t\t\t);\n\t\t} else {\n\t\t\tresult[key] = value;\n\t\t}\n\t}\n\treturn result;\n}\n","import type { ScopedWaniWaniClient } from \"../scoped-client\";\nimport type {\n\tEdge,\n\tExecutionResult,\n\tInterruptQuestionData,\n\tMaybePromise,\n\tNodeHandler,\n} from \"./@types\";\nimport { END, interrupt, isInterrupt, isWidget, showWidget } from \"./@types\";\nimport { deepMerge, deleteNestedValue, getNestedValue } from \"./nested\";\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Check whether a state value counts as \"filled\" (not empty/missing). */\nexport function isFilled(v: unknown): boolean {\n\treturn v !== undefined && v !== null && v !== \"\";\n}\n\nexport type ValidateFn = (\n\tvalue: unknown,\n\t// biome-ignore lint/suspicious/noConfusingVoidType: void needed for async () => {} validators\n) => MaybePromise<Record<string, unknown> | void>;\n\n// ============================================================================\n// Edge resolution\n// ============================================================================\n\nexport async function resolveNextNode<TState extends Record<string, unknown>>(\n\tedge: Edge<TState>,\n\tstate: Partial<TState>,\n): Promise<string> {\n\tif (edge.type === \"direct\") {\n\t\treturn edge.to;\n\t}\n\treturn edge.condition(state);\n}\n\n// ============================================================================\n// Interrupt result builder\n// ============================================================================\n\n/**\n * Build an interrupt ExecutionResult from a list of questions and current state.\n * Filters out already-answered questions and caches the full question list in\n * flowMeta so partial-answer continues can filter without re-executing the handler.\n *\n * Returns `null` when all questions are already filled (caller should advance).\n */\nexport function buildInterruptResult<TState extends Record<string, unknown>>(\n\tquestions: InterruptQuestionData[],\n\tcontext: string | undefined,\n\tcurrentNode: string,\n\tstate: TState,\n): ExecutionResult | null {\n\t// All filled — caller should advance to the next node\n\tif (\n\t\tquestions.every((q) =>\n\t\t\tisFilled(getNestedValue(state as Record<string, unknown>, q.field)),\n\t\t)\n\t) {\n\t\treturn null;\n\t}\n\n\t// Filter out questions whose fields are already answered\n\tconst unanswered = questions.filter(\n\t\t(q) => !isFilled(getNestedValue(state as Record<string, unknown>, q.field)),\n\t);\n\n\t// Single-question shorthand: unwrap for cleaner AI payload\n\tconst isSingle = unanswered.length === 1;\n\tconst q0 = unanswered[0];\n\tconst payload =\n\t\tisSingle && q0\n\t\t\t? {\n\t\t\t\t\tstatus: \"interrupt\" as const,\n\t\t\t\t\tquestion: q0.question,\n\t\t\t\t\tfield: q0.field,\n\t\t\t\t\t...(q0.suggestions ? { suggestions: q0.suggestions } : {}),\n\t\t\t\t\t...(q0.context || context ? { context: q0.context ?? context } : {}),\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tstatus: \"interrupt\" as const,\n\t\t\t\t\tquestions: unanswered,\n\t\t\t\t\t...(context ? { context } : {}),\n\t\t\t\t};\n\n\treturn {\n\t\tcontent: payload,\n\t\tflowTokenContent: {\n\t\t\tstep: currentNode,\n\t\t\tstate,\n\t\t\t...(isSingle && q0 ? { field: q0.field } : {}),\n\t\t},\n\t};\n}\n\n// ============================================================================\n// Execution engine\n// ============================================================================\n\nexport async function executeFrom<TState extends Record<string, unknown>>(\n\tstartNodeName: string,\n\tstartState: TState,\n\tnodes: Map<string, NodeHandler<TState>>,\n\tedges: Map<string, Edge<TState>>,\n\tvalidators: Map<string, ValidateFn>,\n\tmeta?: Record<string, unknown>,\n\twaniwani?: ScopedWaniWaniClient,\n): Promise<ExecutionResult> {\n\tlet currentNode = startNodeName;\n\tlet state = { ...startState };\n\n\t// Safety limit to prevent infinite loops\n\tconst MAX_ITERATIONS = 50;\n\tlet iterations = 0;\n\n\twhile (iterations++ < MAX_ITERATIONS) {\n\t\t// Reached END\n\t\tif (currentNode === END) {\n\t\t\treturn {\n\t\t\t\tcontent: { status: \"complete\" },\n\t\t\t\tflowTokenContent: { state },\n\t\t\t};\n\t\t}\n\n\t\tconst handler = nodes.get(currentNode);\n\t\tif (!handler) {\n\t\t\treturn {\n\t\t\t\tcontent: {\n\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\terror: `Unknown node: \"${currentNode}\"`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\ttry {\n\t\t\t// Build context object for the handler\n\t\t\tconst ctx = {\n\t\t\t\tstate,\n\t\t\t\tmeta,\n\t\t\t\tinterrupt: interrupt as NodeHandler<TState> extends never\n\t\t\t\t\t? never\n\t\t\t\t\t: typeof interrupt,\n\t\t\t\tshowWidget: showWidget as NodeHandler<TState> extends never\n\t\t\t\t\t? never\n\t\t\t\t\t: typeof showWidget,\n\t\t\t\twaniwani,\n\t\t\t};\n\t\t\tconst result = await handler(ctx as Parameters<typeof handler>[0]);\n\n\t\t\t// Interrupt signal — pause and ask the user one or more questions\n\t\t\tif (isInterrupt(result)) {\n\t\t\t\t// Extract and store any validate functions from the interrupt questions\n\t\t\t\tfor (const q of result.questions) {\n\t\t\t\t\tif (q.validate) {\n\t\t\t\t\t\tvalidators.set(`${currentNode}:${q.field}`, q.validate);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst interruptResult = buildInterruptResult(\n\t\t\t\t\tresult.questions,\n\t\t\t\t\tresult.context,\n\t\t\t\t\tcurrentNode,\n\t\t\t\t\tstate,\n\t\t\t\t);\n\n\t\t\t\tif (interruptResult) {\n\t\t\t\t\treturn interruptResult;\n\t\t\t\t}\n\n\t\t\t\t// All questions filled — run validators before advancing\n\t\t\t\tfor (const q of result.questions) {\n\t\t\t\t\tconst fn = validators.get(`${currentNode}:${q.field}`);\n\t\t\t\t\tif (fn) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst value = getNestedValue(\n\t\t\t\t\t\t\t\tstate as Record<string, unknown>,\n\t\t\t\t\t\t\t\tq.field,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tconst vResult = await fn(value);\n\t\t\t\t\t\t\tif (vResult && typeof vResult === \"object\") {\n\t\t\t\t\t\t\t\tstate = deepMerge(\n\t\t\t\t\t\t\t\t\tstate as Record<string, unknown>,\n\t\t\t\t\t\t\t\t\tvResult as Record<string, unknown>,\n\t\t\t\t\t\t\t\t) as TState;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tdeleteNestedValue(state as Record<string, unknown>, q.field);\n\t\t\t\t\t\t\tconst questionsWithError = result.questions.map((qq) =>\n\t\t\t\t\t\t\t\tqq.field === q.field\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\t...qq,\n\t\t\t\t\t\t\t\t\t\t\tcontext: qq.context\n\t\t\t\t\t\t\t\t\t\t\t\t? `ERROR: ${msg}\\n\\n${qq.context}`\n\t\t\t\t\t\t\t\t\t\t\t\t: `ERROR: ${msg}`,\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: qq,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tconst errResult = buildInterruptResult(\n\t\t\t\t\t\t\t\tquestionsWithError,\n\t\t\t\t\t\t\t\tresult.context,\n\t\t\t\t\t\t\t\tcurrentNode,\n\t\t\t\t\t\t\t\tstate,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (errResult) {\n\t\t\t\t\t\t\t\treturn errResult;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// All questions filled and validated — advance to next node\n\t\t\t\tconst edge = edges.get(currentNode);\n\t\t\t\tif (!edge) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\t\terror: `No outgoing edge from node \"${currentNode}\"`,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tcurrentNode = await resolveNextNode(edge, state);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Widget signal — delegate to display tool\n\t\t\tif (isWidget(result)) {\n\t\t\t\tconst widgetField = result.field;\n\t\t\t\tif (widgetField) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tisFilled(\n\t\t\t\t\t\t\tgetNestedValue(state as Record<string, unknown>, widgetField),\n\t\t\t\t\t\t)\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst edge = edges.get(currentNode);\n\t\t\t\t\t\tif (!edge) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\t\t\t\terror: `No outgoing edge from node \"${currentNode}\"`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentNode = await resolveNextNode(edge, state);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"widget\",\n\t\t\t\t\t\ttool: result.tool.id,\n\t\t\t\t\t\tdata: result.data,\n\t\t\t\t\t\tdescription: result.description,\n\t\t\t\t\t\tinteractive: result.interactive !== false,\n\t\t\t\t\t},\n\t\t\t\t\tflowTokenContent: {\n\t\t\t\t\t\tstep: currentNode,\n\t\t\t\t\t\tstate,\n\t\t\t\t\t\tfield: widgetField,\n\t\t\t\t\t\twidgetId: result.tool.id,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Action node — deep-merge state (preserves nested object siblings)\n\t\t\tstate = deepMerge(\n\t\t\t\tstate as Record<string, unknown>,\n\t\t\t\tresult as Record<string, unknown>,\n\t\t\t) as TState;\n\n\t\t\tconst edge = edges.get(currentNode);\n\t\t\tif (!edge) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\terror: `No outgoing edge from node \"${currentNode}\"`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\tcurrentNode = await resolveNextNode(edge, state);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\treturn {\n\t\t\t\tcontent: { status: \"error\", error: message },\n\t\t\t\tflowTokenContent: { step: currentNode, state },\n\t\t\t};\n\t\t}\n\t}\n\n\treturn {\n\t\tcontent: {\n\t\t\tstatus: \"error\",\n\t\t\terror: \"Flow exceeded maximum iterations (possible infinite loop)\",\n\t\t},\n\t};\n}\n","/**\n * Server-side flow state store.\n *\n * Flow state is stored via the WaniWani API, keyed by session ID.\n * The session ID comes from _meta (provided by the MCP client on every call),\n * so the LLM doesn't need to round-trip anything.\n *\n * Tenant isolation is handled by the API key — no manual key prefixing needed.\n *\n * The `FlowStore` interface is exported for custom implementations.\n */\n\nimport type { FlowTokenContent } from \"./@types\";\n\n// ============================================================================\n// Interface\n// ============================================================================\n\nexport interface FlowStore {\n\tget(key: string): Promise<FlowTokenContent | null>;\n\tset(key: string, value: FlowTokenContent): Promise<void>;\n\tdelete(key: string): Promise<void>;\n}\n\n// ============================================================================\n// WaniWani API implementation\n// ============================================================================\n\nconst SDK_NAME = \"@waniwani/sdk\";\nconst DEFAULT_BASE_URL = \"https://app.waniwani.ai\";\n\nexport class WaniwaniFlowStore implements FlowStore {\n\tprivate readonly baseUrl: string;\n\tprivate readonly apiKey: string | undefined;\n\n\tconstructor(options?: { baseUrl?: string; apiKey?: string }) {\n\t\tthis.baseUrl = (\n\t\t\toptions?.baseUrl ??\n\t\t\tprocess.env.WANIWANI_BASE_URL ??\n\t\t\tDEFAULT_BASE_URL\n\t\t).replace(/\\/$/, \"\");\n\t\tthis.apiKey = options?.apiKey ?? process.env.WANIWANI_API_KEY;\n\t}\n\n\tasync get(key: string): Promise<FlowTokenContent | null> {\n\t\tif (!this.apiKey) {\n\t\t\treturn null;\n\t\t}\n\t\ttry {\n\t\t\tconst data = await this.request<FlowTokenContent | null>(\n\t\t\t\t\"/api/mcp/redis/get\",\n\t\t\t\t{ key },\n\t\t\t);\n\t\t\treturn data ?? null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tasync set(key: string, value: FlowTokenContent): Promise<void> {\n\t\tif (!this.apiKey) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait this.request(\"/api/mcp/redis/set\", { key, value });\n\t\t} catch {\n\t\t\t// Non-fatal\n\t\t}\n\t}\n\n\tasync delete(key: string): Promise<void> {\n\t\tif (!this.apiKey) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait this.request(\"/api/mcp/redis/delete\", { key });\n\t\t} catch {\n\t\t\t// Non-fatal\n\t\t}\n\t}\n\n\tprivate async request<T>(path: string, body: unknown): Promise<T> {\n\t\tconst url = `${this.baseUrl}${path}`;\n\t\tconst response = await fetch(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${this.apiKey}`,\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\"X-WaniWani-SDK\": SDK_NAME,\n\t\t\t},\n\t\t\tbody: JSON.stringify(body),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst text = await response.text().catch(() => \"\");\n\t\t\tthrow new Error(text || `Flow state API error: HTTP ${response.status}`);\n\t\t}\n\n\t\tconst json = (await response.json()) as { data: T };\n\t\treturn json.data;\n\t}\n}\n","import type { z } from \"zod\";\nimport type { FlowConfig } from \"./@types\";\nimport { getObjectShape } from \"./nested\";\n\n/** Extract a human-readable label from a Zod schema for the AI protocol */\nfunction describeZodField(schema: z.ZodType): string {\n\tconst desc = schema.description ?? \"\";\n\tconst def = (\n\t\tschema as unknown as {\n\t\t\t_zod: { def: { type: string; entries?: Record<string, string> } };\n\t\t}\n\t)._zod?.def;\n\n\tif (def?.type === \"enum\" && def.entries) {\n\t\tconst vals = Object.keys(def.entries)\n\t\t\t.map((v) => `\"${v}\"`)\n\t\t\t.join(\" | \");\n\t\treturn desc ? `${vals} — ${desc}` : vals;\n\t}\n\n\treturn desc;\n}\n\nexport function buildFlowProtocol(config: FlowConfig): string {\n\tconst lines = [\n\t\t\"\",\n\t\t\"## FLOW EXECUTION PROTOCOL\",\n\t\t\"\",\n\t\t\"This tool implements a multi-step conversational flow. Follow this protocol exactly:\",\n\t\t\"\",\n\t\t'1. Call with `action: \"start\"` to begin. If the user\\'s message already',\n\t\t\" contains answers to likely questions, extract them into `stateUpdates`\",\n\t\t\" as `{ field: value }` pairs. The engine will auto-skip steps whose\",\n\t\t\" fields are already filled.\",\n\t\t\" Only extract values the user explicitly stated — do NOT guess or invent values.\",\n\t];\n\n\tif (config.state) {\n\t\tconst parts: string[] = [];\n\t\tfor (const [key, schema] of Object.entries(config.state)) {\n\t\t\tconst shape = getObjectShape(schema);\n\t\t\tif (shape) {\n\t\t\t\tconst groupDesc = schema.description ?? \"\";\n\t\t\t\tconst subFields = Object.entries(shape)\n\t\t\t\t\t.map(([subKey, subSchema]) => {\n\t\t\t\t\t\tconst info = describeZodField(subSchema);\n\t\t\t\t\t\treturn info\n\t\t\t\t\t\t\t? `\\`${key}.${subKey}\\` (${info})`\n\t\t\t\t\t\t\t: `\\`${key}.${subKey}\\``;\n\t\t\t\t\t})\n\t\t\t\t\t.join(\", \");\n\t\t\t\tparts.push(\n\t\t\t\t\tgroupDesc\n\t\t\t\t\t\t? `\\`${key}\\` (${groupDesc}): ${subFields}`\n\t\t\t\t\t\t: `\\`${key}\\`: ${subFields}`,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tconst info = describeZodField(schema);\n\t\t\t\tparts.push(info ? `\\`${key}\\` (${info})` : `\\`${key}\\``);\n\t\t\t}\n\t\t}\n\t\tlines.push(` Known fields: ${parts.join(\", \")}.`);\n\t}\n\n\tlines.push(\n\t\t\" For grouped fields (shown as `group.subfield`), use dot-notation keys in `stateUpdates`:\",\n\t\t' e.g. `{ \"driver.name\": \"John\", \"driver.license\": \"ABC123\" }`.',\n\t\t\"2. The response JSON `status` field tells you what to do next:\",\n\t\t' - `\"interrupt\"`: Pause and ask the user. Two forms:',\n\t\t\" a. Single question: `{ question, field, context? }` — ask `question`, store answer in `field`.\",\n\t\t\" b. Multi-question: `{ questions: [{question, field}, ...], context? }` — ask ALL questions\",\n\t\t\" in one conversational message, collect all answers.\",\n\t\t\" `context` (if present) is hidden AI instructions — use to shape your response, do NOT show verbatim.\",\n\t\t\" Then call again with:\",\n\t\t' `action: \"continue\"`,',\n\t\t\" `stateUpdates` = answers keyed by their `field` names, plus any other fields the user mentioned.\",\n\t\t' - `\"widget\"`: The flow wants to show a UI widget. Call the tool named in the `tool`',\n\t\t\" field, passing the `data` object as the tool's input.\",\n\t\t\" Check the `interactive` field in the response:\",\n\t\t\" • `interactive: true` — The widget requires user interaction. After calling the display tool,\",\n\t\t\" STOP and WAIT for the user to interact with the widget. Do NOT call this flow tool again\",\n\t\t\" until the user has responded. When they do, call with:\",\n\t\t' `action: \"continue\"`,',\n\t\t\" `stateUpdates` = `{ [field]: <user's selection> }` plus any other fields the user mentioned.\",\n\t\t\" • `interactive: false` — The widget is display-only. Call the display tool, then immediately\",\n\t\t' call THIS flow tool again with `action: \"continue\"`. Do NOT wait for user interaction.',\n\t\t' - `\"complete\"`: The flow is done. Present the result to the user.',\n\t\t' - `\"error\"`: Something went wrong. Show the `error` message.',\n\t\t\"\",\n\t\t\"3. Do NOT invent state values. Only use `stateUpdates` for information the user explicitly provided.\",\n\t\t\"4. Include only the fields the user actually answered in `stateUpdates` — do NOT guess missing ones.\",\n\t\t\" If the user did not answer all pending questions, the engine will re-prompt for the remaining ones.\",\n\t\t\" If the user mentioned values for other known fields, include those too —\",\n\t\t\" they will be applied immediately and those steps will be auto-skipped.\",\n\t);\n\n\treturn lines.join(\"\\n\");\n}\n","import type { Edge, FlowConfig, NodeHandler, RegisteredFlow } from \"./@types\";\nimport { END, START } from \"./@types\";\nimport { compileFlow } from \"./compile\";\nimport type { FlowStore } from \"./flow-store\";\n\nfunction buildMermaidGraph(\n\tnodes: Map<string, unknown>,\n\tedges: Map<string, Edge<unknown>>,\n): string {\n\tconst lines: string[] = [\"flowchart TD\"];\n\tlines.push(` ${START}((Start))`);\n\tfor (const [name] of nodes) {\n\t\tlines.push(` ${name}[${name}]`);\n\t}\n\tlines.push(` ${END}((End))`);\n\tfor (const [from, edge] of edges) {\n\t\tif (edge.type === \"direct\") {\n\t\t\tlines.push(` ${from} --> ${edge.to}`);\n\t\t} else {\n\t\t\tlines.push(` ${from} -.-> ${from}_branch([?])`);\n\t\t}\n\t}\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * A LangGraph-inspired state graph builder for MCP tools.\n *\n * @example\n * ```ts\n * const flow = new StateGraph<MyState>({\n * id: \"onboarding\",\n * title: \"User Onboarding\",\n * description: \"Guides users through onboarding\",\n * })\n * .addNode(\"ask_name\", ({ interrupt }) => interrupt({ question: \"What's your name?\", field: \"name\" }))\n * .addNode(\"greet\", ({ state }) => ({ greeting: `Hello ${state.name}!` }))\n * .addEdge(START, \"ask_name\")\n * .addEdge(\"ask_name\", \"greet\")\n * .addEdge(\"greet\", END)\n * .compile();\n * ```\n */\nexport class StateGraph<\n\tTState extends Record<string, unknown>,\n\tTNodes extends string = never,\n> {\n\tprivate nodes = new Map<string, NodeHandler<TState>>();\n\tprivate edges = new Map<string, Edge<TState>>();\n\tprivate config: FlowConfig;\n\n\tconstructor(config: FlowConfig) {\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * Add a node with a handler.\n\t *\n\t * The handler receives a context object with `state`, `meta`, `interrupt`, and `showWidget`.\n\t */\n\taddNode<TName extends string>(\n\t\tname: TName,\n\t\thandler: NodeHandler<TState>,\n\t): StateGraph<TState, TNodes | TName> {\n\t\tif (name === START || name === END) {\n\t\t\tthrow new Error(\n\t\t\t\t`\"${name}\" is a reserved name and cannot be used as a node name`,\n\t\t\t);\n\t\t}\n\t\tif (this.nodes.has(name)) {\n\t\t\tthrow new Error(`Node \"${name}\" already exists`);\n\t\t}\n\n\t\tthis.nodes.set(name, handler);\n\t\treturn this as unknown as StateGraph<TState, TNodes | TName>;\n\t}\n\n\t/**\n\t * Add a direct edge between two nodes.\n\t *\n\t * Use `START` as `from` to set the entry point.\n\t * Use `END` as `to` to mark a terminal node.\n\t */\n\taddEdge(from: typeof START | TNodes, to: TNodes | typeof END): this {\n\t\tif (this.edges.has(from)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Node \"${from}\" already has an outgoing edge. Use addConditionalEdge for branching.`,\n\t\t\t);\n\t\t}\n\t\tthis.edges.set(from, { type: \"direct\", to });\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add a conditional edge from a node.\n\t *\n\t * The condition function receives current state and returns the name of the next node.\n\t */\n\taddConditionalEdge(\n\t\tfrom: TNodes,\n\t\tcondition: (\n\t\t\tstate: Partial<TState>,\n\t\t) => TNodes | typeof END | Promise<TNodes | typeof END>,\n\t): this {\n\t\tif (this.edges.has(from)) {\n\t\t\tthrow new Error(`Node \"${from}\" already has an outgoing edge.`);\n\t\t}\n\t\tthis.edges.set(from, { type: \"conditional\", condition });\n\t\treturn this;\n\t}\n\n\t/**\n\t * Generate a Mermaid `flowchart TD` diagram of the graph.\n\t *\n\t * Direct edges use solid arrows. Conditional edges use a dashed arrow\n\t * to a placeholder since branch targets are determined at runtime.\n\t *\n\t * @example\n\t * ```ts\n\t * console.log(graph.graph());\n\t * // flowchart TD\n\t * // __start__((Start))\n\t * // ask_name[ask_name]\n\t * // greet[greet]\n\t * // __end__((End))\n\t * // __start__ --> ask_name\n\t * // ask_name --> greet\n\t * // greet --> __end__\n\t * ```\n\t */\n\tgraph(): string {\n\t\treturn buildMermaidGraph(this.nodes, this.edges);\n\t}\n\n\t/**\n\t * Compile the graph into a RegisteredFlow that can be registered on an McpServer.\n\t *\n\t * Validates the graph structure and returns a registration-compatible object.\n\t */\n\tcompile(options?: { store?: FlowStore }): RegisteredFlow {\n\t\tthis.validate();\n\n\t\tconst nodesCopy = new Map(this.nodes);\n\t\tconst edgesCopy = new Map(this.edges);\n\n\t\treturn compileFlow<TState>({\n\t\t\tconfig: this.config,\n\t\t\tnodes: nodesCopy,\n\t\t\tedges: edgesCopy,\n\t\t\tstore: options?.store,\n\t\t\tgraph: () => buildMermaidGraph(nodesCopy, edgesCopy),\n\t\t});\n\t}\n\n\tprivate validate(): void {\n\t\t// Must have a START edge\n\t\tif (!this.edges.has(START)) {\n\t\t\tthrow new Error(\n\t\t\t\t'Flow must have an entry point. Add an edge from START: .addEdge(START, \"first_node\")',\n\t\t\t);\n\t\t}\n\n\t\t// START edge target must exist\n\t\tconst startEdge = this.edges.get(START);\n\t\tif (\n\t\t\tstartEdge?.type === \"direct\" &&\n\t\t\tstartEdge.to !== END &&\n\t\t\t!this.nodes.has(startEdge.to)\n\t\t) {\n\t\t\tthrow new Error(\n\t\t\t\t`START edge references non-existent node: \"${startEdge.to}\"`,\n\t\t\t);\n\t\t}\n\n\t\t// All static edge targets must reference existing nodes (or END)\n\t\tfor (const [from, edge] of this.edges) {\n\t\t\tif (from !== START && !this.nodes.has(from)) {\n\t\t\t\tthrow new Error(`Edge from non-existent node: \"${from}\"`);\n\t\t\t}\n\t\t\tif (\n\t\t\t\tedge.type === \"direct\" &&\n\t\t\t\tedge.to !== END &&\n\t\t\t\t!this.nodes.has(edge.to)\n\t\t\t) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Edge from \"${from}\" references non-existent node: \"${edge.to}\"`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Every node must have an outgoing edge\n\t\tfor (const [name] of this.nodes) {\n\t\t\tif (!this.edges.has(name)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Node \"${name}\" has no outgoing edge. Add one with .addEdge(\"${name}\", ...) or .addConditionalEdge(\"${name}\", ...)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n}\n","import type { z } from \"zod\";\nimport type { FlowConfig, InferFlowState } from \"./@types\";\nimport { StateGraph } from \"./state-graph\";\n\n/**\n * Create a new flow graph — convenience factory for `new StateGraph()`.\n *\n * The state type is automatically inferred from the `state` definition —\n * no explicit generic parameter needed.\n *\n * @example\n * ```ts\n * import { createFlow, interrupt, START, END } from \"@waniwani/sdk/mcp\";\n * import { z } from \"zod\";\n *\n * const flow = createFlow({\n * id: \"onboarding\",\n * title: \"User Onboarding\",\n * description: \"Guides users through onboarding. Use when a user wants to get started.\",\n * state: {\n * name: z.string().describe(\"The user's name\"),\n * email: z.string().describe(\"The user's email address\"),\n * },\n * })\n * .addNode(\"ask_name\", () => interrupt({ question: \"What's your name?\", field: \"name\" }))\n * .addNode(\"ask_email\", () => interrupt({ question: \"What's your email?\", field: \"email\" }))\n * .addEdge(START, \"ask_name\")\n * .addEdge(\"ask_name\", \"ask_email\")\n * .addEdge(\"ask_email\", END)\n * .compile();\n * ```\n */\nexport function createFlow<const TSchema extends Record<string, z.ZodType>>(\n\tconfig: Omit<FlowConfig, \"state\"> & { state: TSchema },\n): StateGraph<InferFlowState<TSchema>> {\n\treturn new StateGraph<InferFlowState<TSchema>>(config);\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type {\n\tFlowCompleteContent,\n\tFlowContent,\n\tFlowErrorContent,\n\tFlowInterruptContent,\n\tFlowTokenContent,\n\tFlowWidgetContent,\n\tRegisteredFlow,\n} from \"./@types\";\nimport type { FlowStore } from \"./flow-store\";\n\n// ============================================================================\n// Test harness for compiled flows\n// ============================================================================\n\ntype WithDecodedState = { decodedState: FlowTokenContent | null };\n\nexport type FlowTestResult =\n\t| (FlowInterruptContent & WithDecodedState)\n\t| (FlowWidgetContent & WithDecodedState)\n\t| (FlowCompleteContent & WithDecodedState)\n\t| (FlowErrorContent & WithDecodedState);\n\ntype Handler = (input: unknown, extra: unknown) => Promise<unknown>;\ntype RegisterToolArgs = [string, Record<string, unknown>, Handler];\n\nfunction parsePayload(result: Record<string, unknown>): FlowContent {\n\tconst content = result.content as Array<{ type: string; text?: string }>;\n\treturn JSON.parse(content[0]?.text ?? \"\") as FlowContent;\n}\n\nexport async function createFlowTestHarness(\n\tflow: RegisteredFlow,\n\toptions?: { stateStore?: FlowStore },\n) {\n\tconst store = options?.stateStore;\n\tconst registered: RegisterToolArgs[] = [];\n\tconst sessionId = `test-session-${Math.random().toString(36).slice(2, 10)}`;\n\n\tconst server = {\n\t\tregisterTool: (...args: unknown[]) => {\n\t\t\tregistered.push(args as RegisterToolArgs);\n\t\t},\n\t} as unknown as McpServer;\n\n\tawait flow.register(server);\n\n\tconst handler = registered[0]?.[2];\n\tif (!handler) {\n\t\tthrow new Error(`Flow \"${flow.id}\" did not register a handler`);\n\t}\n\n\tconst extra = { _meta: { sessionId } };\n\n\tasync function toResult(parsed: FlowContent): Promise<FlowTestResult> {\n\t\treturn {\n\t\t\t...parsed,\n\t\t\tdecodedState: store ? await store.get(sessionId) : null,\n\t\t} satisfies FlowTestResult;\n\t}\n\n\treturn {\n\t\tasync start(\n\t\t\tstateUpdates?: Record<string, unknown>,\n\t\t): Promise<FlowTestResult> {\n\t\t\tconst result = (await handler(\n\t\t\t\t{ action: \"start\", ...(stateUpdates ? { stateUpdates } : {}) },\n\t\t\t\textra,\n\t\t\t)) as Record<string, unknown>;\n\t\t\treturn toResult(parsePayload(result));\n\t\t},\n\n\t\tasync continueWith(\n\t\t\tstateUpdates?: Record<string, unknown>,\n\t\t): Promise<FlowTestResult> {\n\t\t\tconst result = (await handler(\n\t\t\t\t{\n\t\t\t\t\taction: \"continue\",\n\t\t\t\t\t...(stateUpdates ? { stateUpdates } : {}),\n\t\t\t\t},\n\t\t\t\textra,\n\t\t\t)) as Record<string, unknown>;\n\t\t\treturn toResult(parsePayload(result));\n\t\t},\n\n\t\tasync lastState(): Promise<FlowTokenContent | null> {\n\t\t\treturn store ? store.get(sessionId) : null;\n\t\t},\n\t};\n}\n","import type { WidgetCSP } from \"./types\";\n\n/**\n * MIME types for widget resources.\n * OpenAI Apps SDK uses \"text/html+skybridge\"\n * MCP Apps uses \"text/html;profile=mcp-app\"\n */\nexport const MIME_TYPE_OPENAI = \"text/html+skybridge\";\nexport const MIME_TYPE_MCP = \"text/html;profile=mcp-app\";\n\n// ---- HTML fetching ----\n\nexport const fetchHtml = async (\n\tbaseUrl: string,\n\tpath: string,\n): Promise<string> => {\n\tconst normalizedBase = baseUrl.endsWith(\"/\") ? baseUrl.slice(0, -1) : baseUrl;\n\tconst result = await fetch(`${normalizedBase}${path}`);\n\treturn await result.text();\n};\n\n// ---- OpenAI resource metadata ----\n\ninterface OpenAIResourceMeta {\n\t[key: string]: unknown;\n\t\"openai/widgetDescription\"?: string;\n\t\"openai/widgetPrefersBorder\"?: boolean;\n\t\"openai/widgetDomain\"?: string;\n\t\"openai/widgetCSP\"?: WidgetCSP;\n}\n\nexport function buildOpenAIResourceMeta(config: {\n\tdescription?: string;\n\tprefersBorder?: boolean;\n\twidgetDomain: string;\n\twidgetCSP?: WidgetCSP;\n}): OpenAIResourceMeta {\n\treturn {\n\t\t\"openai/widgetDescription\": config.description,\n\t\t\"openai/widgetPrefersBorder\": config.prefersBorder,\n\t\t\"openai/widgetDomain\": config.widgetDomain,\n\t\t...(config.widgetCSP && { \"openai/widgetCSP\": config.widgetCSP }),\n\t};\n}\n\n// ---- MCP Apps resource metadata ----\n\ninterface McpAppsResourceMeta {\n\t[key: string]: unknown;\n\tui?: {\n\t\tcsp?: {\n\t\t\tconnectDomains?: string[];\n\t\t\tresourceDomains?: string[];\n\t\t\tframeDomains?: string[];\n\t\t\tredirectDomains?: string[];\n\t\t};\n\t\tdomain?: string;\n\t\tprefersBorder?: boolean;\n\t};\n}\n\nexport function buildMcpAppsResourceMeta(config: {\n\tdescription?: string;\n\tprefersBorder?: boolean;\n\twidgetDomain?: string;\n\twidgetCSP?: WidgetCSP;\n}): McpAppsResourceMeta {\n\tconst csp = config.widgetCSP\n\t\t? {\n\t\t\t\tconnectDomains: config.widgetCSP.connect_domains,\n\t\t\t\tresourceDomains: config.widgetCSP.resource_domains,\n\t\t\t\tframeDomains: config.widgetCSP.frame_domains,\n\t\t\t\tredirectDomains: config.widgetCSP.redirect_domains,\n\t\t\t}\n\t\t: undefined;\n\n\treturn {\n\t\tui: {\n\t\t\t...(csp && { csp }),\n\t\t\t...(config.widgetDomain && { domain: config.widgetDomain }),\n\t\t\t...(config.prefersBorder !== undefined && {\n\t\t\t\tprefersBorder: config.prefersBorder,\n\t\t\t}),\n\t\t},\n\t};\n}\n\n// ---- Tool metadata (references resource URIs) ----\n\nexport function buildToolMeta(config: {\n\topenaiTemplateUri?: string;\n\tmcpTemplateUri?: string;\n\tinvoking: string;\n\tinvoked: string;\n\tautoHeight?: boolean;\n}) {\n\treturn {\n\t\t// OpenAI metadata\n\t\t...(config.openaiTemplateUri && {\n\t\t\t\"openai/outputTemplate\": config.openaiTemplateUri,\n\t\t}),\n\t\t\"openai/toolInvocation/invoking\": config.invoking,\n\t\t\"openai/toolInvocation/invoked\": config.invoked,\n\t\t\"openai/widgetAccessible\": true,\n\t\t\"openai/resultCanProduceWidget\": true,\n\t\t// MCP Apps metadata (nested)\n\t\t...(config.mcpTemplateUri && {\n\t\t\tui: {\n\t\t\t\tresourceUri: config.mcpTemplateUri,\n\t\t\t\t...(config.autoHeight && { autoHeight: true }),\n\t\t\t},\n\t\t}),\n\t\t// MCP Apps backward-compat flat key (for older hosts)\n\t\t...(config.mcpTemplateUri && {\n\t\t\t\"ui/resourceUri\": config.mcpTemplateUri,\n\t\t}),\n\t};\n}\n","import {\n\tbuildMcpAppsResourceMeta,\n\tbuildOpenAIResourceMeta,\n\tfetchHtml,\n\tMIME_TYPE_MCP,\n\tMIME_TYPE_OPENAI,\n} from \"./meta\";\nimport type { McpServer, RegisteredResource, ResourceConfig } from \"./types\";\n\n/**\n * Creates a reusable UI resource (HTML template) that can be attached\n * to tools or flow nodes.\n *\n * @example\n * ```ts\n * const pricingUI = createResource({\n * id: \"pricing_table\",\n * title: \"Pricing Table\",\n * baseUrl: \"https://my-app.com\",\n * htmlPath: \"/widgets/pricing\",\n * widgetDomain: \"my-app.com\",\n * });\n *\n * await pricingUI.register(server);\n * ```\n */\nexport function createResource(config: ResourceConfig): RegisteredResource {\n\tconst {\n\t\tid,\n\t\ttitle,\n\t\tdescription,\n\t\tbaseUrl,\n\t\thtmlPath,\n\t\twidgetDomain,\n\t\tprefersBorder = true,\n\t\tautoHeight = true,\n\t} = config;\n\n\t// Auto-generate CSP from baseUrl if not explicitly provided\n\tlet widgetCSP = config.widgetCSP ?? {\n\t\tconnect_domains: [baseUrl],\n\t\tresource_domains: [baseUrl],\n\t};\n\n\t// In development with localhost, add extra CSP domains for\n\t// Next.js dev features (WebSocket HMR, Turbopack font serving)\n\tif (process.env.NODE_ENV === \"development\") {\n\t\ttry {\n\t\t\tconst { hostname } = new URL(baseUrl);\n\t\t\tif (hostname === \"localhost\" || hostname === \"127.0.0.1\") {\n\t\t\t\twidgetCSP = {\n\t\t\t\t\t...widgetCSP,\n\t\t\t\t\tconnect_domains: [\n\t\t\t\t\t\t...(widgetCSP.connect_domains || []),\n\t\t\t\t\t\t`ws://${hostname}:*`,\n\t\t\t\t\t\t`wss://${hostname}:*`,\n\t\t\t\t\t],\n\t\t\t\t\tresource_domains: [\n\t\t\t\t\t\t...(widgetCSP.resource_domains || []),\n\t\t\t\t\t\t`http://${hostname}:*`,\n\t\t\t\t\t],\n\t\t\t\t};\n\t\t\t}\n\t\t} catch {\n\t\t\t// Invalid baseUrl — skip dev CSP additions\n\t\t}\n\t}\n\n\tconst openaiUri = `ui://widgets/apps-sdk/${id}.html`;\n\tconst mcpUri = `ui://widgets/ext-apps/${id}.html`;\n\n\t// Lazy HTML — fetched once, shared across all calls\n\tlet htmlPromise: Promise<string> | null = null;\n\tconst getHtml = () => {\n\t\tif (!htmlPromise) {\n\t\t\thtmlPromise = fetchHtml(baseUrl, htmlPath);\n\t\t}\n\t\treturn htmlPromise;\n\t};\n\n\t// Use description for UI metadata\n\tconst uiDescription = description;\n\n\tasync function register(server: McpServer): Promise<void> {\n\t\tconst html = await getHtml();\n\n\t\t// Register OpenAI Apps SDK resource\n\t\tserver.registerResource(\n\t\t\t`${id}-openai-widget`,\n\t\t\topenaiUri,\n\t\t\t{\n\t\t\t\ttitle,\n\t\t\t\tdescription: uiDescription,\n\t\t\t\tmimeType: MIME_TYPE_OPENAI,\n\t\t\t\t_meta: {\n\t\t\t\t\t\"openai/widgetDescription\": uiDescription,\n\t\t\t\t\t\"openai/widgetPrefersBorder\": prefersBorder,\n\t\t\t\t},\n\t\t\t},\n\t\t\tasync (uri) => ({\n\t\t\t\tcontents: [\n\t\t\t\t\t{\n\t\t\t\t\t\turi: uri.href,\n\t\t\t\t\t\tmimeType: MIME_TYPE_OPENAI,\n\t\t\t\t\t\ttext: html,\n\t\t\t\t\t\t_meta: buildOpenAIResourceMeta({\n\t\t\t\t\t\t\tdescription: uiDescription,\n\t\t\t\t\t\t\tprefersBorder,\n\t\t\t\t\t\t\twidgetDomain,\n\t\t\t\t\t\t\twidgetCSP,\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t}),\n\t\t);\n\n\t\t// Register MCP Apps resource\n\t\tserver.registerResource(\n\t\t\t`${id}-mcp-widget`,\n\t\t\tmcpUri,\n\t\t\t{\n\t\t\t\ttitle,\n\t\t\t\tdescription: uiDescription,\n\t\t\t\tmimeType: MIME_TYPE_MCP,\n\t\t\t\t_meta: {\n\t\t\t\t\tui: {\n\t\t\t\t\t\tprefersBorder,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tasync (uri) => ({\n\t\t\t\tcontents: [\n\t\t\t\t\t{\n\t\t\t\t\t\turi: uri.href,\n\t\t\t\t\t\tmimeType: MIME_TYPE_MCP,\n\t\t\t\t\t\ttext: html,\n\t\t\t\t\t\t_meta: buildMcpAppsResourceMeta({\n\t\t\t\t\t\t\tdescription: uiDescription,\n\t\t\t\t\t\t\tprefersBorder,\n\t\t\t\t\t\t\twidgetDomain,\n\t\t\t\t\t\t\twidgetCSP,\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t}),\n\t\t);\n\t}\n\n\treturn {\n\t\tid,\n\t\ttitle,\n\t\tdescription,\n\t\topenaiUri,\n\t\tmcpUri,\n\t\tautoHeight,\n\t\tregister,\n\t};\n}\n","import type { ToolCallback } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { ShapeOutput } from \"@modelcontextprotocol/sdk/server/zod-compat.js\";\nimport type { RequestHandlerExtra } from \"@modelcontextprotocol/sdk/shared/protocol.js\";\nimport type {\n\tServerNotification,\n\tServerRequest,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport type { z } from \"zod\";\nimport { buildToolMeta } from \"../resources/meta\";\nimport { extractScopedClient } from \"../scoped-client\";\nimport type {\n\tMcpServer,\n\tRegisteredTool,\n\tToolConfig,\n\tToolHandler,\n} from \"./types\";\n\n/**\n * Creates an MCP tool with minimal boilerplate.\n *\n * When `handler()` returns `data`, the tool includes it as MCP `structuredContent`.\n * When `config.resource` is provided, the tool also returns widget metadata.\n *\n * @example\n * ```ts\n * // Widget tool (with resource)\n * const pricingTool = createTool({\n * resource: pricingUI,\n * description: \"Show pricing comparison\",\n * inputSchema: { postalCode: z.string() },\n * }, async ({ postalCode }) => ({\n * text: \"Pricing loaded\",\n * data: { postalCode, prices: [] },\n * }));\n *\n * // Plain tool (no resource)\n * const searchTool = createTool({\n * id: \"search\",\n * title: \"Search\",\n * description: \"Search the knowledge base\",\n * inputSchema: { query: z.string() },\n * }, async ({ query }) => ({\n * text: `Results for \"${query}\"`,\n * }));\n * ```\n */\nexport function createTool<TInput extends z.ZodRawShape>(\n\tconfig: ToolConfig<TInput>,\n\thandler: ToolHandler<TInput>,\n): RegisteredTool {\n\tconst {\n\t\tresource,\n\t\tdescription,\n\t\tinputSchema,\n\t\tannotations,\n\t\tautoInjectResultText = true,\n\t} = config;\n\n\tconst id = config.id ?? resource?.id;\n\tconst title = config.title ?? resource?.title;\n\n\tif (!id) {\n\t\tthrow new Error(\n\t\t\t\"createTool: `id` is required when no resource is provided\",\n\t\t);\n\t}\n\tif (!title) {\n\t\tthrow new Error(\n\t\t\t\"createTool: `title` is required when no resource is provided\",\n\t\t);\n\t}\n\n\t// Build widget metadata only when resource is present\n\tconst toolMeta = resource\n\t\t? buildToolMeta({\n\t\t\t\topenaiTemplateUri: resource.openaiUri,\n\t\t\t\tmcpTemplateUri: resource.mcpUri,\n\t\t\t\tinvoking: config.invoking ?? \"Loading...\",\n\t\t\t\tinvoked: config.invoked ?? \"Loaded\",\n\t\t\t\tautoHeight: resource.autoHeight,\n\t\t\t})\n\t\t: undefined;\n\n\treturn {\n\t\tid,\n\t\ttitle,\n\t\tdescription,\n\n\t\tasync register(server: McpServer): Promise<void> {\n\t\t\tserver.registerTool(\n\t\t\t\tid,\n\t\t\t\t{\n\t\t\t\t\ttitle,\n\t\t\t\t\tdescription,\n\t\t\t\t\tinputSchema,\n\t\t\t\t\tannotations,\n\t\t\t\t\t...(toolMeta && { _meta: toolMeta }),\n\t\t\t\t},\n\t\t\t\t(async (args: ShapeOutput<TInput>, extra: unknown) => {\n\t\t\t\t\tconst requestExtra = extra as RequestHandlerExtra<\n\t\t\t\t\t\tServerRequest,\n\t\t\t\t\t\tServerNotification\n\t\t\t\t\t>;\n\t\t\t\t\tconst _meta: Record<string, unknown> = requestExtra._meta ?? {};\n\t\t\t\t\tconst waniwani = extractScopedClient(requestExtra);\n\n\t\t\t\t\tconst result = await handler(args, { extra: { _meta }, waniwani });\n\n\t\t\t\t\t// Widget tool: return structuredContent + widget metadata\n\t\t\t\t\tif (resource && result.data) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: result.text }],\n\t\t\t\t\t\t\tstructuredContent: result.data,\n\t\t\t\t\t\t\t_meta: {\n\t\t\t\t\t\t\t\t...toolMeta,\n\t\t\t\t\t\t\t\t..._meta,\n\t\t\t\t\t\t\t\t...(autoInjectResultText === false\n\t\t\t\t\t\t\t\t\t? { \"waniwani/autoInjectResultText\": false }\n\t\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\t// Plain tool: return text content, plus structuredContent when provided.\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: result.text }],\n\t\t\t\t\t\t...(result.data ? { structuredContent: result.data } : {}),\n\t\t\t\t\t\t...(autoInjectResultText === false\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t_meta: {\n\t\t\t\t\t\t\t\t\t\t\"waniwani/autoInjectResultText\": false,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t};\n\t\t\t\t}) as unknown as ToolCallback<TInput>,\n\t\t\t);\n\t\t},\n\t};\n}\n\n/**\n * Registers multiple tools on the server\n */\nexport async function registerTools(\n\tserver: McpServer,\n\ttools: RegisteredTool[],\n): Promise<void> {\n\tawait Promise.all(tools.map((t) => t.register(server)));\n}\n","// WaniWani SDK - Errors\n\nexport class WaniWaniError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic status: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"WaniWaniError\";\n\t}\n}\n","// KB Client — thin HTTP wrapper for knowledge base API\n\nimport { WaniWaniError } from \"../error.js\";\nimport type { InternalConfig } from \"../types.js\";\nimport type {\n\tKbClient,\n\tKbIngestFile,\n\tKbIngestResult,\n\tKbSearchOptions,\n\tKbSource,\n\tSearchResult,\n} from \"./types.js\";\n\nconst SDK_NAME = \"@waniwani/sdk\";\n\nexport function createKbClient(config: InternalConfig): KbClient {\n\tconst { baseUrl, apiKey } = config;\n\n\tfunction requireApiKey(): string {\n\t\tif (!apiKey) {\n\t\t\tthrow new Error(\"WANIWANI_API_KEY is not set\");\n\t\t}\n\t\treturn apiKey;\n\t}\n\n\tasync function request<T>(\n\t\tmethod: \"GET\" | \"POST\",\n\t\tpath: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tconst key = requireApiKey();\n\t\tconst url = `${baseUrl.replace(/\\/$/, \"\")}${path}`;\n\n\t\tconst headers: Record<string, string> = {\n\t\t\tAuthorization: `Bearer ${key}`,\n\t\t\t\"X-WaniWani-SDK\": SDK_NAME,\n\t\t};\n\n\t\tconst init: RequestInit = { method, headers };\n\n\t\tif (body !== undefined) {\n\t\t\theaders[\"Content-Type\"] = \"application/json\";\n\t\t\tinit.body = JSON.stringify(body);\n\t\t}\n\n\t\tconst response = await fetch(url, init);\n\n\t\tif (!response.ok) {\n\t\t\tconst text = await response.text().catch(() => \"\");\n\t\t\tthrow new WaniWaniError(\n\t\t\t\ttext || `KB API error: HTTP ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t);\n\t\t}\n\n\t\tconst json = (await response.json()) as { data: T };\n\t\treturn json.data;\n\t}\n\n\treturn {\n\t\tasync ingest(files: KbIngestFile[]): Promise<KbIngestResult> {\n\t\t\treturn request<KbIngestResult>(\"POST\", \"/api/mcp/kb/ingest\", {\n\t\t\t\tfiles,\n\t\t\t});\n\t\t},\n\n\t\tasync search(\n\t\t\tquery: string,\n\t\t\toptions?: KbSearchOptions,\n\t\t): Promise<SearchResult[]> {\n\t\t\treturn request<SearchResult[]>(\"POST\", \"/api/mcp/kb/search\", {\n\t\t\t\tquery,\n\t\t\t\t...options,\n\t\t\t});\n\t\t},\n\n\t\tasync sources(): Promise<KbSource[]> {\n\t\t\treturn request<KbSource[]>(\"GET\", \"/api/mcp/kb/sources\");\n\t\t},\n\t};\n}\n","import {\n\textractCorrelationId,\n\textractExternalUserId,\n\textractRequestId,\n\textractSessionId,\n\textractSource,\n\textractTraceId,\n} from \"../mcp/server/utils.js\";\nimport type { EventType, LegacyTrackEvent, TrackInput } from \"./@types.js\";\nimport type { V2CorrelationIds, V2EventEnvelope } from \"./v2-types.js\";\n\nconst DEFAULT_SOURCE = \"@waniwani/sdk\";\n\nexport interface MapTrackEventOptions {\n\tnow?: () => Date;\n\tgenerateId?: () => string;\n\tsource?: string;\n}\n\nexport function mapTrackEventToV2(\n\tinput: TrackInput,\n\toptions: MapTrackEventOptions = {},\n): V2EventEnvelope {\n\tconst now = options.now ?? (() => new Date());\n\tconst generateId = options.generateId ?? createEventId;\n\tconst eventName = resolveEventName(input);\n\tconst meta = toRecord(input.meta);\n\tconst metadata = toRecord(input.metadata);\n\tconst correlation = resolveCorrelationIds(input, meta);\n\tconst eventId = takeNonEmptyString(input.eventId) ?? generateId();\n\tconst timestamp = normalizeTimestamp(input.timestamp, now);\n\tconst source =\n\t\ttakeNonEmptyString(input.source) ??\n\t\textractSource(meta) ??\n\t\toptions.source ??\n\t\tDEFAULT_SOURCE;\n\tconst rawLegacy = isLegacyTrackEvent(input) ? { ...input } : undefined;\n\n\tconst mappedMetadata: Record<string, unknown> = {\n\t\t...metadata,\n\t};\n\tif (Object.keys(meta).length > 0) {\n\t\tmappedMetadata.meta = meta;\n\t}\n\tif (rawLegacy) {\n\t\tmappedMetadata.rawLegacy = rawLegacy;\n\t}\n\n\treturn {\n\t\tid: eventId,\n\t\ttype: \"mcp.event\",\n\t\tname: eventName,\n\t\tsource,\n\t\ttimestamp,\n\t\tcorrelation,\n\t\tproperties: mapProperties(input, eventName),\n\t\tmetadata: mappedMetadata,\n\t\trawLegacy,\n\t};\n}\n\nexport function createEventId(): string {\n\tif (\n\t\ttypeof crypto !== \"undefined\" &&\n\t\ttypeof crypto.randomUUID === \"function\"\n\t) {\n\t\treturn `evt_${crypto.randomUUID()}`;\n\t}\n\n\treturn `evt_${Math.random().toString(36).slice(2, 10)}_${Date.now().toString(36)}`;\n}\n\nfunction mapProperties(\n\tinput: TrackInput,\n\teventName: EventType,\n): Record<string, unknown> {\n\tif (!isLegacyTrackEvent(input)) {\n\t\treturn toRecord(input.properties);\n\t}\n\n\tconst legacyProperties = mapLegacyProperties(input, eventName);\n\tconst explicitProperties = toRecord(input.properties);\n\treturn {\n\t\t...legacyProperties,\n\t\t...explicitProperties,\n\t};\n}\n\nfunction mapLegacyProperties(\n\tinput: LegacyTrackEvent,\n\teventName: EventType,\n): Record<string, unknown> {\n\tswitch (eventName) {\n\t\tcase \"tool.called\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (takeNonEmptyString(input.toolName)) {\n\t\t\t\tproperties.name = input.toolName;\n\t\t\t}\n\t\t\tif (takeNonEmptyString(input.toolType)) {\n\t\t\t\tproperties.type = input.toolType;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tcase \"quote.succeeded\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (typeof input.quoteAmount === \"number\") {\n\t\t\t\tproperties.amount = input.quoteAmount;\n\t\t\t}\n\t\t\tif (takeNonEmptyString(input.quoteCurrency)) {\n\t\t\t\tproperties.currency = input.quoteCurrency;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tcase \"link.clicked\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (takeNonEmptyString(input.linkUrl)) {\n\t\t\t\tproperties.url = input.linkUrl;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tcase \"purchase.completed\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (typeof input.purchaseAmount === \"number\") {\n\t\t\t\tproperties.amount = input.purchaseAmount;\n\t\t\t}\n\t\t\tif (takeNonEmptyString(input.purchaseCurrency)) {\n\t\t\t\tproperties.currency = input.purchaseCurrency;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tdefault:\n\t\t\treturn {};\n\t}\n}\n\nfunction resolveEventName(input: TrackInput): EventType {\n\tif (isLegacyTrackEvent(input)) {\n\t\treturn input.eventType;\n\t}\n\treturn input.event;\n}\n\nfunction resolveCorrelationIds(\n\tinput: TrackInput,\n\tmeta: Record<string, unknown>,\n): V2CorrelationIds {\n\tconst requestId =\n\t\ttakeNonEmptyString(input.requestId) ?? extractRequestId(meta);\n\n\tconst sessionId =\n\t\ttakeNonEmptyString(input.sessionId) ?? extractSessionId(meta);\n\n\tconst traceId = takeNonEmptyString(input.traceId) ?? extractTraceId(meta);\n\n\tconst externalUserId =\n\t\ttakeNonEmptyString(input.externalUserId) ?? extractExternalUserId(meta);\n\n\tconst correlationId =\n\t\ttakeNonEmptyString(input.correlationId) ??\n\t\textractCorrelationId(meta) ??\n\t\trequestId;\n\n\tconst correlation: V2CorrelationIds = {};\n\tif (sessionId) {\n\t\tcorrelation.sessionId = sessionId;\n\t}\n\tif (traceId) {\n\t\tcorrelation.traceId = traceId;\n\t}\n\tif (requestId) {\n\t\tcorrelation.requestId = requestId;\n\t}\n\tif (correlationId) {\n\t\tcorrelation.correlationId = correlationId;\n\t}\n\tif (externalUserId) {\n\t\tcorrelation.externalUserId = externalUserId;\n\t}\n\treturn correlation;\n}\n\nfunction normalizeTimestamp(\n\tinput: string | Date | undefined,\n\tnow: () => Date,\n): string {\n\tif (input instanceof Date) {\n\t\treturn input.toISOString();\n\t}\n\tif (typeof input === \"string\") {\n\t\tconst date = new Date(input);\n\t\tif (!Number.isNaN(date.getTime())) {\n\t\t\treturn date.toISOString();\n\t\t}\n\t}\n\treturn now().toISOString();\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\treturn {};\n\t}\n\treturn value as Record<string, unknown>;\n}\n\nfunction takeNonEmptyString(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") {\n\t\treturn undefined;\n\t}\n\tif (value.trim().length === 0) {\n\t\treturn undefined;\n\t}\n\treturn value;\n}\n\nfunction isLegacyTrackEvent(input: TrackInput): input is LegacyTrackEvent {\n\treturn \"eventType\" in input;\n}\n","import type {\n\tTrackingShutdownOptions,\n\tTrackingShutdownResult,\n} from \"./@types.js\";\nimport type {\n\tV2BatchRejectedEvent,\n\tV2BatchRequest,\n\tV2BatchResponse,\n\tV2EventEnvelope,\n} from \"./v2-types.js\";\n\nconst DEFAULT_ENDPOINT_PATH = \"/api/mcp/events/v2/batch\";\nconst DEFAULT_FLUSH_INTERVAL_MS = 1_000;\nconst DEFAULT_MAX_BATCH_SIZE = 20;\nconst DEFAULT_MAX_BUFFER_SIZE = 1_000;\nconst DEFAULT_MAX_RETRIES = 3;\nconst DEFAULT_RETRY_BASE_DELAY_MS = 200;\nconst DEFAULT_RETRY_MAX_DELAY_MS = 2_000;\nconst DEFAULT_SHUTDOWN_TIMEOUT_MS = 2_000;\nconst SDK_NAME = \"@waniwani/sdk\";\n\nconst AUTH_FAILURE_STATUS = new Set([401, 403]);\nconst RETRYABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504]);\n\ninterface Logger {\n\twarn: (message: string, ...args: unknown[]) => void;\n\terror: (message: string, ...args: unknown[]) => void;\n}\n\nexport interface V2TransportOptions {\n\tbaseUrl: string;\n\tapiKey: string;\n\tendpointPath?: string;\n\tflushIntervalMs?: number;\n\tmaxBatchSize?: number;\n\tmaxBufferSize?: number;\n\tmaxRetries?: number;\n\tretryBaseDelayMs?: number;\n\tretryMaxDelayMs?: number;\n\tshutdownTimeoutMs?: number;\n\tsdkVersion?: string;\n\tfetchFn?: typeof fetch;\n\tlogger?: Logger;\n\tnow?: () => Date;\n\tsleep?: (delayMs: number) => Promise<void>;\n}\n\nexport interface V2BatchTransport {\n\tenqueue: (event: V2EventEnvelope) => void;\n\tflush: () => Promise<void>;\n\tshutdown: (\n\t\toptions?: TrackingShutdownOptions,\n\t) => Promise<TrackingShutdownResult>;\n\tpendingEvents: () => number;\n}\n\ntype SendBatchResult =\n\t| { kind: \"success\" }\n\t| { kind: \"retryable\"; reason: string }\n\t| { kind: \"permanent\"; reason: string }\n\t| { kind: \"auth\"; status: number }\n\t| {\n\t\t\tkind: \"partial\";\n\t\t\tretryable: V2EventEnvelope[];\n\t\t\tpermanent: V2EventEnvelope[];\n\t };\n\nexport function createV2BatchTransport(\n\toptions: V2TransportOptions,\n): V2BatchTransport {\n\treturn new BatchingV2Transport(options);\n}\n\nclass BatchingV2Transport implements V2BatchTransport {\n\tprivate readonly endpointUrl: string;\n\tprivate readonly flushIntervalMs: number;\n\tprivate readonly maxBatchSize: number;\n\tprivate readonly maxBufferSize: number;\n\tprivate readonly maxRetries: number;\n\tprivate readonly retryBaseDelayMs: number;\n\tprivate readonly retryMaxDelayMs: number;\n\tprivate readonly shutdownTimeoutMs: number;\n\tprivate readonly sdkVersion?: string;\n\tprivate readonly fetchFn: typeof fetch;\n\tprivate readonly logger: Logger;\n\tprivate readonly now: () => Date;\n\tprivate readonly sleep: (delayMs: number) => Promise<void>;\n\tprivate readonly apiKey: string;\n\n\tprivate readonly buffer: V2EventEnvelope[] = [];\n\tprivate flushTimer: ReturnType<typeof setInterval> | undefined;\n\tprivate flushScheduled = false;\n\tprivate flushScheduledTimer: ReturnType<typeof setTimeout> | undefined;\n\tprivate flushInFlight: Promise<void> | undefined;\n\tprivate inFlightCount = 0;\n\tprivate isStopped = false;\n\tprivate isShuttingDown = false;\n\n\tconstructor(options: V2TransportOptions) {\n\t\tthis.endpointUrl = joinUrl(\n\t\t\toptions.baseUrl,\n\t\t\toptions.endpointPath ?? DEFAULT_ENDPOINT_PATH,\n\t\t);\n\t\tthis.flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;\n\t\tthis.maxBatchSize = options.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;\n\t\tthis.maxBufferSize = options.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE;\n\t\tthis.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;\n\t\tthis.retryBaseDelayMs =\n\t\t\toptions.retryBaseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS;\n\t\tthis.retryMaxDelayMs =\n\t\t\toptions.retryMaxDelayMs ?? DEFAULT_RETRY_MAX_DELAY_MS;\n\t\tthis.shutdownTimeoutMs =\n\t\t\toptions.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;\n\t\tthis.fetchFn = options.fetchFn ?? fetch;\n\t\tthis.logger = options.logger ?? console;\n\t\tthis.now = options.now ?? (() => new Date());\n\t\tthis.sleep =\n\t\t\toptions.sleep ??\n\t\t\t((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)));\n\t\tthis.apiKey = options.apiKey;\n\t\tthis.sdkVersion = options.sdkVersion;\n\n\t\tif (this.flushIntervalMs > 0) {\n\t\t\tthis.flushTimer = setInterval(() => {\n\t\t\t\tvoid this.flush();\n\t\t\t}, this.flushIntervalMs);\n\t\t}\n\t}\n\n\tenqueue(event: V2EventEnvelope): void {\n\t\tif (this.isStopped || this.isShuttingDown) {\n\t\t\tthis.logger.warn(\n\t\t\t\t\"[WaniWani] Tracking transport is stopped, dropping event %s\",\n\t\t\t\tevent.id,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.buffer.length >= this.maxBufferSize) {\n\t\t\tconst dropCount = this.buffer.length - this.maxBufferSize + 1;\n\t\t\tthis.buffer.splice(0, dropCount);\n\t\t\tthis.logger.warn(\n\t\t\t\t\"[WaniWani] Tracking buffer overflow, dropped %d oldest event(s)\",\n\t\t\t\tdropCount,\n\t\t\t);\n\t\t}\n\n\t\tthis.buffer.push(event);\n\n\t\tif (this.buffer.length >= this.maxBatchSize) {\n\t\t\tvoid this.flush();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.scheduleMicroFlush();\n\t}\n\n\tpendingEvents(): number {\n\t\treturn this.buffer.length + this.inFlightCount;\n\t}\n\n\tasync flush(): Promise<void> {\n\t\tif (this.flushInFlight) {\n\t\t\treturn this.flushInFlight;\n\t\t}\n\t\tthis.flushInFlight = this.flushLoop().finally(() => {\n\t\t\tthis.flushInFlight = undefined;\n\t\t});\n\t\treturn this.flushInFlight;\n\t}\n\n\tasync shutdown(\n\t\toptions?: TrackingShutdownOptions,\n\t): Promise<TrackingShutdownResult> {\n\t\tthis.isShuttingDown = true;\n\t\tif (this.flushTimer) {\n\t\t\tclearInterval(this.flushTimer);\n\t\t\tthis.flushTimer = undefined;\n\t\t}\n\t\tif (this.flushScheduledTimer) {\n\t\t\tclearTimeout(this.flushScheduledTimer);\n\t\t\tthis.flushScheduledTimer = undefined;\n\t\t\tthis.flushScheduled = false;\n\t\t}\n\n\t\tconst timeoutMs = options?.timeoutMs ?? this.shutdownTimeoutMs;\n\t\tconst flushPromise = this.flush();\n\n\t\tif (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n\t\t\tawait flushPromise;\n\t\t\tthis.isStopped = true;\n\t\t\treturn { timedOut: false, pendingEvents: this.pendingEvents() };\n\t\t}\n\n\t\tconst timeoutSignal = Symbol(\"shutdown-timeout\");\n\t\tconst result = await Promise.race([\n\t\t\tflushPromise.then(() => \"flushed\" as const),\n\t\t\tthis.sleep(timeoutMs).then(() => timeoutSignal),\n\t\t]);\n\n\t\tif (result === timeoutSignal) {\n\t\t\tthis.isStopped = true;\n\t\t\treturn { timedOut: true, pendingEvents: this.pendingEvents() };\n\t\t}\n\n\t\tthis.isStopped = true;\n\t\treturn { timedOut: false, pendingEvents: this.pendingEvents() };\n\t}\n\n\tprivate scheduleMicroFlush(): void {\n\t\tif (this.flushScheduled) {\n\t\t\treturn;\n\t\t}\n\t\tthis.flushScheduled = true;\n\t\tthis.flushScheduledTimer = setTimeout(() => {\n\t\t\tthis.flushScheduledTimer = undefined;\n\t\t\tthis.flushScheduled = false;\n\t\t\tvoid this.flush();\n\t\t}, 0);\n\t}\n\n\tprivate async flushLoop(): Promise<void> {\n\t\twhile (this.buffer.length > 0 && !this.isStopped) {\n\t\t\tconst batch = this.buffer.splice(0, this.maxBatchSize);\n\t\t\tawait this.sendBatchWithRetry(batch);\n\t\t}\n\t}\n\n\tprivate async sendBatchWithRetry(batch: V2EventEnvelope[]): Promise<void> {\n\t\tlet attempt = 0;\n\t\tlet pendingBatch = batch;\n\n\t\twhile (pendingBatch.length > 0 && !this.isStopped) {\n\t\t\tthis.inFlightCount = pendingBatch.length;\n\t\t\tconst result = await this.sendBatchOnce(pendingBatch);\n\t\t\tthis.inFlightCount = 0;\n\n\t\t\tswitch (result.kind) {\n\t\t\t\tcase \"success\":\n\t\t\t\t\treturn;\n\t\t\t\tcase \"auth\":\n\t\t\t\t\tthis.stopTransportForAuthFailure(result.status, pendingBatch.length);\n\t\t\t\t\treturn;\n\t\t\t\tcase \"permanent\":\n\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\"[WaniWani] Dropping %d event(s) after permanent failure: %s\",\n\t\t\t\t\t\tpendingBatch.length,\n\t\t\t\t\t\tresult.reason,\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\tcase \"retryable\":\n\t\t\t\t\tif (attempt >= this.maxRetries) {\n\t\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\t\"[WaniWani] Dropping %d event(s) after retry exhaustion: %s\",\n\t\t\t\t\t\t\tpendingBatch.length,\n\t\t\t\t\t\t\tresult.reason,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tawait this.sleep(this.backoffDelayMs(attempt));\n\t\t\t\t\tattempt += 1;\n\t\t\t\t\tcontinue;\n\t\t\t\tcase \"partial\":\n\t\t\t\t\tif (result.permanent.length > 0) {\n\t\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\t\"[WaniWani] Dropping %d event(s) rejected as permanent\",\n\t\t\t\t\t\t\tresult.permanent.length,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (result.retryable.length === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (attempt >= this.maxRetries) {\n\t\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\t\"[WaniWani] Dropping %d retryable event(s) after retry exhaustion\",\n\t\t\t\t\t\t\tresult.retryable.length,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tpendingBatch = result.retryable;\n\t\t\t\t\tawait this.sleep(this.backoffDelayMs(attempt));\n\t\t\t\t\tattempt += 1;\n\t\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async sendBatchOnce(\n\t\tevents: V2EventEnvelope[],\n\t): Promise<SendBatchResult> {\n\t\tlet response: Response;\n\n\t\ttry {\n\t\t\tresponse = await this.fetchFn(this.endpointUrl, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\tAuthorization: `Bearer ${this.apiKey}`,\n\t\t\t\t\t\"X-WaniWani-SDK\": SDK_NAME,\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify(this.makeBatchRequest(events)),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tkind: \"retryable\",\n\t\t\t\treason: getErrorMessage(error),\n\t\t\t};\n\t\t}\n\n\t\tif (AUTH_FAILURE_STATUS.has(response.status)) {\n\t\t\treturn { kind: \"auth\", status: response.status };\n\t\t}\n\n\t\tif (RETRYABLE_STATUS.has(response.status)) {\n\t\t\treturn {\n\t\t\t\tkind: \"retryable\",\n\t\t\t\treason: `HTTP ${response.status}`,\n\t\t\t};\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\treturn {\n\t\t\t\tkind: \"permanent\",\n\t\t\t\treason: `HTTP ${response.status}`,\n\t\t\t};\n\t\t}\n\n\t\tconst data = await parseJsonResponse<V2BatchResponse>(response);\n\t\tif (!data?.rejected || data.rejected.length === 0) {\n\t\t\treturn { kind: \"success\" };\n\t\t}\n\n\t\tconst partial = this.classifyRejectedEvents(events, data.rejected);\n\t\tif (partial.retryable.length === 0 && partial.permanent.length === 0) {\n\t\t\treturn { kind: \"success\" };\n\t\t}\n\n\t\treturn {\n\t\t\tkind: \"partial\",\n\t\t\tretryable: partial.retryable,\n\t\t\tpermanent: partial.permanent,\n\t\t};\n\t}\n\n\tprivate makeBatchRequest(events: V2EventEnvelope[]): V2BatchRequest {\n\t\treturn {\n\t\t\tsentAt: this.now().toISOString(),\n\t\t\tsource: {\n\t\t\t\tsdk: SDK_NAME,\n\t\t\t\tversion: this.sdkVersion ?? \"0.0.0\",\n\t\t\t},\n\t\t\tevents,\n\t\t};\n\t}\n\n\tprivate classifyRejectedEvents(\n\t\tevents: V2EventEnvelope[],\n\t\trejected: V2BatchRejectedEvent[],\n\t): {\n\t\tretryable: V2EventEnvelope[];\n\t\tpermanent: V2EventEnvelope[];\n\t} {\n\t\tconst byId = new Map(events.map((event) => [event.id, event]));\n\t\tconst retryable: V2EventEnvelope[] = [];\n\t\tconst permanent: V2EventEnvelope[] = [];\n\n\t\tfor (const rejectedEvent of rejected) {\n\t\t\tconst event = byId.get(rejectedEvent.eventId);\n\t\t\tif (!event) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (isRetryableRejectedEvent(rejectedEvent)) {\n\t\t\t\tretryable.push(event);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpermanent.push(event);\n\t\t}\n\n\t\treturn { retryable, permanent };\n\t}\n\n\tprivate backoffDelayMs(attempt: number): number {\n\t\tconst rawDelay = this.retryBaseDelayMs * 2 ** attempt;\n\t\treturn Math.min(rawDelay, this.retryMaxDelayMs);\n\t}\n\n\tprivate stopTransportForAuthFailure(\n\t\tstatus: number,\n\t\trejectedCount: number,\n\t): void {\n\t\tthis.isStopped = true;\n\t\tconst buffered = this.buffer.length;\n\t\tthis.buffer.splice(0, buffered);\n\t\tthis.logger.error(\n\t\t\t\"[WaniWani] Auth failure (HTTP %d). Stopping tracking transport and dropping %d queued event(s)\",\n\t\t\tstatus,\n\t\t\trejectedCount + buffered,\n\t\t);\n\t}\n}\n\nfunction isRetryableRejectedEvent(\n\trejectedEvent: V2BatchRejectedEvent,\n): boolean {\n\tif (rejectedEvent.retryable === true) {\n\t\treturn true;\n\t}\n\tconst code = rejectedEvent.code.toLowerCase();\n\treturn (\n\t\tcode.includes(\"timeout\") ||\n\t\tcode.includes(\"temporary\") ||\n\t\tcode.includes(\"unavailable\") ||\n\t\tcode.includes(\"rate_limit\") ||\n\t\tcode.includes(\"transient\") ||\n\t\tcode.includes(\"server\")\n\t);\n}\n\nasync function parseJsonResponse<T>(\n\tresponse: Response,\n): Promise<T | undefined> {\n\tconst body = await response.text();\n\tif (!body) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\treturn JSON.parse(body) as T;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction joinUrl(baseUrl: string, endpointPath: string): string {\n\tconst normalizedBase = baseUrl.endsWith(\"/\") ? baseUrl : `${baseUrl}/`;\n\tconst normalizedPath = endpointPath.startsWith(\"/\")\n\t\t? endpointPath.slice(1)\n\t\t: endpointPath;\n\treturn `${normalizedBase}${normalizedPath}`;\n}\n\nfunction getErrorMessage(error: unknown): string {\n\tif (error instanceof Error) {\n\t\treturn error.message;\n\t}\n\treturn String(error);\n}\n","// Tracking Module\n\nimport type { InternalConfig } from \"../types.js\";\nimport type {\n\tTrackInput,\n\tTrackingClient,\n\tTrackingShutdownOptions,\n} from \"./@types.js\";\nimport { mapTrackEventToV2 } from \"./mapper.js\";\nimport { createV2BatchTransport } from \"./transport.js\";\n\n// Re-export types\nexport type {\n\tEventType,\n\tLegacyTrackEvent,\n\tLinkClickedProperties,\n\tPurchaseCompletedProperties,\n\tQuoteSucceededProperties,\n\tToolCalledProperties,\n\tTrackEvent,\n\tTrackInput,\n\tTrackingClient,\n\tTrackingConfig,\n\tTrackingShutdownOptions,\n\tTrackingShutdownResult,\n} from \"./@types.js\";\nexport { createEventId, mapTrackEventToV2 } from \"./mapper.js\";\nexport type {\n\tV2BatchRejectedEvent,\n\tV2BatchRequest,\n\tV2BatchResponse,\n\tV2CorrelationIds,\n\tV2EnvelopeType,\n\tV2EventEnvelope,\n} from \"./v2-types.js\";\n\nexport function createTrackingClient(config: InternalConfig): TrackingClient {\n\tconst { baseUrl, apiKey, tracking } = config;\n\n\tfunction requireApiKey(): string {\n\t\tif (!apiKey) {\n\t\t\tthrow new Error(\"WANIWANI_API_KEY is not set\");\n\t\t}\n\t\treturn apiKey;\n\t}\n\n\tconst transport = apiKey\n\t\t? createV2BatchTransport({\n\t\t\t\tbaseUrl,\n\t\t\t\tapiKey,\n\t\t\t\tendpointPath: tracking.endpointPath,\n\t\t\t\tflushIntervalMs: tracking.flushIntervalMs,\n\t\t\t\tmaxBatchSize: tracking.maxBatchSize,\n\t\t\t\tmaxBufferSize: tracking.maxBufferSize,\n\t\t\t\tmaxRetries: tracking.maxRetries,\n\t\t\t\tretryBaseDelayMs: tracking.retryBaseDelayMs,\n\t\t\t\tretryMaxDelayMs: tracking.retryMaxDelayMs,\n\t\t\t\tshutdownTimeoutMs: tracking.shutdownTimeoutMs,\n\t\t\t})\n\t\t: undefined;\n\n\tconst client: TrackingClient = {\n\t\tasync identify(\n\t\t\tuserId: string,\n\t\t\tproperties?: Record<string, unknown>,\n\t\t\tmeta?: Record<string, unknown>,\n\t\t): Promise<{ eventId: string }> {\n\t\t\trequireApiKey();\n\t\t\tconst mappedEvent = mapTrackEventToV2({\n\t\t\t\tevent: \"user.identified\",\n\t\t\t\texternalUserId: userId,\n\t\t\t\tproperties,\n\t\t\t\tmeta,\n\t\t\t});\n\t\t\ttransport?.enqueue(mappedEvent);\n\t\t\treturn { eventId: mappedEvent.id };\n\t\t},\n\t\tasync track(event: TrackInput): Promise<{ eventId: string }> {\n\t\t\trequireApiKey();\n\t\t\tconst mappedEvent = mapTrackEventToV2(event);\n\t\t\ttransport?.enqueue(mappedEvent);\n\t\t\treturn { eventId: mappedEvent.id };\n\t\t},\n\t\tasync flush(): Promise<void> {\n\t\t\trequireApiKey();\n\t\t\tawait transport?.flush();\n\t\t},\n\t\tasync shutdown(options?: TrackingShutdownOptions) {\n\t\t\trequireApiKey();\n\t\t\treturn (\n\t\t\t\t(await transport?.shutdown({\n\t\t\t\t\ttimeoutMs: options?.timeoutMs ?? tracking.shutdownTimeoutMs,\n\t\t\t\t})) ?? { timedOut: false, pendingEvents: 0 }\n\t\t\t);\n\t\t},\n\t};\n\n\tif (transport) {\n\t\tattachShutdownHooks(client, tracking.shutdownTimeoutMs);\n\t}\n\treturn client;\n}\n\nfunction attachShutdownHooks(\n\tclient: TrackingClient,\n\tdefaultTimeoutMs: number,\n): void {\n\tif (\n\t\ttypeof process === \"undefined\" ||\n\t\ttypeof process.once !== \"function\" ||\n\t\ttypeof process.on !== \"function\"\n\t) {\n\t\treturn;\n\t}\n\n\tconst shutdown = () => {\n\t\tvoid client.shutdown({ timeoutMs: defaultTimeoutMs });\n\t};\n\n\tprocess.once(\"beforeExit\", shutdown);\n\tprocess.once(\"SIGINT\", shutdown);\n\tprocess.once(\"SIGTERM\", shutdown);\n}\n","// WaniWani SDK - Main Entry\n\nimport { createKbClient } from \"./kb/client.js\";\nimport { createTrackingClient } from \"./tracking/index.js\";\nimport type { WaniWaniClient, WaniWaniConfig } from \"./types.js\";\n\n/**\n * Create a WaniWani SDK client\n *\n * @param config - Configuration options\n * @returns A fully typed WaniWani client\n *\n * @example\n * ```typescript\n * import { waniwani } from \"@waniwani/sdk\";\n * import { toNextJsHandler } from \"@waniwani/sdk/next-js\";\n *\n * const wani = waniwani({ apiKey: \"...\" });\n *\n * // Next.js route handler\n * export const { GET, POST } = toNextJsHandler(wani, {\n * chat: { systemPrompt: \"You are a helpful assistant.\" },\n * });\n * ```\n */\nexport function waniwani(config?: WaniWaniConfig): WaniWaniClient {\n\tconst baseUrl = config?.baseUrl ?? \"https://app.waniwani.ai\";\n\tconst apiKey = config?.apiKey ?? process.env.WANIWANI_API_KEY;\n\tconst trackingConfig = {\n\t\tendpointPath: config?.tracking?.endpointPath ?? \"/api/mcp/events/v2/batch\",\n\t\tflushIntervalMs: config?.tracking?.flushIntervalMs ?? 1_000,\n\t\tmaxBatchSize: config?.tracking?.maxBatchSize ?? 20,\n\t\tmaxBufferSize: config?.tracking?.maxBufferSize ?? 1_000,\n\t\tmaxRetries: config?.tracking?.maxRetries ?? 3,\n\t\tretryBaseDelayMs: config?.tracking?.retryBaseDelayMs ?? 200,\n\t\tretryMaxDelayMs: config?.tracking?.retryMaxDelayMs ?? 2_000,\n\t\tshutdownTimeoutMs: config?.tracking?.shutdownTimeoutMs ?? 2_000,\n\t};\n\n\tconst internalConfig = { baseUrl, apiKey, tracking: trackingConfig };\n\n\t// Compose client from modules\n\tconst trackingClient = createTrackingClient(internalConfig);\n\tconst kbClient = createKbClient(internalConfig);\n\n\treturn {\n\t\t...trackingClient,\n\t\tkb: kbClient,\n\t\t_config: internalConfig,\n\t};\n}\n","/**\n * Server-side API route handler for widget tracking events.\n *\n * Receives batched events from the `useWaniwani` React hook and forwards them\n * to the WaniWani backend using the server-side SDK.\n *\n * @example Next.js App Router\n * ```typescript\n * // app/api/waniwani/track/route.ts\n * import { createTrackingRoute } from \"@waniwani/sdk/mcp\";\n *\n * const handler = createTrackingRoute({\n * apiKey: process.env.WANIWANI_API_KEY,\n * baseUrl: process.env.WANIWANI_BASE_URL,\n * });\n *\n * export { handler as POST };\n * ```\n */\n\nimport type { EventType, TrackInput } from \"../../tracking/@types.js\";\nimport type { WaniWaniConfig } from \"../../types.js\";\nimport { waniwani } from \"../../waniwani.js\";\n\nexport interface TrackingRouteOptions {\n\t/** API key for the WaniWani backend. Defaults to WANIWANI_API_KEY env var. */\n\tapiKey?: string;\n\t/** Base URL for the WaniWani backend. Defaults to https://app.waniwani.ai. */\n\tbaseUrl?: string;\n}\n\n/** Shape of a single event from the WidgetTransport client. */\ninterface WidgetEventPayload {\n\tevent_id?: string;\n\tevent_type?: string;\n\ttimestamp?: string;\n\tsource?: string;\n\tsession_id?: string;\n\ttrace_id?: string;\n\tuser_id?: string;\n\tevent_name?: string;\n\tmetadata?: Record<string, unknown>;\n\t[key: string]: unknown;\n}\n\n/** Batch payload sent by WidgetTransport. */\ninterface BatchPayload {\n\tevents: WidgetEventPayload[];\n\tsentAt?: string;\n}\n\n/**\n * Map a WidgetEvent from the client to the SDK's TrackInput format.\n */\nfunction mapWidgetEvent(ev: WidgetEventPayload): TrackInput {\n\tconst eventType = ev.event_type ?? \"widget_click\";\n\n\t// For manual tracking methods (track, identify, step, conversion),\n\t// use \"widget_<type>\" as the event name. Auto-capture events already\n\t// have the \"widget_\" prefix.\n\tconst isAutoCapture = eventType.startsWith(\"widget_\");\n\tconst eventName: EventType = (\n\t\tisAutoCapture ? eventType : `widget_${eventType}`\n\t) as EventType;\n\n\t// Merge metadata + any extra properties from the event\n\tconst properties: Record<string, unknown> = {\n\t\t...(ev.metadata ?? {}),\n\t};\n\tif (ev.event_name) {\n\t\tproperties.event_name = ev.event_name;\n\t}\n\n\treturn {\n\t\tevent: eventName,\n\t\tproperties,\n\t\tsessionId: ev.session_id,\n\t\ttraceId: ev.trace_id,\n\t\texternalUserId: ev.user_id,\n\t\teventId: ev.event_id,\n\t\ttimestamp: ev.timestamp,\n\t\tsource: ev.source ?? \"widget\",\n\t} as TrackInput;\n}\n\n/**\n * Creates a POST handler that receives tracking events from `useWaniwani`\n * and forwards them to the WaniWani backend.\n */\nexport function createTrackingRoute(options?: TrackingRouteOptions) {\n\tconst config: WaniWaniConfig = {\n\t\tapiKey: options?.apiKey,\n\t\tbaseUrl: options?.baseUrl,\n\t};\n\n\t// Lazy singleton — created on first request\n\tlet client: ReturnType<typeof waniwani> | undefined;\n\n\tfunction getClient() {\n\t\tif (!client) {\n\t\t\tclient = waniwani(config);\n\t\t}\n\t\treturn client;\n\t}\n\n\treturn async function handler(request: Request): Promise<Response> {\n\t\tlet body: BatchPayload;\n\t\ttry {\n\t\t\tbody = (await request.json()) as BatchPayload;\n\t\t} catch {\n\t\t\treturn new Response(JSON.stringify({ error: \"Invalid JSON\" }), {\n\t\t\t\tstatus: 400,\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t});\n\t\t}\n\n\t\tif (!Array.isArray(body.events) || body.events.length === 0) {\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({ error: \"Missing or empty events array\" }),\n\t\t\t\t{\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\tconst c = getClient();\n\t\t\tconst results: string[] = [];\n\n\t\t\tfor (const ev of body.events) {\n\t\t\t\tconst trackInput = mapWidgetEvent(ev);\n\t\t\t\tconst result = await c.track(trackInput);\n\t\t\t\tresults.push(result.eventId);\n\t\t\t}\n\n\t\t\tawait c.flush();\n\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({ ok: true, accepted: results.length }),\n\t\t\t\t{\n\t\t\t\t\tstatus: 200,\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t},\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"Unknown error\";\n\t\t\treturn new Response(JSON.stringify({ error: message }), {\n\t\t\t\tstatus: 500,\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t});\n\t\t}\n\t};\n}\n","/**\n * Widget token minting — fetches short-lived JWTs from the WaniWani backend\n * so browser widgets can POST events directly.\n */\n\ninterface WidgetTokenConfig {\n\tbaseUrl: string;\n\tapiKey: string;\n}\n\ninterface WidgetTokenResult {\n\ttoken: string;\n\texpiresAt: string;\n}\n\ninterface CachedToken {\n\ttoken: string;\n\t/** Unix ms when the token expires */\n\texpiresAt: number;\n}\n\n/** Re-mint when < 2 minutes remain to avoid using nearly-expired tokens. */\nconst EXPIRY_BUFFER_MS = 2 * 60 * 1000;\n\nexport class WidgetTokenCache {\n\tprivate cached: CachedToken | null = null;\n\tprivate pending: Promise<string | null> | null = null;\n\tprivate readonly config: WidgetTokenConfig;\n\n\tconstructor(config: WidgetTokenConfig) {\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * Get a valid widget token. Returns a cached token if still fresh,\n\t * otherwise mints a new one. Returns `null` on failure.\n\t */\n\tasync getToken(sessionId?: string, traceId?: string): Promise<string | null> {\n\t\tif (this.cached && Date.now() < this.cached.expiresAt - EXPIRY_BUFFER_MS) {\n\t\t\treturn this.cached.token;\n\t\t}\n\n\t\t// Deduplicate concurrent requests\n\t\tif (this.pending) {\n\t\t\treturn this.pending;\n\t\t}\n\n\t\tthis.pending = this.mint(sessionId, traceId).finally(() => {\n\t\t\tthis.pending = null;\n\t\t});\n\n\t\treturn this.pending;\n\t}\n\n\tprivate async mint(\n\t\tsessionId?: string,\n\t\ttraceId?: string,\n\t): Promise<string | null> {\n\t\tconst url = joinUrl(this.config.baseUrl, \"/api/mcp/widget-tokens\");\n\n\t\tconst body: Record<string, string> = {};\n\t\tif (sessionId) {\n\t\t\tbody.sessionId = sessionId;\n\t\t}\n\t\tif (traceId) {\n\t\t\tbody.traceId = traceId;\n\t\t}\n\n\t\ttry {\n\t\t\tconst response = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\tAuthorization: `Bearer ${this.config.apiKey}`,\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst json = (await response.json()) as Record<string, unknown>;\n\n\t\t\t// Support both flat { token, expiresAt } and nested { data: { token, expiresAt } }\n\t\t\tconst result = (\n\t\t\t\tjson.data && typeof json.data === \"object\" ? json.data : json\n\t\t\t) as WidgetTokenResult;\n\n\t\t\tconst expiresAtMs = new Date(result.expiresAt).getTime();\n\t\t\tif (!result.token || Number.isNaN(expiresAtMs)) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tthis.cached = {\n\t\t\t\ttoken: result.token,\n\t\t\t\texpiresAt: expiresAtMs,\n\t\t\t};\n\n\t\t\treturn result.token;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n}\n\nfunction joinUrl(baseUrl: string, path: string): string {\n\tconst base = baseUrl.endsWith(\"/\") ? baseUrl.slice(0, -1) : baseUrl;\n\treturn `${base}${path}`;\n}\n","import type {\n\tToolCalledProperties,\n\tTrackInput,\n} from \"../../../tracking/index.js\";\nimport type { WaniWaniClient } from \"../../../types.js\";\nimport { extractSessionId, extractSource } from \"../utils.js\";\nimport type { WidgetTokenCache } from \"../widget-token.js\";\n\ntype UnknownRecord = Record<string, unknown>;\n\nexport type WaniwaniTracker = Pick<\n\tWaniWaniClient,\n\t\"flush\" | \"track\" | \"identify\" | \"kb\" | \"_config\"\n>;\n\nconst SESSION_ID_KEY = \"waniwani/sessionId\";\nconst GEO_LOCATION_KEY = \"waniwani/geoLocation\";\nconst LEGACY_USER_LOCATION_KEY = \"waniwani/userLocation\";\n\nexport function isRecord(value: unknown): value is UnknownRecord {\n\treturn Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nexport function extractMeta(extra: unknown): UnknownRecord | undefined {\n\tif (!isRecord(extra)) {\n\t\treturn undefined;\n\t}\n\tconst meta = extra._meta;\n\treturn isRecord(meta) ? meta : undefined;\n}\n\nexport function extractErrorText(result: unknown): string | undefined {\n\tif (!isRecord(result)) {\n\t\treturn undefined;\n\t}\n\tconst content = (result as UnknownRecord).content;\n\tif (!Array.isArray(content)) {\n\t\treturn undefined;\n\t}\n\tconst textPart = content.find(\n\t\t(c: unknown) =>\n\t\t\tisRecord(c) && c.type === \"text\" && typeof c.text === \"string\",\n\t) as UnknownRecord | undefined;\n\treturn textPart?.text as string | undefined;\n}\n\nexport function resolveToolType(\n\ttoolName: string,\n\ttoolTypeOption:\n\t\t| ToolCalledProperties[\"type\"]\n\t\t| ((toolName: string) => ToolCalledProperties[\"type\"] | undefined)\n\t\t| undefined,\n): ToolCalledProperties[\"type\"] {\n\tif (typeof toolTypeOption === \"function\") {\n\t\treturn toolTypeOption(toolName) ?? \"other\";\n\t}\n\treturn toolTypeOption ?? \"other\";\n}\n\nexport function buildTrackInput(\n\ttoolName: string,\n\textra: unknown,\n\toptions: {\n\t\ttoolType?: typeof resolveToolType extends (n: string, o: infer T) => unknown\n\t\t\t? T\n\t\t\t: never;\n\t\tmetadata?: UnknownRecord;\n\t},\n\ttiming?: { durationMs: number; status: string; errorMessage?: string },\n\tclientInfo?: { name: string; version: string },\n\tio?: { input?: unknown; output?: unknown },\n): TrackInput {\n\tconst toolType = resolveToolType(toolName, options.toolType);\n\tconst meta = extractMeta(extra);\n\tconsole.log(\n\t\t\"[waniwani:debug] buildTrackInput meta:\",\n\t\tJSON.stringify(meta),\n\t\t\"-> source:\",\n\t\textractSource(meta),\n\t);\n\n\treturn {\n\t\tevent: \"tool.called\",\n\t\tproperties: {\n\t\t\tname: toolName,\n\t\t\ttype: toolType,\n\t\t\t...(timing ?? {}),\n\t\t\t...(io?.input !== undefined && { input: io.input }),\n\t\t\t...(io?.output !== undefined && { output: io.output }),\n\t\t},\n\t\tmeta,\n\t\tsource: extractSource(meta),\n\t\tmetadata: {\n\t\t\t...(options.metadata ?? {}),\n\t\t\t...(clientInfo && { clientInfo }),\n\t\t},\n\t};\n}\n\nexport async function safeTrack(\n\ttracker: Pick<WaniWaniClient, \"track\">,\n\tinput: TrackInput,\n\tonError?: (error: Error) => void,\n): Promise<void> {\n\ttry {\n\t\tawait tracker.track(input);\n\t} catch (error) {\n\t\tonError?.(toError(error));\n\t}\n}\n\nexport async function safeFlush(\n\ttracker: Pick<WaniWaniClient, \"flush\">,\n\tonError?: (error: Error) => void,\n): Promise<void> {\n\ttry {\n\t\tawait tracker.flush();\n\t} catch (error) {\n\t\tonError?.(toError(error));\n\t}\n}\n\nexport async function injectWidgetConfig(\n\tresult: unknown,\n\tcache: WidgetTokenCache | null,\n\tbaseUrl: string,\n\tonError?: (error: Error) => void,\n): Promise<void> {\n\tif (!isRecord(result)) {\n\t\treturn;\n\t}\n\n\tif (!isRecord(result._meta)) {\n\t\t(result as UnknownRecord)._meta = {};\n\t}\n\n\tconst meta = (result as UnknownRecord)._meta as UnknownRecord;\n\tconst existingWaniwaniConfig = isRecord(meta.waniwani)\n\t\t? (meta.waniwani as UnknownRecord)\n\t\t: undefined;\n\tconst waniwaniConfig: UnknownRecord = {\n\t\t...(existingWaniwaniConfig ?? {}),\n\t\tendpoint:\n\t\t\texistingWaniwaniConfig?.endpoint ??\n\t\t\t`${baseUrl.replace(/\\/$/, \"\")}/api/mcp/events/v2/batch`,\n\t};\n\n\tif (cache) {\n\t\ttry {\n\t\t\tconst token = await cache.getToken();\n\t\t\tif (token) {\n\t\t\t\twaniwaniConfig.token = token;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tonError?.(toError(error));\n\t\t}\n\t}\n\n\tconst sessionId = extractSessionId(meta);\n\tif (sessionId) {\n\t\tif (!waniwaniConfig.sessionId) {\n\t\t\twaniwaniConfig.sessionId = sessionId;\n\t\t}\n\t}\n\n\tconst geoLocation = extractGeoLocation(meta);\n\tif (geoLocation !== undefined) {\n\t\tif (!waniwaniConfig.geoLocation) {\n\t\t\twaniwaniConfig.geoLocation = geoLocation;\n\t\t}\n\t}\n\n\tmeta.waniwani = waniwaniConfig;\n}\n\nexport function injectRequestMetadata(result: unknown, extra: unknown): void {\n\tconst requestMeta = extractMeta(extra);\n\tif (!requestMeta) {\n\t\treturn;\n\t}\n\n\tif (!isRecord(result)) {\n\t\treturn;\n\t}\n\n\tif (!isRecord(result._meta)) {\n\t\t(result as UnknownRecord)._meta = {};\n\t}\n\n\tconst resultMeta = (result as UnknownRecord)._meta as UnknownRecord;\n\tconst sessionId = extractSessionId(requestMeta);\n\tif (sessionId && !resultMeta[SESSION_ID_KEY]) {\n\t\tresultMeta[SESSION_ID_KEY] = sessionId;\n\t}\n\n\tconst geoLocation = extractGeoLocation(requestMeta);\n\tif (!geoLocation) {\n\t\treturn;\n\t}\n\n\tif (!resultMeta[GEO_LOCATION_KEY]) {\n\t\tresultMeta[GEO_LOCATION_KEY] = geoLocation;\n\t}\n\n\tif (!resultMeta[LEGACY_USER_LOCATION_KEY]) {\n\t\tresultMeta[LEGACY_USER_LOCATION_KEY] = geoLocation;\n\t}\n}\n\nfunction extractGeoLocation(\n\tmeta: UnknownRecord | undefined,\n): UnknownRecord | string | undefined {\n\tif (!meta) {\n\t\treturn undefined;\n\t}\n\n\tconst geoLocation = meta[GEO_LOCATION_KEY] ?? meta[LEGACY_USER_LOCATION_KEY];\n\tif (isRecord(geoLocation) || typeof geoLocation === \"string\") {\n\t\treturn geoLocation;\n\t}\n\n\treturn undefined;\n}\n\nfunction toError(error: unknown): Error {\n\tif (error instanceof Error) {\n\t\treturn error;\n\t}\n\treturn new Error(String(error));\n}\n","import type { ToolCalledProperties } from \"../../../tracking/index.js\";\nimport { createScopedClient, SCOPED_CLIENT_KEY } from \"../scoped-client.js\";\nimport type { McpServer } from \"../tools/types\";\nimport { WidgetTokenCache } from \"../widget-token.js\";\nimport {\n\tbuildTrackInput,\n\textractErrorText,\n\textractMeta,\n\tinjectRequestMetadata,\n\tinjectWidgetConfig,\n\tisRecord,\n\tsafeFlush,\n\tsafeTrack,\n\ttype WaniwaniTracker,\n} from \"./helpers.js\";\n\ntype UnknownRecord = Record<string, unknown>;\n\ntype WrappedServer = McpServer & {\n\t__waniwaniWrapped?: true;\n};\n\n/**\n * Options for withWaniwani().\n */\nexport type WithWaniwaniOptions = {\n\t/**\n\t * The WaniWani client instance. All tracking calls made through this client\n\t * during tool execution will automatically include session metadata.\n\t */\n\tclient: WaniwaniTracker;\n\t/**\n\t * Optional explicit tool type. Defaults to `\"other\"`.\n\t */\n\ttoolType?:\n\t\t| ToolCalledProperties[\"type\"]\n\t\t| ((toolName: string) => ToolCalledProperties[\"type\"] | undefined);\n\t/**\n\t * Optional metadata merged into every tracked event.\n\t */\n\tmetadata?: UnknownRecord;\n\t/**\n\t * Flush tracking transport after each tool call.\n\t */\n\tflushAfterToolCall?: boolean;\n\t/**\n\t * Optional error callback for non-fatal tracking errors.\n\t */\n\tonError?: (error: Error) => void;\n\t/**\n\t * Inject widget tracking config into tool response `_meta.waniwani` so browser\n\t * widgets can send events directly to the WaniWani backend.\n\t *\n\t * Always injects `endpoint`. Injects `token` when an API key is configured\n\t * and token minting succeeds.\n\t *\n\t * @default true\n\t */\n\tinjectWidgetToken?: boolean;\n};\n\nconst DEFAULT_BASE_URL = \"https://app.waniwani.ai\";\n\n/**\n * Wrap an MCP server so tool handlers automatically emit `tool.called` events.\n *\n * The wrapper intercepts `server.registerTool(...)`, tracks each invocation,\n * then forwards execution to the original tool handler.\n *\n * When `injectWidgetToken` is enabled (default), tracking config is injected\n * into tool response `_meta.waniwani` so browser widgets can post events\n * directly to the WaniWani backend without a server-side proxy.\n */\nexport function withWaniwani(\n\tserver: McpServer,\n\toptions: WithWaniwaniOptions,\n): McpServer {\n\tconst wrappedServer = server as WrappedServer;\n\tif (wrappedServer.__waniwaniWrapped) {\n\t\treturn wrappedServer;\n\t}\n\n\twrappedServer.__waniwaniWrapped = true;\n\n\tconst tracker = options.client;\n\tconst injectToken = options.injectWidgetToken !== false;\n\n\tlet tokenCache: WidgetTokenCache | null = null;\n\n\tfunction getTokenCache(): WidgetTokenCache | null {\n\t\tif (tokenCache) {\n\t\t\treturn tokenCache;\n\t\t}\n\t\tconst apiKey = tracker._config.apiKey;\n\t\tif (!apiKey) {\n\t\t\treturn null;\n\t\t}\n\t\ttokenCache = new WidgetTokenCache({\n\t\t\tbaseUrl: tracker._config.baseUrl ?? DEFAULT_BASE_URL,\n\t\t\tapiKey,\n\t\t});\n\t\treturn tokenCache;\n\t}\n\n\tconst originalRegisterTool = server.registerTool.bind(server) as (\n\t\t...args: unknown[]\n\t) => unknown;\n\n\twrappedServer.registerTool = ((...args: unknown[]) => {\n\t\tconst [toolNameRaw, config, handlerRaw] = args;\n\t\tconst toolName =\n\t\t\ttypeof toolNameRaw === \"string\" && toolNameRaw.trim().length > 0\n\t\t\t\t? toolNameRaw\n\t\t\t\t: \"unknown\";\n\n\t\tif (typeof handlerRaw !== \"function\") {\n\t\t\treturn originalRegisterTool(...args);\n\t\t}\n\n\t\tconst handler = handlerRaw as (\n\t\t\tinput: unknown,\n\t\t\textra: unknown,\n\t\t) => Promise<unknown> | unknown;\n\n\t\tconst wrappedHandler = async (input: unknown, extra: unknown) => {\n\t\t\t// Inject scoped client into extra so createTool/flows can surface it\n\t\t\tconst meta = extractMeta(extra) ?? {};\n\t\t\tconst scopedClient = createScopedClient(tracker, meta);\n\t\t\tif (isRecord(extra)) {\n\t\t\t\t(extra as UnknownRecord)[SCOPED_CLIENT_KEY] = scopedClient;\n\t\t\t}\n\n\t\t\tconst startTime = performance.now();\n\t\t\tconst clientInfo = (\n\t\t\t\tserver as {\n\t\t\t\t\tserver?: {\n\t\t\t\t\t\tgetClientVersion?: () =>\n\t\t\t\t\t\t\t| { name: string; version: string }\n\t\t\t\t\t\t\t| undefined;\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t).server?.getClientVersion?.();\n\t\t\ttry {\n\t\t\t\tconst result = await handler(input, extra);\n\t\t\t\tconst durationMs = Math.round(performance.now() - startTime);\n\n\t\t\t\tconst isErrorResult =\n\t\t\t\t\tisRecord(result) && (result as UnknownRecord).isError === true;\n\n\t\t\t\tif (isErrorResult) {\n\t\t\t\t\tconst errorText = extractErrorText(result);\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`[waniwani] Tool \"${toolName}\" returned error${errorText ? `: ${errorText}` : \"\"}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tawait safeTrack(\n\t\t\t\t\ttracker,\n\t\t\t\t\tbuildTrackInput(\n\t\t\t\t\t\ttoolName,\n\t\t\t\t\t\textra,\n\t\t\t\t\t\toptions,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdurationMs,\n\t\t\t\t\t\t\tstatus: isErrorResult ? \"error\" : \"ok\",\n\t\t\t\t\t\t\t...(isErrorResult && {\n\t\t\t\t\t\t\t\terrorMessage: extractErrorText(result) ?? \"Unknown tool error\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tclientInfo,\n\t\t\t\t\t\t{ input, output: result },\n\t\t\t\t\t),\n\t\t\t\t\toptions.onError,\n\t\t\t\t);\n\n\t\t\t\tif (options.flushAfterToolCall) {\n\t\t\t\t\tawait safeFlush(tracker, options.onError);\n\t\t\t\t}\n\n\t\t\t\tinjectRequestMetadata(result, extra);\n\n\t\t\t\tif (injectToken) {\n\t\t\t\t\tawait injectWidgetConfig(\n\t\t\t\t\t\tresult,\n\t\t\t\t\t\tgetTokenCache(),\n\t\t\t\t\t\ttracker._config.baseUrl ?? DEFAULT_BASE_URL,\n\t\t\t\t\t\toptions.onError,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\treturn result;\n\t\t\t} catch (error) {\n\t\t\t\tconst durationMs = Math.round(performance.now() - startTime);\n\n\t\t\t\tawait safeTrack(\n\t\t\t\t\ttracker,\n\t\t\t\t\tbuildTrackInput(\n\t\t\t\t\t\ttoolName,\n\t\t\t\t\t\textra,\n\t\t\t\t\t\toptions,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdurationMs,\n\t\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\t\terrorMessage:\n\t\t\t\t\t\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tclientInfo,\n\t\t\t\t\t\t{ input },\n\t\t\t\t\t),\n\t\t\t\t\toptions.onError,\n\t\t\t\t);\n\n\t\t\t\tif (options.flushAfterToolCall) {\n\t\t\t\t\tawait safeFlush(tracker, options.onError);\n\t\t\t\t}\n\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t};\n\n\t\treturn originalRegisterTool(toolNameRaw, config, wrappedHandler);\n\t}) as McpServer[\"registerTool\"];\n\n\treturn wrappedServer;\n}\n"],"mappings":"AAWO,SAASA,GAAiC,CAChD,OAAI,OAAO,OAAW,KAAe,WAAY,OACzC,SAED,UACR,CAKO,SAASC,IAAoB,CACnC,OAAOD,EAAe,IAAM,QAC7B,CAKO,SAASE,IAAqB,CACpC,OAAOF,EAAe,IAAM,UAC7B,CClBO,IAAMG,EAAQ,YACRC,EAAM,UAMbC,GAAY,OAAO,IAAI,yBAAyB,EAChDC,GAAS,OAAO,IAAI,sBAAsB,EAyDzC,SAASC,GACfC,EACAC,EACkB,CAClB,IAAMC,EAAUD,GAAQ,QAClBE,EAAiC,CAAC,EAExC,OAAW,CAACC,EAAKC,CAAK,IAAK,OAAO,QAAQL,CAAM,EAC/C,GAAI,OAAOK,GAAU,UAAYA,IAAU,MAAQ,aAAcA,EAAO,CACvE,IAAMC,EAAID,EASVF,EAAU,KAAK,CACd,SAAUG,EAAE,SACZ,MAAOF,EACP,YAAaE,EAAE,YACf,QAASA,EAAE,QACX,SAAUA,EAAE,QACb,CAAC,CACF,CAGD,MAAO,CACN,OAAQT,GACR,UAAAM,EACA,QAAAD,CACD,CACD,CAMO,SAASK,GACfC,EACAP,EAMe,CACf,MAAO,CAAE,OAAQH,GAAQ,KAAAU,EAAM,GAAGP,CAAO,CAC1C,CAEO,SAASQ,GAAYJ,EAA0C,CACrE,OACC,OAAOA,GAAU,UACjBA,IAAU,MACV,WAAYA,GACXA,EAA0B,SAAWR,EAExC,CAEO,SAASa,GAASL,EAAuC,CAC/D,OACC,OAAOA,GAAU,UACjBA,IAAU,MACV,WAAYA,GACXA,EAAuB,SAAWP,EAErC,CC1IA,OAAS,KAAAa,MAAS,MCCX,IAAMC,EAAoB,kBA4B1B,SAASC,EACfC,EACmC,CACnC,GAAI,OAAOA,GAAU,UAAYA,IAAU,KAC1C,OAAQA,EAAkCF,CAAiB,CAK7D,CAEO,SAASG,GACfC,EACAC,EACuB,CACvB,MAAO,CACN,MAAMC,EAAO,CACZ,OAAOF,EAAK,MAAM,CACjB,GAAGE,EACH,KAAM,CAAE,GAAGD,EAAM,GAAGC,EAAM,IAAK,CAChC,CAAC,CACF,EACA,SAASC,EAAQC,EAAY,CAC5B,OAAOJ,EAAK,SAASG,EAAQC,EAAYH,CAAI,CAC9C,EACA,GAAID,EAAK,EACV,CACD,CCzDA,SAASK,EACRC,EACAC,EACqB,CACrB,QAAWC,KAAOD,EAAM,CACvB,IAAME,EAAQH,EAAKE,CAAG,EACtB,GAAI,OAAOC,GAAU,UAAYA,EAAM,OAAS,EAC/C,OAAOA,CAET,CAED,CAIA,IAAMC,GAAkB,CACvB,qBACA,mBACA,YACA,iBACA,qBACD,EAEMC,GAAkB,CACvB,qBACA,mBACA,YACA,eACD,EAEMC,GAAgB,CACrB,mBACA,iBACA,UACA,cACA,mBACA,WACD,EAEMC,GAAwB,CAC7B,kBACA,gBACA,iBACA,SACA,SACD,EAEMC,GAAsB,CAAC,gBAAiB,kBAAkB,EAIzD,SAASC,EACfT,EACqB,CACrB,OAAOA,EAAOD,EAAUC,EAAMI,EAAe,EAAI,MAClD,CAEO,SAASM,GACfV,EACqB,CACrB,OAAOA,EAAOD,EAAUC,EAAMK,EAAe,EAAI,MAClD,CAEO,SAASM,GACfX,EACqB,CACrB,OAAOA,EAAOD,EAAUC,EAAMM,EAAa,EAAI,MAChD,CAEO,SAASM,GACfZ,EACqB,CACrB,OAAOA,EAAOD,EAAUC,EAAMO,EAAqB,EAAI,MACxD,CAEO,SAASM,GACfb,EACqB,CACrB,OAAOA,EAAOD,EAAUC,EAAMQ,EAAmB,EAAI,MACtD,CAEA,IAAMM,GAAsB,CAC3B,CAAE,IAAK,qBAAsB,OAAQ,SAAU,EAC/C,CAAE,IAAK,mBAAoB,OAAQ,SAAU,EAC7C,CAAE,IAAK,sBAAuB,OAAQ,QAAS,CAChD,EAEO,SAASC,EACff,EACqB,CACrB,GAAI,CAACA,EACJ,OAGD,IAAMgB,EAAWhB,EAAK,iBAAiB,EACvC,GAAI,OAAOgB,GAAa,UAAYA,EAAS,OAAS,EACrD,OAAOA,EAGR,OAAW,CAAE,IAAAd,EAAK,OAAAe,CAAO,IAAKH,GAAqB,CAClD,IAAMX,EAAQH,EAAKE,CAAG,EACtB,GAAI,OAAOC,GAAU,UAAYA,EAAM,OAAS,EAC/C,OAAOc,CAET,CAED,CCjGO,SAASC,GACfC,EACmC,CACnC,IAAMC,EACLD,EAGC,MAAM,IACR,OAAIC,GAAK,OAAS,UAAYA,EAAI,MAC1BA,EAAI,MAEL,IACR,CAOO,SAASC,EACfC,EACAC,EACU,CACV,IAAMC,EAAQD,EAAK,MAAM,GAAG,EACxBE,EAAmBH,EACvB,QAAWI,KAAQF,EAAO,CACzB,GAAIC,GAAW,MAAQ,OAAOA,GAAY,SACzC,OAEDA,EAAWA,EAAoCC,CAAI,CACpD,CACA,OAAOD,CACR,CAGO,SAASE,GACfL,EACAC,EACAK,EACO,CACP,IAAMJ,EAAQD,EAAK,MAAM,GAAG,EACtBM,EAAUL,EAAM,IAAI,EAC1B,GAAI,CAACK,EACJ,OAED,IAAIJ,EAAUH,EACd,QAAWI,KAAQF,GAEjBC,EAAQC,CAAI,GAAK,MACjB,OAAOD,EAAQC,CAAI,GAAM,UACzB,MAAM,QAAQD,EAAQC,CAAI,CAAC,KAE3BD,EAAQC,CAAI,EAAI,CAAC,GAElBD,EAAUA,EAAQC,CAAI,EAEvBD,EAAQI,CAAO,EAAID,CACpB,CAGO,SAASE,GACfR,EACAC,EACO,CACP,IAAMC,EAAQD,EAAK,MAAM,GAAG,EACtBM,EAAUL,EAAM,IAAI,EAC1B,GAAI,CAACK,EACJ,OAED,IAAIJ,EAAmBH,EACvB,QAAWI,KAAQF,EAAO,CACzB,GAAIC,GAAW,MAAQ,OAAOA,GAAY,SACzC,OAEDA,EAAWA,EAAoCC,CAAI,CACpD,CACID,GAAW,MAAQ,OAAOA,GAAY,UACzC,OAAQA,EAAoCI,CAAO,CAErD,CAUO,SAASE,EACfC,EAC0B,CAC1B,IAAMC,EAAkC,CAAC,EACzC,OAAW,CAACC,EAAKN,CAAK,IAAK,OAAO,QAAQI,CAAI,EACzCE,EAAI,SAAS,GAAG,EACnBP,GAAeM,EAAQC,EAAKN,CAAK,EAEjCK,EAAOC,CAAG,EAAIN,EAGhB,OAAOK,CACR,CAMO,SAASE,EACfC,EACAC,EAC0B,CAC1B,IAAMJ,EAAS,CAAE,GAAGG,CAAO,EAC3B,OAAW,CAACF,EAAKN,CAAK,IAAK,OAAO,QAAQS,CAAM,EAE9CT,IAAU,MACV,OAAOA,GAAU,UACjB,CAAC,MAAM,QAAQA,CAAK,GACpBK,EAAOC,CAAG,IAAM,MAChB,OAAOD,EAAOC,CAAG,GAAM,UACvB,CAAC,MAAM,QAAQD,EAAOC,CAAG,CAAC,EAE1BD,EAAOC,CAAG,EAAIC,EACbF,EAAOC,CAAG,EACVN,CACD,EAEAK,EAAOC,CAAG,EAAIN,EAGhB,OAAOK,CACR,CChIO,SAASK,EAASC,EAAqB,CAC7C,OAA0BA,GAAM,MAAQA,IAAM,EAC/C,CAWA,eAAsBC,EACrBC,EACAC,EACkB,CAClB,OAAID,EAAK,OAAS,SACVA,EAAK,GAENA,EAAK,UAAUC,CAAK,CAC5B,CAaO,SAASC,GACfC,EACAC,EACAC,EACAJ,EACyB,CAEzB,GACCE,EAAU,MAAOG,GAChBT,EAASU,EAAeN,EAAkCK,EAAE,KAAK,CAAC,CACnE,EAEA,OAAO,KAIR,IAAME,EAAaL,EAAU,OAC3BG,GAAM,CAACT,EAASU,EAAeN,EAAkCK,EAAE,KAAK,CAAC,CAC3E,EAGMG,EAAWD,EAAW,SAAW,EACjCE,EAAKF,EAAW,CAAC,EAgBvB,MAAO,CACN,QAfAC,GAAYC,EACT,CACA,OAAQ,YACR,SAAUA,EAAG,SACb,MAAOA,EAAG,MACV,GAAIA,EAAG,YAAc,CAAE,YAAaA,EAAG,WAAY,EAAI,CAAC,EACxD,GAAIA,EAAG,SAAWN,EAAU,CAAE,QAASM,EAAG,SAAWN,CAAQ,EAAI,CAAC,CACnE,EACC,CACA,OAAQ,YACR,UAAWI,EACX,GAAIJ,EAAU,CAAE,QAAAA,CAAQ,EAAI,CAAC,CAC9B,EAIF,iBAAkB,CACjB,KAAMC,EACN,MAAAJ,EACA,GAAIQ,GAAYC,EAAK,CAAE,MAAOA,EAAG,KAAM,EAAI,CAAC,CAC7C,CACD,CACD,CAMA,eAAsBC,EACrBC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EAC2B,CAC3B,IAAIb,EAAcO,EACdX,EAAQ,CAAE,GAAGY,CAAW,EAGtBM,EAAiB,GACnBC,EAAa,EAEjB,KAAOA,IAAeD,GAAgB,CAErC,GAAId,IAAgBgB,EACnB,MAAO,CACN,QAAS,CAAE,OAAQ,UAAW,EAC9B,iBAAkB,CAAE,MAAApB,CAAM,CAC3B,EAGD,IAAMqB,EAAUR,EAAM,IAAIT,CAAW,EACrC,GAAI,CAACiB,EACJ,MAAO,CACN,QAAS,CACR,OAAQ,QACR,MAAO,kBAAkBjB,CAAW,GACrC,CACD,EAGD,GAAI,CAaH,IAAMkB,EAAS,MAAMD,EAXT,CACX,MAAArB,EACA,KAAAgB,EACA,UAAWO,GAGX,WAAYC,GAGZ,SAAAP,CACD,CACiE,EAGjE,GAAIQ,GAAYH,CAAM,EAAG,CAExB,QAAWjB,KAAKiB,EAAO,UAClBjB,EAAE,UACLU,EAAW,IAAI,GAAGX,CAAW,IAAIC,EAAE,KAAK,GAAIA,EAAE,QAAQ,EAIxD,IAAMqB,EAAkBzB,GACvBqB,EAAO,UACPA,EAAO,QACPlB,EACAJ,CACD,EAEA,GAAI0B,EACH,OAAOA,EAIR,QAAWrB,KAAKiB,EAAO,UAAW,CACjC,IAAMK,EAAKZ,EAAW,IAAI,GAAGX,CAAW,IAAIC,EAAE,KAAK,EAAE,EACrD,GAAIsB,EACH,GAAI,CACH,IAAMC,EAAQtB,EACbN,EACAK,EAAE,KACH,EACMwB,EAAU,MAAMF,EAAGC,CAAK,EAC1BC,GAAW,OAAOA,GAAY,WACjC7B,EAAQ8B,EACP9B,EACA6B,CACD,EAEF,OAASE,EAAK,CACb,IAAMC,EAAMD,aAAe,MAAQA,EAAI,QAAU,OAAOA,CAAG,EAC3DE,GAAkBjC,EAAkCK,EAAE,KAAK,EAC3D,IAAM6B,EAAqBZ,EAAO,UAAU,IAAKa,GAChDA,EAAG,QAAU9B,EAAE,MACZ,CACA,GAAG8B,EACH,QAASA,EAAG,QACT,UAAUH,CAAG;AAAA;AAAA,EAAOG,EAAG,OAAO,GAC9B,UAAUH,CAAG,EACjB,EACCG,CACJ,EACMC,EAAYnC,GACjBiC,EACAZ,EAAO,QACPlB,EACAJ,CACD,EACA,GAAIoC,EACH,OAAOA,EAER,KACD,CAEF,CAGA,IAAMrC,EAAOe,EAAM,IAAIV,CAAW,EAClC,GAAI,CAACL,EACJ,MAAO,CACN,QAAS,CACR,OAAQ,QACR,MAAO,+BAA+BK,CAAW,GAClD,CACD,EAEDA,EAAc,MAAMN,EAAgBC,EAAMC,CAAK,EAC/C,QACD,CAGA,GAAIqC,GAASf,CAAM,EAAG,CACrB,IAAMgB,EAAchB,EAAO,MAC3B,GAAIgB,GAEF1C,EACCU,EAAeN,EAAkCsC,CAAW,CAC7D,EACC,CACD,IAAMvC,EAAOe,EAAM,IAAIV,CAAW,EAClC,GAAI,CAACL,EACJ,MAAO,CACN,QAAS,CACR,OAAQ,QACR,MAAO,+BAA+BK,CAAW,GAClD,CACD,EAEDA,EAAc,MAAMN,EAAgBC,EAAMC,CAAK,EAC/C,QACD,CAGD,MAAO,CACN,QAAS,CACR,OAAQ,SACR,KAAMsB,EAAO,KAAK,GAClB,KAAMA,EAAO,KACb,YAAaA,EAAO,YACpB,YAAaA,EAAO,cAAgB,EACrC,EACA,iBAAkB,CACjB,KAAMlB,EACN,MAAAJ,EACA,MAAOsC,EACP,SAAUhB,EAAO,KAAK,EACvB,CACD,CACD,CAGAtB,EAAQ8B,EACP9B,EACAsB,CACD,EAEA,IAAMvB,EAAOe,EAAM,IAAIV,CAAW,EAClC,GAAI,CAACL,EACJ,MAAO,CACN,QAAS,CACR,OAAQ,QACR,MAAO,+BAA+BK,CAAW,GAClD,CACD,EAEDA,EAAc,MAAMN,EAAgBC,EAAMC,CAAK,CAChD,OAASuC,EAAO,CAEf,MAAO,CACN,QAAS,CAAE,OAAQ,QAAS,MAFbA,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,CAEzB,EAC3C,iBAAkB,CAAE,KAAMnC,EAAa,MAAAJ,CAAM,CAC9C,CACD,CACD,CAEA,MAAO,CACN,QAAS,CACR,OAAQ,QACR,MAAO,2DACR,CACD,CACD,CChRA,IAAMwC,GAAW,gBACXC,GAAmB,0BAEZC,EAAN,KAA6C,CAClC,QACA,OAEjB,YAAYC,EAAiD,CAC5D,KAAK,SACJA,GAAS,SACT,QAAQ,IAAI,mBACZF,IACC,QAAQ,MAAO,EAAE,EACnB,KAAK,OAASE,GAAS,QAAU,QAAQ,IAAI,gBAC9C,CAEA,MAAM,IAAIC,EAA+C,CACxD,GAAI,CAAC,KAAK,OACT,OAAO,KAER,GAAI,CAKH,OAJa,MAAM,KAAK,QACvB,qBACA,CAAE,IAAAA,CAAI,CACP,GACe,IAChB,MAAQ,CACP,OAAO,IACR,CACD,CAEA,MAAM,IAAIA,EAAaC,EAAwC,CAC9D,GAAK,KAAK,OAGV,GAAI,CACH,MAAM,KAAK,QAAQ,qBAAsB,CAAE,IAAAD,EAAK,MAAAC,CAAM,CAAC,CACxD,MAAQ,CAER,CACD,CAEA,MAAM,OAAOD,EAA4B,CACxC,GAAK,KAAK,OAGV,GAAI,CACH,MAAM,KAAK,QAAQ,wBAAyB,CAAE,IAAAA,CAAI,CAAC,CACpD,MAAQ,CAER,CACD,CAEA,MAAc,QAAWE,EAAcC,EAA2B,CACjE,IAAMC,EAAM,GAAG,KAAK,OAAO,GAAGF,CAAI,GAC5BG,EAAW,MAAM,MAAMD,EAAK,CACjC,OAAQ,OACR,QAAS,CACR,cAAe,UAAU,KAAK,MAAM,GACpC,eAAgB,mBAChB,iBAAkBR,EACnB,EACA,KAAM,KAAK,UAAUO,CAAI,CAC1B,CAAC,EAED,GAAI,CAACE,EAAS,GAAI,CACjB,IAAMC,EAAO,MAAMD,EAAS,KAAK,EAAE,MAAM,IAAM,EAAE,EACjD,MAAM,IAAI,MAAMC,GAAQ,8BAA8BD,EAAS,MAAM,EAAE,CACxE,CAGA,OADc,MAAMA,EAAS,KAAK,GACtB,IACb,CACD,EChGA,SAASE,GAAiBC,EAA2B,CACpD,IAAMC,EAAOD,EAAO,aAAe,GAC7BE,EACLF,EAGC,MAAM,IAER,GAAIE,GAAK,OAAS,QAAUA,EAAI,QAAS,CACxC,IAAMC,EAAO,OAAO,KAAKD,EAAI,OAAO,EAClC,IAAKE,GAAM,IAAIA,CAAC,GAAG,EACnB,KAAK,KAAK,EACZ,OAAOH,EAAO,GAAGE,CAAI,WAAMF,CAAI,GAAKE,CACrC,CAEA,OAAOF,CACR,CAEO,SAASI,GAAkBC,EAA4B,CAC7D,IAAMC,EAAQ,CACb,GACA,6BACA,GACA,uFACA,GACA,0EACA,4EACA,wEACA,gCACA,yFACD,EAEA,GAAID,EAAO,MAAO,CACjB,IAAME,EAAkB,CAAC,EACzB,OAAW,CAACC,EAAKT,CAAM,IAAK,OAAO,QAAQM,EAAO,KAAK,EAAG,CACzD,IAAMI,EAAQC,GAAeX,CAAM,EACnC,GAAIU,EAAO,CACV,IAAME,EAAYZ,EAAO,aAAe,GAClCa,EAAY,OAAO,QAAQH,CAAK,EACpC,IAAI,CAAC,CAACI,EAAQC,CAAS,IAAM,CAC7B,IAAMC,EAAOjB,GAAiBgB,CAAS,EACvC,OAAOC,EACJ,KAAKP,CAAG,IAAIK,CAAM,OAAOE,CAAI,IAC7B,KAAKP,CAAG,IAAIK,CAAM,IACtB,CAAC,EACA,KAAK,IAAI,EACXN,EAAM,KACLI,EACG,KAAKH,CAAG,OAAOG,CAAS,MAAMC,CAAS,GACvC,KAAKJ,CAAG,OAAOI,CAAS,EAC5B,CACD,KAAO,CACN,IAAMG,EAAOjB,GAAiBC,CAAM,EACpCQ,EAAM,KAAKQ,EAAO,KAAKP,CAAG,OAAOO,CAAI,IAAM,KAAKP,CAAG,IAAI,CACxD,CACD,CACAF,EAAM,KAAK,oBAAoBC,EAAM,KAAK,IAAI,CAAC,GAAG,CACnD,CAEA,OAAAD,EAAM,KACL,8FACA,mEACA,iEACA,yDACA,2GACA,uGACA,8DACA,iHACA,6BACA,6BACA,wGACA,yFACA,6DACA,sDACA,+GACA,kGACA,gEACA,+BACA,sGACA,8GACA,gGACA,uEACA,kEACA,GACA,uGACA,4GACA,yGACA,mFACA,2EACD,EAEOA,EAAM,KAAK;AAAA,CAAI,CACvB,CNvEA,IAAMU,GAAc,CACnB,OAAQC,EACN,KAAK,CAAC,QAAS,UAAU,CAAC,EAC1B,SACA,qFACD,EACD,aAAcA,EACZ,OAAOA,EAAE,OAAO,EAAGA,EAAE,QAAQ,CAAC,EAC9B,SAAS,EACT,SACA,oLACD,CACF,EAMO,SAASC,GACfC,EACiB,CACjB,GAAM,CAAE,OAAAC,EAAQ,MAAAC,EAAO,MAAAC,CAAM,EAAIH,EAC3BI,EAAWC,GAAkBJ,CAAM,EACnCK,EAAkB,GAAGL,EAAO,WAAW;AAAA,EAAKG,CAAQ,GAGpDG,EAAmBP,EAAM,OAAS,IAAIQ,EAItCC,EAAa,IAAI,IAEvB,eAAeC,EACdC,EACAC,EACAC,EACAC,EACC,CACD,GAAIH,EAAK,SAAW,QAAS,CAC5B,IAAMI,EAAYZ,EAAM,IAAIa,CAAK,EACjC,GAAI,CAACD,EACJ,MAAO,CACN,QAAS,CAAE,OAAQ,QAAkB,MAAO,eAAgB,CAC7D,EAGD,IAAME,EAAaC,EAAeP,EAAK,cAAgB,CAAC,CAAC,EACnDQ,EAAY,MAAMC,EAAgBL,EAAWE,CAAU,EAC7D,OAAOI,EACNF,EACAF,EACAf,EACAC,EACAM,EACAI,EACAC,CACD,CACD,CAEA,GAAIH,EAAK,SAAW,WAAY,CAC/B,GAAI,CAACC,EACJ,MAAO,CACN,QAAS,CACR,OAAQ,QACR,MAAO,8CACR,CACD,EAGD,IAAMU,EAAY,MAAMf,EAAM,IAAIK,CAAS,EAE3C,GAAI,CAACU,EACJ,MAAO,CACN,QAAS,CACR,OAAQ,QACR,MAAO,kDACR,CACD,EAGD,IAAMC,EAAQD,EAAU,MAClBE,EAAOF,EAAU,KACvB,GAAI,CAACE,EACJ,MAAO,CACN,QAAS,CACR,OAAQ,QACR,MACC,oEACF,CACD,EAGD,IAAMC,EAAeC,EACpBH,EACAL,EAAeP,EAAK,cAAgB,CAAC,CAAC,CACvC,EAGA,GAAIW,EAAU,SAAU,CACvB,IAAMK,EAAOxB,EAAM,IAAIqB,CAAI,EAC3B,GAAI,CAACG,EACJ,MAAO,CACN,QAAS,CACR,OAAQ,QACR,MAAO,sBAAsBH,CAAI,GAClC,CACD,EAED,IAAMI,EAAW,MAAMR,EAAgBO,EAAMF,CAAY,EACzD,OAAOJ,EACNO,EACAH,EACAvB,EACAC,EACAM,EACAI,EACAC,CACD,CACD,CAKA,OAAOO,EACNG,EACAC,EACAvB,EACAC,EACAM,EACAI,EACAC,CACD,CACD,CAEA,MAAO,CACN,QAAS,CACR,OAAQ,QACR,MAAO,oBAAoBH,EAAK,MAAM,GACvC,CACD,CACD,CAEA,MAAO,CACN,GAAIV,EAAO,GACX,MAAOA,EAAO,MACd,YAAaK,EACb,MAAON,EAAM,MAEb,MAAM,SAAS6B,EAAkC,CAChDA,EAAO,aACN5B,EAAO,GACP,CACC,MAAOA,EAAO,MACd,YAAaK,EACb,YAAAT,GACA,YAAaI,EAAO,WACrB,GACC,MAAOU,EAAqBmB,IAAmB,CAC/C,IAAMC,EAAeD,EAIfE,EAAiCD,EAAa,OAAS,CAAC,EACxDnB,EAAYqB,EAAiBD,CAAK,EAClClB,EAAWoB,EAAoBH,CAAY,EAE3CI,EAAS,MAAMzB,EAAeC,EAAMC,EAAWoB,EAAOlB,CAAQ,EAGpE,OAAIqB,EAAO,kBAAoBvB,GAC9B,MAAML,EAAM,IAAIK,EAAWuB,EAAO,gBAAgB,EAU5C,CACN,QARe,CACf,CACC,KAAM,OACN,KAAM,KAAK,UAAUA,EAAO,QAAS,KAAM,CAAC,CAC7C,CACD,EAIC,MAAAH,EACA,GAAIG,EAAO,QAAQ,SAAW,QAAU,CAAE,QAAS,EAAK,EAAI,CAAC,CAC9D,CACD,EACD,CACD,CACD,CACD,COlNA,SAASC,GACRC,EACAC,EACS,CACT,IAAMC,EAAkB,CAAC,cAAc,EACvCA,EAAM,KAAK,KAAKC,CAAK,WAAW,EAChC,OAAW,CAACC,CAAI,IAAKJ,EACpBE,EAAM,KAAK,KAAKE,CAAI,IAAIA,CAAI,GAAG,EAEhCF,EAAM,KAAK,KAAKG,CAAG,SAAS,EAC5B,OAAW,CAACC,EAAMC,CAAI,IAAKN,EACtBM,EAAK,OAAS,SACjBL,EAAM,KAAK,KAAKI,CAAI,QAAQC,EAAK,EAAE,EAAE,EAErCL,EAAM,KAAK,KAAKI,CAAI,SAASA,CAAI,cAAc,EAGjD,OAAOJ,EAAM,KAAK;AAAA,CAAI,CACvB,CAoBO,IAAMM,EAAN,KAGL,CACO,MAAQ,IAAI,IACZ,MAAQ,IAAI,IACZ,OAER,YAAYC,EAAoB,CAC/B,KAAK,OAASA,CACf,CAOA,QACCL,EACAM,EACqC,CACrC,GAAIN,IAASD,GAASC,IAASC,EAC9B,MAAM,IAAI,MACT,IAAID,CAAI,wDACT,EAED,GAAI,KAAK,MAAM,IAAIA,CAAI,EACtB,MAAM,IAAI,MAAM,SAASA,CAAI,kBAAkB,EAGhD,YAAK,MAAM,IAAIA,EAAMM,CAAO,EACrB,IACR,CAQA,QAAQJ,EAA6BK,EAA+B,CACnE,GAAI,KAAK,MAAM,IAAIL,CAAI,EACtB,MAAM,IAAI,MACT,SAASA,CAAI,uEACd,EAED,YAAK,MAAM,IAAIA,EAAM,CAAE,KAAM,SAAU,GAAAK,CAAG,CAAC,EACpC,IACR,CAOA,mBACCL,EACAM,EAGO,CACP,GAAI,KAAK,MAAM,IAAIN,CAAI,EACtB,MAAM,IAAI,MAAM,SAASA,CAAI,iCAAiC,EAE/D,YAAK,MAAM,IAAIA,EAAM,CAAE,KAAM,cAAe,UAAAM,CAAU,CAAC,EAChD,IACR,CAqBA,OAAgB,CACf,OAAOb,GAAkB,KAAK,MAAO,KAAK,KAAK,CAChD,CAOA,QAAQc,EAAiD,CACxD,KAAK,SAAS,EAEd,IAAMC,EAAY,IAAI,IAAI,KAAK,KAAK,EAC9BC,EAAY,IAAI,IAAI,KAAK,KAAK,EAEpC,OAAOC,GAAoB,CAC1B,OAAQ,KAAK,OACb,MAAOF,EACP,MAAOC,EACP,MAAOF,GAAS,MAChB,MAAO,IAAMd,GAAkBe,EAAWC,CAAS,CACpD,CAAC,CACF,CAEQ,UAAiB,CAExB,GAAI,CAAC,KAAK,MAAM,IAAIZ,CAAK,EACxB,MAAM,IAAI,MACT,sFACD,EAID,IAAMc,EAAY,KAAK,MAAM,IAAId,CAAK,EACtC,GACCc,GAAW,OAAS,UACpBA,EAAU,KAAOZ,GACjB,CAAC,KAAK,MAAM,IAAIY,EAAU,EAAE,EAE5B,MAAM,IAAI,MACT,6CAA6CA,EAAU,EAAE,GAC1D,EAID,OAAW,CAACX,EAAMC,CAAI,IAAK,KAAK,MAAO,CACtC,GAAID,IAASH,GAAS,CAAC,KAAK,MAAM,IAAIG,CAAI,EACzC,MAAM,IAAI,MAAM,iCAAiCA,CAAI,GAAG,EAEzD,GACCC,EAAK,OAAS,UACdA,EAAK,KAAOF,GACZ,CAAC,KAAK,MAAM,IAAIE,EAAK,EAAE,EAEvB,MAAM,IAAI,MACT,cAAcD,CAAI,oCAAoCC,EAAK,EAAE,GAC9D,CAEF,CAGA,OAAW,CAACH,CAAI,IAAK,KAAK,MACzB,GAAI,CAAC,KAAK,MAAM,IAAIA,CAAI,EACvB,MAAM,IAAI,MACT,SAASA,CAAI,kDAAkDA,CAAI,mCAAmCA,CAAI,SAC3G,CAGH,CACD,ECvKO,SAASc,GACfC,EACsC,CACtC,OAAO,IAAIC,EAAoCD,CAAM,CACtD,CCTA,SAASE,GAAaC,EAA8C,CACnE,IAAMC,EAAUD,EAAO,QACvB,OAAO,KAAK,MAAMC,EAAQ,CAAC,GAAG,MAAQ,EAAE,CACzC,CAEA,eAAsBC,GACrBC,EACAC,EACC,CACD,IAAMC,EAAQD,GAAS,WACjBE,EAAiC,CAAC,EAClCC,EAAY,gBAAgB,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,EAAG,EAAE,CAAC,GAEnEC,EAAS,CACd,aAAc,IAAIC,IAAoB,CACrCH,EAAW,KAAKG,CAAwB,CACzC,CACD,EAEA,MAAMN,EAAK,SAASK,CAAM,EAE1B,IAAME,EAAUJ,EAAW,CAAC,IAAI,CAAC,EACjC,GAAI,CAACI,EACJ,MAAM,IAAI,MAAM,SAASP,EAAK,EAAE,8BAA8B,EAG/D,IAAMQ,EAAQ,CAAE,MAAO,CAAE,UAAAJ,CAAU,CAAE,EAErC,eAAeK,EAASC,EAA8C,CACrE,MAAO,CACN,GAAGA,EACH,aAAcR,EAAQ,MAAMA,EAAM,IAAIE,CAAS,EAAI,IACpD,CACD,CAEA,MAAO,CACN,MAAM,MACLO,EAC0B,CAC1B,IAAMd,EAAU,MAAMU,EACrB,CAAE,OAAQ,QAAS,GAAII,EAAe,CAAE,aAAAA,CAAa,EAAI,CAAC,CAAG,EAC7DH,CACD,EACA,OAAOC,EAASb,GAAaC,CAAM,CAAC,CACrC,EAEA,MAAM,aACLc,EAC0B,CAC1B,IAAMd,EAAU,MAAMU,EACrB,CACC,OAAQ,WACR,GAAII,EAAe,CAAE,aAAAA,CAAa,EAAI,CAAC,CACxC,EACAH,CACD,EACA,OAAOC,EAASb,GAAaC,CAAM,CAAC,CACrC,EAEA,MAAM,WAA8C,CACnD,OAAOK,EAAQA,EAAM,IAAIE,CAAS,EAAI,IACvC,CACD,CACD,CCnFO,IAAMQ,EAAmB,sBACnBC,EAAgB,4BAIhBC,GAAY,MACxBC,EACAC,IACqB,CACrB,IAAMC,EAAiBF,EAAQ,SAAS,GAAG,EAAIA,EAAQ,MAAM,EAAG,EAAE,EAAIA,EAEtE,OAAO,MADQ,MAAM,MAAM,GAAGE,CAAc,GAAGD,CAAI,EAAE,GACjC,KAAK,CAC1B,EAYO,SAASE,GAAwBC,EAKjB,CACtB,MAAO,CACN,2BAA4BA,EAAO,YACnC,6BAA8BA,EAAO,cACrC,sBAAuBA,EAAO,aAC9B,GAAIA,EAAO,WAAa,CAAE,mBAAoBA,EAAO,SAAU,CAChE,CACD,CAkBO,SAASC,GAAyBD,EAKjB,CACvB,IAAME,EAAMF,EAAO,UAChB,CACA,eAAgBA,EAAO,UAAU,gBACjC,gBAAiBA,EAAO,UAAU,iBAClC,aAAcA,EAAO,UAAU,cAC/B,gBAAiBA,EAAO,UAAU,gBACnC,EACC,OAEH,MAAO,CACN,GAAI,CACH,GAAIE,GAAO,CAAE,IAAAA,CAAI,EACjB,GAAIF,EAAO,cAAgB,CAAE,OAAQA,EAAO,YAAa,EACzD,GAAIA,EAAO,gBAAkB,QAAa,CACzC,cAAeA,EAAO,aACvB,CACD,CACD,CACD,CAIO,SAASG,EAAcH,EAM3B,CACF,MAAO,CAEN,GAAIA,EAAO,mBAAqB,CAC/B,wBAAyBA,EAAO,iBACjC,EACA,iCAAkCA,EAAO,SACzC,gCAAiCA,EAAO,QACxC,0BAA2B,GAC3B,gCAAiC,GAEjC,GAAIA,EAAO,gBAAkB,CAC5B,GAAI,CACH,YAAaA,EAAO,eACpB,GAAIA,EAAO,YAAc,CAAE,WAAY,EAAK,CAC7C,CACD,EAEA,GAAIA,EAAO,gBAAkB,CAC5B,iBAAkBA,EAAO,cAC1B,CACD,CACD,CC3FO,SAASI,GAAeC,EAA4C,CAC1E,GAAM,CACL,GAAAC,EACA,MAAAC,EACA,YAAAC,EACA,QAAAC,EACA,SAAAC,EACA,aAAAC,EACA,cAAAC,EAAgB,GAChB,WAAAC,EAAa,EACd,EAAIR,EAGAS,EAAYT,EAAO,WAAa,CACnC,gBAAiB,CAACI,CAAO,EACzB,iBAAkB,CAACA,CAAO,CAC3B,EAIA,GAAI,QAAQ,IAAI,WAAa,cAC5B,GAAI,CACH,GAAM,CAAE,SAAAM,CAAS,EAAI,IAAI,IAAIN,CAAO,GAChCM,IAAa,aAAeA,IAAa,eAC5CD,EAAY,CACX,GAAGA,EACH,gBAAiB,CAChB,GAAIA,EAAU,iBAAmB,CAAC,EAClC,QAAQC,CAAQ,KAChB,SAASA,CAAQ,IAClB,EACA,iBAAkB,CACjB,GAAID,EAAU,kBAAoB,CAAC,EACnC,UAAUC,CAAQ,IACnB,CACD,EAEF,MAAQ,CAER,CAGD,IAAMC,EAAY,yBAAyBV,CAAE,QACvCW,EAAS,yBAAyBX,CAAE,QAGtCY,EAAsC,KACpCC,EAAU,KACVD,IACJA,EAAcE,GAAUX,EAASC,CAAQ,GAEnCQ,GAIFG,EAAgBb,EAEtB,eAAec,EAASC,EAAkC,CACzD,IAAMC,EAAO,MAAML,EAAQ,EAG3BI,EAAO,iBACN,GAAGjB,CAAE,iBACLU,EACA,CACC,MAAAT,EACA,YAAac,EACb,SAAUI,EACV,MAAO,CACN,2BAA4BJ,EAC5B,6BAA8BT,CAC/B,CACD,EACA,MAAOc,IAAS,CACf,SAAU,CACT,CACC,IAAKA,EAAI,KACT,SAAUD,EACV,KAAMD,EACN,MAAOG,GAAwB,CAC9B,YAAaN,EACb,cAAAT,EACA,aAAAD,EACA,UAAAG,CACD,CAAC,CACF,CACD,CACD,EACD,EAGAS,EAAO,iBACN,GAAGjB,CAAE,cACLW,EACA,CACC,MAAAV,EACA,YAAac,EACb,SAAUO,EACV,MAAO,CACN,GAAI,CACH,cAAAhB,CACD,CACD,CACD,EACA,MAAOc,IAAS,CACf,SAAU,CACT,CACC,IAAKA,EAAI,KACT,SAAUE,EACV,KAAMJ,EACN,MAAOK,GAAyB,CAC/B,YAAaR,EACb,cAAAT,EACA,aAAAD,EACA,UAAAG,CACD,CAAC,CACF,CACD,CACD,EACD,CACD,CAEA,MAAO,CACN,GAAAR,EACA,MAAAC,EACA,YAAAC,EACA,UAAAQ,EACA,OAAAC,EACA,WAAAJ,EACA,SAAAS,CACD,CACD,CC/GO,SAASQ,GACfC,EACAC,EACiB,CACjB,GAAM,CACL,SAAAC,EACA,YAAAC,EACA,YAAAC,EACA,YAAAC,EACA,qBAAAC,EAAuB,EACxB,EAAIN,EAEEO,EAAKP,EAAO,IAAME,GAAU,GAC5BM,EAAQR,EAAO,OAASE,GAAU,MAExC,GAAI,CAACK,EACJ,MAAM,IAAI,MACT,2DACD,EAED,GAAI,CAACC,EACJ,MAAM,IAAI,MACT,8DACD,EAID,IAAMC,EAAWP,EACdQ,EAAc,CACd,kBAAmBR,EAAS,UAC5B,eAAgBA,EAAS,OACzB,SAAUF,EAAO,UAAY,aAC7B,QAASA,EAAO,SAAW,SAC3B,WAAYE,EAAS,UACtB,CAAC,EACA,OAEH,MAAO,CACN,GAAAK,EACA,MAAAC,EACA,YAAAL,EAEA,MAAM,SAASQ,EAAkC,CAChDA,EAAO,aACNJ,EACA,CACC,MAAAC,EACA,YAAAL,EACA,YAAAC,EACA,YAAAC,EACA,GAAII,GAAY,CAAE,MAAOA,CAAS,CACnC,GACC,MAAOG,EAA2BC,IAAmB,CACrD,IAAMC,EAAeD,EAIfE,EAAiCD,EAAa,OAAS,CAAC,EACxDE,EAAWC,EAAoBH,CAAY,EAE3CI,EAAS,MAAMjB,EAAQW,EAAM,CAAE,MAAO,CAAE,MAAAG,CAAM,EAAG,SAAAC,CAAS,CAAC,EAGjE,OAAId,GAAYgB,EAAO,KACf,CACN,QAAS,CAAC,CAAE,KAAM,OAAQ,KAAMA,EAAO,IAAK,CAAC,EAC7C,kBAAmBA,EAAO,KAC1B,MAAO,CACN,GAAGT,EACH,GAAGM,EACH,GAAIT,IAAyB,GAC1B,CAAE,gCAAiC,EAAM,EACzC,CAAC,CACL,CACD,EAIM,CACN,QAAS,CAAC,CAAE,KAAM,OAAiB,KAAMY,EAAO,IAAK,CAAC,EACtD,GAAIA,EAAO,KAAO,CAAE,kBAAmBA,EAAO,IAAK,EAAI,CAAC,EACxD,GAAIZ,IAAyB,GAC1B,CACA,MAAO,CACN,gCAAiC,EAClC,CACD,EACC,CAAC,CACL,CACD,EACD,CACD,CACD,CACD,CAKA,eAAsBa,GACrBR,EACAS,EACgB,CAChB,MAAM,QAAQ,IAAIA,EAAM,IAAKC,GAAMA,EAAE,SAASV,CAAM,CAAC,CAAC,CACvD,CCnJO,IAAMW,EAAN,cAA4B,KAAM,CACxC,YACCC,EACOC,EACN,CACD,MAAMD,CAAO,EAFN,YAAAC,EAGP,KAAK,KAAO,eACb,CACD,ECGA,IAAMC,GAAW,gBAEV,SAASC,GAAeC,EAAkC,CAChE,GAAM,CAAE,QAAAC,EAAS,OAAAC,CAAO,EAAIF,EAE5B,SAASG,GAAwB,CAChC,GAAI,CAACD,EACJ,MAAM,IAAI,MAAM,6BAA6B,EAE9C,OAAOA,CACR,CAEA,eAAeE,EACdC,EACAC,EACAC,EACa,CACb,IAAMC,EAAML,EAAc,EACpBM,EAAM,GAAGR,EAAQ,QAAQ,MAAO,EAAE,CAAC,GAAGK,CAAI,GAE1CI,EAAkC,CACvC,cAAe,UAAUF,CAAG,GAC5B,iBAAkBV,EACnB,EAEMa,EAAoB,CAAE,OAAAN,EAAQ,QAAAK,CAAQ,EAExCH,IAAS,SACZG,EAAQ,cAAc,EAAI,mBAC1BC,EAAK,KAAO,KAAK,UAAUJ,CAAI,GAGhC,IAAMK,EAAW,MAAM,MAAMH,EAAKE,CAAI,EAEtC,GAAI,CAACC,EAAS,GAAI,CACjB,IAAMC,EAAO,MAAMD,EAAS,KAAK,EAAE,MAAM,IAAM,EAAE,EACjD,MAAM,IAAIE,EACTD,GAAQ,sBAAsBD,EAAS,MAAM,GAC7CA,EAAS,MACV,CACD,CAGA,OADc,MAAMA,EAAS,KAAK,GACtB,IACb,CAEA,MAAO,CACN,MAAM,OAAOG,EAAgD,CAC5D,OAAOX,EAAwB,OAAQ,qBAAsB,CAC5D,MAAAW,CACD,CAAC,CACF,EAEA,MAAM,OACLC,EACAC,EAC0B,CAC1B,OAAOb,EAAwB,OAAQ,qBAAsB,CAC5D,MAAAY,EACA,GAAGC,CACJ,CAAC,CACF,EAEA,MAAM,SAA+B,CACpC,OAAOb,EAAoB,MAAO,qBAAqB,CACxD,CACD,CACD,CCrEA,IAAMc,GAAiB,gBAQhB,SAASC,EACfC,EACAC,EAAgC,CAAC,EACf,CAClB,IAAMC,EAAMD,EAAQ,MAAQ,IAAM,IAAI,MAChCE,EAAaF,EAAQ,YAAcG,GACnCC,EAAYC,GAAiBN,CAAK,EAClCO,EAAOC,EAASR,EAAM,IAAI,EAC1BS,EAAWD,EAASR,EAAM,QAAQ,EAClCU,EAAcC,GAAsBX,EAAOO,CAAI,EAC/CK,EAAUC,EAAmBb,EAAM,OAAO,GAAKG,EAAW,EAC1DW,EAAYC,GAAmBf,EAAM,UAAWE,CAAG,EACnDc,EACLH,EAAmBb,EAAM,MAAM,GAC/BiB,EAAcV,CAAI,GAClBN,EAAQ,QACRH,GACKoB,EAAYC,EAAmBnB,CAAK,EAAI,CAAE,GAAGA,CAAM,EAAI,OAEvDoB,EAA0C,CAC/C,GAAGX,CACJ,EACA,OAAI,OAAO,KAAKF,CAAI,EAAE,OAAS,IAC9Ba,EAAe,KAAOb,GAEnBW,IACHE,EAAe,UAAYF,GAGrB,CACN,GAAIN,EACJ,KAAM,YACN,KAAMP,EACN,OAAAW,EACA,UAAAF,EACA,YAAAJ,EACA,WAAYW,GAAcrB,EAAOK,CAAS,EAC1C,SAAUe,EACV,UAAAF,CACD,CACD,CAEO,SAASd,IAAwB,CACvC,OACC,OAAO,OAAW,KAClB,OAAO,OAAO,YAAe,WAEtB,OAAO,OAAO,WAAW,CAAC,GAG3B,OAAO,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,EAAG,EAAE,CAAC,IAAI,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,EACjF,CAEA,SAASiB,GACRrB,EACAK,EAC0B,CAC1B,GAAI,CAACc,EAAmBnB,CAAK,EAC5B,OAAOQ,EAASR,EAAM,UAAU,EAGjC,IAAMsB,EAAmBC,GAAoBvB,EAAOK,CAAS,EACvDmB,EAAqBhB,EAASR,EAAM,UAAU,EACpD,MAAO,CACN,GAAGsB,EACH,GAAGE,CACJ,CACD,CAEA,SAASD,GACRvB,EACAK,EAC0B,CAC1B,OAAQA,EAAW,CAClB,IAAK,cAAe,CACnB,IAAMoB,EAAsC,CAAC,EAC7C,OAAIZ,EAAmBb,EAAM,QAAQ,IACpCyB,EAAW,KAAOzB,EAAM,UAErBa,EAAmBb,EAAM,QAAQ,IACpCyB,EAAW,KAAOzB,EAAM,UAElByB,CACR,CACA,IAAK,kBAAmB,CACvB,IAAMA,EAAsC,CAAC,EAC7C,OAAI,OAAOzB,EAAM,aAAgB,WAChCyB,EAAW,OAASzB,EAAM,aAEvBa,EAAmBb,EAAM,aAAa,IACzCyB,EAAW,SAAWzB,EAAM,eAEtByB,CACR,CACA,IAAK,eAAgB,CACpB,IAAMA,EAAsC,CAAC,EAC7C,OAAIZ,EAAmBb,EAAM,OAAO,IACnCyB,EAAW,IAAMzB,EAAM,SAEjByB,CACR,CACA,IAAK,qBAAsB,CAC1B,IAAMA,EAAsC,CAAC,EAC7C,OAAI,OAAOzB,EAAM,gBAAmB,WACnCyB,EAAW,OAASzB,EAAM,gBAEvBa,EAAmBb,EAAM,gBAAgB,IAC5CyB,EAAW,SAAWzB,EAAM,kBAEtByB,CACR,CACA,QACC,MAAO,CAAC,CACV,CACD,CAEA,SAASnB,GAAiBN,EAA8B,CACvD,OAAImB,EAAmBnB,CAAK,EACpBA,EAAM,UAEPA,EAAM,KACd,CAEA,SAASW,GACRX,EACAO,EACmB,CACnB,IAAMmB,EACLb,EAAmBb,EAAM,SAAS,GAAK2B,GAAiBpB,CAAI,EAEvDqB,EACLf,EAAmBb,EAAM,SAAS,GAAK6B,EAAiBtB,CAAI,EAEvDuB,EAAUjB,EAAmBb,EAAM,OAAO,GAAK+B,GAAexB,CAAI,EAElEyB,EACLnB,EAAmBb,EAAM,cAAc,GAAKiC,GAAsB1B,CAAI,EAEjE2B,EACLrB,EAAmBb,EAAM,aAAa,GACtCmC,GAAqB5B,CAAI,GACzBmB,EAEKhB,EAAgC,CAAC,EACvC,OAAIkB,IACHlB,EAAY,UAAYkB,GAErBE,IACHpB,EAAY,QAAUoB,GAEnBJ,IACHhB,EAAY,UAAYgB,GAErBQ,IACHxB,EAAY,cAAgBwB,GAEzBF,IACHtB,EAAY,eAAiBsB,GAEvBtB,CACR,CAEA,SAASK,GACRf,EACAE,EACS,CACT,GAAIF,aAAiB,KACpB,OAAOA,EAAM,YAAY,EAE1B,GAAI,OAAOA,GAAU,SAAU,CAC9B,IAAMoC,EAAO,IAAI,KAAKpC,CAAK,EAC3B,GAAI,CAAC,OAAO,MAAMoC,EAAK,QAAQ,CAAC,EAC/B,OAAOA,EAAK,YAAY,CAE1B,CACA,OAAOlC,EAAI,EAAE,YAAY,CAC1B,CAEA,SAASM,EAAS6B,EAAyC,CAC1D,MAAI,CAACA,GAAS,OAAOA,GAAU,UAAY,MAAM,QAAQA,CAAK,EACtD,CAAC,EAEFA,CACR,CAEA,SAASxB,EAAmBwB,EAAoC,CAC/D,GAAI,OAAOA,GAAU,UAGjBA,EAAM,KAAK,EAAE,SAAW,EAG5B,OAAOA,CACR,CAEA,SAASlB,EAAmBnB,EAA8C,CACzE,MAAO,cAAeA,CACvB,CC7MA,IAAMsC,GAAwB,2BAQ9B,IAAMC,GAAW,gBAEXC,GAAsB,IAAI,IAAI,CAAC,IAAK,GAAG,CAAC,EACxCC,GAAmB,IAAI,IAAI,CAAC,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,GAAG,CAAC,EA6C7D,SAASC,GACfC,EACmB,CACnB,OAAO,IAAIC,GAAoBD,CAAO,CACvC,CAEA,IAAMC,GAAN,KAAsD,CACpC,YACA,gBACA,aACA,cACA,WACA,iBACA,gBACA,kBACA,WACA,QACA,OACA,IACA,MACA,OAEA,OAA4B,CAAC,EACtC,WACA,eAAiB,GACjB,oBACA,cACA,cAAgB,EAChB,UAAY,GACZ,eAAiB,GAEzB,YAAYD,EAA6B,CACxC,KAAK,YAAcE,GAClBF,EAAQ,QACRA,EAAQ,cAAgBG,EACzB,EACA,KAAK,gBAAkBH,EAAQ,iBAAmB,IAClD,KAAK,aAAeA,EAAQ,cAAgB,GAC5C,KAAK,cAAgBA,EAAQ,eAAiB,IAC9C,KAAK,WAAaA,EAAQ,YAAc,EACxC,KAAK,iBACJA,EAAQ,kBAAoB,IAC7B,KAAK,gBACJA,EAAQ,iBAAmB,IAC5B,KAAK,kBACJA,EAAQ,mBAAqB,IAC9B,KAAK,QAAUA,EAAQ,SAAW,MAClC,KAAK,OAASA,EAAQ,QAAU,QAChC,KAAK,IAAMA,EAAQ,MAAQ,IAAM,IAAI,MACrC,KAAK,MACJA,EAAQ,QACNI,GAAY,IAAI,QAASC,GAAY,WAAWA,EAASD,CAAO,CAAC,GACpE,KAAK,OAASJ,EAAQ,OACtB,KAAK,WAAaA,EAAQ,WAEtB,KAAK,gBAAkB,IAC1B,KAAK,WAAa,YAAY,IAAM,CAC9B,KAAK,MAAM,CACjB,EAAG,KAAK,eAAe,EAEzB,CAEA,QAAQM,EAA8B,CACrC,GAAI,KAAK,WAAa,KAAK,eAAgB,CAC1C,KAAK,OAAO,KACX,8DACAA,EAAM,EACP,EACA,MACD,CAEA,GAAI,KAAK,OAAO,QAAU,KAAK,cAAe,CAC7C,IAAMC,EAAY,KAAK,OAAO,OAAS,KAAK,cAAgB,EAC5D,KAAK,OAAO,OAAO,EAAGA,CAAS,EAC/B,KAAK,OAAO,KACX,kEACAA,CACD,CACD,CAIA,GAFA,KAAK,OAAO,KAAKD,CAAK,EAElB,KAAK,OAAO,QAAU,KAAK,aAAc,CACvC,KAAK,MAAM,EAChB,MACD,CAEA,KAAK,mBAAmB,CACzB,CAEA,eAAwB,CACvB,OAAO,KAAK,OAAO,OAAS,KAAK,aAClC,CAEA,MAAM,OAAuB,CAC5B,OAAI,KAAK,cACD,KAAK,eAEb,KAAK,cAAgB,KAAK,UAAU,EAAE,QAAQ,IAAM,CACnD,KAAK,cAAgB,MACtB,CAAC,EACM,KAAK,cACb,CAEA,MAAM,SACLN,EACkC,CAClC,KAAK,eAAiB,GAClB,KAAK,aACR,cAAc,KAAK,UAAU,EAC7B,KAAK,WAAa,QAEf,KAAK,sBACR,aAAa,KAAK,mBAAmB,EACrC,KAAK,oBAAsB,OAC3B,KAAK,eAAiB,IAGvB,IAAMQ,EAAYR,GAAS,WAAa,KAAK,kBACvCS,EAAe,KAAK,MAAM,EAEhC,GAAI,CAAC,OAAO,SAASD,CAAS,GAAKA,GAAa,EAC/C,aAAMC,EACN,KAAK,UAAY,GACV,CAAE,SAAU,GAAO,cAAe,KAAK,cAAc,CAAE,EAG/D,IAAMC,EAAgB,OAAO,kBAAkB,EAM/C,OALe,MAAM,QAAQ,KAAK,CACjCD,EAAa,KAAK,IAAM,SAAkB,EAC1C,KAAK,MAAMD,CAAS,EAAE,KAAK,IAAME,CAAa,CAC/C,CAAC,IAEcA,GACd,KAAK,UAAY,GACV,CAAE,SAAU,GAAM,cAAe,KAAK,cAAc,CAAE,IAG9D,KAAK,UAAY,GACV,CAAE,SAAU,GAAO,cAAe,KAAK,cAAc,CAAE,EAC/D,CAEQ,oBAA2B,CAC9B,KAAK,iBAGT,KAAK,eAAiB,GACtB,KAAK,oBAAsB,WAAW,IAAM,CAC3C,KAAK,oBAAsB,OAC3B,KAAK,eAAiB,GACjB,KAAK,MAAM,CACjB,EAAG,CAAC,EACL,CAEA,MAAc,WAA2B,CACxC,KAAO,KAAK,OAAO,OAAS,GAAK,CAAC,KAAK,WAAW,CACjD,IAAMC,EAAQ,KAAK,OAAO,OAAO,EAAG,KAAK,YAAY,EACrD,MAAM,KAAK,mBAAmBA,CAAK,CACpC,CACD,CAEA,MAAc,mBAAmBA,EAAyC,CACzE,IAAIC,EAAU,EACVC,EAAeF,EAEnB,KAAOE,EAAa,OAAS,GAAK,CAAC,KAAK,WAAW,CAClD,KAAK,cAAgBA,EAAa,OAClC,IAAMC,EAAS,MAAM,KAAK,cAAcD,CAAY,EAGpD,OAFA,KAAK,cAAgB,EAEbC,EAAO,KAAM,CACpB,IAAK,UACJ,OACD,IAAK,OACJ,KAAK,4BAA4BA,EAAO,OAAQD,EAAa,MAAM,EACnE,OACD,IAAK,YACJ,KAAK,OAAO,MACX,8DACAA,EAAa,OACbC,EAAO,MACR,EACA,OACD,IAAK,YACJ,GAAIF,GAAW,KAAK,WAAY,CAC/B,KAAK,OAAO,MACX,6DACAC,EAAa,OACbC,EAAO,MACR,EACA,MACD,CACA,MAAM,KAAK,MAAM,KAAK,eAAeF,CAAO,CAAC,EAC7CA,GAAW,EACX,SACD,IAAK,UAOJ,GANIE,EAAO,UAAU,OAAS,GAC7B,KAAK,OAAO,MACX,wDACAA,EAAO,UAAU,MAClB,EAEGA,EAAO,UAAU,SAAW,EAC/B,OAED,GAAIF,GAAW,KAAK,WAAY,CAC/B,KAAK,OAAO,MACX,mEACAE,EAAO,UAAU,MAClB,EACA,MACD,CACAD,EAAeC,EAAO,UACtB,MAAM,KAAK,MAAM,KAAK,eAAeF,CAAO,CAAC,EAC7CA,GAAW,EACX,QACF,CACD,CACD,CAEA,MAAc,cACbG,EAC2B,CAC3B,IAAIC,EAEJ,GAAI,CACHA,EAAW,MAAM,KAAK,QAAQ,KAAK,YAAa,CAC/C,OAAQ,OACR,QAAS,CACR,eAAgB,mBAChB,cAAe,UAAU,KAAK,MAAM,GACpC,iBAAkBpB,EACnB,EACA,KAAM,KAAK,UAAU,KAAK,iBAAiBmB,CAAM,CAAC,CACnD,CAAC,CACF,OAASE,EAAO,CACf,MAAO,CACN,KAAM,YACN,OAAQC,GAAgBD,CAAK,CAC9B,CACD,CAEA,GAAIpB,GAAoB,IAAImB,EAAS,MAAM,EAC1C,MAAO,CAAE,KAAM,OAAQ,OAAQA,EAAS,MAAO,EAGhD,GAAIlB,GAAiB,IAAIkB,EAAS,MAAM,EACvC,MAAO,CACN,KAAM,YACN,OAAQ,QAAQA,EAAS,MAAM,EAChC,EAGD,GAAI,CAACA,EAAS,GACb,MAAO,CACN,KAAM,YACN,OAAQ,QAAQA,EAAS,MAAM,EAChC,EAGD,IAAMG,EAAO,MAAMC,GAAmCJ,CAAQ,EAC9D,GAAI,CAACG,GAAM,UAAYA,EAAK,SAAS,SAAW,EAC/C,MAAO,CAAE,KAAM,SAAU,EAG1B,IAAME,EAAU,KAAK,uBAAuBN,EAAQI,EAAK,QAAQ,EACjE,OAAIE,EAAQ,UAAU,SAAW,GAAKA,EAAQ,UAAU,SAAW,EAC3D,CAAE,KAAM,SAAU,EAGnB,CACN,KAAM,UACN,UAAWA,EAAQ,UACnB,UAAWA,EAAQ,SACpB,CACD,CAEQ,iBAAiBN,EAA2C,CACnE,MAAO,CACN,OAAQ,KAAK,IAAI,EAAE,YAAY,EAC/B,OAAQ,CACP,IAAKnB,GACL,QAAS,KAAK,YAAc,OAC7B,EACA,OAAAmB,CACD,CACD,CAEQ,uBACPA,EACAO,EAIC,CACD,IAAMC,EAAO,IAAI,IAAIR,EAAO,IAAKT,GAAU,CAACA,EAAM,GAAIA,CAAK,CAAC,CAAC,EACvDkB,EAA+B,CAAC,EAChCC,EAA+B,CAAC,EAEtC,QAAWC,KAAiBJ,EAAU,CACrC,IAAMhB,EAAQiB,EAAK,IAAIG,EAAc,OAAO,EAC5C,GAAKpB,EAGL,IAAIqB,GAAyBD,CAAa,EAAG,CAC5CF,EAAU,KAAKlB,CAAK,EACpB,QACD,CACAmB,EAAU,KAAKnB,CAAK,EACrB,CAEA,MAAO,CAAE,UAAAkB,EAAW,UAAAC,CAAU,CAC/B,CAEQ,eAAeb,EAAyB,CAC/C,IAAMgB,EAAW,KAAK,iBAAmB,GAAKhB,EAC9C,OAAO,KAAK,IAAIgB,EAAU,KAAK,eAAe,CAC/C,CAEQ,4BACPC,EACAC,EACO,CACP,KAAK,UAAY,GACjB,IAAMC,EAAW,KAAK,OAAO,OAC7B,KAAK,OAAO,OAAO,EAAGA,CAAQ,EAC9B,KAAK,OAAO,MACX,iGACAF,EACAC,EAAgBC,CACjB,CACD,CACD,EAEA,SAASJ,GACRD,EACU,CACV,GAAIA,EAAc,YAAc,GAC/B,MAAO,GAER,IAAMM,EAAON,EAAc,KAAK,YAAY,EAC5C,OACCM,EAAK,SAAS,SAAS,GACvBA,EAAK,SAAS,WAAW,GACzBA,EAAK,SAAS,aAAa,GAC3BA,EAAK,SAAS,YAAY,GAC1BA,EAAK,SAAS,WAAW,GACzBA,EAAK,SAAS,QAAQ,CAExB,CAEA,eAAeZ,GACdJ,EACyB,CACzB,IAAMiB,EAAO,MAAMjB,EAAS,KAAK,EACjC,GAAKiB,EAGL,GAAI,CACH,OAAO,KAAK,MAAMA,CAAI,CACvB,MAAQ,CACP,MACD,CACD,CAEA,SAAS/B,GAAQgC,EAAiBC,EAA8B,CAC/D,IAAMC,EAAiBF,EAAQ,SAAS,GAAG,EAAIA,EAAU,GAAGA,CAAO,IAC7DG,EAAiBF,EAAa,WAAW,GAAG,EAC/CA,EAAa,MAAM,CAAC,EACpBA,EACH,MAAO,GAAGC,CAAc,GAAGC,CAAc,EAC1C,CAEA,SAASnB,GAAgBD,EAAwB,CAChD,OAAIA,aAAiB,MACbA,EAAM,QAEP,OAAOA,CAAK,CACpB,CCzZO,SAASqB,GAAqBC,EAAwC,CAC5E,GAAM,CAAE,QAAAC,EAAS,OAAAC,EAAQ,SAAAC,CAAS,EAAIH,EAEtC,SAASI,GAAwB,CAChC,GAAI,CAACF,EACJ,MAAM,IAAI,MAAM,6BAA6B,EAE9C,OAAOA,CACR,CAEA,IAAMG,EAAYH,EACfI,GAAuB,CACvB,QAAAL,EACA,OAAAC,EACA,aAAcC,EAAS,aACvB,gBAAiBA,EAAS,gBAC1B,aAAcA,EAAS,aACvB,cAAeA,EAAS,cACxB,WAAYA,EAAS,WACrB,iBAAkBA,EAAS,iBAC3B,gBAAiBA,EAAS,gBAC1B,kBAAmBA,EAAS,iBAC7B,CAAC,EACA,OAEGI,EAAyB,CAC9B,MAAM,SACLC,EACAC,EACAC,EAC+B,CAC/BN,EAAc,EACd,IAAMO,EAAcC,EAAkB,CACrC,MAAO,kBACP,eAAgBJ,EAChB,WAAAC,EACA,KAAAC,CACD,CAAC,EACD,OAAAL,GAAW,QAAQM,CAAW,EACvB,CAAE,QAASA,EAAY,EAAG,CAClC,EACA,MAAM,MAAME,EAAiD,CAC5DT,EAAc,EACd,IAAMO,EAAcC,EAAkBC,CAAK,EAC3C,OAAAR,GAAW,QAAQM,CAAW,EACvB,CAAE,QAASA,EAAY,EAAG,CAClC,EACA,MAAM,OAAuB,CAC5BP,EAAc,EACd,MAAMC,GAAW,MAAM,CACxB,EACA,MAAM,SAASS,EAAmC,CACjD,OAAAV,EAAc,EAEZ,MAAMC,GAAW,SAAS,CAC1B,UAAWS,GAAS,WAAaX,EAAS,iBAC3C,CAAC,GAAM,CAAE,SAAU,GAAO,cAAe,CAAE,CAE7C,CACD,EAEA,OAAIE,GACHU,GAAoBR,EAAQJ,EAAS,iBAAiB,EAEhDI,CACR,CAEA,SAASQ,GACRR,EACAS,EACO,CACP,GACC,OAAO,QAAY,KACnB,OAAO,QAAQ,MAAS,YACxB,OAAO,QAAQ,IAAO,WAEtB,OAGD,IAAMC,EAAW,IAAM,CACjBV,EAAO,SAAS,CAAE,UAAWS,CAAiB,CAAC,CACrD,EAEA,QAAQ,KAAK,aAAcC,CAAQ,EACnC,QAAQ,KAAK,SAAUA,CAAQ,EAC/B,QAAQ,KAAK,UAAWA,CAAQ,CACjC,CCjGO,SAASC,GAASC,EAAyC,CACjE,IAAMC,EAAUD,GAAQ,SAAW,0BAC7BE,EAASF,GAAQ,QAAU,QAAQ,IAAI,iBACvCG,EAAiB,CACtB,aAAcH,GAAQ,UAAU,cAAgB,2BAChD,gBAAiBA,GAAQ,UAAU,iBAAmB,IACtD,aAAcA,GAAQ,UAAU,cAAgB,GAChD,cAAeA,GAAQ,UAAU,eAAiB,IAClD,WAAYA,GAAQ,UAAU,YAAc,EAC5C,iBAAkBA,GAAQ,UAAU,kBAAoB,IACxD,gBAAiBA,GAAQ,UAAU,iBAAmB,IACtD,kBAAmBA,GAAQ,UAAU,mBAAqB,GAC3D,EAEMI,EAAiB,CAAE,QAAAH,EAAS,OAAAC,EAAQ,SAAUC,CAAe,EAG7DE,EAAiBC,GAAqBF,CAAc,EACpDG,EAAWC,GAAeJ,CAAc,EAE9C,MAAO,CACN,GAAGC,EACH,GAAIE,EACJ,QAASH,CACV,CACD,CCIA,SAASK,GAAeC,EAAoC,CAC3D,IAAMC,EAAYD,EAAG,YAAc,eAM7BE,EADgBD,EAAU,WAAW,SAAS,EAEnCA,EAAY,UAAUA,CAAS,GAI1CE,EAAsC,CAC3C,GAAIH,EAAG,UAAY,CAAC,CACrB,EACA,OAAIA,EAAG,aACNG,EAAW,WAAaH,EAAG,YAGrB,CACN,MAAOE,EACP,WAAAC,EACA,UAAWH,EAAG,WACd,QAASA,EAAG,SACZ,eAAgBA,EAAG,QACnB,QAASA,EAAG,SACZ,UAAWA,EAAG,UACd,OAAQA,EAAG,QAAU,QACtB,CACD,CAMO,SAASI,GAAoBC,EAAgC,CACnE,IAAMC,EAAyB,CAC9B,OAAQD,GAAS,OACjB,QAASA,GAAS,OACnB,EAGIE,EAEJ,SAASC,GAAY,CACpB,OAAKD,IACJA,EAASE,GAASH,CAAM,GAElBC,CACR,CAEA,OAAO,eAAuBG,EAAqC,CAClE,IAAIC,EACJ,GAAI,CACHA,EAAQ,MAAMD,EAAQ,KAAK,CAC5B,MAAQ,CACP,OAAO,IAAI,SAAS,KAAK,UAAU,CAAE,MAAO,cAAe,CAAC,EAAG,CAC9D,OAAQ,IACR,QAAS,CAAE,eAAgB,kBAAmB,CAC/C,CAAC,CACF,CAEA,GAAI,CAAC,MAAM,QAAQC,EAAK,MAAM,GAAKA,EAAK,OAAO,SAAW,EACzD,OAAO,IAAI,SACV,KAAK,UAAU,CAAE,MAAO,+BAAgC,CAAC,EACzD,CACC,OAAQ,IACR,QAAS,CAAE,eAAgB,kBAAmB,CAC/C,CACD,EAGD,GAAI,CACH,IAAMC,EAAIJ,EAAU,EACdK,EAAoB,CAAC,EAE3B,QAAWb,KAAMW,EAAK,OAAQ,CAC7B,IAAMG,EAAaf,GAAeC,CAAE,EAC9Be,EAAS,MAAMH,EAAE,MAAME,CAAU,EACvCD,EAAQ,KAAKE,EAAO,OAAO,CAC5B,CAEA,aAAMH,EAAE,MAAM,EAEP,IAAI,SACV,KAAK,UAAU,CAAE,GAAI,GAAM,SAAUC,EAAQ,MAAO,CAAC,EACrD,CACC,OAAQ,IACR,QAAS,CAAE,eAAgB,kBAAmB,CAC/C,CACD,CACD,OAASG,EAAO,CACf,IAAMC,EAAUD,aAAiB,MAAQA,EAAM,QAAU,gBACzD,OAAO,IAAI,SAAS,KAAK,UAAU,CAAE,MAAOC,CAAQ,CAAC,EAAG,CACvD,OAAQ,IACR,QAAS,CAAE,eAAgB,kBAAmB,CAC/C,CAAC,CACF,CACD,CACD,CCjIO,IAAMC,EAAN,KAAuB,CACrB,OAA6B,KAC7B,QAAyC,KAChC,OAEjB,YAAYC,EAA2B,CACtC,KAAK,OAASA,CACf,CAMA,MAAM,SAASC,EAAoBC,EAA0C,CAC5E,OAAI,KAAK,QAAU,KAAK,IAAI,EAAI,KAAK,OAAO,UAAY,KAChD,KAAK,OAAO,MAIhB,KAAK,QACD,KAAK,SAGb,KAAK,QAAU,KAAK,KAAKD,EAAWC,CAAO,EAAE,QAAQ,IAAM,CAC1D,KAAK,QAAU,IAChB,CAAC,EAEM,KAAK,QACb,CAEA,MAAc,KACbD,EACAC,EACyB,CACzB,IAAMC,EAAMC,GAAQ,KAAK,OAAO,QAAS,wBAAwB,EAE3DC,EAA+B,CAAC,EAClCJ,IACHI,EAAK,UAAYJ,GAEdC,IACHG,EAAK,QAAUH,GAGhB,GAAI,CACH,IAAMI,EAAW,MAAM,MAAMH,EAAK,CACjC,OAAQ,OACR,QAAS,CACR,eAAgB,mBAChB,cAAe,UAAU,KAAK,OAAO,MAAM,EAC5C,EACA,KAAM,KAAK,UAAUE,CAAI,CAC1B,CAAC,EAED,GAAI,CAACC,EAAS,GACb,OAAO,KAGR,IAAMC,EAAQ,MAAMD,EAAS,KAAK,EAG5BE,EACLD,EAAK,MAAQ,OAAOA,EAAK,MAAS,SAAWA,EAAK,KAAOA,EAGpDE,EAAc,IAAI,KAAKD,EAAO,SAAS,EAAE,QAAQ,EACvD,MAAI,CAACA,EAAO,OAAS,OAAO,MAAMC,CAAW,EACrC,MAGR,KAAK,OAAS,CACb,MAAOD,EAAO,MACd,UAAWC,CACZ,EAEOD,EAAO,MACf,MAAQ,CACP,OAAO,IACR,CACD,CACD,EAEA,SAASJ,GAAQM,EAAiBC,EAAsB,CAEvD,MAAO,GADMD,EAAQ,SAAS,GAAG,EAAIA,EAAQ,MAAM,EAAG,EAAE,EAAIA,CAC9C,GAAGC,CAAI,EACtB,CC9FA,IAAMC,GAAiB,qBACjBC,GAAmB,uBACnBC,GAA2B,wBAE1B,SAASC,EAASC,EAAwC,CAChE,MAAO,EAAQA,GAAU,OAAOA,GAAU,UAAY,CAAC,MAAM,QAAQA,CAAK,CAC3E,CAEO,SAASC,EAAYC,EAA2C,CACtE,GAAI,CAACH,EAASG,CAAK,EAClB,OAED,IAAMC,EAAOD,EAAM,MACnB,OAAOH,EAASI,CAAI,EAAIA,EAAO,MAChC,CAEO,SAASC,GAAiBC,EAAqC,CACrE,GAAI,CAACN,EAASM,CAAM,EACnB,OAED,IAAMC,EAAWD,EAAyB,QAC1C,OAAK,MAAM,QAAQC,CAAO,EAGTA,EAAQ,KACvBC,GACAR,EAASQ,CAAC,GAAKA,EAAE,OAAS,QAAU,OAAOA,EAAE,MAAS,QACxD,GACiB,KANhB,MAOF,CAEO,SAASC,GACfC,EACAC,EAI+B,CAC/B,OAAI,OAAOA,GAAmB,WACtBA,EAAeD,CAAQ,GAAK,QAE7BC,GAAkB,OAC1B,CAEO,SAASC,GACfF,EACAP,EACAU,EAMAC,EACAC,EACAC,EACa,CACb,IAAMC,EAAWR,GAAgBC,EAAUG,EAAQ,QAAQ,EACrDT,EAAOF,EAAYC,CAAK,EAC9B,eAAQ,IACP,yCACA,KAAK,UAAUC,CAAI,EACnB,aACAc,EAAcd,CAAI,CACnB,EAEO,CACN,MAAO,cACP,WAAY,CACX,KAAMM,EACN,KAAMO,EACN,GAAIH,GAAU,CAAC,EACf,GAAIE,GAAI,QAAU,QAAa,CAAE,MAAOA,EAAG,KAAM,EACjD,GAAIA,GAAI,SAAW,QAAa,CAAE,OAAQA,EAAG,MAAO,CACrD,EACA,KAAAZ,EACA,OAAQc,EAAcd,CAAI,EAC1B,SAAU,CACT,GAAIS,EAAQ,UAAY,CAAC,EACzB,GAAIE,GAAc,CAAE,WAAAA,CAAW,CAChC,CACD,CACD,CAEA,eAAsBI,GACrBC,EACAC,EACAC,EACgB,CAChB,GAAI,CACH,MAAMF,EAAQ,MAAMC,CAAK,CAC1B,OAASE,EAAO,CACfD,IAAUE,GAAQD,CAAK,CAAC,CACzB,CACD,CAEA,eAAsBE,GACrBL,EACAE,EACgB,CAChB,GAAI,CACH,MAAMF,EAAQ,MAAM,CACrB,OAASG,EAAO,CACfD,IAAUE,GAAQD,CAAK,CAAC,CACzB,CACD,CAEA,eAAsBG,GACrBpB,EACAqB,EACAC,EACAN,EACgB,CAChB,GAAI,CAACtB,EAASM,CAAM,EACnB,OAGIN,EAASM,EAAO,KAAK,IACxBA,EAAyB,MAAQ,CAAC,GAGpC,IAAMF,EAAQE,EAAyB,MACjCuB,EAAyB7B,EAASI,EAAK,QAAQ,EACjDA,EAAK,SACN,OACG0B,EAAgC,CACrC,GAAID,GAA0B,CAAC,EAC/B,SACCA,GAAwB,UACxB,GAAGD,EAAQ,QAAQ,MAAO,EAAE,CAAC,0BAC/B,EAEA,GAAID,EACH,GAAI,CACH,IAAMI,EAAQ,MAAMJ,EAAM,SAAS,EAC/BI,IACHD,EAAe,MAAQC,EAEzB,OAASR,EAAO,CACfD,IAAUE,GAAQD,CAAK,CAAC,CACzB,CAGD,IAAMS,EAAYC,EAAiB7B,CAAI,EACnC4B,IACEF,EAAe,YACnBA,EAAe,UAAYE,IAI7B,IAAME,EAAcC,GAAmB/B,CAAI,EACvC8B,IAAgB,SACdJ,EAAe,cACnBA,EAAe,YAAcI,IAI/B9B,EAAK,SAAW0B,CACjB,CAEO,SAASM,GAAsB9B,EAAiBH,EAAsB,CAC5E,IAAMkC,EAAcnC,EAAYC,CAAK,EAKrC,GAJI,CAACkC,GAID,CAACrC,EAASM,CAAM,EACnB,OAGIN,EAASM,EAAO,KAAK,IACxBA,EAAyB,MAAQ,CAAC,GAGpC,IAAMgC,EAAchC,EAAyB,MACvC0B,EAAYC,EAAiBI,CAAW,EAC1CL,GAAa,CAACM,EAAWzC,EAAc,IAC1CyC,EAAWzC,EAAc,EAAImC,GAG9B,IAAME,EAAcC,GAAmBE,CAAW,EAC7CH,IAIAI,EAAWxC,EAAgB,IAC/BwC,EAAWxC,EAAgB,EAAIoC,GAG3BI,EAAWvC,EAAwB,IACvCuC,EAAWvC,EAAwB,EAAImC,GAEzC,CAEA,SAASC,GACR/B,EACqC,CACrC,GAAI,CAACA,EACJ,OAGD,IAAM8B,EAAc9B,EAAKN,EAAgB,GAAKM,EAAKL,EAAwB,EAC3E,GAAIC,EAASkC,CAAW,GAAK,OAAOA,GAAgB,SACnD,OAAOA,CAIT,CAEA,SAASV,GAAQD,EAAuB,CACvC,OAAIA,aAAiB,MACbA,EAED,IAAI,MAAM,OAAOA,CAAK,CAAC,CAC/B,CCxKA,IAAMgB,GAAmB,0BAYlB,SAASC,GACfC,EACAC,EACY,CACZ,IAAMC,EAAgBF,EACtB,GAAIE,EAAc,kBACjB,OAAOA,EAGRA,EAAc,kBAAoB,GAElC,IAAMC,EAAUF,EAAQ,OAClBG,EAAcH,EAAQ,oBAAsB,GAE9CI,EAAsC,KAE1C,SAASC,GAAyC,CACjD,GAAID,EACH,OAAOA,EAER,IAAME,EAASJ,EAAQ,QAAQ,OAC/B,OAAKI,GAGLF,EAAa,IAAIG,EAAiB,CACjC,QAASL,EAAQ,QAAQ,SAAWL,GACpC,OAAAS,CACD,CAAC,EACMF,GANC,IAOT,CAEA,IAAMI,EAAuBT,EAAO,aAAa,KAAKA,CAAM,EAI5D,OAAAE,EAAc,cAAgB,IAAIQ,IAAoB,CACrD,GAAM,CAACC,EAAaC,EAAQC,CAAU,EAAIH,EACpCI,EACL,OAAOH,GAAgB,UAAYA,EAAY,KAAK,EAAE,OAAS,EAC5DA,EACA,UAEJ,GAAI,OAAOE,GAAe,WACzB,OAAOJ,EAAqB,GAAGC,CAAI,EAGpC,IAAMK,EAAUF,EAqGhB,OAAOJ,EAAqBE,EAAaC,EAhGlB,MAAOI,EAAgBC,IAAmB,CAEhE,IAAMC,EAAOC,EAAYF,CAAK,GAAK,CAAC,EAC9BG,EAAeC,GAAmBlB,EAASe,CAAI,EACjDI,EAASL,CAAK,IAChBA,EAAwBM,CAAiB,EAAIH,GAG/C,IAAMI,EAAY,YAAY,IAAI,EAC5BC,EACLzB,EAOC,QAAQ,mBAAmB,EAC7B,GAAI,CACH,IAAM0B,EAAS,MAAMX,EAAQC,EAAOC,CAAK,EACnCU,EAAa,KAAK,MAAM,YAAY,IAAI,EAAIH,CAAS,EAErDI,EACLN,EAASI,CAAM,GAAMA,EAAyB,UAAY,GAE3D,GAAIE,EAAe,CAClB,IAAMC,GAAYC,GAAiBJ,CAAM,EACzC,QAAQ,MACP,oBAAoBZ,CAAQ,mBAAmBe,GAAY,KAAKA,EAAS,GAAK,EAAE,EACjF,CACD,CAEA,aAAME,GACL5B,EACA6B,GACClB,EACAG,EACAhB,EACA,CACC,WAAA0B,EACA,OAAQC,EAAgB,QAAU,KAClC,GAAIA,GAAiB,CACpB,aAAcE,GAAiBJ,CAAM,GAAK,oBAC3C,CACD,EACAD,EACA,CAAE,MAAAT,EAAO,OAAQU,CAAO,CACzB,EACAzB,EAAQ,OACT,EAEIA,EAAQ,oBACX,MAAMgC,GAAU9B,EAASF,EAAQ,OAAO,EAGzCiC,GAAsBR,EAAQT,CAAK,EAE/Bb,GACH,MAAM+B,GACLT,EACApB,EAAc,EACdH,EAAQ,QAAQ,SAAWL,GAC3BG,EAAQ,OACT,EAGMyB,CACR,OAASU,EAAO,CACf,IAAMT,EAAa,KAAK,MAAM,YAAY,IAAI,EAAIH,CAAS,EAE3D,YAAMO,GACL5B,EACA6B,GACClB,EACAG,EACAhB,EACA,CACC,WAAA0B,EACA,OAAQ,QACR,aACCS,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,CACvD,EACAX,EACA,CAAE,MAAAT,CAAM,CACT,EACAf,EAAQ,OACT,EAEIA,EAAQ,oBACX,MAAMgC,GAAU9B,EAASF,EAAQ,OAAO,EAGnCmC,CACP,CACD,CAE+D,CAChE,GAEOlC,CACR","names":["detectPlatform","isOpenAI","isMCPApps","START","END","INTERRUPT","WIDGET","interrupt","fields","config","context","questions","key","value","q","showWidget","tool","isInterrupt","isWidget","z","SCOPED_CLIENT_KEY","extractScopedClient","extra","createScopedClient","base","meta","event","userId","properties","pickFirst","meta","keys","key","value","SESSION_ID_KEYS","REQUEST_ID_KEYS","TRACE_ID_KEYS","EXTERNAL_USER_ID_KEYS","CORRELATION_ID_KEYS","extractSessionId","extractRequestId","extractTraceId","extractExternalUserId","extractCorrelationId","SOURCE_SESSION_KEYS","extractSource","explicit","source","getObjectShape","schema","def","getNestedValue","obj","path","parts","current","part","setNestedValue","value","lastKey","deleteNestedValue","expandDotPaths","flat","result","key","deepMerge","target","source","isFilled","v","resolveNextNode","edge","state","buildInterruptResult","questions","context","currentNode","q","getNestedValue","unanswered","isSingle","q0","executeFrom","startNodeName","startState","nodes","edges","validators","meta","waniwani","MAX_ITERATIONS","iterations","END","handler","result","interrupt","showWidget","isInterrupt","interruptResult","fn","value","vResult","deepMerge","err","msg","deleteNestedValue","questionsWithError","qq","errResult","isWidget","widgetField","error","SDK_NAME","DEFAULT_BASE_URL","WaniwaniFlowStore","options","key","value","path","body","url","response","text","describeZodField","schema","desc","def","vals","v","buildFlowProtocol","config","lines","parts","key","shape","getObjectShape","groupDesc","subFields","subKey","subSchema","info","inputSchema","z","compileFlow","input","config","nodes","edges","protocol","buildFlowProtocol","fullDescription","store","WaniwaniFlowStore","validators","handleToolCall","args","sessionId","meta","waniwani","startEdge","START","startState","expandDotPaths","firstNode","resolveNextNode","executeFrom","flowState","state","step","updatedState","deepMerge","edge","nextNode","server","extra","requestExtra","_meta","extractSessionId","extractScopedClient","result","buildMermaidGraph","nodes","edges","lines","START","name","END","from","edge","StateGraph","config","handler","to","condition","options","nodesCopy","edgesCopy","compileFlow","startEdge","createFlow","config","StateGraph","parsePayload","result","content","createFlowTestHarness","flow","options","store","registered","sessionId","server","args","handler","extra","toResult","parsed","stateUpdates","MIME_TYPE_OPENAI","MIME_TYPE_MCP","fetchHtml","baseUrl","path","normalizedBase","buildOpenAIResourceMeta","config","buildMcpAppsResourceMeta","csp","buildToolMeta","createResource","config","id","title","description","baseUrl","htmlPath","widgetDomain","prefersBorder","autoHeight","widgetCSP","hostname","openaiUri","mcpUri","htmlPromise","getHtml","fetchHtml","uiDescription","register","server","html","MIME_TYPE_OPENAI","uri","buildOpenAIResourceMeta","MIME_TYPE_MCP","buildMcpAppsResourceMeta","createTool","config","handler","resource","description","inputSchema","annotations","autoInjectResultText","id","title","toolMeta","buildToolMeta","server","args","extra","requestExtra","_meta","waniwani","extractScopedClient","result","registerTools","tools","t","WaniWaniError","message","status","SDK_NAME","createKbClient","config","baseUrl","apiKey","requireApiKey","request","method","path","body","key","url","headers","init","response","text","WaniWaniError","files","query","options","DEFAULT_SOURCE","mapTrackEventToV2","input","options","now","generateId","createEventId","eventName","resolveEventName","meta","toRecord","metadata","correlation","resolveCorrelationIds","eventId","takeNonEmptyString","timestamp","normalizeTimestamp","source","extractSource","rawLegacy","isLegacyTrackEvent","mappedMetadata","mapProperties","legacyProperties","mapLegacyProperties","explicitProperties","properties","requestId","extractRequestId","sessionId","extractSessionId","traceId","extractTraceId","externalUserId","extractExternalUserId","correlationId","extractCorrelationId","date","value","DEFAULT_ENDPOINT_PATH","SDK_NAME","AUTH_FAILURE_STATUS","RETRYABLE_STATUS","createV2BatchTransport","options","BatchingV2Transport","joinUrl","DEFAULT_ENDPOINT_PATH","delayMs","resolve","event","dropCount","timeoutMs","flushPromise","timeoutSignal","batch","attempt","pendingBatch","result","events","response","error","getErrorMessage","data","parseJsonResponse","partial","rejected","byId","retryable","permanent","rejectedEvent","isRetryableRejectedEvent","rawDelay","status","rejectedCount","buffered","code","body","baseUrl","endpointPath","normalizedBase","normalizedPath","createTrackingClient","config","baseUrl","apiKey","tracking","requireApiKey","transport","createV2BatchTransport","client","userId","properties","meta","mappedEvent","mapTrackEventToV2","event","options","attachShutdownHooks","defaultTimeoutMs","shutdown","waniwani","config","baseUrl","apiKey","trackingConfig","internalConfig","trackingClient","createTrackingClient","kbClient","createKbClient","mapWidgetEvent","ev","eventType","eventName","properties","createTrackingRoute","options","config","client","getClient","waniwani","request","body","c","results","trackInput","result","error","message","WidgetTokenCache","config","sessionId","traceId","url","joinUrl","body","response","json","result","expiresAtMs","baseUrl","path","SESSION_ID_KEY","GEO_LOCATION_KEY","LEGACY_USER_LOCATION_KEY","isRecord","value","extractMeta","extra","meta","extractErrorText","result","content","c","resolveToolType","toolName","toolTypeOption","buildTrackInput","options","timing","clientInfo","io","toolType","extractSource","safeTrack","tracker","input","onError","error","toError","safeFlush","injectWidgetConfig","cache","baseUrl","existingWaniwaniConfig","waniwaniConfig","token","sessionId","extractSessionId","geoLocation","extractGeoLocation","injectRequestMetadata","requestMeta","resultMeta","DEFAULT_BASE_URL","withWaniwani","server","options","wrappedServer","tracker","injectToken","tokenCache","getTokenCache","apiKey","WidgetTokenCache","originalRegisterTool","args","toolNameRaw","config","handlerRaw","toolName","handler","input","extra","meta","extractMeta","scopedClient","createScopedClient","isRecord","SCOPED_CLIENT_KEY","startTime","clientInfo","result","durationMs","isErrorResult","errorText","extractErrorText","safeTrack","buildTrackInput","safeFlush","injectRequestMetadata","injectWidgetConfig","error"]}
|