@waniwani/sdk 0.10.1 → 0.10.3-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -979,6 +979,24 @@ declare function createFlow<const TSchema extends Record<string, z.ZodType>>(con
979
979
  state: TSchema;
980
980
  }): StateGraph<InferFlowState<TSchema>>;
981
981
 
982
+ /**
983
+ * Mark a Zod schema as PII — its value will be replaced with `"REDACTED"` in
984
+ * any `tool.called` event payload sent to the WaniWani API. The handler still
985
+ * receives the original value; only the tracked copy is scrubbed.
986
+ *
987
+ * Apply at the end of the schema chain so the marker is attached to the final
988
+ * schema:
989
+ *
990
+ * ```ts
991
+ * ages: redacted(z.string().describe("Comma-separated ages")),
992
+ * zipcode: redacted(z.string().describe("Spanish postal code")),
993
+ * ```
994
+ *
995
+ * Uses Zod v4's `.meta()` registry, so the marker is preserved across schema
996
+ * clones (`.optional()`, `.default()`, etc.).
997
+ */
998
+ declare function redacted<T extends z.ZodType>(schema: T): T;
999
+
982
1000
  type WithDecodedState = {
983
1001
  decodedState: FlowTokenContent | null;
984
1002
  };
@@ -1000,6 +1018,7 @@ declare function createFlowTestHarness(flow: RegisteredFlow, options?: {
1000
1018
  * Config is read from env vars:
1001
1019
  * - `WANIWANI_API_KEY` (required)
1002
1020
  * - `WANIWANI_API_URL` (optional, defaults to https://app.waniwani.ai)
1021
+ * - `WANIWANI_ENCRYPTION_KEY` (optional, base64-encoded 32-byte key for AES-256-GCM encryption)
1003
1022
  */
1004
1023
  interface KvStore<T = Record<string, unknown>> {
1005
1024
  get(key: string): Promise<T | null>;
@@ -1009,6 +1028,7 @@ interface KvStore<T = Record<string, unknown>> {
1009
1028
  declare class WaniwaniKvStore<T = Record<string, unknown>> implements KvStore<T> {
1010
1029
  private get baseUrl();
1011
1030
  private get apiKey();
1031
+ private get encryptionKey();
1012
1032
  get(key: string): Promise<T | null>;
1013
1033
  set(key: string, value: T): Promise<void>;
1014
1034
  delete(key: string): Promise<void>;
@@ -1160,6 +1180,29 @@ type WithWaniwaniOptions = {
1160
1180
  * @default true
1161
1181
  */
1162
1182
  injectWidgetToken?: boolean;
1183
+ /**
1184
+ * Strip `latitude`/`longitude` from known location `_meta` entries
1185
+ * (`openai/userLocation`, `waniwani/geoLocation`, `waniwani/userLocation`)
1186
+ * before events are sent to the WaniWani API. Applied to both the
1187
+ * request-level `_meta` and any `_meta` on the tool response.
1188
+ *
1189
+ * Enable this when precise geo coordinates should not reach the tracking
1190
+ * backend; city/region/country fields are preserved.
1191
+ *
1192
+ * @default false
1193
+ */
1194
+ stripGeoCoordinates?: boolean;
1195
+ /**
1196
+ * Replace `input.stateUpdates[field]` with `"REDACTED"` for any field
1197
+ * marked via `redacted()` on a flow state schema. When `false` (default),
1198
+ * the declarative markers are ignored and raw values are tracked.
1199
+ *
1200
+ * Wire this to an env var when you want real values in development logs
1201
+ * but redacted values in production.
1202
+ *
1203
+ * @default false
1204
+ */
1205
+ applyFieldRedactions?: boolean;
1163
1206
  };
1164
1207
  /**
1165
1208
  * Wrap an MCP server so tool handlers automatically emit `tool.called` events.
@@ -1183,4 +1226,4 @@ type WithWaniwaniOptions = {
1183
1226
  */
1184
1227
  declare function withWaniwani(server: McpServer, options?: WithWaniwaniOptions): McpServer;
1185
1228
 
1186
- export { type ConditionFn, END, type FlowConfig, type FlowTestResult, type HostContext, type InferFlowState, type InterruptSignal, type KvStore, type NodeContext, type NodeHandler, type RegisteredFlow, type RegisteredResource, type RegisteredTool, type ResourceConfig, START, type ScopedWaniWaniClient, StateGraph, type ToolCallResult, type ToolConfig, type ToolHandler, type ToolHandlerContext, type ToolResult, type ToolToolCallback, type TrackingRouteOptions, type TypedInterrupt, type TypedShowWidget, type UnifiedWidgetClient, WaniwaniKvStore, type WidgetCSP, type WidgetPlatform, type WidgetSignal, type WithWaniwaniOptions, createFlow, createFlowTestHarness, createResource, createTool, createTrackingRoute, detectPlatform, isMCPApps, isOpenAI, registerTools, withWaniwani };
1229
+ export { type ConditionFn, END, type FlowConfig, type FlowTestResult, type HostContext, type InferFlowState, type InterruptSignal, type KvStore, type NodeContext, type NodeHandler, type RegisteredFlow, type RegisteredResource, type RegisteredTool, type ResourceConfig, START, type ScopedWaniWaniClient, StateGraph, type ToolCallResult, type ToolConfig, type ToolHandler, type ToolHandlerContext, type ToolResult, type ToolToolCallback, type TrackingRouteOptions, type TypedInterrupt, type TypedShowWidget, type UnifiedWidgetClient, WaniwaniKvStore, type WidgetCSP, type WidgetPlatform, type WidgetSignal, type WithWaniwaniOptions, createFlow, createFlowTestHarness, createResource, createTool, createTrackingRoute, detectPlatform, isMCPApps, isOpenAI, redacted, registerTools, withWaniwani };
package/dist/mcp/index.js CHANGED
@@ -1,7 +1,7 @@
1
- function J(){return typeof window<"u"&&"openai"in window?"openai":"mcp-apps"}function et(){return J()==="openai"}function tt(){return J()==="mcp-apps"}var I="__start__",E="__end__",ge=Symbol.for("waniwani.flow.interrupt"),me=Symbol.for("waniwani.flow.widget");function we(e,t){let n=t?.context,r=[];for(let[o,i]of Object.entries(e))if(typeof i=="object"&&i!==null&&"question"in i){let a=i;r.push({question:a.question,field:o,suggestions:a.suggestions,context:a.context,validate:a.validate})}return{__type:ge,questions:r,context:n}}function he(e,t){return{__type:me,tool:typeof e=="string"?e:e.id,...t}}function ye(e){return typeof e=="object"&&e!==null&&"__type"in e&&e.__type===ge}function Te(e){return typeof e=="object"&&e!==null&&"__type"in e&&e.__type===me}import{z as D}from"zod";var Z="waniwani/client";function j(e){if(typeof e=="object"&&e!==null)return e[Z]}function ke(e,t,n){return{track(r){return e.track({...r,meta:{...t,...r.meta}})},identify(r,o){return e.identify(r,o,t)},kb:e.kb,_config:n}}function A(e,t){for(let n of t){let r=e[n];if(typeof r=="string"&&r.length>0)return r}}var nt=["waniwani/sessionId","openai/sessionId","openai/session","sessionId","conversationId","mcp-session-id"],rt=["waniwani/requestId","openai/requestId","requestId","mcp/requestId"],ot=["waniwani/traceId","openai/traceId","traceId","mcp/traceId","openai/requestId","requestId"],it=["waniwani/userId","openai/userId","externalUserId","userId","actorId"],st=["correlationId","openai/requestId"];function C(e){return e?A(e,nt):void 0}function Se(e){return e?A(e,rt):void 0}function ve(e){return e?A(e,ot):void 0}function xe(e){return e?A(e,it):void 0}function Re(e){return e?A(e,st):void 0}var at=[{key:"waniwani/sessionId",source:"chatbar"},{key:"openai/sessionId",source:"chatgpt"},{key:"openai/session",source:"chatgpt"}];function b(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 at){let o=e[n];if(typeof o=="string"&&o.length>0)return r}}function Ie(e){let t=e._zod?.def;return t?.type==="object"&&t.shape?t.shape:null}function N(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 ct(e,t,n){let r=t.split("."),o=r.pop();if(!o)return;let i=e;for(let a of r)(i[a]==null||typeof i[a]!="object"||Array.isArray(i[a]))&&(i[a]={}),i=i[a];i[o]=n}function Ee(e,t){let n=t.split("."),r=n.pop();if(!r)return;let o=e;for(let i of n){if(o==null||typeof o!="object")return;o=o[i]}o!=null&&typeof o=="object"&&delete o[r]}function X(e){let t={};for(let[n,r]of Object.entries(e))n.includes(".")?ct(t,n,r):t[n]=r;return t}function _(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]=_(n[r],o):n[r]=o;return n}function Q(e){return e!=null&&e!==""}async function P(e,t){return e.type==="direct"?e.to:e.condition(t)}function Ce(e,t,n,r){if(e.every(c=>Q(N(r,c.field))))return null;let o=e.filter(c=>!Q(N(r,c.field))),i=o.length===1,a=o[0];return{content:i&&a?{status:"interrupt",question:a.question,field:a.field,...a.suggestions?{suggestions:a.suggestions}:{},...a.context||t?{context:a.context??t}:{}}:{status:"interrupt",questions:o,...t?{context:t}:{}},flowTokenContent:{step:n,state:r,...i&&a?{field:a.field}:{}}}}async function K(e,t,n,r,o,i,a){let s=e,c={...t},p=50,f=0;for(;f++<p;){if(s===E)return{content:{status:"complete"},flowTokenContent:{state:c}};let d=n.get(s);if(!d)return{content:{status:"error",error:`Unknown node: "${s}"`}};try{let g=await d({state:c,meta:i,interrupt:we,showWidget:he,waniwani:a});if(ye(g)){for(let u of g.questions)u.validate&&o.set(`${s}:${u.field}`,u.validate);let y=Ce(g.questions,g.context,s,c);if(y)return y;for(let u of g.questions){let v=o.get(`${s}:${u.field}`);if(v)try{let S=N(c,u.field),x=await v(S);x&&typeof x=="object"&&(c=_(c,x))}catch(S){let x=S instanceof Error?S.message:String(S);Ee(c,u.field);let le=g.questions.map(F=>F.field===u.field?{...F,context:F.context?`ERROR: ${x}
1
+ function te(){return typeof window<"u"&&"openai"in window?"openai":"mcp-apps"}function pt(){return te()==="openai"}function lt(){return te()==="mcp-apps"}var I="__start__",C="__end__",ye=Symbol.for("waniwani.flow.interrupt"),he=Symbol.for("waniwani.flow.widget");function Te(e,t){let n=t?.context,r=[];for(let[o,i]of Object.entries(e))if(typeof i=="object"&&i!==null&&"question"in i){let s=i;r.push({question:s.question,field:o,suggestions:s.suggestions,context:s.context,validate:s.validate})}return{__type:ye,questions:r,context:n}}function ke(e,t){return{__type:he,tool:typeof e=="string"?e:e.id,...t}}function Se(e){return typeof e=="object"&&e!==null&&"__type"in e&&e.__type===ye}function ve(e){return typeof e=="object"&&e!==null&&"__type"in e&&e.__type===he}import{z as O}from"zod";var ne="waniwani/client";function $(e){if(typeof e=="object"&&e!==null)return e[ne]}function Re(e,t,n){return{track(r){return e.track({...r,meta:{...t,...r.meta}})},identify(r,o){return e.identify(r,o,t)},kb:e.kb,_config:n}}function U(e,t){for(let n of t){let r=e[n];if(typeof r=="string"&&r.length>0)return r}}var ft=["waniwani/sessionId","openai/sessionId","openai/session","sessionId","conversationId","mcp-session-id"],gt=["waniwani/requestId","openai/requestId","requestId","mcp/requestId"],wt=["waniwani/traceId","openai/traceId","traceId","mcp/traceId","openai/requestId","requestId"],mt=["waniwani/userId","openai/userId","externalUserId","userId","actorId"],yt=["correlationId","openai/requestId"];function b(e){return e?U(e,ft):void 0}function xe(e){return e?U(e,gt):void 0}function Ee(e){return e?U(e,wt):void 0}function Ie(e){return e?U(e,mt):void 0}function Ce(e){return e?U(e,yt):void 0}var ht=[{key:"waniwani/sessionId",source:"chatbar"},{key:"openai/sessionId",source:"chatgpt"},{key:"openai/session",source:"chatgpt"}];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 ht){let o=e[n];if(typeof o=="string"&&o.length>0)return r}}function be(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 Tt(e,t,n){let r=t.split("."),o=r.pop();if(!o)return;let i=e;for(let s of r)(i[s]==null||typeof i[s]!="object"||Array.isArray(i[s]))&&(i[s]={}),i=i[s];i[o]=n}function _e(e,t){let n=t.split("."),r=n.pop();if(!r)return;let o=e;for(let i of n){if(o==null||typeof o!="object")return;o=o[i]}o!=null&&typeof o=="object"&&delete o[r]}function re(e){let t={};for(let[n,r]of Object.entries(e))n.includes(".")?Tt(t,n,r):t[n]=r;return t}function P(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]=P(n[r],o):n[r]=o;return n}function oe(e){return e!=null&&e!==""}async function W(e,t){return e.type==="direct"?e.to:e.condition(t)}function Pe(e,t,n,r){if(e.every(c=>oe(D(r,c.field))))return null;let o=e.filter(c=>!oe(D(r,c.field))),i=o.length===1,s=o[0];return{content:i&&s?{status:"interrupt",question:s.question,field:s.field,...s.suggestions?{suggestions:s.suggestions}:{},...s.context||t?{context:s.context??t}:{}}:{status:"interrupt",questions:o,...t?{context:t}:{}},flowTokenContent:{step:n,state:r,...i&&s?{field:s.field}:{}}}}async function B(e,t,n,r,o,i,s){let a=e,c={...t},d=50,l=0;for(;l++<d;){if(a===C)return{content:{status:"complete"},flowTokenContent:{state:c}};let f=n.get(a);if(!f)return{content:{status:"error",error:`Unknown node: "${a}"`}};try{let p=await f({state:c,meta:i,interrupt:Te,showWidget:ke,waniwani:s});if(Se(p)){for(let R of p.questions)R.validate&&o.set(`${a}:${R.field}`,R.validate);let k=Pe(p.questions,p.context,a,c);if(k)return k;for(let R of p.questions){let S=o.get(`${a}:${R.field}`);if(S)try{let y=D(c,R.field),v=await S(y);v&&typeof v=="object"&&(c=P(c,v))}catch(y){let v=y instanceof Error?y.message:String(y);_e(c,R.field);let E=p.questions.map(F=>F.field===R.field?{...F,context:F.context?`ERROR: ${v}
2
2
 
3
- ${F.context}`:`ERROR: ${x}`}:F),fe=Ce(le,g.context,s,c);if(fe)return fe;break}}let m=r.get(s);if(!m)return{content:{status:"error",error:`No outgoing edge from node "${s}"`}};s=await P(m,c);continue}if(Te(g)){let y=g.field;if(y&&Q(N(c,y))){let m=r.get(s);if(!m)return{content:{status:"error",error:`No outgoing edge from node "${s}"`}};s=await P(m,c);continue}return{content:{status:"widget",tool:g.tool,data:g.data,description:g.description,interactive:g.interactive!==!1},flowTokenContent:{step:s,state:c,field:y,widgetId:g.tool}}}c=_(c,g);let T=r.get(s);if(!T)return{content:{status:"error",error:`No outgoing edge from node "${s}"`}};s=await P(T,c)}catch(l){return{content:{status:"error",error:l instanceof Error?l.message:String(l)},flowTokenContent:{step:s,state:c}}}}return{content:{status:"error",error:"Flow exceeded maximum iterations (possible infinite loop)"}}}var dt="@waniwani/sdk",ut="https://app.waniwani.ai",W=class{get baseUrl(){return(process.env.WANIWANI_API_URL??ut).replace(/\/$/,"")}get apiKey(){return process.env.WANIWANI_API_KEY}async get(t){if(!this.apiKey)throw new Error("[WaniWani KV] No API key configured. Set WANIWANI_API_KEY env var.");return await this.request("/api/mcp/redis/get",{key:t})??null}async set(t,n){if(!this.apiKey)throw new Error("[WaniWani KV] No API key configured. Set WANIWANI_API_KEY env var.");await this.request("/api/mcp/redis/set",{key:t,value:n})}async delete(t){if(this.apiKey)try{await this.request("/api/mcp/redis/delete",{key:t})}catch(n){console.error("[WaniWani KV] delete failed for key:",t,n)}}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":dt},body:JSON.stringify(n)});if(!o.ok){let a=await o.text().catch(()=>"");throw new Error(a||`KV store API error: HTTP ${o.status}`)}return(await o.json()).data}};var U=class{store=new W;get(t){return this.store.get(t)}set(t,n){return this.store.set(t,n)}delete(t){return this.store.delete(t)}};function be(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 _e(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 and include `intent`.'," `intent` must be a brief summary of the user's goal for this flow,"," including relevant prior context that led to triggering it, if available."," Do NOT invent missing context."," 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 i=Ie(o);if(i){let a=o.description??"",s=Object.entries(i).map(([c,p])=>{let f=be(p);return f?`\`${r}.${c}\` (${f})`:`\`${r}.${c}\``}).join(", ");n.push(a?`\`${r}\` (${a}): ${s}`:`\`${r}\`: ${s}`)}else{let a=be(o);n.push(a?`\`${r}\` (${a})`:`\`${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 pt={action:D.enum(["start","continue"]).describe('"start" to begin the flow, "continue" to resume after a pause (interrupt or widget)'),intent:D.string().optional().describe(`Required when action is "start". Provide a brief summary of the user's goal for this flow, including relevant prior context that led to triggering it, if available. Do not invent missing context.`),stateUpdates:D.record(D.string(),D.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 Pe(e){let{config:t,nodes:n,edges:r}=e,o=_e(t),i=`${t.description}
5
- ${o}`,a=e.store??new U,s=new Map;async function c(d,l,g,T){if(d.action==="start"){let y=typeof d.intent=="string"?d.intent.trim():void 0;if(!y)return{content:{status:"error",error:`Missing required "intent" for action "start". Include a brief summary of the user's goal for this flow and any relevant prior context that led to triggering it, if available.`}};d.intent=y;let m=r.get(I);if(!m)return{content:{status:"error",error:"No start edge"}};let u=X(d.stateUpdates??{}),v=await P(m,u);return K(v,u,n,r,s,g,T)}if(d.action==="continue"){if(!l)return{content:{status:"error",error:"No session ID available for continue action."}};let y;try{y=await a.get(l)}catch(S){let x=S instanceof Error?S.message:String(S);return{content:{status:"error",error:`Failed to load flow state (session "${l}"): ${x}`}}}if(!y)return{content:{status:"error",error:`Flow state not found for session "${l}". The flow may have expired.`}};let m=y.state,u=y.step;if(!u)return{content:{status:"error",error:'This flow has already completed. Use action "start" to begin a new flow.'}};let v=_(m,X(d.stateUpdates??{}));if(y.widgetId){let S=r.get(u);if(!S)return{content:{status:"error",error:`No edge from step "${u}"`}};let x=await P(S,v);return K(x,v,n,r,s,g,T)}return K(u,v,n,r,s,g,T)}return{content:{status:"error",error:`Unknown action: "${d.action}"`}}}let p={title:t.title,description:i,inputSchema:pt,annotations:t.annotations},f=(async(d,l)=>{let g=l,T=g._meta??{},y=C(T),m=j(g),u=await c(d,y,T,m);if(y)if(u.flowTokenContent&&u.content.status!=="complete")try{await a.set(y,u.flowTokenContent)}catch(S){let x=S instanceof Error?S.message:String(S);return{content:[{type:"text",text:JSON.stringify({status:"error",error:`Flow state failed to persist (session "${y}"): ${x}`},null,2)}],_meta:T,isError:!0}}else u.content.status==="complete"&&await a.delete(y);return{content:[{type:"text",text:JSON.stringify(u.content,null,2)}],_meta:T,...u.content.status==="error"?{isError:!0}:{}}});return{name:t.id,config:p,handler:f,async register(d){d.registerTool(t.id,p,f)},graph:e.graph}}function We(e,t){let n=["flowchart TD"];n.push(` ${I}((Start))`);for(let[r]of e)n.push(` ${r}[${r}]`);n.push(` ${E}((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 M=class{nodes=new Map;edges=new Map;config;constructor(t){this.config=t}addNode(t,n){if(t===I||t===E)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 We(this.nodes,this.edges)}compile(t){this.validate();let n=new Map(this.nodes),r=new Map(this.edges);return Pe({config:this.config,nodes:n,edges:r,store:t?.store,graph:()=>We(n,r)})}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!==E&&!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!==E&&!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 Me(e){return new M(e)}function Fe(e){let t=e.content;return JSON.parse(t[0]?.text??"")}async function Ae(e,t){let n=t?.stateStore,r=[],o=`test-session-${Math.random().toString(36).slice(2,10)}`,i={registerTool:(...p)=>{r.push(p)}};await e.register(i);let a=r[0]?.[2];if(!a)throw new Error(`Flow "${e.name}" did not register a handler`);let s={_meta:{sessionId:o}};async function c(p){return{...p,decodedState:n?await n.get(o):null}}return{async start(p,f){let d=await a({action:"start",intent:p,...f?{stateUpdates:f}:{}},s);return c(Fe(d))},async continueWith(p){let f=await a({action:"continue",...p?{stateUpdates:p}:{}},s);return c(Fe(f))},async lastState(){return n?n.get(o):null}}}var $="text/html+skybridge",q="text/html;profile=mcp-app",Ne=async(e,t)=>{let n=e.endsWith("/")?e.slice(0,-1):e;return await(await fetch(`${n}${t}`)).text()};function Ue(e){return{"openai/widgetDescription":e.description,"openai/widgetPrefersBorder":e.prefersBorder,"openai/widgetDomain":e.widgetDomain,...e.widgetCSP&&{"openai/widgetCSP":e.widgetCSP}}}function De(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 ee(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 Oe(e){let{id:t,title:n,description:r,baseUrl:o,htmlPath:i,widgetDomain:a,prefersBorder:s=!0,autoHeight:c=!0}=e,p=e.widgetCSP??{connect_domains:[o],resource_domains:[o]};if(process.env.NODE_ENV==="development")try{let{hostname:m}=new URL(o);(m==="localhost"||m==="127.0.0.1")&&(p={...p,connect_domains:[...p.connect_domains||[],`ws://${m}:*`,`wss://${m}:*`],resource_domains:[...p.resource_domains||[],`http://${m}:*`]})}catch{}let f=`ui://widgets/apps-sdk/${t}.html`,d=`ui://widgets/ext-apps/${t}.html`,l=null,g=()=>(l||(l=Ne(o,i)),l),T=r;async function y(m){let u=await g();m.registerResource(`${t}-openai-widget`,f,{title:n,description:T,mimeType:$,_meta:{"openai/widgetDescription":T,"openai/widgetPrefersBorder":s}},async v=>({contents:[{uri:v.href,mimeType:$,text:u,_meta:Ue({description:T,prefersBorder:s,widgetDomain:a,widgetCSP:p})}]})),m.registerResource(`${t}-mcp-widget`,d,{title:n,description:T,mimeType:q,_meta:{ui:{prefersBorder:s}}},async v=>({contents:[{uri:v.href,mimeType:q,text:u,_meta:De({description:T,prefersBorder:s,widgetDomain:a,widgetCSP:p})}]}))}return{id:t,title:n,description:r,openaiUri:f,mcpUri:d,autoHeight:c,register:y}}function lt(e,t){let{resource:n,description:r,inputSchema:o,annotations:i,autoInjectResultText:a=!0}=e,s=e.id??n?.id,c=e.title??n?.title;if(!s)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 p=n?ee({openaiTemplateUri:n.openaiUri,mcpTemplateUri:n.mcpUri,invoking:e.invoking??"Loading...",invoked:e.invoked??"Loaded",autoHeight:n.autoHeight}):void 0;return{id:s,title:c,description:r,async register(f){f.registerTool(s,{title:c,description:r,inputSchema:o,annotations:i,...p&&{_meta:p}},(async(d,l)=>{let g=l,T=g._meta??{},y=j(g),m=await t(d,{extra:{_meta:T},waniwani:y});return n&&m.data?{content:[{type:"text",text:m.text}],structuredContent:m.data,_meta:{...p,...T,...a===!1?{"waniwani/autoInjectResultText":!1}:{}}}:{content:[{type:"text",text:m.text}],...m.data?{structuredContent:m.data}:{},...a===!1?{_meta:{"waniwani/autoInjectResultText":!1}}:{}}}))}}}async function ft(e,t){await Promise.all(t.map(n=>n.register(e)))}var B=class extends Error{constructor(n,r){super(n);this.status=r;this.name="WaniWaniError"}};var gt="@waniwani/sdk";function je(e){let{apiUrl:t,apiKey:n}=e;function r(){if(!n)throw new Error("WANIWANI_API_KEY is not set");return n}async function o(i,a,s){let c=r(),p=`${t.replace(/\/$/,"")}${a}`,f={Authorization:`Bearer ${c}`,"X-WaniWani-SDK":gt},d={method:i,headers:f};s!==void 0&&(f["Content-Type"]="application/json",d.body=JSON.stringify(s));let l=await fetch(p,d);if(!l.ok){let T=await l.text().catch(()=>"");throw new B(T||`KB API error: HTTP ${l.status}`,l.status)}return(await l.json()).data}return{async ingest(i){return o("POST","/api/mcp/kb/ingest",{files:i})},async search(i,a){return o("POST","/api/mcp/kb/search",{query:i,...a})},async sources(){return o("GET","/api/mcp/kb/sources")}}}var mt="@waniwani/sdk";function L(e,t={}){let n=t.now??(()=>new Date),r=t.generateId??Ke,o=yt(e),i=H(e.meta),a=H(e.metadata),s=Tt(e,i),c=R(e.eventId)??r(),p=kt(e.timestamp,n),f=R(e.source)??b(i)??t.source??mt,d=te(e)?{...e}:void 0,l={...a};return Object.keys(i).length>0&&(l.meta=i),d&&(l.rawLegacy=d),{id:c,type:"mcp.event",name:o,source:f,timestamp:p,correlation:s,properties:wt(e,o),metadata:l,rawLegacy:d}}function Ke(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?`evt_${crypto.randomUUID()}`:`evt_${Math.random().toString(36).slice(2,10)}_${Date.now().toString(36)}`}function wt(e,t){if(!te(e))return H(e.properties);let n=ht(e,t),r=H(e.properties);return{...n,...r}}function ht(e,t){switch(t){case"tool.called":{let n={};return R(e.toolName)&&(n.name=e.toolName),R(e.toolType)&&(n.type=e.toolType),n}case"quote.succeeded":{let n={};return typeof e.quoteAmount=="number"&&(n.amount=e.quoteAmount),R(e.quoteCurrency)&&(n.currency=e.quoteCurrency),n}case"link.clicked":{let n={};return R(e.linkUrl)&&(n.url=e.linkUrl),n}case"purchase.completed":{let n={};return typeof e.purchaseAmount=="number"&&(n.amount=e.purchaseAmount),R(e.purchaseCurrency)&&(n.currency=e.purchaseCurrency),n}default:return{}}}function yt(e){return te(e)?e.eventType:e.event}function Tt(e,t){let n=R(e.requestId)??Se(t),r=R(e.sessionId)??C(t),o=R(e.traceId)??ve(t),i=R(e.externalUserId)??xe(t),a=R(e.correlationId)??Re(t)??n,s={};return r&&(s.sessionId=r),o&&(s.traceId=o),n&&(s.requestId=n),a&&(s.correlationId=a),i&&(s.externalUserId=i),s}function kt(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 H(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function R(e){if(typeof e=="string"&&e.trim().length!==0)return e}function te(e){return"eventType"in e}var St="/api/mcp/events/v2/batch";var $e="@waniwani/sdk",vt=new Set([401,403]),xt=new Set([408,425,429,500,502,503,504]);function qe(e){return new ne(e)}var ne=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=Et(t.apiUrl,t.endpointPath??St),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":$e},body:JSON.stringify(this.makeBatchRequest(t))})}catch(i){return{kind:"retryable",reason:Ct(i)}}if(vt.has(n.status))return{kind:"auth",status:n.status};if(xt.has(n.status))return{kind:"retryable",reason:`HTTP ${n.status}`};if(!n.ok)return{kind:"permanent",reason:`HTTP ${n.status}`};let r=await It(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:$e,version:this.sdkVersion??"0.0.0"},events:t}}classifyRejectedEvents(t,n){let r=new Map(t.map(a=>[a.id,a])),o=[],i=[];for(let a of n){let s=r.get(a.eventId);if(s){if(Rt(a)){o.push(s);continue}i.push(s)}}return{retryable:o,permanent:i}}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 Rt(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 It(e){let t=await e.text();if(t)try{return JSON.parse(t)}catch{return}}function Et(e,t){let n=e.endsWith("/")?e:`${e}/`,r=t.startsWith("/")?t.slice(1):t;return`${n}${r}`}function Ct(e){return e instanceof Error?e.message:String(e)}function Be(e){let{apiUrl:t,apiKey:n,tracking:r}=e;function o(){if(!n)throw new Error("WANIWANI_API_KEY is not set");return n}let i=n?qe({apiUrl: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,a={async identify(s,c,p){o();let f=L({event:"user.identified",externalUserId:s,properties:c,meta:p});return i?.enqueue(f),{eventId:f.id}},async track(s){o();let c=L(s);return i?.enqueue(c),{eventId:c.id}},async flush(){o(),await i?.flush()},async shutdown(s){return o(),await i?.shutdown({timeoutMs:s?.timeoutMs??r.shutdownTimeoutMs})??{timedOut:!1,pendingEvents:0}}};return i&&bt(a,r.shutdownTimeoutMs),a}function bt(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 V(e){let t=e,n=t?.apiUrl??"https://app.waniwani.ai",r=t?.apiKey??process.env.WANIWANI_API_KEY,o={endpointPath:t?.tracking?.endpointPath??"/api/mcp/events/v2/batch",flushIntervalMs:t?.tracking?.flushIntervalMs??1e3,maxBatchSize:t?.tracking?.maxBatchSize??20,maxBufferSize:t?.tracking?.maxBufferSize??1e3,maxRetries:t?.tracking?.maxRetries??3,retryBaseDelayMs:t?.tracking?.retryBaseDelayMs??200,retryMaxDelayMs:t?.tracking?.retryMaxDelayMs??2e3,shutdownTimeoutMs:t?.tracking?.shutdownTimeoutMs??2e3},i={apiUrl:n,apiKey:r,tracking:o},a=Be(i),s=je(i);return{...a,kb:s,_config:i}}function _t(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 Pt(e){let t={apiKey:e?.apiKey,apiUrl:e?.apiUrl},n;function r(){return n||(n=V(t)),n}return async function(i){let a;try{a=await i.json()}catch{return new Response(JSON.stringify({error:"Invalid JSON"}),{status:400,headers:{"Content-Type":"application/json"}})}if(!Array.isArray(a.events)||a.events.length===0)return new Response(JSON.stringify({error:"Missing or empty events array"}),{status:400,headers:{"Content-Type":"application/json"}});try{let s=r(),c=[];for(let p of a.events){let f=_t(p),d=await s.track(f);c.push(d.eventId)}return await s.flush(),new Response(JSON.stringify({ok:!0,accepted:c.length}),{status:200,headers:{"Content-Type":"application/json"}})}catch(s){let c=s instanceof Error?s.message:"Unknown error";return new Response(JSON.stringify({error:c}),{status:500,headers:{"Content-Type":"application/json"}})}}}function z(e,t){return t?(...n)=>console.log(`[waniwani:${e}]`,...n):()=>{}}var re=z("widget-token",!!process.env.WANIWANI_DEBUG),Wt=120*1e3,Y=class{cached=null;pending=null;config;constructor(t){this.config=t}async getToken(t,n){return this.cached&&Date.now()<this.cached.expiresAt-Wt?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=Mt(this.config.apiUrl,"/api/mcp/widget-tokens");re("minting token from",r);let o={};t&&(o.sessionId=t),n&&(o.traceId=n);try{let i=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify(o),signal:AbortSignal.timeout(5e3)});if(re("mint response:",i.status),!i.ok)return null;let a=await i.json(),s=a.data&&typeof a.data=="object"?a.data:a,c=new Date(s.expiresAt).getTime();return!s.token||Number.isNaN(c)?null:(this.cached={token:s.token,expiresAt:c},s.token)}catch(i){return re("mint failed:",i),null}}};function Mt(e,t){return`${e.endsWith("/")?e.slice(0,-1):e}${t}`}var He="waniwani/sessionId",oe="waniwani/geoLocation",ie="waniwani/userLocation";function k(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}function O(e){if(!k(e))return;let t=e._meta;return k(t)?t:void 0}function se(e){if(!k(e))return;let t=e.content;return Array.isArray(t)?t.find(r=>k(r)&&r.type==="text"&&typeof r.text=="string")?.text:void 0}function Ft(e,t){return typeof t=="function"?t(e)??"other":t??"other"}function ae(e,t,n,r,o,i){let a=Ft(e,n.toolType),s=O(t);return console.log("[waniwani:debug] buildTrackInput meta:",JSON.stringify(s),"-> source:",b(s)),{event:"tool.called",properties:{name:e,type:a,...r??{},...i?.input!==void 0&&{input:i.input},...i?.output!==void 0&&{output:i.output}},meta:s,source:b(s),metadata:{...n.metadata??{},...o&&{clientInfo:o}}}}async function ce(e,t,n){try{await e.track(t)}catch(r){n?.(ue(r))}}async function de(e,t){try{await e.flush()}catch(n){t?.(ue(n))}}async function Ve(e,t,n,r,o){if(!k(e))return;k(e._meta)||(e._meta={});let i=e._meta,a=k(i.waniwani)?i.waniwani:void 0,s={...a??{},endpoint:a?.endpoint??`${n.replace(/\/$/,"")}/api/mcp/events/v2/batch`};if(t)try{let d=await t.getToken();d&&(s.token=d)}catch(d){o?.(ue(d))}let c=C(i);c&&(s.sessionId||(s.sessionId=c));let p=Ge(i);p!==void 0&&(s.geoLocation||(s.geoLocation=p));let f=b(O(r));f&&!s.source&&(s.source=f),i.waniwani=s}var Le=["openai/outputTemplate","openai/widgetAccessible","openai/resultCanProduceWidget","openai/toolInvocation/invoking","openai/toolInvocation/invoked","ui/resourceUri","ui"];function ze(e,t){if(!t||!k(e))return;let n=!1;for(let o of Le)if(o in t){n=!0;break}if(!n)return;k(e._meta)||(e._meta={});let r=e._meta;for(let o of Le)o in t&&(o in r||(r[o]=t[o]))}function Ye(e,t){let n=O(t);if(!n||!k(e))return;k(e._meta)||(e._meta={});let r=e._meta,o=C(n);o&&!r[He]&&(r[He]=o);let i=Ge(n);i&&(r[oe]||(r[oe]=i),r[ie]||(r[ie]=i))}function Ge(e){if(!e)return;let t=e[oe]??e[ie];if(k(t)||typeof t=="string")return t}function ue(e){return e instanceof Error?e:new Error(String(e))}function pe(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Je(e){if(console.log("[waniwani:debug] extractTransportSessionId extra keys:",Object.keys(e)),console.log("[waniwani:debug] extractTransportSessionId extra.sessionId:",typeof e.sessionId,e.sessionId),typeof e.sessionId=="string"&&e.sessionId)return console.log("[waniwani:debug] extractTransportSessionId found extra.sessionId:",e.sessionId),e.sessionId;if(pe(e.requestInfo)){let t=e.requestInfo.headers;if(console.log("[waniwani:debug] extractTransportSessionId requestInfo.headers:",pe(t)?Object.keys(t):t),pe(t)){let n={};for(let o of Object.keys(t))n[o.toLowerCase()]=t[o];let r=n["mcp-session-id"];if(console.log("[waniwani:debug] extractTransportSessionId headers['mcp-session-id']:",r),typeof r=="string"&&r)return r}}else console.log("[waniwani:debug] extractTransportSessionId no requestInfo on extra")}var Xe=Symbol.for("waniwani.wrappedHandler"),G=z("mcp",!!process.env.WANIWANI_DEBUG),Qe="https://app.waniwani.ai";function Ze(e,t,n,r){let{server:o,tracker:i,opts:a,tokenCache:s,injectToken:c}=n,p=async(f,d)=>{let l=O(d)??{},g=C(l);if(console.log("[waniwani:debug] bridge sessionId \u2014 existingSessionId from meta:",g,"| meta keys:",Object.keys(l)),!g&&k(d)){let u=Je(d);console.log("[waniwani:debug] bridge sessionId \u2014 transportSid:",u),u&&(l["waniwani/sessionId"]=u,d._meta=l)}let T=ke(i,l,{apiUrl:i._config.apiUrl,apiKey:i._config.apiKey});k(d)&&(d[Z]=T);let y=performance.now(),m=o.server?.getClientVersion?.();try{let u=await t(f,d),v=Math.round(performance.now()-y);G(`tool "${e}" handler returned in ${v}ms, running post-processing...`);let S=k(u)&&u.isError===!0;if(S){let x=se(u);console.error(`[waniwani] Tool "${e}" returned error${x?`: ${x}`:""}`)}return await ce(i,ae(e,d,a,{durationMs:v,status:S?"error":"ok",...S&&{errorMessage:se(u)??"Unknown tool error"}},m,{input:f,output:u}),a.onError),G(`tool "${e}" tracking done`),a.flushAfterToolCall&&await de(i,a.onError),Ye(u,d),ze(u,r),c&&(await Ve(u,s,i._config.apiUrl??Qe,d,a.onError),G(`tool "${e}" widget config injected`)),G(`tool "${e}" post-processing complete, returning result`),u}catch(u){let v=Math.round(performance.now()-y);throw await ce(i,ae(e,d,a,{durationMs:v,status:"error",errorMessage:u instanceof Error?u.message:String(u)},m,{input:f}),a.onError),a.flushAfterToolCall&&await de(i,a.onError),u}};return p[Xe]=!0,p}function At(e,t){let n=e;if(n.__waniwaniWrapped)return n;n.__waniwaniWrapped=!0;let r=t??{},o=r.client??V(),i=r.injectWidgetToken!==!1,a=o._config.apiKey?new Y({apiUrl:o._config.apiUrl??Qe,apiKey:o._config.apiKey}):null,s={server:e,tracker:o,opts:r,tokenCache:a,injectToken:i},c=e.registerTool.bind(e);n.registerTool=((...f)=>{let[d,l,g]=f;if(typeof g!="function")return c(...f);let T=typeof d=="string"&&d.trim().length>0?d:"unknown",y=k(l)&&k(l._meta)?l._meta:void 0,m=Ze(T,g,s,y);return c(d,l,m)});let p=e._registeredTools;if(k(p))for(let[f,d]of Object.entries(p)){if(!k(d))continue;let l=d.handler;if(typeof l!="function"||l[Xe])continue;let g=k(d._meta)?d._meta:void 0;d.handler=Ze(f,l,s,g)}return n}export{E as END,I as START,M as StateGraph,W as WaniwaniKvStore,Me as createFlow,Ae as createFlowTestHarness,Oe as createResource,lt as createTool,Pt as createTrackingRoute,J as detectPlatform,tt as isMCPApps,et as isOpenAI,ft as registerTools,At as withWaniwani};
3
+ ${F.context}`:`ERROR: ${v}`}:F),_=Pe(E,p.context,a,c);if(_)return _;break}}let g=r.get(a);if(!g)return{content:{status:"error",error:`No outgoing edge from node "${a}"`}};a=await W(g,c);continue}if(ve(p)){let k=p.field;if(k&&oe(D(c,k))){let g=r.get(a);if(!g)return{content:{status:"error",error:`No outgoing edge from node "${a}"`}};a=await W(g,c);continue}return{content:{status:"widget",tool:p.tool,data:p.data,description:p.description,interactive:p.interactive!==!1},flowTokenContent:{step:a,state:c,field:k,widgetId:p.tool}}}c=P(c,p);let h=r.get(a);if(!h)return{content:{status:"error",error:`No outgoing edge from node "${a}"`}};a=await W(h,c)}catch(u){return{content:{status:"error",error:u instanceof Error?u.message:String(u)},flowTokenContent:{step:a,state:c}}}}return{content:{status:"error",error:"Flow exceeded maximum iterations (possible infinite loop)"}}}function ie(e){return typeof e=="object"&&e!==null&&e.__ww_enc===1&&typeof e.ct=="string"&&typeof e.iv=="string"}var We=new Map;async function Ae(e){let t=We.get(e);if(t)return t;let n=Buffer.from(e,"base64");if(n.length!==32)throw new Error("[WaniWani KV] WANIWANI_ENCRYPTION_KEY must be a base64-encoded 32-byte (256-bit) key.");let r=await globalThis.crypto.subtle.importKey("raw",n,{name:"AES-GCM"},!1,["encrypt","decrypt"]);return We.set(e,r),r}async function Me(e,t){let n=await Ae(t),r=globalThis.crypto.getRandomValues(new Uint8Array(12)),o=new TextEncoder().encode(JSON.stringify(e)),i=await globalThis.crypto.subtle.encrypt({name:"AES-GCM",iv:r},n,o);return{__ww_enc:1,ct:Buffer.from(i).toString("base64"),iv:Buffer.from(r).toString("base64")}}async function Fe(e,t){let n=await Ae(t),r=Buffer.from(e.ct,"base64"),o=Buffer.from(e.iv,"base64"),i;try{i=await globalThis.crypto.subtle.decrypt({name:"AES-GCM",iv:o},n,r)}catch{throw new Error("[WaniWani KV] Decryption failed. The encryption key may be incorrect or the data may be corrupted.")}return JSON.parse(new TextDecoder().decode(i))}var kt="@waniwani/sdk",St="https://app.waniwani.ai",A=class{get baseUrl(){return(process.env.WANIWANI_API_URL??St).replace(/\/$/,"")}get apiKey(){return process.env.WANIWANI_API_KEY}get encryptionKey(){return process.env.WANIWANI_ENCRYPTION_KEY}async get(t){if(!this.apiKey)throw new Error("[WaniWani KV] No API key configured. Set WANIWANI_API_KEY env var.");let n=await this.request("/api/mcp/redis/get",{key:t});if(n==null)return null;if(ie(n)){if(!this.encryptionKey)throw new Error("[WaniWani KV] Encrypted data found but WANIWANI_ENCRYPTION_KEY is not set.");return Fe(n,this.encryptionKey)}return n}async set(t,n){if(!this.apiKey)throw new Error("[WaniWani KV] No API key configured. Set WANIWANI_API_KEY env var.");let r=this.encryptionKey?await Me(n,this.encryptionKey):n;await this.request("/api/mcp/redis/set",{key:t,value:r})}async delete(t){if(this.apiKey)try{await this.request("/api/mcp/redis/delete",{key:t})}catch(n){console.error("[WaniWani KV] delete failed for key:",t,n)}}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":kt},body:JSON.stringify(n)});if(!o.ok){let s=await o.text().catch(()=>"");throw new Error(s||`KV store API error: HTTP ${o.status}`)}return(await o.json()).data}};var K=class{store=new A;get(t){return this.store.get(t)}set(t,n){return this.store.set(t,n)}delete(t){return this.store.delete(t)}};function Ue(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 Ne(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 and include `intent`.'," `intent` must be a brief summary of the user's goal for this flow,"," including relevant prior context that led to triggering it, if available."," Do NOT invent missing context."," 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 i=be(o);if(i){let s=o.description??"",a=Object.entries(i).map(([c,d])=>{let l=Ue(d);return l?`\`${r}.${c}\` (${l})`:`\`${r}.${c}\``}).join(", ");n.push(s?`\`${r}\` (${s}): ${a}`:`\`${r}\`: ${a}`)}else{let s=Ue(o);n.push(s?`\`${r}\` (${s})`:`\`${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
+ `)}function se(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}function De(e){let t=Ke(e),n=se(t.waniwani)?t.waniwani:{};return e.meta({...t,waniwani:{...n,redacted:!0}})}function vt(e){let n=Ke(e).waniwani;return se(n)&&n.redacted===!0}function Ke(e){let t=e.meta;if(typeof t!="function")return{};let n=t.call(e);return se(n)?n:{}}function Oe(e){if(!e)return[];let t=[];for(let[n,r]of Object.entries(e))vt(r)&&t.push(n);return t}var q="waniwani/redactedStateUpdateFields";var Rt={action:O.enum(["start","continue"]).describe('"start" to begin the flow, "continue" to resume after a pause (interrupt or widget)'),intent:O.string().optional().describe(`Required when action is "start". Provide a brief summary of the user's goal for this flow, including relevant prior context that led to triggering it, if available. Do not invent missing context.`),stateUpdates:O.record(O.string(),O.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 je(e){let{config:t,nodes:n,edges:r}=e,o=Ne(t),i=`${t.description}
5
+ ${o}`,s=e.store??new K,a=new Map;async function c(u,p,h,k){if(u.action==="start"){let g=typeof u.intent=="string"?u.intent.trim():void 0;if(!g)return{content:{status:"error",error:`Missing required "intent" for action "start". Include a brief summary of the user's goal for this flow and any relevant prior context that led to triggering it, if available.`}};u.intent=g;let R=r.get(I);if(!R)return{content:{status:"error",error:"No start edge"}};let S=re(u.stateUpdates??{}),y=await W(R,S);return B(y,S,n,r,a,h,k)}if(u.action==="continue"){if(!p)return{content:{status:"error",error:"No session ID available for continue action."}};let g;try{g=await s.get(p)}catch(v){let E=v instanceof Error?v.message:String(v);return{content:{status:"error",error:`Failed to load flow state (session "${p}"): ${E}`}}}if(!g)return{content:{status:"error",error:`Flow state not found for session "${p}". The flow may have expired.`}};let R=g.state,S=g.step;if(!S)return{content:{status:"error",error:'This flow has already completed. Use action "start" to begin a new flow.'}};let y=P(R,re(u.stateUpdates??{}));if(g.widgetId){let v=r.get(S);if(!v)return{content:{status:"error",error:`No edge from step "${S}"`}};let E=await W(v,y);return B(E,y,n,r,a,h,k)}return B(S,y,n,r,a,h,k)}return{content:{status:"error",error:`Unknown action: "${u.action}"`}}}let d=Oe(t.state),l={title:t.title,description:i,inputSchema:Rt,annotations:t.annotations,...d.length>0&&{_meta:{[q]:d}}},f=(async(u,p)=>{let h=p,k=h._meta??{},g=b(k),R=$(h),S=await c(u,g,k,R);if(g)if(S.flowTokenContent&&S.content.status!=="complete")try{await s.set(g,S.flowTokenContent)}catch(v){let E=v instanceof Error?v.message:String(v);return{content:[{type:"text",text:JSON.stringify({status:"error",error:`Flow state failed to persist (session "${g}"): ${E}`},null,2)}],_meta:k,isError:!0}}else S.content.status==="complete"&&await s.delete(g);return{content:[{type:"text",text:JSON.stringify(S.content,null,2)}],_meta:k,...S.content.status==="error"?{isError:!0}:{}}});return{name:t.id,config:l,handler:f,async register(u){u.registerTool(t.id,l,f)},graph:e.graph}}function $e(e,t){let n=["flowchart TD"];n.push(` ${I}((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 M=class{nodes=new Map;edges=new Map;config;constructor(t){this.config=t}addNode(t,n){if(t===I||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 $e(this.nodes,this.edges)}compile(t){this.validate();let n=new Map(this.nodes),r=new Map(this.edges);return je({config:this.config,nodes:n,edges:r,store:t?.store,graph:()=>$e(n,r)})}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!==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!==I&&!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 Be(e){return new M(e)}function qe(e){let t=e.content;return JSON.parse(t[0]?.text??"")}async function Le(e,t){let n=t?.stateStore,r=[],o=`test-session-${Math.random().toString(36).slice(2,10)}`,i={registerTool:(...d)=>{r.push(d)}};await e.register(i);let s=r[0]?.[2];if(!s)throw new Error(`Flow "${e.name}" 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,l){let f=await s({action:"start",intent:d,...l?{stateUpdates:l}:{}},a);return c(qe(f))},async continueWith(d){let l=await s({action:"continue",...d?{stateUpdates:d}:{}},a);return c(qe(l))},async lastState(){return n?n.get(o):null}}}var L="text/html+skybridge",H="text/html;profile=mcp-app",He=async(e,t)=>{let n=e.endsWith("/")?e.slice(0,-1):e;return await(await fetch(`${n}${t}`)).text()};function Ve(e){return{"openai/widgetDescription":e.description,"openai/widgetPrefersBorder":e.prefersBorder,"openai/widgetDomain":e.widgetDomain,...e.widgetCSP&&{"openai/widgetCSP":e.widgetCSP}}}function ze(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 ae(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 Ye(e){let{id:t,title:n,description:r,baseUrl:o,htmlPath:i,widgetDomain:s,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:g}=new URL(o);(g==="localhost"||g==="127.0.0.1")&&(d={...d,connect_domains:[...d.connect_domains||[],`ws://${g}:*`,`wss://${g}:*`],resource_domains:[...d.resource_domains||[],`http://${g}:*`]})}catch{}let l=`ui://widgets/apps-sdk/${t}.html`,f=`ui://widgets/ext-apps/${t}.html`,u=null,p=()=>(u||(u=He(o,i)),u),h=r;async function k(g){let R=await p();g.registerResource(`${t}-openai-widget`,l,{title:n,description:h,mimeType:L,_meta:{"openai/widgetDescription":h,"openai/widgetPrefersBorder":a}},async S=>({contents:[{uri:S.href,mimeType:L,text:R,_meta:Ve({description:h,prefersBorder:a,widgetDomain:s,widgetCSP:d})}]})),g.registerResource(`${t}-mcp-widget`,f,{title:n,description:h,mimeType:H,_meta:{ui:{prefersBorder:a}}},async S=>({contents:[{uri:S.href,mimeType:H,text:R,_meta:ze({description:h,prefersBorder:a,widgetDomain:s,widgetCSP:d})}]}))}return{id:t,title:n,description:r,openaiUri:l,mcpUri:f,autoHeight:c,register:k}}function xt(e,t){let{resource:n,description:r,inputSchema:o,annotations:i,autoInjectResultText:s=!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?ae({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(l){l.registerTool(a,{title:c,description:r,inputSchema:o,annotations:i,...d&&{_meta:d}},(async(f,u)=>{let p=u,h=p._meta??{},k=$(p),g=await t(f,{extra:{_meta:h},waniwani:k});return n&&g.data?{content:[{type:"text",text:g.text}],structuredContent:g.data,_meta:{...d,...h,...s===!1?{"waniwani/autoInjectResultText":!1}:{}}}:{content:[{type:"text",text:g.text}],...g.data?{structuredContent:g.data}:{},...s===!1?{_meta:{"waniwani/autoInjectResultText":!1}}:{}}}))}}}async function Et(e,t){await Promise.all(t.map(n=>n.register(e)))}var V=class extends Error{constructor(n,r){super(n);this.status=r;this.name="WaniWaniError"}};var It="@waniwani/sdk";function Ge(e){let{apiUrl:t,apiKey:n}=e;function r(){if(!n)throw new Error("WANIWANI_API_KEY is not set");return n}async function o(i,s,a){let c=r(),d=`${t.replace(/\/$/,"")}${s}`,l={Authorization:`Bearer ${c}`,"X-WaniWani-SDK":It},f={method:i,headers:l};a!==void 0&&(l["Content-Type"]="application/json",f.body=JSON.stringify(a));let u=await fetch(d,f);if(!u.ok){let h=await u.text().catch(()=>"");throw new V(h||`KB API error: HTTP ${u.status}`,u.status)}return(await u.json()).data}return{async ingest(i){return o("POST","/api/mcp/kb/ingest",{files:i})},async search(i,s){return o("POST","/api/mcp/kb/search",{query:i,...s})},async sources(){return o("GET","/api/mcp/kb/sources")}}}var Ct="@waniwani/sdk";function Y(e,t={}){let n=t.now??(()=>new Date),r=t.generateId??Ze,o=Pt(e),i=z(e.meta),s=z(e.metadata),a=Wt(e,i),c=x(e.eventId)??r(),d=At(e.timestamp,n),l=x(e.source)??N(i)??t.source??Ct,f=ce(e)?{...e}:void 0,u={...s};return Object.keys(i).length>0&&(u.meta=i),f&&(u.rawLegacy=f),{id:c,type:"mcp.event",name:o,source:l,timestamp:d,correlation:a,properties:bt(e,o),metadata:u,rawLegacy:f}}function Ze(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?`evt_${crypto.randomUUID()}`:`evt_${Math.random().toString(36).slice(2,10)}_${Date.now().toString(36)}`}function bt(e,t){if(!ce(e))return z(e.properties);let n=_t(e,t),r=z(e.properties);return{...n,...r}}function _t(e,t){switch(t){case"tool.called":{let n={};return x(e.toolName)&&(n.name=e.toolName),x(e.toolType)&&(n.type=e.toolType),n}case"quote.succeeded":{let n={};return typeof e.quoteAmount=="number"&&(n.amount=e.quoteAmount),x(e.quoteCurrency)&&(n.currency=e.quoteCurrency),n}case"link.clicked":{let n={};return x(e.linkUrl)&&(n.url=e.linkUrl),n}case"purchase.completed":{let n={};return typeof e.purchaseAmount=="number"&&(n.amount=e.purchaseAmount),x(e.purchaseCurrency)&&(n.currency=e.purchaseCurrency),n}default:return{}}}function Pt(e){return ce(e)?e.eventType:e.event}function Wt(e,t){let n=x(e.requestId)??xe(t),r=x(e.sessionId)??b(t),o=x(e.traceId)??Ee(t),i=x(e.externalUserId)??Ie(t),s=x(e.correlationId)??Ce(t)??n,a={};return r&&(a.sessionId=r),o&&(a.traceId=o),n&&(a.requestId=n),s&&(a.correlationId=s),i&&(a.externalUserId=i),a}function At(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 z(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function x(e){if(typeof e=="string"&&e.trim().length!==0)return e}function ce(e){return"eventType"in e}var Mt="/api/mcp/events/v2/batch";var Je="@waniwani/sdk",Ft=new Set([401,403]),Ut=new Set([408,425,429,500,502,503,504]);function Xe(e){return new de(e)}var de=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=Kt(t.apiUrl,t.endpointPath??Mt),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":Je},body:JSON.stringify(this.makeBatchRequest(t))})}catch(i){return{kind:"retryable",reason:Ot(i)}}if(Ft.has(n.status))return{kind:"auth",status:n.status};if(Ut.has(n.status))return{kind:"retryable",reason:`HTTP ${n.status}`};if(!n.ok)return{kind:"permanent",reason:`HTTP ${n.status}`};let r=await Dt(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:Je,version:this.sdkVersion??"0.0.0"},events:t}}classifyRejectedEvents(t,n){let r=new Map(t.map(s=>[s.id,s])),o=[],i=[];for(let s of n){let a=r.get(s.eventId);if(a){if(Nt(s)){o.push(a);continue}i.push(a)}}return{retryable:o,permanent:i}}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 Nt(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 Dt(e){let t=await e.text();if(t)try{return JSON.parse(t)}catch{return}}function Kt(e,t){let n=e.endsWith("/")?e:`${e}/`,r=t.startsWith("/")?t.slice(1):t;return`${n}${r}`}function Ot(e){return e instanceof Error?e.message:String(e)}function Qe(e){let{apiUrl:t,apiKey:n,tracking:r}=e;function o(){if(!n)throw new Error("WANIWANI_API_KEY is not set");return n}let i=n?Xe({apiUrl: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,s={async identify(a,c,d){o();let l=Y({event:"user.identified",externalUserId:a,properties:c,meta:d});return i?.enqueue(l),{eventId:l.id}},async track(a){o();let c=Y(a);return i?.enqueue(c),{eventId:c.id}},async flush(){o(),await i?.flush()},async shutdown(a){return o(),await i?.shutdown({timeoutMs:a?.timeoutMs??r.shutdownTimeoutMs})??{timedOut:!1,pendingEvents:0}}};return i&&jt(s,r.shutdownTimeoutMs),s}function jt(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 G(e){let t=e,n=t?.apiUrl??"https://app.waniwani.ai",r=t?.apiKey??process.env.WANIWANI_API_KEY,o={endpointPath:t?.tracking?.endpointPath??"/api/mcp/events/v2/batch",flushIntervalMs:t?.tracking?.flushIntervalMs??1e3,maxBatchSize:t?.tracking?.maxBatchSize??20,maxBufferSize:t?.tracking?.maxBufferSize??1e3,maxRetries:t?.tracking?.maxRetries??3,retryBaseDelayMs:t?.tracking?.retryBaseDelayMs??200,retryMaxDelayMs:t?.tracking?.retryMaxDelayMs??2e3,shutdownTimeoutMs:t?.tracking?.shutdownTimeoutMs??2e3},i={apiUrl:n,apiKey:r,tracking:o},s=Qe(i),a=Ge(i);return{...s,kb:a,_config:i}}function $t(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 Bt(e){let t={apiKey:e?.apiKey,apiUrl:e?.apiUrl},n;function r(){return n||(n=G(t)),n}return async function(i){let s;try{s=await i.json()}catch{return new Response(JSON.stringify({error:"Invalid JSON"}),{status:400,headers:{"Content-Type":"application/json"}})}if(!Array.isArray(s.events)||s.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 s.events){let l=$t(d),f=await a.track(l);c.push(f.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"}})}}}function Z(e,t){return t?(...n)=>console.log(`[waniwani:${e}]`,...n):()=>{}}var ue=Z("widget-token",!!process.env.WANIWANI_DEBUG),qt=120*1e3,J=class{cached=null;pending=null;config;constructor(t){this.config=t}async getToken(t,n){return this.cached&&Date.now()<this.cached.expiresAt-qt?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=Lt(this.config.apiUrl,"/api/mcp/widget-tokens");ue("minting token from",r);let o={};t&&(o.sessionId=t),n&&(o.traceId=n);try{let i=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify(o),signal:AbortSignal.timeout(5e3)});if(ue("mint response:",i.status),!i.ok)return null;let s=await i.json(),a=s.data&&typeof s.data=="object"?s.data:s,c=new Date(a.expiresAt).getTime();return!a.token||Number.isNaN(c)?null:(this.cached={token:a.token,expiresAt:c},a.token)}catch(i){return ue("mint failed:",i),null}}};function Lt(e,t){return`${e.endsWith("/")?e.slice(0,-1):e}${t}`}var et="waniwani/sessionId",X="waniwani/geoLocation",Q="waniwani/userLocation",Ht="openai/userLocation",Vt=[Ht,X,Q],zt=["latitude","longitude"];function tt(e){let t;for(let n of Vt){let r=e[n];if(!T(r))continue;let o;for(let i of zt)i in r&&(o||(o={...r}),delete o[i]);o&&(t||(t={...e}),t[n]=o)}return t??e}function T(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}function j(e){if(!T(e))return;let t=e._meta;return T(t)?t:void 0}function pe(e){if(!T(e))return;let t=e.content;return Array.isArray(t)?t.find(r=>T(r)&&r.type==="text"&&typeof r.text=="string")?.text:void 0}function Yt(e,t){return typeof t=="function"?t(e)??"other":t??"other"}function le(e,t,n,r,o,i){let s=Yt(e,n.toolType),a=j(t),c=a&&n.stripGeoCoordinates?tt(a):a,d=i?.input!==void 0&&n.redactInput?n.redactInput(i.input):i?.input,l=n.stripGeoCoordinates&&T(i?.output)&&T(i.output._meta)?{...i.output,_meta:tt(i.output._meta)}:i?.output;return{event:"tool.called",properties:{name:e,type:s,...r??{},...d!==void 0&&{input:d},...l!==void 0&&{output:l}},meta:c,source:N(c),metadata:{...n.metadata??{},...o&&{clientInfo:o}}}}async function fe(e,t,n){try{await e.track(t)}catch(r){n?.(we(r))}}async function ge(e,t){try{await e.flush()}catch(n){t?.(we(n))}}async function rt(e,t,n,r,o){if(!T(e))return;T(e._meta)||(e._meta={});let i=e._meta,s=T(i.waniwani)?i.waniwani:void 0,a={...s??{},endpoint:s?.endpoint??`${n.replace(/\/$/,"")}/api/mcp/events/v2/batch`};if(t)try{let f=await t.getToken();f&&(a.token=f)}catch(f){o?.(we(f))}let c=b(i);c&&(a.sessionId||(a.sessionId=c));let d=st(i);d!==void 0&&(a.geoLocation||(a.geoLocation=d));let l=N(j(r));l&&!a.source&&(a.source=l),i.waniwani=a}var nt=["openai/outputTemplate","openai/widgetAccessible","openai/resultCanProduceWidget","openai/toolInvocation/invoking","openai/toolInvocation/invoked","ui/resourceUri","ui"];function ot(e,t){if(!t||!T(e))return;let n=!1;for(let o of nt)if(o in t){n=!0;break}if(!n)return;T(e._meta)||(e._meta={});let r=e._meta;for(let o of nt)o in t&&(o in r||(r[o]=t[o]))}function it(e,t){let n=j(t);if(!n||!T(e))return;T(e._meta)||(e._meta={});let r=e._meta,o=b(n);o&&!r[et]&&(r[et]=o);let i=st(n);i&&(r[X]||(r[X]=i),r[Q]||(r[Q]=i))}function st(e){if(!e)return;let t=e[X]??e[Q];if(T(t)||typeof t=="string")return t}function we(e){return e instanceof Error?e:new Error(String(e))}function me(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function at(e){if(console.log("[waniwani:debug] extractTransportSessionId extra keys:",Object.keys(e)),console.log("[waniwani:debug] extractTransportSessionId extra.sessionId:",typeof e.sessionId,e.sessionId),typeof e.sessionId=="string"&&e.sessionId)return console.log("[waniwani:debug] extractTransportSessionId found extra.sessionId:",e.sessionId),e.sessionId;if(me(e.requestInfo)){let t=e.requestInfo.headers;if(console.log("[waniwani:debug] extractTransportSessionId requestInfo.headers:",me(t)?Object.keys(t):t),me(t)){let n={};for(let o of Object.keys(t))n[o.toLowerCase()]=t[o];let r=n["mcp-session-id"];if(console.log("[waniwani:debug] extractTransportSessionId headers['mcp-session-id']:",r),typeof r=="string"&&r)return r}}else console.log("[waniwani:debug] extractTransportSessionId no requestInfo on extra")}var dt=Symbol.for("waniwani.wrappedHandler"),ee=Z("mcp",!!process.env.WANIWANI_DEBUG),ut="https://app.waniwani.ai",Gt="REDACTED";function Zt(e){if(!e)return;let t=e[q];if(!Array.isArray(t)||t.length===0)return;let n=new Set(t.filter(r=>typeof r=="string"));if(n.size!==0)return r=>{if(!T(r))return r;let o=r.stateUpdates;if(!T(o))return r;let i=!1,s={...o};for(let a of n)a in s&&(s[a]=Gt,i=!0);return i?{...r,stateUpdates:s}:r}}function ct(e,t,n,r){let{server:o,tracker:i,opts:s,tokenCache:a,injectToken:c}=n,d=s.applyFieldRedactions===!0?Zt(r):void 0,l=d?{...s,redactInput:d}:s,f=async(u,p)=>{let h=j(p)??{},k=b(h);if(console.log("[waniwani:debug] bridge sessionId \u2014 existingSessionId from meta:",k,"| meta keys:",Object.keys(h)),!k&&T(p)){let y=at(p);console.log("[waniwani:debug] bridge sessionId \u2014 transportSid:",y),y&&(h["waniwani/sessionId"]=y,p._meta=h)}let g=Re(i,h,{apiUrl:i._config.apiUrl,apiKey:i._config.apiKey});T(p)&&(p[ne]=g);let R=performance.now(),S=o.server?.getClientVersion?.();try{let y=await t(u,p),v=Math.round(performance.now()-R);ee(`tool "${e}" handler returned in ${v}ms, running post-processing...`);let E=T(y)&&y.isError===!0;if(E){let _=pe(y);console.error(`[waniwani] Tool "${e}" returned error${_?`: ${_}`:""}`)}return await fe(i,le(e,p,l,{durationMs:v,status:E?"error":"ok",...E&&{errorMessage:pe(y)??"Unknown tool error"}},S,{input:u,output:y}),s.onError),ee(`tool "${e}" tracking done`),s.flushAfterToolCall&&await ge(i,s.onError),it(y,p),ot(y,r),c&&(await rt(y,a,i._config.apiUrl??ut,p,s.onError),ee(`tool "${e}" widget config injected`)),ee(`tool "${e}" post-processing complete, returning result`),y}catch(y){let v=Math.round(performance.now()-R);throw await fe(i,le(e,p,l,{durationMs:v,status:"error",errorMessage:y instanceof Error?y.message:String(y)},S,{input:u}),s.onError),s.flushAfterToolCall&&await ge(i,s.onError),y}};return f[dt]=!0,f}function Jt(e,t){let n=e;if(n.__waniwaniWrapped)return n;n.__waniwaniWrapped=!0;let r=t??{},o=r.client??G(),i=r.injectWidgetToken!==!1,s=o._config.apiKey?new J({apiUrl:o._config.apiUrl??ut,apiKey:o._config.apiKey}):null,a={server:e,tracker:o,opts:r,tokenCache:s,injectToken:i},c=e.registerTool.bind(e);n.registerTool=((...l)=>{let[f,u,p]=l;if(typeof p!="function")return c(...l);let h=typeof f=="string"&&f.trim().length>0?f:"unknown",k=T(u)&&T(u._meta)?u._meta:void 0,g=ct(h,p,a,k);return c(f,u,g)});let d=e._registeredTools;if(T(d))for(let[l,f]of Object.entries(d)){if(!T(f))continue;let u=f.handler;if(typeof u!="function"||u[dt])continue;let p=T(f._meta)?f._meta:void 0;f.handler=ct(l,u,a,p)}return n}export{C as END,I as START,M as StateGraph,A as WaniwaniKvStore,Be as createFlow,Le as createFlowTestHarness,Ye as createResource,xt as createTool,Bt as createTrackingRoute,te as detectPlatform,lt as isMCPApps,pt as isOpenAI,De as redacted,Et as registerTools,Jt as withWaniwani};
7
7
  //# sourceMappingURL=index.js.map