@waniwani/sdk 0.4.3-beta.1 → 0.4.3-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.
@@ -1,10 +1,10 @@
1
- function U(t,e){return e?(...o)=>console.log(`[waniwani:${t}]`,...o):()=>{}}var x=class extends Error{constructor(o,a){super(o);this.status=a;this.name="WaniWaniError"}};function A(t){let e=t.headers,o=e.get("x-vercel-ip-city")??e.get("cf-ipcity")??void 0,a=o?$(o):void 0,n=e.get("x-vercel-ip-country")??e.get("cf-ipcountry")??void 0,d=e.get("x-vercel-ip-country-region")??void 0,m=e.get("x-vercel-ip-latitude")??e.get("cf-iplatitude")??void 0,s=e.get("x-vercel-ip-longitude")??e.get("cf-iplongitude")??void 0,r=e.get("x-vercel-ip-timezone")??e.get("cf-iptimezone")??void 0,i=e.get("x-real-ip")??e.get("x-forwarded-for")?.split(",")[0]?.trim()??e.get("cf-connecting-ip")??void 0;return{city:a,country:n,countryRegion:d,latitude:m,longitude:s,timezone:r,ip:i}}function $(t){try{return decodeURIComponent(t)}catch{return t}}function T(t){if(!t)return!1;let e=Array.isArray(t.content)&&t.content.length>0,o=typeof t.structuredContent=="object"&&t.structuredContent!==null&&Object.keys(t.structuredContent).length>0;return e||o}function O(t){if(!T(t))return"";let e=["## Widget Model Context","This hidden context was supplied by an MCP App via `ui/update-model-context`.","Use it for the next assistant turn only. If it includes flow continuation or tool-call instructions, follow them exactly."];if(t.content?.length){let o=t.content.map(a=>a.type==="text"&&typeof a.text=="string"?a.text.trim():JSON.stringify(a,null,2)).filter(Boolean).join(`
1
+ function S(t,e){return e?(...o)=>console.log(`[waniwani:${t}]`,...o):()=>{}}var x=class extends Error{constructor(o,a){super(o);this.status=a;this.name="WaniWaniError"}};function O(t){let e=t.headers,o=e.get("x-vercel-ip-city")??e.get("cf-ipcity")??void 0,a=o?D(o):void 0,n=e.get("x-vercel-ip-country")??e.get("cf-ipcountry")??void 0,d=e.get("x-vercel-ip-country-region")??void 0,m=e.get("x-vercel-ip-latitude")??e.get("cf-iplatitude")??void 0,s=e.get("x-vercel-ip-longitude")??e.get("cf-iplongitude")??void 0,r=e.get("x-vercel-ip-timezone")??e.get("cf-iptimezone")??void 0,i=e.get("x-real-ip")??e.get("x-forwarded-for")?.split(",")[0]?.trim()??e.get("cf-connecting-ip")??void 0;return{city:a,country:n,countryRegion:d,latitude:m,longitude:s,timezone:r,ip:i}}function D(t){try{return decodeURIComponent(t)}catch{return t}}function T(t){if(!t)return!1;let e=Array.isArray(t.content)&&t.content.length>0,o=typeof t.structuredContent=="object"&&t.structuredContent!==null&&Object.keys(t.structuredContent).length>0;return e||o}function N(t){if(!T(t))return"";let e=["## Widget Model Context","This hidden context was supplied by an MCP App via `ui/update-model-context`.","Use it for the next assistant turn only. If it includes flow continuation or tool-call instructions, follow them exactly."];if(t.content?.length){let o=t.content.map(a=>a.type==="text"&&typeof a.text=="string"?a.text.trim():JSON.stringify(a,null,2)).filter(Boolean).join(`
2
2
 
3
3
  `);o&&e.push(`Content blocks:
4
4
  ${o}`)}return t.structuredContent&&Object.keys(t.structuredContent).length>0&&e.push(`Structured content JSON:
5
5
  ${JSON.stringify(t.structuredContent,null,2)}`),e.join(`
6
6
 
7
- `)}function N(t,e){if(!T(e))return t;let o=O(e);return o?[t,o].filter(Boolean).join(`
7
+ `)}function I(t,e){if(!T(e))return t;let o=N(e);return o?[t,o].filter(Boolean).join(`
8
8
 
9
- `):t}function I(t){let{apiKey:e,baseUrl:o,systemPrompt:a,maxSteps:n,beforeRequest:d,mcpServerUrl:m,resolveConfig:s,debug:r}=t,i=U("chat",r);return async function(g){i("\u2192 POST",g.url);try{let c=await g.json(),l=c.messages??[],u=c.sessionId,h=c.modelContext,C=a,f=c.visitorContext??null,S=A(g),b={geo:S,client:f};if(i("body parsed \u2014 messages:",l.length,"sessionId:",u??"(none)","geo:",JSON.stringify(S)),d){i("running beforeRequest hook");try{let p=await d({messages:l,sessionId:u,modelContext:h,request:g,visitor:b});p&&(p.messages&&(l=p.messages),p.systemPrompt!==void 0&&(C=p.systemPrompt),p.sessionId!==void 0&&(u=p.sessionId),p.modelContext!==void 0&&(h=p.modelContext)),i("beforeRequest hook done \u2014 messages:",l.length,"sessionId:",u??"(none)")}catch(p){console.error("[waniwani:chat] beforeRequest hook error:",p);let E=p instanceof x?p.status:400,G=p instanceof Error?p.message:"Request rejected";return i("\u2190 returning",E,"from hook error"),Response.json({error:G},{status:E})}}let w=m??(await s()).mcpServerUrl;i("mcpServerUrl:",w),C=N(C,h);let H=`${o}/api/mcp/chat`;i("forwarding to",H);let y=await fetch(H,{method:"POST",headers:{"Content-Type":"application/json",...e?{Authorization:`Bearer ${e}`}:{}},body:JSON.stringify({messages:l,mcpServerUrl:w,sessionId:u,systemPrompt:C,maxSteps:n,visitor:b}),signal:g.signal});if(i("upstream response status:",y.status),!y.ok){let p=await y.text().catch(()=>"");return i("\u2190 returning",y.status,"upstream error:",p),new Response(p,{status:y.status,headers:{"Content-Type":y.headers.get("Content-Type")??"application/json"}})}let j=new Headers({"Content-Type":y.headers.get("Content-Type")??"text/event-stream"}),k=y.headers.get("x-session-id");return k&&j.set("x-session-id",k),i("\u2190 streaming response",y.status,"body null?",y.body===null),new Response(y.body,{status:y.status,headers:j})}catch(c){console.error("[waniwani:chat] handler error:",c);let l=c instanceof Error?c.message:"Unknown error occurred",u=c instanceof x?c.status:500;return i("\u2190 returning",u,"from caught error"),Response.json({error:l},{status:u})}}}function W(t){let{mcpServerUrl:e,resolveConfig:o,debug:a}=t,n=U("resource",a);return async function(m){n("\u2192 GET",m.toString());try{let s=m.searchParams.get("uri");if(n("uri:",s??"(missing)"),!s)return n("\u2190 400 missing uri"),Response.json({error:"Missing uri query parameter"},{status:400});let r=e??(await o()).mcpServerUrl;n("mcpServerUrl:",r);let i,R;try{[{createMCPClient:i},{StreamableHTTPClientTransport:R}]=await Promise.all([import("@ai-sdk/mcp"),import("@modelcontextprotocol/sdk/client/streamableHttp.js")]),n("MCP deps loaded")}catch(c){return console.error("[waniwani:resource] MCP deps import failed:",c),Response.json({error:"MCP resource handler requires @ai-sdk/mcp and @modelcontextprotocol/sdk. Install them to enable resource serving."},{status:501})}n("creating MCP client for",r);let g=await i({transport:new R(new URL(r))});try{n("reading resource:",s);let c=await g.readResource({uri:s});n("resource contents count:",c.contents.length);let l=c.contents[0];if(!l)return n("\u2190 404 resource not found"),Response.json({error:"Resource not found"},{status:404});let u;return"text"in l&&typeof l.text=="string"?u=l.text:"blob"in l&&typeof l.blob=="string"&&(u=atob(l.blob)),u?(n("\u2190 200 HTML length:",u.length),new Response(u,{headers:{"Content-Type":"text/html","Cache-Control":"private, max-age=300"}})):(n("\u2190 404 resource has no content, keys:",Object.keys(l)),Response.json({error:"Resource has no content"},{status:404}))}finally{await g.close(),n("MCP client closed")}}catch(s){console.error("[waniwani:resource] handler error:",s);let r=s instanceof Error?s.message:"Unknown error occurred",i=s instanceof x?s.status:500;return n("\u2190 returning",i,"from caught error"),Response.json({error:r},{status:i})}}}function J(t){let{mcpServerUrl:e,resolveConfig:o,debug:a}=t,n=U("tool",a);return async function(m){n("\u2192 POST",m.url);try{let s=await m.json(),{name:r,arguments:i}=s;if(!r||typeof r!="string")return n("\u2190 400 missing tool name"),Response.json({error:"Missing tool name"},{status:400});n("tool:",r,"args:",JSON.stringify(i));let R=e??(await o()).mcpServerUrl;n("mcpServerUrl:",R);let g,c;try{[{Client:g},{StreamableHTTPClientTransport:c}]=await Promise.all([import("@modelcontextprotocol/sdk/client/index.js"),import("@modelcontextprotocol/sdk/client/streamableHttp.js")]),n("MCP deps loaded")}catch(h){return console.error("[waniwani:tool] MCP deps import failed:",h),Response.json({error:"MCP tool handler requires @modelcontextprotocol/sdk. Install it to enable tool calls."},{status:501})}n("creating MCP client for",R);let l=new c(new URL(R)),u=new g({name:"waniwani-tool-caller",version:"1.0.0"});await u.connect(l);try{n("calling tool:",r);let h=await u.callTool({name:r,arguments:i??{}});return n("tool result received"),Response.json({content:h.content,structuredContent:h.structuredContent,_meta:h._meta,isError:h.isError})}finally{await u.close(),n("MCP client closed")}}catch(s){console.error("[waniwani:tool] handler error:",s);let r=s instanceof Error?s.message:"Unknown error occurred",i=s instanceof x?s.status:500;return n("\u2190 returning",i,"from caught error"),Response.json({error:r},{status:i})}}}var D=300*1e3;function L(t,e){let o=null,a=null;return async function(){if(o&&Date.now()<o.expiresAt)return o.config;if(a)return a;a=(async()=>{if(!e)throw new x("WANIWANI_API_KEY is required for createChatHandler",401);let d=await fetch(`${t}/api/mcp/environments/config`,{method:"GET",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"}});if(!d.ok){let s=await d.text().catch(()=>"");throw new x(`Failed to resolve MCP environment config: ${d.status} ${s}`,d.status)}let m=await d.json();return o={config:m,expiresAt:Date.now()+D},m})();try{return await a}finally{a=null}}}function v(t,e){return new Response(JSON.stringify(t),{headers:{"Content-Type":"application/json"},status:e})}function B(t={}){let{apiKey:e=process.env.WANIWANI_API_KEY,baseUrl:o="https://app.waniwani.ai",systemPrompt:a,maxSteps:n=5,beforeRequest:d,mcpServerUrl:m,debug:s=!1}=t,r=U("router",s),i=L(o,e),R=I({apiKey:e,baseUrl:o,systemPrompt:a,maxSteps:n,beforeRequest:d,mcpServerUrl:m,resolveConfig:i,debug:s}),g=W({mcpServerUrl:m,resolveConfig:i,debug:s}),c=J({mcpServerUrl:m,resolveConfig:i,debug:s});async function l(){return v({debug:s},200)}async function u(C){r("\u2192 GET",C.url);try{let f=new URL(C.url),b=f.pathname.replace(/\/$/,"").split("/").filter(Boolean).at(-1);if(r("pathname:",f.pathname,"subRoute:",b),b==="resource"){r("dispatching to resource handler");let w=await g(f);return r("\u2190 resource handler returned",w.status),w}if(b==="config"){r("dispatching to config handler");let w=await l();return r("\u2190 config handler returned",w.status),w}return r("\u2190 404 no matching sub-route for",b),v({error:"Not found"},404)}catch(f){console.error("[waniwani:router] GET handler error:",f);let S=f instanceof Error?f.message:"Unknown error occurred";return r("\u2190 500 from caught error"),v({error:S},500)}}async function h(C){r("\u2192 POST",C.url);try{let f=new URL(C.url),b=f.pathname.replace(/\/$/,"").split("/").filter(Boolean).at(-1);if(r("pathname:",f.pathname,"subRoute:",b),b==="tool"){r("dispatching to tool handler");let w=await c(C);return r("\u2190 tool handler returned",w.status),w}return r("dispatching to chat handler"),R(C)}catch(f){console.error("[waniwani:router] POST handler error:",f);let S=f instanceof Error?f.message:"Unknown error occurred";return r("\u2190 500 from caught error"),v({error:S},500)}}return{handleChat:R,handleResource:g,handleTool:c,routeGet:u,routePost:h}}function Ue(t,e){let{apiKey:o,baseUrl:a}=t._config,n=e?.debug??process.env.WANIWANI_DEBUG==="1",d=B({...e?.chat,apiKey:o,baseUrl:a,debug:n});return{POST:d.routePost,GET:d.routeGet}}export{Ue as toNextJsHandler};
9
+ `):t}function W(t){let{apiKey:e,baseUrl:o,systemPrompt:a,maxSteps:n,beforeRequest:d,mcpServerUrl:m,resolveConfig:s,debug:r}=t,i=S("chat",r);return async function(g){i("\u2192 POST",g.url);try{let c=await g.json(),l=c.messages??[],u=c.sessionId,h=c.modelContext,C=a,f=c.visitorContext??null,U=O(g),b={geo:U,client:f};if(i("body parsed \u2014 messages:",l.length,"sessionId:",u??"(none)","geo:",JSON.stringify(U)),d){i("running beforeRequest hook");try{let p=await d({messages:l,sessionId:u,modelContext:h,request:g,visitor:b});p&&(p.messages&&(l=p.messages),p.systemPrompt!==void 0&&(C=p.systemPrompt),p.sessionId!==void 0&&(u=p.sessionId),p.modelContext!==void 0&&(h=p.modelContext)),i("beforeRequest hook done \u2014 messages:",l.length,"sessionId:",u??"(none)")}catch(p){console.error("[waniwani:chat] beforeRequest hook error:",p);let A=p instanceof x?p.status:400,$=p instanceof Error?p.message:"Request rejected";return i("\u2190 returning",A,"from hook error"),Response.json({error:$},{status:A})}}let w=m??(await s()).mcpServerUrl;i("mcpServerUrl:",w),C=I(C,h);let H=`${o}/api/mcp/chat`;i("forwarding to",H);let j=g.headers.get("user-agent"),y=await fetch(H,{method:"POST",headers:{"Content-Type":"application/json",...e?{Authorization:`Bearer ${e}`}:{},...j?{"X-Client-User-Agent":j}:{}},body:JSON.stringify({messages:l,mcpServerUrl:w,sessionId:u,systemPrompt:C,maxSteps:n,visitor:b}),signal:g.signal});if(i("upstream response status:",y.status),!y.ok){let p=await y.text().catch(()=>"");return i("\u2190 returning",y.status,"upstream error:",p),new Response(p,{status:y.status,headers:{"Content-Type":y.headers.get("Content-Type")??"application/json"}})}let k=new Headers({"Content-Type":y.headers.get("Content-Type")??"text/event-stream"}),E=y.headers.get("x-session-id");return E&&k.set("x-session-id",E),i("\u2190 streaming response",y.status,"body null?",y.body===null),new Response(y.body,{status:y.status,headers:k})}catch(c){console.error("[waniwani:chat] handler error:",c);let l=c instanceof Error?c.message:"Unknown error occurred",u=c instanceof x?c.status:500;return i("\u2190 returning",u,"from caught error"),Response.json({error:l},{status:u})}}}function J(t){let{mcpServerUrl:e,resolveConfig:o,debug:a}=t,n=S("resource",a);return async function(m){n("\u2192 GET",m.toString());try{let s=m.searchParams.get("uri");if(n("uri:",s??"(missing)"),!s)return n("\u2190 400 missing uri"),Response.json({error:"Missing uri query parameter"},{status:400});let r=e??(await o()).mcpServerUrl;n("mcpServerUrl:",r);let i,R;try{[{createMCPClient:i},{StreamableHTTPClientTransport:R}]=await Promise.all([import("@ai-sdk/mcp"),import("@modelcontextprotocol/sdk/client/streamableHttp.js")]),n("MCP deps loaded")}catch(c){return console.error("[waniwani:resource] MCP deps import failed:",c),Response.json({error:"MCP resource handler requires @ai-sdk/mcp and @modelcontextprotocol/sdk. Install them to enable resource serving."},{status:501})}n("creating MCP client for",r);let g=await i({transport:new R(new URL(r))});try{n("reading resource:",s);let c=await g.readResource({uri:s});n("resource contents count:",c.contents.length);let l=c.contents[0];if(!l)return n("\u2190 404 resource not found"),Response.json({error:"Resource not found"},{status:404});let u;return"text"in l&&typeof l.text=="string"?u=l.text:"blob"in l&&typeof l.blob=="string"&&(u=atob(l.blob)),u?(n("\u2190 200 HTML length:",u.length),new Response(u,{headers:{"Content-Type":"text/html","Cache-Control":"private, max-age=300"}})):(n("\u2190 404 resource has no content, keys:",Object.keys(l)),Response.json({error:"Resource has no content"},{status:404}))}finally{await g.close(),n("MCP client closed")}}catch(s){console.error("[waniwani:resource] handler error:",s);let r=s instanceof Error?s.message:"Unknown error occurred",i=s instanceof x?s.status:500;return n("\u2190 returning",i,"from caught error"),Response.json({error:r},{status:i})}}}function L(t){let{mcpServerUrl:e,resolveConfig:o,debug:a}=t,n=S("tool",a);return async function(m){n("\u2192 POST",m.url);try{let s=await m.json(),{name:r,arguments:i}=s;if(!r||typeof r!="string")return n("\u2190 400 missing tool name"),Response.json({error:"Missing tool name"},{status:400});n("tool:",r,"args:",JSON.stringify(i));let R=e??(await o()).mcpServerUrl;n("mcpServerUrl:",R);let g,c;try{[{Client:g},{StreamableHTTPClientTransport:c}]=await Promise.all([import("@modelcontextprotocol/sdk/client/index.js"),import("@modelcontextprotocol/sdk/client/streamableHttp.js")]),n("MCP deps loaded")}catch(h){return console.error("[waniwani:tool] MCP deps import failed:",h),Response.json({error:"MCP tool handler requires @modelcontextprotocol/sdk. Install it to enable tool calls."},{status:501})}n("creating MCP client for",R);let l=new c(new URL(R)),u=new g({name:"waniwani-tool-caller",version:"1.0.0"});await u.connect(l);try{n("calling tool:",r);let h=await u.callTool({name:r,arguments:i??{}});return n("tool result received"),Response.json({content:h.content,structuredContent:h.structuredContent,_meta:h._meta,isError:h.isError})}finally{await u.close(),n("MCP client closed")}}catch(s){console.error("[waniwani:tool] handler error:",s);let r=s instanceof Error?s.message:"Unknown error occurred",i=s instanceof x?s.status:500;return n("\u2190 returning",i,"from caught error"),Response.json({error:r},{status:i})}}}var _=300*1e3;function B(t,e){let o=null,a=null;return async function(){if(o&&Date.now()<o.expiresAt)return o.config;if(a)return a;a=(async()=>{if(!e)throw new x("WANIWANI_API_KEY is required for createChatHandler",401);let d=await fetch(`${t}/api/mcp/environments/config`,{method:"GET",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"}});if(!d.ok){let s=await d.text().catch(()=>"");throw new x(`Failed to resolve MCP environment config: ${d.status} ${s}`,d.status)}let m=await d.json();return o={config:m,expiresAt:Date.now()+_},m})();try{return await a}finally{a=null}}}function v(t,e){return new Response(JSON.stringify(t),{headers:{"Content-Type":"application/json"},status:e})}function G(t={}){let{apiKey:e=process.env.WANIWANI_API_KEY,baseUrl:o="https://app.waniwani.ai",systemPrompt:a,maxSteps:n=5,beforeRequest:d,mcpServerUrl:m,debug:s=!1}=t,r=S("router",s),i=B(o,e),R=W({apiKey:e,baseUrl:o,systemPrompt:a,maxSteps:n,beforeRequest:d,mcpServerUrl:m,resolveConfig:i,debug:s}),g=J({mcpServerUrl:m,resolveConfig:i,debug:s}),c=L({mcpServerUrl:m,resolveConfig:i,debug:s});async function l(){return v({debug:s},200)}async function u(C){r("\u2192 GET",C.url);try{let f=new URL(C.url),b=f.pathname.replace(/\/$/,"").split("/").filter(Boolean).at(-1);if(r("pathname:",f.pathname,"subRoute:",b),b==="resource"){r("dispatching to resource handler");let w=await g(f);return r("\u2190 resource handler returned",w.status),w}if(b==="config"){r("dispatching to config handler");let w=await l();return r("\u2190 config handler returned",w.status),w}return r("\u2190 404 no matching sub-route for",b),v({error:"Not found"},404)}catch(f){console.error("[waniwani:router] GET handler error:",f);let U=f instanceof Error?f.message:"Unknown error occurred";return r("\u2190 500 from caught error"),v({error:U},500)}}async function h(C){r("\u2192 POST",C.url);try{let f=new URL(C.url),b=f.pathname.replace(/\/$/,"").split("/").filter(Boolean).at(-1);if(r("pathname:",f.pathname,"subRoute:",b),b==="tool"){r("dispatching to tool handler");let w=await c(C);return r("\u2190 tool handler returned",w.status),w}return r("dispatching to chat handler"),R(C)}catch(f){console.error("[waniwani:router] POST handler error:",f);let U=f instanceof Error?f.message:"Unknown error occurred";return r("\u2190 500 from caught error"),v({error:U},500)}}return{handleChat:R,handleResource:g,handleTool:c,routeGet:u,routePost:h}}function ve(t,e){let{apiKey:o,baseUrl:a}=t._config,n=e?.debug??process.env.WANIWANI_DEBUG==="1",d=G({...e?.chat,apiKey:o,baseUrl:a,debug:n});return{POST:d.routePost,GET:d.routeGet}}export{ve as toNextJsHandler};
10
10
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/utils/logger.ts","../../../src/error.ts","../../../src/chat/server/geo.ts","../../../src/shared/model-context.ts","../../../src/chat/server/model-context.ts","../../../src/chat/server/handle-chat.ts","../../../src/chat/server/handle-resource.ts","../../../src/chat/server/handle-tool.ts","../../../src/chat/server/mcp-config-resolver.ts","../../../src/chat/server/api-handler.ts","../../../src/chat/server/next-js/index.ts"],"sourcesContent":["/**\n * Creates a namespaced logger that writes to console.log when enabled,\n * or is a no-op when disabled.\n *\n * @example\n * const log = createLogger(\"chat\", debug);\n * log(\"→ POST\", request.url); // [waniwani:chat] → POST ...\n */\nexport function createLogger(\n\tnamespace: string,\n\tenabled: boolean,\n): (...args: unknown[]) => void {\n\treturn enabled\n\t\t? (...args: unknown[]) => console.log(`[waniwani:${namespace}]`, ...args)\n\t\t: () => {};\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","// Geo — Extract location metadata from platform request headers\n\n/**\n * Server-side geolocation extracted from platform headers (Vercel, Cloudflare).\n * All fields are optional — in local dev, no headers are present.\n */\nexport interface GeoLocation {\n\tcity?: string;\n\tcountry?: string;\n\tcountryRegion?: string;\n\tlatitude?: string;\n\tlongitude?: string;\n\ttimezone?: string;\n\tip?: string;\n}\n\n/**\n * Extracts geolocation from server-side request headers.\n *\n * Supports Vercel (`x-vercel-ip-*`), Cloudflare (`cf-ip*`, `cf-connecting-ip`),\n * and generic IP headers (`x-real-ip`, `x-forwarded-for`).\n *\n * Returns a `GeoLocation` with all fields optional (empty object in local dev).\n */\nexport function extractGeoFromHeaders(request: Request): GeoLocation {\n\tconst h = request.headers;\n\n\t// Vercel URL-encodes city names (e.g. \"S%C3%A3o%20Paulo\")\n\tconst rawCity = h.get(\"x-vercel-ip-city\") ?? h.get(\"cf-ipcity\") ?? undefined;\n\tconst city = rawCity ? safeDecodeURI(rawCity) : undefined;\n\n\tconst country =\n\t\th.get(\"x-vercel-ip-country\") ?? h.get(\"cf-ipcountry\") ?? undefined;\n\tconst countryRegion = h.get(\"x-vercel-ip-country-region\") ?? undefined;\n\tconst latitude =\n\t\th.get(\"x-vercel-ip-latitude\") ?? h.get(\"cf-iplatitude\") ?? undefined;\n\tconst longitude =\n\t\th.get(\"x-vercel-ip-longitude\") ?? h.get(\"cf-iplongitude\") ?? undefined;\n\tconst timezone =\n\t\th.get(\"x-vercel-ip-timezone\") ?? h.get(\"cf-iptimezone\") ?? undefined;\n\tconst ip =\n\t\th.get(\"x-real-ip\") ??\n\t\th.get(\"x-forwarded-for\")?.split(\",\")[0]?.trim() ??\n\t\th.get(\"cf-connecting-ip\") ??\n\t\tundefined;\n\n\treturn { city, country, countryRegion, latitude, longitude, timezone, ip };\n}\n\nfunction safeDecodeURI(value: string): string {\n\ttry {\n\t\treturn decodeURIComponent(value);\n\t} catch {\n\t\treturn value;\n\t}\n}\n","import type { ContentBlock } from \"@modelcontextprotocol/sdk/types.js\";\n\nexport type ModelContextContentBlock = ContentBlock;\n\nexport type ModelContextUpdate = {\n\tcontent?: ModelContextContentBlock[];\n\tstructuredContent?: Record<string, unknown>;\n};\n\nexport function hasModelContext(\n\tvalue: ModelContextUpdate | null | undefined,\n): value is ModelContextUpdate {\n\tif (!value) {\n\t\treturn false;\n\t}\n\tconst hasContent = Array.isArray(value.content) && value.content.length > 0;\n\tconst hasStructuredContent =\n\t\ttypeof value.structuredContent === \"object\" &&\n\t\tvalue.structuredContent !== null &&\n\t\tObject.keys(value.structuredContent).length > 0;\n\treturn hasContent || hasStructuredContent;\n}\n\nexport function mergeModelContext(\n\tcurrent: ModelContextUpdate | null | undefined,\n\tnext: ModelContextUpdate | null | undefined,\n): ModelContextUpdate | null {\n\tif (!hasModelContext(current) && !hasModelContext(next)) {\n\t\treturn null;\n\t}\n\tif (!hasModelContext(current)) {\n\t\treturn {\n\t\t\t...(next?.content ? { content: [...next.content] } : {}),\n\t\t\t...(next?.structuredContent\n\t\t\t\t? { structuredContent: { ...next.structuredContent } }\n\t\t\t\t: {}),\n\t\t};\n\t}\n\tif (!hasModelContext(next)) {\n\t\treturn {\n\t\t\t...(current.content ? { content: [...current.content] } : {}),\n\t\t\t...(current.structuredContent\n\t\t\t\t? { structuredContent: { ...current.structuredContent } }\n\t\t\t\t: {}),\n\t\t};\n\t}\n\n\treturn {\n\t\t...(current.content || next.content\n\t\t\t? { content: [...(current.content ?? []), ...(next.content ?? [])] }\n\t\t\t: {}),\n\t\t...(current.structuredContent || next.structuredContent\n\t\t\t? {\n\t\t\t\t\tstructuredContent: {\n\t\t\t\t\t\t...(current.structuredContent ?? {}),\n\t\t\t\t\t\t...(next.structuredContent ?? {}),\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t: {}),\n\t};\n}\n\nexport function formatModelContextForPrompt(\n\tvalue: ModelContextUpdate | null | undefined,\n): string {\n\tif (!hasModelContext(value)) {\n\t\treturn \"\";\n\t}\n\n\tconst sections: string[] = [\n\t\t\"## Widget Model Context\",\n\t\t\"This hidden context was supplied by an MCP App via `ui/update-model-context`.\",\n\t\t\"Use it for the next assistant turn only. If it includes flow continuation or tool-call instructions, follow them exactly.\",\n\t];\n\n\tif (value.content?.length) {\n\t\tconst renderedBlocks = value.content\n\t\t\t.map((block) => {\n\t\t\t\tif (block.type === \"text\" && typeof block.text === \"string\") {\n\t\t\t\t\treturn block.text.trim();\n\t\t\t\t}\n\t\t\t\treturn JSON.stringify(block, null, 2);\n\t\t\t})\n\t\t\t.filter(Boolean)\n\t\t\t.join(\"\\n\\n\");\n\t\tif (renderedBlocks) {\n\t\t\tsections.push(`Content blocks:\\n${renderedBlocks}`);\n\t\t}\n\t}\n\n\tif (\n\t\tvalue.structuredContent &&\n\t\tObject.keys(value.structuredContent).length > 0\n\t) {\n\t\tsections.push(\n\t\t\t`Structured content JSON:\\n${JSON.stringify(value.structuredContent, null, 2)}`,\n\t\t);\n\t}\n\n\treturn sections.join(\"\\n\\n\");\n}\n","import {\n\tformatModelContextForPrompt,\n\thasModelContext,\n\ttype ModelContextUpdate,\n} from \"../../shared/model-context\";\n\nexport function applyModelContextToSystemPrompt(\n\tsystemPrompt: string | undefined,\n\tmodelContext: ModelContextUpdate | undefined,\n): string | undefined {\n\tif (!hasModelContext(modelContext)) {\n\t\treturn systemPrompt;\n\t}\n\n\tconst widgetContext = formatModelContextForPrompt(modelContext);\n\tif (!widgetContext) {\n\t\treturn systemPrompt;\n\t}\n\n\treturn [systemPrompt, widgetContext].filter(Boolean).join(\"\\n\\n\");\n}\n","// Handle Chat - Proxies chat requests to the WaniWani API\n\nimport { WaniWaniError } from \"../../error\";\nimport { createLogger } from \"../../utils/logger.js\";\nimport type {\n\tApiHandlerDeps,\n\tClientVisitorContext,\n\tVisitorMeta,\n} from \"./@types\";\nimport { extractGeoFromHeaders } from \"./geo\";\nimport { applyModelContextToSystemPrompt } from \"./model-context\";\n\nexport function createChatRequestHandler(deps: ApiHandlerDeps) {\n\tconst {\n\t\tapiKey,\n\t\tbaseUrl,\n\t\tsystemPrompt,\n\t\tmaxSteps,\n\t\tbeforeRequest,\n\t\tmcpServerUrl: mcpServerUrlOverride,\n\t\tresolveConfig,\n\t\tdebug,\n\t} = deps;\n\n\tconst log = createLogger(\"chat\", debug);\n\n\treturn async function handleChat(request: Request): Promise<Response> {\n\t\tlog(\"→ POST\", request.url);\n\t\ttry {\n\t\t\t// 1. Parse request body\n\t\t\tconst body = await request.json();\n\t\t\tlet messages = body.messages ?? [];\n\t\t\tlet sessionId: string | undefined = body.sessionId;\n\t\t\tlet modelContext = body.modelContext;\n\t\t\tlet effectiveSystemPrompt = systemPrompt;\n\n\t\t\t// Extract visitor context (client-side + server-side geo)\n\t\t\tconst clientVisitorContext: ClientVisitorContext | null =\n\t\t\t\tbody.visitorContext ?? null;\n\t\t\tconst geo = extractGeoFromHeaders(request);\n\t\t\tconst visitor: VisitorMeta = { geo, client: clientVisitorContext };\n\n\t\t\tlog(\n\t\t\t\t\"body parsed — messages:\",\n\t\t\t\tmessages.length,\n\t\t\t\t\"sessionId:\",\n\t\t\t\tsessionId ?? \"(none)\",\n\t\t\t\t\"geo:\",\n\t\t\t\tJSON.stringify(geo),\n\t\t\t);\n\n\t\t\t// 2. Run beforeRequest hook\n\t\t\tif (beforeRequest) {\n\t\t\t\tlog(\"running beforeRequest hook\");\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await beforeRequest({\n\t\t\t\t\t\tmessages,\n\t\t\t\t\t\tsessionId,\n\t\t\t\t\t\tmodelContext,\n\t\t\t\t\t\trequest,\n\t\t\t\t\t\tvisitor,\n\t\t\t\t\t});\n\n\t\t\t\t\tif (result) {\n\t\t\t\t\t\tif (result.messages) {\n\t\t\t\t\t\t\tmessages = result.messages;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (result.systemPrompt !== undefined) {\n\t\t\t\t\t\t\teffectiveSystemPrompt = result.systemPrompt;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (result.sessionId !== undefined) {\n\t\t\t\t\t\t\tsessionId = result.sessionId;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (result.modelContext !== undefined) {\n\t\t\t\t\t\t\tmodelContext = result.modelContext;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tlog(\n\t\t\t\t\t\t\"beforeRequest hook done — messages:\",\n\t\t\t\t\t\tmessages.length,\n\t\t\t\t\t\t\"sessionId:\",\n\t\t\t\t\t\tsessionId ?? \"(none)\",\n\t\t\t\t\t);\n\t\t\t\t} catch (hookError) {\n\t\t\t\t\tconsole.error(\"[waniwani:chat] beforeRequest hook error:\", hookError);\n\t\t\t\t\tconst status =\n\t\t\t\t\t\thookError instanceof WaniWaniError ? hookError.status : 400;\n\t\t\t\t\tconst message =\n\t\t\t\t\t\thookError instanceof Error ? hookError.message : \"Request rejected\";\n\t\t\t\t\tlog(\"← returning\", status, \"from hook error\");\n\t\t\t\t\treturn Response.json({ error: message }, { status });\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 3. Resolve MCP server URL\n\t\t\tconst mcpServerUrl =\n\t\t\t\tmcpServerUrlOverride ?? (await resolveConfig()).mcpServerUrl;\n\t\t\tlog(\"mcpServerUrl:\", mcpServerUrl);\n\t\t\teffectiveSystemPrompt = applyModelContextToSystemPrompt(\n\t\t\t\teffectiveSystemPrompt,\n\t\t\t\tmodelContext,\n\t\t\t);\n\n\t\t\t// 4. Forward to WaniWani API\n\t\t\tconst upstreamUrl = `${baseUrl}/api/mcp/chat`;\n\t\t\tlog(\"forwarding to\", upstreamUrl);\n\t\t\tconst response = await fetch(upstreamUrl, {\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\t...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmessages,\n\t\t\t\t\tmcpServerUrl,\n\t\t\t\t\tsessionId,\n\t\t\t\t\tsystemPrompt: effectiveSystemPrompt,\n\t\t\t\t\tmaxSteps,\n\t\t\t\t\tvisitor,\n\t\t\t\t}),\n\t\t\t\tsignal: request.signal,\n\t\t\t});\n\n\t\t\tlog(\"upstream response status:\", response.status);\n\n\t\t\tif (!response.ok) {\n\t\t\t\tconst errorBody = await response.text().catch(() => \"\");\n\t\t\t\tlog(\"← returning\", response.status, \"upstream error:\", errorBody);\n\t\t\t\treturn new Response(errorBody, {\n\t\t\t\t\tstatus: response.status,\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\":\n\t\t\t\t\t\t\tresponse.headers.get(\"Content-Type\") ?? \"application/json\",\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// 5. Stream the response back\n\t\t\tconst headers = new Headers({\n\t\t\t\t\"Content-Type\":\n\t\t\t\t\tresponse.headers.get(\"Content-Type\") ?? \"text/event-stream\",\n\t\t\t});\n\t\t\tconst upstreamSessionId = response.headers.get(\"x-session-id\");\n\t\t\tif (upstreamSessionId) {\n\t\t\t\theaders.set(\"x-session-id\", upstreamSessionId);\n\t\t\t}\n\n\t\t\tlog(\n\t\t\t\t\"← streaming response\",\n\t\t\t\tresponse.status,\n\t\t\t\t\"body null?\",\n\t\t\t\tresponse.body === null,\n\t\t\t);\n\t\t\treturn new Response(response.body, {\n\t\t\t\tstatus: response.status,\n\t\t\t\theaders,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[waniwani:chat] handler error:\", error);\n\t\t\tconst message =\n\t\t\t\terror instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tconst status = error instanceof WaniWaniError ? error.status : 500;\n\t\t\tlog(\"← returning\", status, \"from caught error\");\n\t\t\treturn Response.json({ error: message }, { status });\n\t\t}\n\t};\n}\n","// Handle Resource - Serves MCP resource content (HTML widgets)\n\nimport { WaniWaniError } from \"../../error\";\nimport { createLogger } from \"../../utils/logger.js\";\nimport type { ResourceHandlerDeps } from \"./@types\";\n\nexport function createResourceHandler(deps: ResourceHandlerDeps) {\n\tconst { mcpServerUrl: mcpServerUrlOverride, resolveConfig, debug } = deps;\n\n\tconst log = createLogger(\"resource\", debug);\n\n\treturn async function handleResource(url: URL): Promise<Response> {\n\t\tlog(\"→ GET\", url.toString());\n\t\ttry {\n\t\t\tconst uri = url.searchParams.get(\"uri\");\n\t\t\tlog(\"uri:\", uri ?? \"(missing)\");\n\n\t\t\tif (!uri) {\n\t\t\t\tlog(\"← 400 missing uri\");\n\t\t\t\treturn Response.json(\n\t\t\t\t\t{ error: \"Missing uri query parameter\" },\n\t\t\t\t\t{ status: 400 },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst mcpServerUrl =\n\t\t\t\tmcpServerUrlOverride ?? (await resolveConfig()).mcpServerUrl;\n\t\t\tlog(\"mcpServerUrl:\", mcpServerUrl);\n\n\t\t\t// Dynamic imports — these are optional peer dependencies\n\t\t\tlet createMCPClient: typeof import(\"@ai-sdk/mcp\")[\"createMCPClient\"];\n\t\t\tlet StreamableHTTPClientTransport: typeof import(\"@modelcontextprotocol/sdk/client/streamableHttp.js\")[\"StreamableHTTPClientTransport\"];\n\n\t\t\ttry {\n\t\t\t\t[{ createMCPClient }, { StreamableHTTPClientTransport }] =\n\t\t\t\t\tawait Promise.all([\n\t\t\t\t\t\timport(\"@ai-sdk/mcp\"),\n\t\t\t\t\t\timport(\"@modelcontextprotocol/sdk/client/streamableHttp.js\"),\n\t\t\t\t\t]);\n\t\t\t\tlog(\"MCP deps loaded\");\n\t\t\t} catch (importError) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t\"[waniwani:resource] MCP deps import failed:\",\n\t\t\t\t\timportError,\n\t\t\t\t);\n\t\t\t\treturn Response.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror:\n\t\t\t\t\t\t\t\"MCP resource handler requires @ai-sdk/mcp and @modelcontextprotocol/sdk. Install them to enable resource serving.\",\n\t\t\t\t\t},\n\t\t\t\t\t{ status: 501 },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tlog(\"creating MCP client for\", mcpServerUrl);\n\t\t\tconst mcp = await createMCPClient({\n\t\t\t\ttransport: new StreamableHTTPClientTransport(new URL(mcpServerUrl)),\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\tlog(\"reading resource:\", uri);\n\t\t\t\tconst result = await mcp.readResource({ uri });\n\t\t\t\tlog(\"resource contents count:\", result.contents.length);\n\n\t\t\t\tconst content = result.contents[0];\n\t\t\t\tif (!content) {\n\t\t\t\t\tlog(\"← 404 resource not found\");\n\t\t\t\t\treturn Response.json(\n\t\t\t\t\t\t{ error: \"Resource not found\" },\n\t\t\t\t\t\t{ status: 404 },\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tlet html: string | undefined;\n\t\t\t\tif (\"text\" in content && typeof content.text === \"string\") {\n\t\t\t\t\thtml = content.text;\n\t\t\t\t} else if (\"blob\" in content && typeof content.blob === \"string\") {\n\t\t\t\t\thtml = atob(content.blob);\n\t\t\t\t}\n\n\t\t\t\tif (!html) {\n\t\t\t\t\tlog(\"← 404 resource has no content, keys:\", Object.keys(content));\n\t\t\t\t\treturn Response.json(\n\t\t\t\t\t\t{ error: \"Resource has no content\" },\n\t\t\t\t\t\t{ status: 404 },\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tlog(\"← 200 HTML length:\", html.length);\n\t\t\t\treturn new Response(html, {\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\": \"text/html\",\n\t\t\t\t\t\t\"Cache-Control\": \"private, max-age=300\",\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} finally {\n\t\t\t\tawait mcp.close();\n\t\t\t\tlog(\"MCP client closed\");\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[waniwani:resource] handler error:\", error);\n\t\t\tconst message =\n\t\t\t\terror instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tconst status = error instanceof WaniWaniError ? error.status : 500;\n\t\t\tlog(\"← returning\", status, \"from caught error\");\n\t\t\treturn Response.json({ error: message }, { status });\n\t\t}\n\t};\n}\n","// Handle Tool - Calls MCP server tools directly and returns JSON results\n\nimport { WaniWaniError } from \"../../error\";\nimport { createLogger } from \"../../utils/logger.js\";\nimport type { ResourceHandlerDeps } from \"./@types\";\n\nexport function createToolHandler(deps: ResourceHandlerDeps) {\n\tconst { mcpServerUrl: mcpServerUrlOverride, resolveConfig, debug } = deps;\n\n\tconst log = createLogger(\"tool\", debug);\n\n\treturn async function handleTool(request: Request): Promise<Response> {\n\t\tlog(\"→ POST\", request.url);\n\t\ttry {\n\t\t\tconst body = await request.json();\n\t\t\tconst { name, arguments: args } = body as {\n\t\t\t\tname: string;\n\t\t\t\targuments: Record<string, unknown>;\n\t\t\t};\n\n\t\t\tif (!name || typeof name !== \"string\") {\n\t\t\t\tlog(\"← 400 missing tool name\");\n\t\t\t\treturn Response.json({ error: \"Missing tool name\" }, { status: 400 });\n\t\t\t}\n\n\t\t\tlog(\"tool:\", name, \"args:\", JSON.stringify(args));\n\n\t\t\tconst mcpServerUrl =\n\t\t\t\tmcpServerUrlOverride ?? (await resolveConfig()).mcpServerUrl;\n\t\t\tlog(\"mcpServerUrl:\", mcpServerUrl);\n\n\t\t\t// Dynamic imports — these are optional peer dependencies\n\t\t\tlet Client: typeof import(\"@modelcontextprotocol/sdk/client/index.js\")[\"Client\"];\n\t\t\tlet StreamableHTTPClientTransport: typeof import(\"@modelcontextprotocol/sdk/client/streamableHttp.js\")[\"StreamableHTTPClientTransport\"];\n\n\t\t\ttry {\n\t\t\t\t[{ Client }, { StreamableHTTPClientTransport }] = await Promise.all([\n\t\t\t\t\timport(\"@modelcontextprotocol/sdk/client/index.js\"),\n\t\t\t\t\timport(\"@modelcontextprotocol/sdk/client/streamableHttp.js\"),\n\t\t\t\t]);\n\t\t\t\tlog(\"MCP deps loaded\");\n\t\t\t} catch (importError) {\n\t\t\t\tconsole.error(\"[waniwani:tool] MCP deps import failed:\", importError);\n\t\t\t\treturn Response.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror:\n\t\t\t\t\t\t\t\"MCP tool handler requires @modelcontextprotocol/sdk. Install it to enable tool calls.\",\n\t\t\t\t\t},\n\t\t\t\t\t{ status: 501 },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tlog(\"creating MCP client for\", mcpServerUrl);\n\t\t\tconst transport = new StreamableHTTPClientTransport(\n\t\t\t\tnew URL(mcpServerUrl),\n\t\t\t);\n\t\t\tconst client = new Client({\n\t\t\t\tname: \"waniwani-tool-caller\",\n\t\t\t\tversion: \"1.0.0\",\n\t\t\t});\n\t\t\tawait client.connect(transport);\n\n\t\t\ttry {\n\t\t\t\tlog(\"calling tool:\", name);\n\t\t\t\tconst result = await client.callTool({\n\t\t\t\t\tname,\n\t\t\t\t\targuments: args ?? {},\n\t\t\t\t});\n\t\t\t\tlog(\"tool result received\");\n\n\t\t\t\treturn Response.json({\n\t\t\t\t\tcontent: result.content,\n\t\t\t\t\tstructuredContent: result.structuredContent,\n\t\t\t\t\t_meta: result._meta,\n\t\t\t\t\tisError: result.isError,\n\t\t\t\t});\n\t\t\t} finally {\n\t\t\t\tawait client.close();\n\t\t\t\tlog(\"MCP client closed\");\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[waniwani:tool] handler error:\", error);\n\t\t\tconst message =\n\t\t\t\terror instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tconst status = error instanceof WaniWaniError ? error.status : 500;\n\t\t\tlog(\"← returning\", status, \"from caught error\");\n\t\t\treturn Response.json({ error: message }, { status });\n\t\t}\n\t};\n}\n","// MCP Config Resolver - Lazy-loads and caches MCP environment config\n\nimport { WaniWaniError } from \"../../error\";\n\ninterface McpEnvironmentConfig {\n\tmcpServerUrl: string;\n}\n\nconst TTL_MS = 5 * 60 * 1000; // 5 minutes\n\nexport function createMcpConfigResolver(\n\tbaseUrl: string,\n\tapiKey: string | undefined,\n) {\n\tlet cached: { config: McpEnvironmentConfig; expiresAt: number } | null = null;\n\tlet inflight: Promise<McpEnvironmentConfig> | null = null;\n\n\treturn async function resolve(): Promise<McpEnvironmentConfig> {\n\t\tif (cached && Date.now() < cached.expiresAt) {\n\t\t\treturn cached.config;\n\t\t}\n\n\t\t// Deduplicate concurrent requests (cold start scenario)\n\t\tif (inflight) {\n\t\t\treturn inflight;\n\t\t}\n\n\t\tinflight = (async () => {\n\t\t\tif (!apiKey) {\n\t\t\t\tthrow new WaniWaniError(\n\t\t\t\t\t\"WANIWANI_API_KEY is required for createChatHandler\",\n\t\t\t\t\t401,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst response = await fetch(`${baseUrl}/api/mcp/environments/config`, {\n\t\t\t\tmethod: \"GET\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\tconst body = await response.text().catch(() => \"\");\n\t\t\t\tthrow new WaniWaniError(\n\t\t\t\t\t`Failed to resolve MCP environment config: ${response.status} ${body}`,\n\t\t\t\t\tresponse.status,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst data = (await response.json()) as McpEnvironmentConfig;\n\t\t\tcached = { config: data, expiresAt: Date.now() + TTL_MS };\n\t\t\treturn data;\n\t\t})();\n\n\t\ttry {\n\t\t\treturn await inflight;\n\t\t} finally {\n\t\t\tinflight = null;\n\t\t}\n\t};\n}\n","// API Handler - Composes chat and resource handlers into a unified API handler\n\nimport { createLogger } from \"../../utils/logger.js\";\nimport type { ApiHandler, ApiHandlerOptions } from \"./@types\";\nimport { createChatRequestHandler } from \"./handle-chat\";\nimport { createResourceHandler } from \"./handle-resource\";\nimport { createToolHandler } from \"./handle-tool\";\nimport { createMcpConfigResolver } from \"./mcp-config-resolver\";\n\n/**\n * Create a JSON response with the given data and status code.\n * @param data - The data to be serialized to JSON.\n * @param status - The HTTP status code to be returned.\n * @returns A Response object with the JSON data and the given status code.\n */\nfunction jsonResponse(data: object, status: number): Response {\n\treturn new Response(JSON.stringify(data), {\n\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\tstatus,\n\t});\n}\n\n/**\n * Create a framework-agnostic API handler for chat and MCP resources.\n *\n * Returns an object with handler methods that can be wired into\n * any framework (Next.js, Hono, Express, etc.):\n *\n * - `handleChat(request)` → proxies chat messages to WaniWani API\n * - `handleResource(url)` → serves MCP resource content (HTML widgets)\n * - `routeGet(request)` → routes GET sub-paths (e.g. /resource)\n *\n * @example\n * ```typescript\n * import { waniwani } from \"@waniwani/sdk\";\n * import { toNextJsHandler } from \"@waniwani/sdk/next-js\";\n *\n * const wani = waniwani();\n *\n * export const { GET, POST, dynamic } = toNextJsHandler(wani, {\n * chat: { systemPrompt: \"You are a helpful assistant.\" },\n * });\n * ```\n */\nexport function createApiHandler(options: ApiHandlerOptions = {}): ApiHandler {\n\tconst {\n\t\tapiKey = process.env.WANIWANI_API_KEY,\n\t\tbaseUrl = \"https://app.waniwani.ai\",\n\t\tsystemPrompt,\n\t\tmaxSteps = 5,\n\t\tbeforeRequest,\n\t\tmcpServerUrl,\n\t\tdebug = false,\n\t} = options;\n\n\tconst log = createLogger(\"router\", debug);\n\n\tconst resolveConfig = createMcpConfigResolver(baseUrl, apiKey);\n\n\tconst handleChat = createChatRequestHandler({\n\t\tapiKey,\n\t\tbaseUrl,\n\t\tsystemPrompt,\n\t\tmaxSteps,\n\t\tbeforeRequest,\n\t\tmcpServerUrl,\n\t\tresolveConfig,\n\t\tdebug,\n\t});\n\n\tconst handleResource = createResourceHandler({\n\t\tmcpServerUrl,\n\t\tresolveConfig,\n\t\tdebug,\n\t});\n\n\tconst handleTool = createToolHandler({\n\t\tmcpServerUrl,\n\t\tresolveConfig,\n\t\tdebug,\n\t});\n\n\tasync function handleConfig(): Promise<Response> {\n\t\treturn jsonResponse({ debug }, 200);\n\t}\n\n\tasync function routeGet(request: Request): Promise<Response> {\n\t\tlog(\"→ GET\", request.url);\n\t\ttry {\n\t\t\tconst url = new URL(request.url);\n\t\t\tconst segments = url.pathname\n\t\t\t\t.replace(/\\/$/, \"\")\n\t\t\t\t.split(\"/\")\n\t\t\t\t.filter(Boolean);\n\t\t\tconst subRoute = segments.at(-1);\n\t\t\tlog(\"pathname:\", url.pathname, \"subRoute:\", subRoute);\n\n\t\t\tif (subRoute === \"resource\") {\n\t\t\t\tlog(\"dispatching to resource handler\");\n\t\t\t\tconst response = await handleResource(url);\n\t\t\t\tlog(\"← resource handler returned\", response.status);\n\t\t\t\treturn response;\n\t\t\t}\n\n\t\t\tif (subRoute === \"config\") {\n\t\t\t\tlog(\"dispatching to config handler\");\n\t\t\t\tconst response = await handleConfig();\n\t\t\t\tlog(\"← config handler returned\", response.status);\n\t\t\t\treturn response;\n\t\t\t}\n\n\t\t\tlog(\"← 404 no matching sub-route for\", subRoute);\n\t\t\treturn jsonResponse({ error: \"Not found\" }, 404);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[waniwani:router] GET handler error:\", error);\n\t\t\tconst message =\n\t\t\t\terror instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tlog(\"← 500 from caught error\");\n\t\t\treturn jsonResponse({ error: message }, 500);\n\t\t}\n\t}\n\n\tasync function routePost(request: Request): Promise<Response> {\n\t\tlog(\"→ POST\", request.url);\n\t\ttry {\n\t\t\tconst url = new URL(request.url);\n\t\t\tconst segments = url.pathname\n\t\t\t\t.replace(/\\/$/, \"\")\n\t\t\t\t.split(\"/\")\n\t\t\t\t.filter(Boolean);\n\t\t\tconst subRoute = segments.at(-1);\n\t\t\tlog(\"pathname:\", url.pathname, \"subRoute:\", subRoute);\n\n\t\t\tif (subRoute === \"tool\") {\n\t\t\t\tlog(\"dispatching to tool handler\");\n\t\t\t\tconst response = await handleTool(request);\n\t\t\t\tlog(\"← tool handler returned\", response.status);\n\t\t\t\treturn response;\n\t\t\t}\n\n\t\t\t// Default: treat as chat request\n\t\t\tlog(\"dispatching to chat handler\");\n\t\t\treturn handleChat(request);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[waniwani:router] POST handler error:\", error);\n\t\t\tconst message =\n\t\t\t\terror instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tlog(\"← 500 from caught error\");\n\t\t\treturn jsonResponse({ error: message }, 500);\n\t\t}\n\t}\n\n\treturn { handleChat, handleResource, handleTool, routeGet, routePost };\n}\n","// WaniWani SDK - Next.js Adapter\n\nimport type { WaniWaniClient } from \"../../../types.js\";\nimport { createApiHandler } from \"../api-handler.js\";\nimport type { NextJsHandlerOptions, NextJsHandlerResult } from \"./@types.js\";\n\nexport type { NextJsHandlerOptions, NextJsHandlerResult } from \"./@types.js\";\n\n/**\n * Create Next.js route handlers from a WaniWani client.\n *\n * Returns `{ GET, POST }` for use with catch-all routes.\n * Mount at `app/api/waniwani/[[...path]]/route.ts`:\n *\n * - `POST /api/waniwani` → chat (proxied to WaniWani API)\n * - `GET /api/waniwani/resource?uri=…` → MCP resource content\n *\n * @example\n * ```typescript\n * // app/api/waniwani/[[...path]]/route.ts\n * import { waniwani } from \"@waniwani/sdk\";\n * import { toNextJsHandler } from \"@waniwani/sdk/next-js\";\n *\n * const wani = waniwani();\n *\n * export const { GET, POST } = toNextJsHandler(wani, {\n * chat: {\n * systemPrompt: \"You are a helpful assistant.\",\n * mcpServerUrl: process.env.MCP_SERVER_URL!,\n * },\n * });\n * ```\n */\nexport function toNextJsHandler(\n\tclient: WaniWaniClient,\n\toptions?: NextJsHandlerOptions,\n): NextJsHandlerResult {\n\tconst { apiKey, baseUrl } = client._config;\n\n\tconst debugEnabled = options?.debug ?? process.env.WANIWANI_DEBUG === \"1\";\n\n\tconst handler = createApiHandler({\n\t\t...options?.chat,\n\t\tapiKey,\n\t\tbaseUrl,\n\t\tdebug: debugEnabled,\n\t});\n\n\treturn {\n\t\tPOST: handler.routePost,\n\t\tGET: handler.routeGet,\n\t};\n}\n"],"mappings":"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","names":["createLogger","namespace","enabled","args","WaniWaniError","message","status","extractGeoFromHeaders","request","h","rawCity","city","safeDecodeURI","country","countryRegion","latitude","longitude","timezone","ip","value","hasModelContext","value","hasContent","hasStructuredContent","formatModelContextForPrompt","value","hasModelContext","sections","renderedBlocks","block","applyModelContextToSystemPrompt","systemPrompt","modelContext","hasModelContext","widgetContext","formatModelContextForPrompt","createChatRequestHandler","deps","apiKey","baseUrl","systemPrompt","maxSteps","beforeRequest","mcpServerUrlOverride","resolveConfig","debug","log","createLogger","request","body","messages","sessionId","modelContext","effectiveSystemPrompt","clientVisitorContext","geo","extractGeoFromHeaders","visitor","result","hookError","status","WaniWaniError","message","mcpServerUrl","applyModelContextToSystemPrompt","upstreamUrl","response","errorBody","headers","upstreamSessionId","error","createResourceHandler","deps","mcpServerUrlOverride","resolveConfig","debug","log","createLogger","url","uri","mcpServerUrl","createMCPClient","StreamableHTTPClientTransport","importError","mcp","result","content","html","error","message","status","WaniWaniError","createToolHandler","deps","mcpServerUrlOverride","resolveConfig","debug","log","createLogger","request","body","name","args","mcpServerUrl","Client","StreamableHTTPClientTransport","importError","transport","client","result","error","message","status","WaniWaniError","TTL_MS","createMcpConfigResolver","baseUrl","apiKey","cached","inflight","WaniWaniError","response","body","data","jsonResponse","data","status","createApiHandler","options","apiKey","baseUrl","systemPrompt","maxSteps","beforeRequest","mcpServerUrl","debug","log","createLogger","resolveConfig","createMcpConfigResolver","handleChat","createChatRequestHandler","handleResource","createResourceHandler","handleTool","createToolHandler","handleConfig","routeGet","request","url","subRoute","response","error","message","routePost","toNextJsHandler","client","options","apiKey","baseUrl","debugEnabled","handler","createApiHandler"]}
1
+ {"version":3,"sources":["../../../src/utils/logger.ts","../../../src/error.ts","../../../src/chat/server/geo.ts","../../../src/shared/model-context.ts","../../../src/chat/server/model-context.ts","../../../src/chat/server/handle-chat.ts","../../../src/chat/server/handle-resource.ts","../../../src/chat/server/handle-tool.ts","../../../src/chat/server/mcp-config-resolver.ts","../../../src/chat/server/api-handler.ts","../../../src/chat/server/next-js/index.ts"],"sourcesContent":["/**\n * Creates a namespaced logger that writes to console.log when enabled,\n * or is a no-op when disabled.\n *\n * @example\n * const log = createLogger(\"chat\", debug);\n * log(\"→ POST\", request.url); // [waniwani:chat] → POST ...\n */\nexport function createLogger(\n\tnamespace: string,\n\tenabled: boolean,\n): (...args: unknown[]) => void {\n\treturn enabled\n\t\t? (...args: unknown[]) => console.log(`[waniwani:${namespace}]`, ...args)\n\t\t: () => {};\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","// Geo — Extract location metadata from platform request headers\n\n/**\n * Server-side geolocation extracted from platform headers (Vercel, Cloudflare).\n * All fields are optional — in local dev, no headers are present.\n */\nexport interface GeoLocation {\n\tcity?: string;\n\tcountry?: string;\n\tcountryRegion?: string;\n\tlatitude?: string;\n\tlongitude?: string;\n\ttimezone?: string;\n\tip?: string;\n}\n\n/**\n * Extracts geolocation from server-side request headers.\n *\n * Supports Vercel (`x-vercel-ip-*`), Cloudflare (`cf-ip*`, `cf-connecting-ip`),\n * and generic IP headers (`x-real-ip`, `x-forwarded-for`).\n *\n * Returns a `GeoLocation` with all fields optional (empty object in local dev).\n */\nexport function extractGeoFromHeaders(request: Request): GeoLocation {\n\tconst h = request.headers;\n\n\t// Vercel URL-encodes city names (e.g. \"S%C3%A3o%20Paulo\")\n\tconst rawCity = h.get(\"x-vercel-ip-city\") ?? h.get(\"cf-ipcity\") ?? undefined;\n\tconst city = rawCity ? safeDecodeURI(rawCity) : undefined;\n\n\tconst country =\n\t\th.get(\"x-vercel-ip-country\") ?? h.get(\"cf-ipcountry\") ?? undefined;\n\tconst countryRegion = h.get(\"x-vercel-ip-country-region\") ?? undefined;\n\tconst latitude =\n\t\th.get(\"x-vercel-ip-latitude\") ?? h.get(\"cf-iplatitude\") ?? undefined;\n\tconst longitude =\n\t\th.get(\"x-vercel-ip-longitude\") ?? h.get(\"cf-iplongitude\") ?? undefined;\n\tconst timezone =\n\t\th.get(\"x-vercel-ip-timezone\") ?? h.get(\"cf-iptimezone\") ?? undefined;\n\tconst ip =\n\t\th.get(\"x-real-ip\") ??\n\t\th.get(\"x-forwarded-for\")?.split(\",\")[0]?.trim() ??\n\t\th.get(\"cf-connecting-ip\") ??\n\t\tundefined;\n\n\treturn { city, country, countryRegion, latitude, longitude, timezone, ip };\n}\n\nfunction safeDecodeURI(value: string): string {\n\ttry {\n\t\treturn decodeURIComponent(value);\n\t} catch {\n\t\treturn value;\n\t}\n}\n","import type { ContentBlock } from \"@modelcontextprotocol/sdk/types.js\";\n\nexport type ModelContextContentBlock = ContentBlock;\n\nexport type ModelContextUpdate = {\n\tcontent?: ModelContextContentBlock[];\n\tstructuredContent?: Record<string, unknown>;\n};\n\nexport function hasModelContext(\n\tvalue: ModelContextUpdate | null | undefined,\n): value is ModelContextUpdate {\n\tif (!value) {\n\t\treturn false;\n\t}\n\tconst hasContent = Array.isArray(value.content) && value.content.length > 0;\n\tconst hasStructuredContent =\n\t\ttypeof value.structuredContent === \"object\" &&\n\t\tvalue.structuredContent !== null &&\n\t\tObject.keys(value.structuredContent).length > 0;\n\treturn hasContent || hasStructuredContent;\n}\n\nexport function mergeModelContext(\n\tcurrent: ModelContextUpdate | null | undefined,\n\tnext: ModelContextUpdate | null | undefined,\n): ModelContextUpdate | null {\n\tif (!hasModelContext(current) && !hasModelContext(next)) {\n\t\treturn null;\n\t}\n\tif (!hasModelContext(current)) {\n\t\treturn {\n\t\t\t...(next?.content ? { content: [...next.content] } : {}),\n\t\t\t...(next?.structuredContent\n\t\t\t\t? { structuredContent: { ...next.structuredContent } }\n\t\t\t\t: {}),\n\t\t};\n\t}\n\tif (!hasModelContext(next)) {\n\t\treturn {\n\t\t\t...(current.content ? { content: [...current.content] } : {}),\n\t\t\t...(current.structuredContent\n\t\t\t\t? { structuredContent: { ...current.structuredContent } }\n\t\t\t\t: {}),\n\t\t};\n\t}\n\n\treturn {\n\t\t...(current.content || next.content\n\t\t\t? { content: [...(current.content ?? []), ...(next.content ?? [])] }\n\t\t\t: {}),\n\t\t...(current.structuredContent || next.structuredContent\n\t\t\t? {\n\t\t\t\t\tstructuredContent: {\n\t\t\t\t\t\t...(current.structuredContent ?? {}),\n\t\t\t\t\t\t...(next.structuredContent ?? {}),\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t: {}),\n\t};\n}\n\nexport function formatModelContextForPrompt(\n\tvalue: ModelContextUpdate | null | undefined,\n): string {\n\tif (!hasModelContext(value)) {\n\t\treturn \"\";\n\t}\n\n\tconst sections: string[] = [\n\t\t\"## Widget Model Context\",\n\t\t\"This hidden context was supplied by an MCP App via `ui/update-model-context`.\",\n\t\t\"Use it for the next assistant turn only. If it includes flow continuation or tool-call instructions, follow them exactly.\",\n\t];\n\n\tif (value.content?.length) {\n\t\tconst renderedBlocks = value.content\n\t\t\t.map((block) => {\n\t\t\t\tif (block.type === \"text\" && typeof block.text === \"string\") {\n\t\t\t\t\treturn block.text.trim();\n\t\t\t\t}\n\t\t\t\treturn JSON.stringify(block, null, 2);\n\t\t\t})\n\t\t\t.filter(Boolean)\n\t\t\t.join(\"\\n\\n\");\n\t\tif (renderedBlocks) {\n\t\t\tsections.push(`Content blocks:\\n${renderedBlocks}`);\n\t\t}\n\t}\n\n\tif (\n\t\tvalue.structuredContent &&\n\t\tObject.keys(value.structuredContent).length > 0\n\t) {\n\t\tsections.push(\n\t\t\t`Structured content JSON:\\n${JSON.stringify(value.structuredContent, null, 2)}`,\n\t\t);\n\t}\n\n\treturn sections.join(\"\\n\\n\");\n}\n","import {\n\tformatModelContextForPrompt,\n\thasModelContext,\n\ttype ModelContextUpdate,\n} from \"../../shared/model-context\";\n\nexport function applyModelContextToSystemPrompt(\n\tsystemPrompt: string | undefined,\n\tmodelContext: ModelContextUpdate | undefined,\n): string | undefined {\n\tif (!hasModelContext(modelContext)) {\n\t\treturn systemPrompt;\n\t}\n\n\tconst widgetContext = formatModelContextForPrompt(modelContext);\n\tif (!widgetContext) {\n\t\treturn systemPrompt;\n\t}\n\n\treturn [systemPrompt, widgetContext].filter(Boolean).join(\"\\n\\n\");\n}\n","// Handle Chat - Proxies chat requests to the WaniWani API\n\nimport { WaniWaniError } from \"../../error\";\nimport { createLogger } from \"../../utils/logger.js\";\nimport type {\n\tApiHandlerDeps,\n\tClientVisitorContext,\n\tVisitorMeta,\n} from \"./@types\";\nimport { extractGeoFromHeaders } from \"./geo\";\nimport { applyModelContextToSystemPrompt } from \"./model-context\";\n\nexport function createChatRequestHandler(deps: ApiHandlerDeps) {\n\tconst {\n\t\tapiKey,\n\t\tbaseUrl,\n\t\tsystemPrompt,\n\t\tmaxSteps,\n\t\tbeforeRequest,\n\t\tmcpServerUrl: mcpServerUrlOverride,\n\t\tresolveConfig,\n\t\tdebug,\n\t} = deps;\n\n\tconst log = createLogger(\"chat\", debug);\n\n\treturn async function handleChat(request: Request): Promise<Response> {\n\t\tlog(\"→ POST\", request.url);\n\t\ttry {\n\t\t\t// 1. Parse request body\n\t\t\tconst body = await request.json();\n\t\t\tlet messages = body.messages ?? [];\n\t\t\tlet sessionId: string | undefined = body.sessionId;\n\t\t\tlet modelContext = body.modelContext;\n\t\t\tlet effectiveSystemPrompt = systemPrompt;\n\n\t\t\t// Extract visitor context (client-side + server-side geo)\n\t\t\tconst clientVisitorContext: ClientVisitorContext | null =\n\t\t\t\tbody.visitorContext ?? null;\n\t\t\tconst geo = extractGeoFromHeaders(request);\n\t\t\tconst visitor: VisitorMeta = { geo, client: clientVisitorContext };\n\n\t\t\tlog(\n\t\t\t\t\"body parsed — messages:\",\n\t\t\t\tmessages.length,\n\t\t\t\t\"sessionId:\",\n\t\t\t\tsessionId ?? \"(none)\",\n\t\t\t\t\"geo:\",\n\t\t\t\tJSON.stringify(geo),\n\t\t\t);\n\n\t\t\t// 2. Run beforeRequest hook\n\t\t\tif (beforeRequest) {\n\t\t\t\tlog(\"running beforeRequest hook\");\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await beforeRequest({\n\t\t\t\t\t\tmessages,\n\t\t\t\t\t\tsessionId,\n\t\t\t\t\t\tmodelContext,\n\t\t\t\t\t\trequest,\n\t\t\t\t\t\tvisitor,\n\t\t\t\t\t});\n\n\t\t\t\t\tif (result) {\n\t\t\t\t\t\tif (result.messages) {\n\t\t\t\t\t\t\tmessages = result.messages;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (result.systemPrompt !== undefined) {\n\t\t\t\t\t\t\teffectiveSystemPrompt = result.systemPrompt;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (result.sessionId !== undefined) {\n\t\t\t\t\t\t\tsessionId = result.sessionId;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (result.modelContext !== undefined) {\n\t\t\t\t\t\t\tmodelContext = result.modelContext;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tlog(\n\t\t\t\t\t\t\"beforeRequest hook done — messages:\",\n\t\t\t\t\t\tmessages.length,\n\t\t\t\t\t\t\"sessionId:\",\n\t\t\t\t\t\tsessionId ?? \"(none)\",\n\t\t\t\t\t);\n\t\t\t\t} catch (hookError) {\n\t\t\t\t\tconsole.error(\"[waniwani:chat] beforeRequest hook error:\", hookError);\n\t\t\t\t\tconst status =\n\t\t\t\t\t\thookError instanceof WaniWaniError ? hookError.status : 400;\n\t\t\t\t\tconst message =\n\t\t\t\t\t\thookError instanceof Error ? hookError.message : \"Request rejected\";\n\t\t\t\t\tlog(\"← returning\", status, \"from hook error\");\n\t\t\t\t\treturn Response.json({ error: message }, { status });\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 3. Resolve MCP server URL\n\t\t\tconst mcpServerUrl =\n\t\t\t\tmcpServerUrlOverride ?? (await resolveConfig()).mcpServerUrl;\n\t\t\tlog(\"mcpServerUrl:\", mcpServerUrl);\n\t\t\teffectiveSystemPrompt = applyModelContextToSystemPrompt(\n\t\t\t\teffectiveSystemPrompt,\n\t\t\t\tmodelContext,\n\t\t\t);\n\n\t\t\t// 4. Forward to WaniWani API\n\t\t\tconst upstreamUrl = `${baseUrl}/api/mcp/chat`;\n\t\t\tlog(\"forwarding to\", upstreamUrl);\n\t\t\tconst clientUserAgent = request.headers.get(\"user-agent\");\n\n\t\t\tconst response = await fetch(upstreamUrl, {\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\t...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),\n\t\t\t\t\t...(clientUserAgent\n\t\t\t\t\t\t? { \"X-Client-User-Agent\": clientUserAgent }\n\t\t\t\t\t\t: {}),\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmessages,\n\t\t\t\t\tmcpServerUrl,\n\t\t\t\t\tsessionId,\n\t\t\t\t\tsystemPrompt: effectiveSystemPrompt,\n\t\t\t\t\tmaxSteps,\n\t\t\t\t\tvisitor,\n\t\t\t\t}),\n\t\t\t\tsignal: request.signal,\n\t\t\t});\n\n\t\t\tlog(\"upstream response status:\", response.status);\n\n\t\t\tif (!response.ok) {\n\t\t\t\tconst errorBody = await response.text().catch(() => \"\");\n\t\t\t\tlog(\"← returning\", response.status, \"upstream error:\", errorBody);\n\t\t\t\treturn new Response(errorBody, {\n\t\t\t\t\tstatus: response.status,\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\":\n\t\t\t\t\t\t\tresponse.headers.get(\"Content-Type\") ?? \"application/json\",\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// 5. Stream the response back\n\t\t\tconst headers = new Headers({\n\t\t\t\t\"Content-Type\":\n\t\t\t\t\tresponse.headers.get(\"Content-Type\") ?? \"text/event-stream\",\n\t\t\t});\n\t\t\tconst upstreamSessionId = response.headers.get(\"x-session-id\");\n\t\t\tif (upstreamSessionId) {\n\t\t\t\theaders.set(\"x-session-id\", upstreamSessionId);\n\t\t\t}\n\n\t\t\tlog(\n\t\t\t\t\"← streaming response\",\n\t\t\t\tresponse.status,\n\t\t\t\t\"body null?\",\n\t\t\t\tresponse.body === null,\n\t\t\t);\n\t\t\treturn new Response(response.body, {\n\t\t\t\tstatus: response.status,\n\t\t\t\theaders,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[waniwani:chat] handler error:\", error);\n\t\t\tconst message =\n\t\t\t\terror instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tconst status = error instanceof WaniWaniError ? error.status : 500;\n\t\t\tlog(\"← returning\", status, \"from caught error\");\n\t\t\treturn Response.json({ error: message }, { status });\n\t\t}\n\t};\n}\n","// Handle Resource - Serves MCP resource content (HTML widgets)\n\nimport { WaniWaniError } from \"../../error\";\nimport { createLogger } from \"../../utils/logger.js\";\nimport type { ResourceHandlerDeps } from \"./@types\";\n\nexport function createResourceHandler(deps: ResourceHandlerDeps) {\n\tconst { mcpServerUrl: mcpServerUrlOverride, resolveConfig, debug } = deps;\n\n\tconst log = createLogger(\"resource\", debug);\n\n\treturn async function handleResource(url: URL): Promise<Response> {\n\t\tlog(\"→ GET\", url.toString());\n\t\ttry {\n\t\t\tconst uri = url.searchParams.get(\"uri\");\n\t\t\tlog(\"uri:\", uri ?? \"(missing)\");\n\n\t\t\tif (!uri) {\n\t\t\t\tlog(\"← 400 missing uri\");\n\t\t\t\treturn Response.json(\n\t\t\t\t\t{ error: \"Missing uri query parameter\" },\n\t\t\t\t\t{ status: 400 },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst mcpServerUrl =\n\t\t\t\tmcpServerUrlOverride ?? (await resolveConfig()).mcpServerUrl;\n\t\t\tlog(\"mcpServerUrl:\", mcpServerUrl);\n\n\t\t\t// Dynamic imports — these are optional peer dependencies\n\t\t\tlet createMCPClient: typeof import(\"@ai-sdk/mcp\")[\"createMCPClient\"];\n\t\t\tlet StreamableHTTPClientTransport: typeof import(\"@modelcontextprotocol/sdk/client/streamableHttp.js\")[\"StreamableHTTPClientTransport\"];\n\n\t\t\ttry {\n\t\t\t\t[{ createMCPClient }, { StreamableHTTPClientTransport }] =\n\t\t\t\t\tawait Promise.all([\n\t\t\t\t\t\timport(\"@ai-sdk/mcp\"),\n\t\t\t\t\t\timport(\"@modelcontextprotocol/sdk/client/streamableHttp.js\"),\n\t\t\t\t\t]);\n\t\t\t\tlog(\"MCP deps loaded\");\n\t\t\t} catch (importError) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t\"[waniwani:resource] MCP deps import failed:\",\n\t\t\t\t\timportError,\n\t\t\t\t);\n\t\t\t\treturn Response.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror:\n\t\t\t\t\t\t\t\"MCP resource handler requires @ai-sdk/mcp and @modelcontextprotocol/sdk. Install them to enable resource serving.\",\n\t\t\t\t\t},\n\t\t\t\t\t{ status: 501 },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tlog(\"creating MCP client for\", mcpServerUrl);\n\t\t\tconst mcp = await createMCPClient({\n\t\t\t\ttransport: new StreamableHTTPClientTransport(new URL(mcpServerUrl)),\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\tlog(\"reading resource:\", uri);\n\t\t\t\tconst result = await mcp.readResource({ uri });\n\t\t\t\tlog(\"resource contents count:\", result.contents.length);\n\n\t\t\t\tconst content = result.contents[0];\n\t\t\t\tif (!content) {\n\t\t\t\t\tlog(\"← 404 resource not found\");\n\t\t\t\t\treturn Response.json(\n\t\t\t\t\t\t{ error: \"Resource not found\" },\n\t\t\t\t\t\t{ status: 404 },\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tlet html: string | undefined;\n\t\t\t\tif (\"text\" in content && typeof content.text === \"string\") {\n\t\t\t\t\thtml = content.text;\n\t\t\t\t} else if (\"blob\" in content && typeof content.blob === \"string\") {\n\t\t\t\t\thtml = atob(content.blob);\n\t\t\t\t}\n\n\t\t\t\tif (!html) {\n\t\t\t\t\tlog(\"← 404 resource has no content, keys:\", Object.keys(content));\n\t\t\t\t\treturn Response.json(\n\t\t\t\t\t\t{ error: \"Resource has no content\" },\n\t\t\t\t\t\t{ status: 404 },\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tlog(\"← 200 HTML length:\", html.length);\n\t\t\t\treturn new Response(html, {\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\": \"text/html\",\n\t\t\t\t\t\t\"Cache-Control\": \"private, max-age=300\",\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} finally {\n\t\t\t\tawait mcp.close();\n\t\t\t\tlog(\"MCP client closed\");\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[waniwani:resource] handler error:\", error);\n\t\t\tconst message =\n\t\t\t\terror instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tconst status = error instanceof WaniWaniError ? error.status : 500;\n\t\t\tlog(\"← returning\", status, \"from caught error\");\n\t\t\treturn Response.json({ error: message }, { status });\n\t\t}\n\t};\n}\n","// Handle Tool - Calls MCP server tools directly and returns JSON results\n\nimport { WaniWaniError } from \"../../error\";\nimport { createLogger } from \"../../utils/logger.js\";\nimport type { ResourceHandlerDeps } from \"./@types\";\n\nexport function createToolHandler(deps: ResourceHandlerDeps) {\n\tconst { mcpServerUrl: mcpServerUrlOverride, resolveConfig, debug } = deps;\n\n\tconst log = createLogger(\"tool\", debug);\n\n\treturn async function handleTool(request: Request): Promise<Response> {\n\t\tlog(\"→ POST\", request.url);\n\t\ttry {\n\t\t\tconst body = await request.json();\n\t\t\tconst { name, arguments: args } = body as {\n\t\t\t\tname: string;\n\t\t\t\targuments: Record<string, unknown>;\n\t\t\t};\n\n\t\t\tif (!name || typeof name !== \"string\") {\n\t\t\t\tlog(\"← 400 missing tool name\");\n\t\t\t\treturn Response.json({ error: \"Missing tool name\" }, { status: 400 });\n\t\t\t}\n\n\t\t\tlog(\"tool:\", name, \"args:\", JSON.stringify(args));\n\n\t\t\tconst mcpServerUrl =\n\t\t\t\tmcpServerUrlOverride ?? (await resolveConfig()).mcpServerUrl;\n\t\t\tlog(\"mcpServerUrl:\", mcpServerUrl);\n\n\t\t\t// Dynamic imports — these are optional peer dependencies\n\t\t\tlet Client: typeof import(\"@modelcontextprotocol/sdk/client/index.js\")[\"Client\"];\n\t\t\tlet StreamableHTTPClientTransport: typeof import(\"@modelcontextprotocol/sdk/client/streamableHttp.js\")[\"StreamableHTTPClientTransport\"];\n\n\t\t\ttry {\n\t\t\t\t[{ Client }, { StreamableHTTPClientTransport }] = await Promise.all([\n\t\t\t\t\timport(\"@modelcontextprotocol/sdk/client/index.js\"),\n\t\t\t\t\timport(\"@modelcontextprotocol/sdk/client/streamableHttp.js\"),\n\t\t\t\t]);\n\t\t\t\tlog(\"MCP deps loaded\");\n\t\t\t} catch (importError) {\n\t\t\t\tconsole.error(\"[waniwani:tool] MCP deps import failed:\", importError);\n\t\t\t\treturn Response.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror:\n\t\t\t\t\t\t\t\"MCP tool handler requires @modelcontextprotocol/sdk. Install it to enable tool calls.\",\n\t\t\t\t\t},\n\t\t\t\t\t{ status: 501 },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tlog(\"creating MCP client for\", mcpServerUrl);\n\t\t\tconst transport = new StreamableHTTPClientTransport(\n\t\t\t\tnew URL(mcpServerUrl),\n\t\t\t);\n\t\t\tconst client = new Client({\n\t\t\t\tname: \"waniwani-tool-caller\",\n\t\t\t\tversion: \"1.0.0\",\n\t\t\t});\n\t\t\tawait client.connect(transport);\n\n\t\t\ttry {\n\t\t\t\tlog(\"calling tool:\", name);\n\t\t\t\tconst result = await client.callTool({\n\t\t\t\t\tname,\n\t\t\t\t\targuments: args ?? {},\n\t\t\t\t});\n\t\t\t\tlog(\"tool result received\");\n\n\t\t\t\treturn Response.json({\n\t\t\t\t\tcontent: result.content,\n\t\t\t\t\tstructuredContent: result.structuredContent,\n\t\t\t\t\t_meta: result._meta,\n\t\t\t\t\tisError: result.isError,\n\t\t\t\t});\n\t\t\t} finally {\n\t\t\t\tawait client.close();\n\t\t\t\tlog(\"MCP client closed\");\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[waniwani:tool] handler error:\", error);\n\t\t\tconst message =\n\t\t\t\terror instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tconst status = error instanceof WaniWaniError ? error.status : 500;\n\t\t\tlog(\"← returning\", status, \"from caught error\");\n\t\t\treturn Response.json({ error: message }, { status });\n\t\t}\n\t};\n}\n","// MCP Config Resolver - Lazy-loads and caches MCP environment config\n\nimport { WaniWaniError } from \"../../error\";\n\ninterface McpEnvironmentConfig {\n\tmcpServerUrl: string;\n}\n\nconst TTL_MS = 5 * 60 * 1000; // 5 minutes\n\nexport function createMcpConfigResolver(\n\tbaseUrl: string,\n\tapiKey: string | undefined,\n) {\n\tlet cached: { config: McpEnvironmentConfig; expiresAt: number } | null = null;\n\tlet inflight: Promise<McpEnvironmentConfig> | null = null;\n\n\treturn async function resolve(): Promise<McpEnvironmentConfig> {\n\t\tif (cached && Date.now() < cached.expiresAt) {\n\t\t\treturn cached.config;\n\t\t}\n\n\t\t// Deduplicate concurrent requests (cold start scenario)\n\t\tif (inflight) {\n\t\t\treturn inflight;\n\t\t}\n\n\t\tinflight = (async () => {\n\t\t\tif (!apiKey) {\n\t\t\t\tthrow new WaniWaniError(\n\t\t\t\t\t\"WANIWANI_API_KEY is required for createChatHandler\",\n\t\t\t\t\t401,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst response = await fetch(`${baseUrl}/api/mcp/environments/config`, {\n\t\t\t\tmethod: \"GET\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\tconst body = await response.text().catch(() => \"\");\n\t\t\t\tthrow new WaniWaniError(\n\t\t\t\t\t`Failed to resolve MCP environment config: ${response.status} ${body}`,\n\t\t\t\t\tresponse.status,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst data = (await response.json()) as McpEnvironmentConfig;\n\t\t\tcached = { config: data, expiresAt: Date.now() + TTL_MS };\n\t\t\treturn data;\n\t\t})();\n\n\t\ttry {\n\t\t\treturn await inflight;\n\t\t} finally {\n\t\t\tinflight = null;\n\t\t}\n\t};\n}\n","// API Handler - Composes chat and resource handlers into a unified API handler\n\nimport { createLogger } from \"../../utils/logger.js\";\nimport type { ApiHandler, ApiHandlerOptions } from \"./@types\";\nimport { createChatRequestHandler } from \"./handle-chat\";\nimport { createResourceHandler } from \"./handle-resource\";\nimport { createToolHandler } from \"./handle-tool\";\nimport { createMcpConfigResolver } from \"./mcp-config-resolver\";\n\n/**\n * Create a JSON response with the given data and status code.\n * @param data - The data to be serialized to JSON.\n * @param status - The HTTP status code to be returned.\n * @returns A Response object with the JSON data and the given status code.\n */\nfunction jsonResponse(data: object, status: number): Response {\n\treturn new Response(JSON.stringify(data), {\n\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\tstatus,\n\t});\n}\n\n/**\n * Create a framework-agnostic API handler for chat and MCP resources.\n *\n * Returns an object with handler methods that can be wired into\n * any framework (Next.js, Hono, Express, etc.):\n *\n * - `handleChat(request)` → proxies chat messages to WaniWani API\n * - `handleResource(url)` → serves MCP resource content (HTML widgets)\n * - `routeGet(request)` → routes GET sub-paths (e.g. /resource)\n *\n * @example\n * ```typescript\n * import { waniwani } from \"@waniwani/sdk\";\n * import { toNextJsHandler } from \"@waniwani/sdk/next-js\";\n *\n * const wani = waniwani();\n *\n * export const { GET, POST, dynamic } = toNextJsHandler(wani, {\n * chat: { systemPrompt: \"You are a helpful assistant.\" },\n * });\n * ```\n */\nexport function createApiHandler(options: ApiHandlerOptions = {}): ApiHandler {\n\tconst {\n\t\tapiKey = process.env.WANIWANI_API_KEY,\n\t\tbaseUrl = \"https://app.waniwani.ai\",\n\t\tsystemPrompt,\n\t\tmaxSteps = 5,\n\t\tbeforeRequest,\n\t\tmcpServerUrl,\n\t\tdebug = false,\n\t} = options;\n\n\tconst log = createLogger(\"router\", debug);\n\n\tconst resolveConfig = createMcpConfigResolver(baseUrl, apiKey);\n\n\tconst handleChat = createChatRequestHandler({\n\t\tapiKey,\n\t\tbaseUrl,\n\t\tsystemPrompt,\n\t\tmaxSteps,\n\t\tbeforeRequest,\n\t\tmcpServerUrl,\n\t\tresolveConfig,\n\t\tdebug,\n\t});\n\n\tconst handleResource = createResourceHandler({\n\t\tmcpServerUrl,\n\t\tresolveConfig,\n\t\tdebug,\n\t});\n\n\tconst handleTool = createToolHandler({\n\t\tmcpServerUrl,\n\t\tresolveConfig,\n\t\tdebug,\n\t});\n\n\tasync function handleConfig(): Promise<Response> {\n\t\treturn jsonResponse({ debug }, 200);\n\t}\n\n\tasync function routeGet(request: Request): Promise<Response> {\n\t\tlog(\"→ GET\", request.url);\n\t\ttry {\n\t\t\tconst url = new URL(request.url);\n\t\t\tconst segments = url.pathname\n\t\t\t\t.replace(/\\/$/, \"\")\n\t\t\t\t.split(\"/\")\n\t\t\t\t.filter(Boolean);\n\t\t\tconst subRoute = segments.at(-1);\n\t\t\tlog(\"pathname:\", url.pathname, \"subRoute:\", subRoute);\n\n\t\t\tif (subRoute === \"resource\") {\n\t\t\t\tlog(\"dispatching to resource handler\");\n\t\t\t\tconst response = await handleResource(url);\n\t\t\t\tlog(\"← resource handler returned\", response.status);\n\t\t\t\treturn response;\n\t\t\t}\n\n\t\t\tif (subRoute === \"config\") {\n\t\t\t\tlog(\"dispatching to config handler\");\n\t\t\t\tconst response = await handleConfig();\n\t\t\t\tlog(\"← config handler returned\", response.status);\n\t\t\t\treturn response;\n\t\t\t}\n\n\t\t\tlog(\"← 404 no matching sub-route for\", subRoute);\n\t\t\treturn jsonResponse({ error: \"Not found\" }, 404);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[waniwani:router] GET handler error:\", error);\n\t\t\tconst message =\n\t\t\t\terror instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tlog(\"← 500 from caught error\");\n\t\t\treturn jsonResponse({ error: message }, 500);\n\t\t}\n\t}\n\n\tasync function routePost(request: Request): Promise<Response> {\n\t\tlog(\"→ POST\", request.url);\n\t\ttry {\n\t\t\tconst url = new URL(request.url);\n\t\t\tconst segments = url.pathname\n\t\t\t\t.replace(/\\/$/, \"\")\n\t\t\t\t.split(\"/\")\n\t\t\t\t.filter(Boolean);\n\t\t\tconst subRoute = segments.at(-1);\n\t\t\tlog(\"pathname:\", url.pathname, \"subRoute:\", subRoute);\n\n\t\t\tif (subRoute === \"tool\") {\n\t\t\t\tlog(\"dispatching to tool handler\");\n\t\t\t\tconst response = await handleTool(request);\n\t\t\t\tlog(\"← tool handler returned\", response.status);\n\t\t\t\treturn response;\n\t\t\t}\n\n\t\t\t// Default: treat as chat request\n\t\t\tlog(\"dispatching to chat handler\");\n\t\t\treturn handleChat(request);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[waniwani:router] POST handler error:\", error);\n\t\t\tconst message =\n\t\t\t\terror instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tlog(\"← 500 from caught error\");\n\t\t\treturn jsonResponse({ error: message }, 500);\n\t\t}\n\t}\n\n\treturn { handleChat, handleResource, handleTool, routeGet, routePost };\n}\n","// WaniWani SDK - Next.js Adapter\n\nimport type { WaniWaniClient } from \"../../../types.js\";\nimport { createApiHandler } from \"../api-handler.js\";\nimport type { NextJsHandlerOptions, NextJsHandlerResult } from \"./@types.js\";\n\nexport type { NextJsHandlerOptions, NextJsHandlerResult } from \"./@types.js\";\n\n/**\n * Create Next.js route handlers from a WaniWani client.\n *\n * Returns `{ GET, POST }` for use with catch-all routes.\n * Mount at `app/api/waniwani/[[...path]]/route.ts`:\n *\n * - `POST /api/waniwani` → chat (proxied to WaniWani API)\n * - `GET /api/waniwani/resource?uri=…` → MCP resource content\n *\n * @example\n * ```typescript\n * // app/api/waniwani/[[...path]]/route.ts\n * import { waniwani } from \"@waniwani/sdk\";\n * import { toNextJsHandler } from \"@waniwani/sdk/next-js\";\n *\n * const wani = waniwani();\n *\n * export const { GET, POST } = toNextJsHandler(wani, {\n * chat: {\n * systemPrompt: \"You are a helpful assistant.\",\n * mcpServerUrl: process.env.MCP_SERVER_URL!,\n * },\n * });\n * ```\n */\nexport function toNextJsHandler(\n\tclient: WaniWaniClient,\n\toptions?: NextJsHandlerOptions,\n): NextJsHandlerResult {\n\tconst { apiKey, baseUrl } = client._config;\n\n\tconst debugEnabled = options?.debug ?? process.env.WANIWANI_DEBUG === \"1\";\n\n\tconst handler = createApiHandler({\n\t\t...options?.chat,\n\t\tapiKey,\n\t\tbaseUrl,\n\t\tdebug: debugEnabled,\n\t});\n\n\treturn {\n\t\tPOST: handler.routePost,\n\t\tGET: handler.routeGet,\n\t};\n}\n"],"mappings":"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","names":["createLogger","namespace","enabled","args","WaniWaniError","message","status","extractGeoFromHeaders","request","h","rawCity","city","safeDecodeURI","country","countryRegion","latitude","longitude","timezone","ip","value","hasModelContext","value","hasContent","hasStructuredContent","formatModelContextForPrompt","value","hasModelContext","sections","renderedBlocks","block","applyModelContextToSystemPrompt","systemPrompt","modelContext","hasModelContext","widgetContext","formatModelContextForPrompt","createChatRequestHandler","deps","apiKey","baseUrl","systemPrompt","maxSteps","beforeRequest","mcpServerUrlOverride","resolveConfig","debug","log","createLogger","request","body","messages","sessionId","modelContext","effectiveSystemPrompt","clientVisitorContext","geo","extractGeoFromHeaders","visitor","result","hookError","status","WaniWaniError","message","mcpServerUrl","applyModelContextToSystemPrompt","upstreamUrl","clientUserAgent","response","errorBody","headers","upstreamSessionId","error","createResourceHandler","deps","mcpServerUrlOverride","resolveConfig","debug","log","createLogger","url","uri","mcpServerUrl","createMCPClient","StreamableHTTPClientTransport","importError","mcp","result","content","html","error","message","status","WaniWaniError","createToolHandler","deps","mcpServerUrlOverride","resolveConfig","debug","log","createLogger","request","body","name","args","mcpServerUrl","Client","StreamableHTTPClientTransport","importError","transport","client","result","error","message","status","WaniWaniError","TTL_MS","createMcpConfigResolver","baseUrl","apiKey","cached","inflight","WaniWaniError","response","body","data","jsonResponse","data","status","createApiHandler","options","apiKey","baseUrl","systemPrompt","maxSteps","beforeRequest","mcpServerUrl","debug","log","createLogger","resolveConfig","createMcpConfigResolver","handleChat","createChatRequestHandler","handleResource","createResourceHandler","handleTool","createToolHandler","handleConfig","routeGet","request","url","subRoute","response","error","message","routePost","toNextJsHandler","client","options","apiKey","baseUrl","debugEnabled","handler","createApiHandler"]}
@@ -585,10 +585,7 @@ type InterruptQuestionData = {
585
585
  suggestions?: string[];
586
586
  context?: string;
587
587
  };
588
- type FlowContentBase = {
589
- flowToken?: string;
590
- };
591
- type FlowInterruptContent = FlowContentBase & {
588
+ type FlowInterruptContent = {
592
589
  status: "interrupt";
593
590
  /** Single-question shorthand */
594
591
  question?: string;
@@ -598,7 +595,7 @@ type FlowInterruptContent = FlowContentBase & {
598
595
  questions?: InterruptQuestionData[];
599
596
  context?: string;
600
597
  };
601
- type FlowWidgetContent = FlowContentBase & {
598
+ type FlowWidgetContent = {
602
599
  status: "widget";
603
600
  /** Display tool to call */
604
601
  tool: string;
@@ -608,10 +605,10 @@ type FlowWidgetContent = FlowContentBase & {
608
605
  /** Whether the widget expects user interaction before continuing */
609
606
  interactive?: boolean;
610
607
  };
611
- type FlowCompleteContent = FlowContentBase & {
608
+ type FlowCompleteContent = {
612
609
  status: "complete";
613
610
  };
614
- type FlowErrorContent = FlowContentBase & {
611
+ type FlowErrorContent = {
615
612
  status: "error";
616
613
  error: string;
617
614
  };
@@ -701,22 +698,6 @@ declare function createFlow<const TSchema extends Record<string, z.ZodType>>(con
701
698
  state: TSchema;
702
699
  }): StateGraph<InferFlowState<TSchema>>;
703
700
 
704
- /**
705
- * Opaque compressed token for flow state round-tripping.
706
- *
707
- * Flow state is compressed (zlib deflate) then base64-encoded. This produces
708
- * a token that:
709
- * 1. Looks like random noise — models can't mentally decode it
710
- * 2. Is smaller than raw base64 (~30% compression on typical state)
711
- * 3. The model treats it as an opaque string and passes it back unchanged
712
- *
713
- * Only encoded/decoded on the server (Node.js). Browser code should never
714
- * need to decode flow tokens.
715
- */
716
-
717
- declare function encodeFlowToken(data: FlowTokenContent): string;
718
- declare function decodeFlowToken(token: string): FlowTokenContent | null;
719
-
720
701
  type WithDecodedState = {
721
702
  decodedState: FlowTokenContent | null;
722
703
  };
@@ -1098,4 +1079,4 @@ type WithWaniwaniOptions = {
1098
1079
  */
1099
1080
  declare function withWaniwani(server: McpServer, options?: WithWaniwaniOptions): McpServer;
1100
1081
 
1101
- export { type ConditionFn, END, type FlowConfig, type FlowTestResult, type HostContext, type InferFlowState, type InterruptSignal, type NodeContext, type NodeHandler, type RegisteredFlow, type RegisteredResource, type RegisteredTool, type ResourceConfig, START, StateGraph, type ToolCallResult, type ToolConfig, type ToolHandler, type ToolHandlerContext, type ToolResult, type ToolToolCallback, type TrackingRouteOptions, type TypedInterrupt, type TypedShowWidget, type UnifiedWidgetClient, type WidgetCSP, type WidgetPlatform, type WidgetSignal, type WithWaniwaniOptions, createFlow, createFlowTestHarness, createResource, createTool, createTrackingRoute, decodeFlowToken, detectPlatform, encodeFlowToken, isMCPApps, isOpenAI, registerTools, withWaniwani };
1082
+ export { type ConditionFn, END, type FlowConfig, type FlowTestResult, type HostContext, type InferFlowState, type InterruptSignal, type NodeContext, type NodeHandler, type RegisteredFlow, type RegisteredResource, type RegisteredTool, type ResourceConfig, START, StateGraph, type ToolCallResult, type ToolConfig, type ToolHandler, type ToolHandlerContext, type ToolResult, type ToolToolCallback, type TrackingRouteOptions, type TypedInterrupt, type TypedShowWidget, type UnifiedWidgetClient, type WidgetCSP, type WidgetPlatform, type WidgetSignal, type WithWaniwaniOptions, createFlow, createFlowTestHarness, createResource, createTool, createTrackingRoute, detectPlatform, isMCPApps, isOpenAI, registerTools, withWaniwani };
package/dist/mcp/index.js CHANGED
@@ -1,6 +1,6 @@
1
- function q(){return typeof window<"u"&&"openai"in window?"openai":"mcp-apps"}function Ce(){return q()==="openai"}function Fe(){return q()==="mcp-apps"}var x="__start__",E="__end__",J=Symbol.for("waniwani.flow.interrupt"),X=Symbol.for("waniwani.flow.widget");function Z(e,t){let n=t?.context,r=[];for(let[o,a]of Object.entries(e))if(typeof a=="object"&&a!==null&&"question"in a){let i=a;r.push({question:i.question,field:o,suggestions:i.suggestions,context:i.context,validate:i.validate})}return{__type:J,questions:r,context:n}}function G(e,t){return{__type:X,tool:e,...t}}function Q(e){return typeof e=="object"&&e!==null&&"__type"in e&&e.__type===J}function ee(e){return typeof e=="object"&&e!==null&&"__type"in e&&e.__type===X}import{z as M}from"zod";var be=["waniwani/sessionId","openai/sessionId","sessionId","conversationId","anthropic/sessionId"];function te(e){if(e)for(let t of be){let n=e[t];if(typeof n=="string"&&n.length>0)return n}}function $(e){return e!=null&&e!==""}async function R(e,t){return e.type==="direct"?e.to:e.condition(t)}function ne(e,t,n,r){if(e.every(c=>$(r[c.field])))return null;let o=e.filter(c=>!$(r[c.field])),a=o.length===1,i=o[0];return{content:a&&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,...a&&i?{field:i.field}:{}}}}async function W(e,t,n,r,o,a){let i=e,s={...t},c=50,l=0;for(;l++<c;){if(i===E)return{content:{status:"complete"},flowTokenContent:{state:s}};let w=n.get(i);if(!w)return{content:{status:"error",error:`Unknown node: "${i}"`}};try{let d=await w({state:s,meta:a,interrupt:Z,showWidget:G});if(Q(d)){for(let p of d.questions)p.validate&&o.set(`${i}:${p.field}`,p.validate);let u=ne(d.questions,d.context,i,s);if(u)return u;for(let p of d.questions){let S=o.get(`${i}:${p.field}`);if(S)try{let y=s[p.field],k=await S(y);k&&typeof k=="object"&&(s={...s,...k})}catch(y){let k=y instanceof Error?y.message:String(y);delete s[p.field];let Ie=d.questions.map(b=>b.field===p.field?{...b,context:b.context?`ERROR: ${k}
1
+ function q(){return typeof window<"u"&&"openai"in window?"openai":"mcp-apps"}function ve(){return q()==="openai"}function xe(){return q()==="mcp-apps"}var x="__start__",E="__end__",X=Symbol.for("waniwani.flow.interrupt"),J=Symbol.for("waniwani.flow.widget");function Z(e,t){let n=t?.context,r=[];for(let[o,a]of Object.entries(e))if(typeof a=="object"&&a!==null&&"question"in a){let i=a;r.push({question:i.question,field:o,suggestions:i.suggestions,context:i.context,validate:i.validate})}return{__type:X,questions:r,context:n}}function G(e,t){return{__type:J,tool:e,...t}}function Q(e){return typeof e=="object"&&e!==null&&"__type"in e&&e.__type===X}function ee(e){return typeof e=="object"&&e!==null&&"__type"in e&&e.__type===J}import{z as W}from"zod";var Ee=["waniwani/sessionId","openai/sessionId","sessionId","conversationId","anthropic/sessionId"];function te(e){if(e)for(let t of Ee){let n=e[t];if(typeof n=="string"&&n.length>0)return n}}function $(e){return e!=null&&e!==""}async function R(e,t){return e.type==="direct"?e.to:e.condition(t)}function ne(e,t,n,r){if(e.every(u=>$(r[u.field])))return null;let o=e.filter(u=>!$(r[u.field])),a=o.length===1,i=o[0];return{content:a&&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,...a&&i?{field:i.field}:{}}}}async function P(e,t,n,r,o,a){let i=e,s={...t},u=50,c=0;for(;c++<u;){if(i===E)return{content:{status:"complete"},flowTokenContent:{state:s}};let f=n.get(i);if(!f)return{content:{status:"error",error:`Unknown node: "${i}"`}};try{let d=await f({state:s,meta:a,interrupt:Z,showWidget:G});if(Q(d)){for(let g of d.questions)g.validate&&o.set(`${i}:${g.field}`,g.validate);let l=ne(d.questions,d.context,i,s);if(l)return l;for(let g of d.questions){let v=o.get(`${i}:${g.field}`);if(v)try{let k=s[g.field],T=await v(k);T&&typeof T=="object"&&(s={...s,...T})}catch(k){let T=k instanceof Error?k.message:String(k);delete s[g.field];let b=d.questions.map(_=>_.field===g.field?{..._,context:_.context?`ERROR: ${T}
2
2
 
3
- ${b.context}`:`ERROR: ${k}`}:b),Y=ne(Ie,d.context,i,s);if(Y)return Y;break}}let g=r.get(i);if(!g)return{content:{status:"error",error:`No outgoing edge from node "${i}"`}};i=await R(g,s);continue}if(ee(d)){let u=d.field;if(u&&$(s[u])){let g=r.get(i);if(!g)return{content:{status:"error",error:`No outgoing edge from node "${i}"`}};i=await R(g,s);continue}return{content:{status:"widget",tool:d.tool.id,data:d.data,description:d.description,...d.interactive===!1?{interactive:!1}:{}},flowTokenContent:{step:i,state:s,field:u,widgetId:d.tool.id}}}s={...s,...d};let T=r.get(i);if(!T)return{content:{status:"error",error:`No outgoing edge from node "${i}"`}};i=await R(T,s)}catch(f){return{content:{status:"error",error:f instanceof Error?f.message:String(f)},flowTokenContent:{step:i,state:s}}}}return{content:{status:"error",error:"Flow exceeded maximum iterations (possible infinite loop)"}}}import{randomBytes as _e}from"crypto";var Me="@waniwani/sdk",Pe="https://app.waniwani.ai";function re(){return _e(4).toString("hex")}var _=class{baseUrl;apiKey;constructor(t){this.baseUrl=(t?.baseUrl??process.env.WANIWANI_BASE_URL??Pe).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":Me},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 We(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 oe(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=Object.entries(e.state).map(([r,o])=>{let a=We(o);return a?`\`${r}\` (${a})`:`\`${r}\``}).join(", ");t.push(` Known fields: ${n}.`)}return t.push("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"`, `flowToken` = the `flowToken` from the response (pass back exactly as received),'," `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."," If the response includes `interactive: false`, the widget is display-only:"," call the display tool, show the widget, then immediately call THIS flow tool again with",' `action: "continue"` and the same `flowToken`. In that case, do NOT wait for the user'," to click or use the widget, and do NOT ask them to interact with it unless the"," description explicitly says otherwise."," Otherwise, present the widget result to the user. When the user makes a choice or interacts"," with the widget, call THIS flow tool again with:",' `action: "continue"`, `flowToken` = the `flowToken` from the response,'," `stateUpdates` = `{ [field]: <user's selection> }` plus any other fields the user mentioned.",' - `"complete"`: The flow is done. Present the result to the user.',' - `"error"`: Something went wrong. Show the `error` message.',"","3. ALWAYS pass back the `flowToken` string exactly as received \u2014 it is an opaque token, do not modify it.","4. Do NOT invent state values. Only use `stateUpdates` for information the user explicitly provided.","5. 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
- `)}import{deflateSync as Ae,inflateSync as De}from"zlib";function ie(e){let t=JSON.stringify(e);return Ae(t).toString("base64")}function I(e){try{let t=Buffer.from(e,"base64"),n=De(t).toString("utf-8");return JSON.parse(n)}catch{return null}}async function se(e,t,n){if(n){let r=await t.get(n);if(r)return r}if(e.flowToken){let r=await t.get(e.flowToken);if(r)return r;let o=I(e.flowToken);if(o)return o}return null}var Ue={action:M.enum(["start","continue"]).describe('"start" to begin the flow, "continue" to resume after a pause (interrupt or widget)'),stateUpdates:M.record(M.string(),M.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."),flowToken:M.string().optional().describe("Opaque flow token from the previous response. Pass back exactly as received.")};function ae(e){let{config:t,nodes:n,edges:r}=e,o=oe(t),a=`${t.description}
5
- ${o}`,i=e.store??new _,s=new Map;async function c(l,w,f){if(l.action==="start"){let d=r.get(x);if(!d)return{content:{status:"error",error:"No start edge"}};let T={...l.stateUpdates??{}},u=await R(d,T);return W(u,T,n,r,s,f)}if(l.action==="continue"){let d=await se(l,i,w);if(!d)return{content:{status:"error",error:"Flow state not found for continue action. Pass back the flowToken from the previous response exactly as received."}};let T=d.state,u=d.step;if(!u)return{content:{status:"error",error:"Flow state is missing the current step. The flow may have expired."}};let g={...T,...l.stateUpdates??{}};if(d.widgetId){let p=r.get(u);if(!p)return{content:{status:"error",error:`No edge from step "${u}"`}};let S=await R(p,g);return W(S,g,n,r,s,f)}return W(u,g,n,r,s,f)}return{content:{status:"error",error:`Unknown action: "${l.action}"`}}}return{id:t.id,title:t.title,description:a,async register(l){l.registerTool(t.id,{title:t.title,description:a,inputSchema:Ue,annotations:t.annotations},(async(w,f)=>{let T=f._meta??{},u=te(T),g=await c(w,u,T),p;if(g.flowTokenContent)if(u)await i.set(u,g.flowTokenContent),p=u;else{let k=re();await i.set(k,g.flowTokenContent),p=k}let S={...g.content,...p?{flowToken:p}:{}};return{content:[{type:"text",text:JSON.stringify(S,null,2)}],_meta:T,...g.content.status==="error"?{isError:!0}:{}}}))}}}var C=class{nodes=new Map;edges=new Map;config;constructor(t){this.config=t}addNode(t,n){if(t===x||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}compile(t){return this.validate(),ae({config:this.config,nodes:new Map(this.nodes),edges:new Map(this.edges),store:t?.store})}validate(){if(!this.edges.has(x))throw new Error('Flow must have an entry point. Add an edge from START: .addEdge(START, "first_node")');let t=this.edges.get(x);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!==x&&!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 ce(e){return new C(e)}function de(e){let t=e.content;return JSON.parse(t[0]?.text??"")}async function le(e,t){if(!e)return null;if(t){let n=await t.get(e);if(n)return n}return I(e)}async function ue(e,t){let n=t?.stateStore,r=[],o={registerTool:(...c)=>{r.push(c)}};await e.register(o);let a=r[0]?.[2];if(!a)throw new Error(`Flow "${e.id}" did not register a handler`);let i;async function s(c){return i=c.flowToken,{...c,decodedState:await le(i,n)}}return{async start(c){let l=await a({action:"start",...c?{stateUpdates:c}:{}},{});return s(de(l))},async continueWith(c){if(!i)throw new Error("No flowToken \u2014 call start() first");let l=await a({action:"continue",flowToken:i,...c?{stateUpdates:c}:{}},{});return s(de(l))},async lastState(){return le(i,n)}}}var A="text/html+skybridge",D="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 fe(e){return{"openai/widgetDescription":e.description,"openai/widgetPrefersBorder":e.prefersBorder,"openai/widgetDomain":e.widgetDomain,...e.widgetCSP&&{"openai/widgetCSP":e.widgetCSP}}}function ge(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 L(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}}}}}function me(e){let{id:t,title:n,description:r,baseUrl:o,htmlPath:a,widgetDomain:i,prefersBorder:s=!0,autoHeight:c=!0}=e,l=e.widgetCSP??{connect_domains:[o],resource_domains:[o]};if(process.env.NODE_ENV==="development")try{let{hostname:p}=new URL(o);(p==="localhost"||p==="127.0.0.1")&&(l={...l,connect_domains:[...l.connect_domains||[],`ws://${p}:*`,`wss://${p}:*`],resource_domains:[...l.resource_domains||[],`http://${p}:*`]})}catch{}let w=`ui://widgets/apps-sdk/${t}.html`,f=`ui://widgets/ext-apps/${t}.html`,d=null,T=()=>(d||(d=pe(o,a)),d),u=r;async function g(p){let S=await T();p.registerResource(`${t}-openai-widget`,w,{title:n,description:u,mimeType:A,_meta:{"openai/widgetDescription":u,"openai/widgetPrefersBorder":s}},async y=>({contents:[{uri:y.href,mimeType:A,text:S,_meta:fe({description:u,prefersBorder:s,widgetDomain:i,widgetCSP:l})}]})),p.registerResource(`${t}-mcp-widget`,f,{title:n,description:u,mimeType:D,_meta:{ui:{prefersBorder:s}}},async y=>({contents:[{uri:y.href,mimeType:D,text:S,_meta:ge({description:u,prefersBorder:s,widgetCSP:l})}]}))}return{id:t,title:n,description:r,openaiUri:w,mcpUri:f,autoHeight:c,register:g}}function Ne(e,t){let{resource:n,description:r,inputSchema:o,annotations:a,autoInjectResultText:i=!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 l=n?L({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(w){w.registerTool(s,{title:c,description:r,inputSchema:o,annotations:a,...l&&{_meta:l}},(async(f,d)=>{let u=d._meta??{},g=await t(f,{extra:{_meta:u}});return n&&g.data?{content:[{type:"text",text:g.text}],structuredContent:g.data,_meta:{...l,...u,...i===!1?{"waniwani/autoInjectResultText":!1}:{}}}:{content:[{type:"text",text:g.text}],...g.data?{structuredContent:g.data}:{},...i===!1?{_meta:{"waniwani/autoInjectResultText":!1}}:{}}}))}}}async function Oe(e,t){await Promise.all(t.map(n=>n.register(e)))}var U=class extends Error{constructor(n,r){super(n);this.status=r;this.name="WaniWaniError"}};var Be="@waniwani/sdk";function he(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(a,i,s){let c=r(),l=`${t.replace(/\/$/,"")}${i}`,w={Authorization:`Bearer ${c}`,"X-WaniWani-SDK":Be},f={method:a,headers:w};s!==void 0&&(w["Content-Type"]="application/json",f.body=JSON.stringify(s));let d=await fetch(l,f);if(!d.ok){let u=await d.text().catch(()=>"");throw new U(u||`KB API error: HTTP ${d.status}`,d.status)}return(await d.json()).data}return{async ingest(a){return o("POST","/api/mcp/kb/ingest",{files:a})},async search(a,i){return o("POST","/api/mcp/kb/search",{query:a,...i})},async sources(){return o("GET","/api/mcp/kb/sources")}}}var je="@waniwani/sdk";function O(e,t={}){let n=t.now??(()=>new Date),r=t.generateId??we,o=Le(e),a=N(e.meta),i=N(e.metadata),s=Ke(e,a),c=v(e.eventId)??r(),l=He(e.timestamp,n),w=v(e.source)??t.source??je,f=K(e)?{...e}:void 0,d={...i};return Object.keys(a).length>0&&(d.meta=a),f&&(d.rawLegacy=f),{id:c,type:"mcp.event",name:o,source:w,timestamp:l,correlation:s,properties:qe(e,o),metadata:d,rawLegacy:f}}function we(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?`evt_${crypto.randomUUID()}`:`evt_${Math.random().toString(36).slice(2,10)}_${Date.now().toString(36)}`}function qe(e,t){if(!K(e))return N(e.properties);let n=$e(e,t),r=N(e.properties);return{...n,...r}}function $e(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 Le(e){return K(e)?e.eventType:e.event}function Ke(e,t){let n=v(e.requestId)??P(t,["openai/requestId","requestId","mcp/requestId"]),r=v(e.sessionId)??P(t,["openai/sessionId","sessionId","conversationId","anthropic/sessionId"]),o=v(e.traceId)??P(t,["openai/traceId","traceId","mcp/traceId","openai/requestId","requestId"]),a=v(e.externalUserId)??P(t,["openai/userId","externalUserId","userId","actorId"]),i=v(e.correlationId)??P(t,["correlationId","openai/requestId"])??n,s={};return r&&(s.sessionId=r),o&&(s.traceId=o),n&&(s.requestId=n),i&&(s.correlationId=i),a&&(s.externalUserId=a),s}function He(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 P(e,t){for(let n of t){let r=e[n];if(typeof r=="string"&&r.trim().length>0)return r}}function N(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function v(e){if(typeof e=="string"&&e.trim().length!==0)return e}function K(e){return"eventType"in e}var Ve="/api/mcp/events/v2/batch";var ye="@waniwani/sdk",ze=new Set([401,403]),Ye=new Set([408,425,429,500,502,503,504]);function Te(e){return new H(e)}var H=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=Ze(t.baseUrl,t.endpointPath??Ve),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":ye},body:JSON.stringify(this.makeBatchRequest(t))})}catch(a){return{kind:"retryable",reason:Ge(a)}}if(ze.has(n.status))return{kind:"auth",status:n.status};if(Ye.has(n.status))return{kind:"retryable",reason:`HTTP ${n.status}`};if(!n.ok)return{kind:"permanent",reason:`HTTP ${n.status}`};let r=await Xe(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:ye,version:this.sdkVersion??"0.0.0"},events:t}}classifyRejectedEvents(t,n){let r=new Map(t.map(i=>[i.id,i])),o=[],a=[];for(let i of n){let s=r.get(i.eventId);if(s){if(Je(i)){o.push(s);continue}a.push(s)}}return{retryable:o,permanent:a}}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 Je(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 Xe(e){let t=await e.text();if(t)try{return JSON.parse(t)}catch{return}}function Ze(e,t){let n=e.endsWith("/")?e:`${e}/`,r=t.startsWith("/")?t.slice(1):t;return`${n}${r}`}function Ge(e){return e instanceof Error?e.message:String(e)}function ke(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 a=n?Te({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(s,c){o();let l=O({event:"user.identified",externalUserId:s,properties:c});return a?.enqueue(l),{eventId:l.id}},async track(s){o();let c=O(s);return a?.enqueue(c),{eventId:c.id}},async flush(){o(),await a?.flush()},async shutdown(s){return o(),await a?.shutdown({timeoutMs:s?.timeoutMs??r.shutdownTimeoutMs})??{timedOut:!1,pendingEvents:0}}};return a&&Qe(i,r.shutdownTimeoutMs),i}function Qe(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 B(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},a=ke(o),i=he(o);return{...a,kb:i,_config:o}}function et(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 tt(e){let t={apiKey:e?.apiKey,baseUrl:e?.baseUrl},n;function r(){return n||(n=B(t)),n}return async function(a){let i;try{i=await a.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 s=r(),c=[];for(let l of i.events){let w=et(l),f=await s.track(w);c.push(f.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"}})}}}var j=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=nt(this.config.baseUrl,"/api/mcp/widget-tokens"),o={};t&&(o.sessionId=t),n&&(o.traceId=n);try{let a=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify(o)});if(!a.ok)return null;let i=await a.json(),s=i.data&&typeof i.data=="object"?i.data:i,c=new Date(s.expiresAt).getTime();return!s.token||Number.isNaN(c)?null:(this.cached={token:s.token,expiresAt:c},s.token)}catch{return null}}};function nt(e,t){return`${e.endsWith("/")?e.slice(0,-1):e}${t}`}var Se="https://app.waniwani.ai";function rt(e,t={}){let n=e;if(n.__waniwaniWrapped)return n;n.__waniwaniWrapped=!0;let r=t.client??B(t.config),o=t.injectWidgetToken!==!1,a=null;function i(){if(a)return a;let c=r._config.apiKey;return c?(a=new j({baseUrl:r._config.baseUrl??Se,apiKey:c}),a):null}let s=e.registerTool.bind(e);return n.registerTool=((...c)=>{let[l,w,f]=c,d=typeof l=="string"&&l.trim().length>0?l:"unknown";if(typeof f!="function")return s(...c);let T=f;return s(l,w,async(g,p)=>{let S=performance.now();try{let y=await T(g,p),k=Math.round(performance.now()-S);return await xe(r,ve(d,p,t,{durationMs:k,status:"ok"}),t.onError),t.flushAfterToolCall&&await Ee(r,t.onError),o&&await ot(y,i(),r._config.baseUrl??Se,t.onError),st(y,p),y}catch(y){let k=Math.round(performance.now()-S);throw await xe(r,ve(d,p,t,{durationMs:k,status:"error",errorMessage:y instanceof Error?y.message:String(y)}),t.onError),t.flushAfterToolCall&&await Ee(r,t.onError),y}})}),n}async function ot(e,t,n,r){if(!F(e))return;F(e._meta)||(e._meta={});let o=e._meta,a={endpoint:`${n.replace(/\/$/,"")}/api/mcp/events/v2/batch`};if(t)try{let i=await t.getToken();i&&(a.token=i)}catch(i){r?.(z(i))}o.waniwani=a}function ve(e,t,n,r){let o=it(e,n.toolType),a=Re(t);return{event:"tool.called",properties:{name:e,type:o,...r??{}},meta:a,metadata:{source:"withWaniwani",...n.metadata??{}}}}function it(e,t){return typeof t=="function"?t(e)??"other":t??"other"}function Re(e){if(!F(e))return;let t=e._meta;if(F(t))return t}function F(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}async function xe(e,t,n){try{await e.track(t)}catch(r){n?.(z(r))}}async function Ee(e,t){try{await e.flush()}catch(n){t?.(z(n))}}function z(e){return e instanceof Error?e:new Error(String(e))}var V="waniwani/userLocation";function st(e,t){let n=Re(t);if(!n)return;let r=n[V];if(!r||!F(e))return;F(e._meta)||(e._meta={});let o=e._meta;o[V]||(o[V]=r)}export{E as END,x as START,C as StateGraph,ce as createFlow,ue as createFlowTestHarness,me as createResource,Ne as createTool,tt as createTrackingRoute,I as decodeFlowToken,q as detectPlatform,ie as encodeFlowToken,Fe as isMCPApps,Ce as isOpenAI,Oe as registerTools,rt as withWaniwani};
3
+ ${_.context}`:`ERROR: ${T}`}:_),Y=ne(b,d.context,i,s);if(Y)return Y;break}}let h=r.get(i);if(!h)return{content:{status:"error",error:`No outgoing edge from node "${i}"`}};i=await R(h,s);continue}if(ee(d)){let l=d.field;if(l&&$(s[l])){let h=r.get(i);if(!h)return{content:{status:"error",error:`No outgoing edge from node "${i}"`}};i=await R(h,s);continue}return{content:{status:"widget",tool:d.tool.id,data:d.data,description:d.description,...d.interactive===!1?{interactive:!1}:{}},flowTokenContent:{step:i,state:s,field:l,widgetId:d.tool.id}}}s={...s,...d};let y=r.get(i);if(!y)return{content:{status:"error",error:`No outgoing edge from node "${i}"`}};i=await R(y,s)}catch(p){return{content:{status:"error",error:p instanceof Error?p.message:String(p)},flowTokenContent:{step:i,state:s}}}}return{content:{status:"error",error:"Flow exceeded maximum iterations (possible infinite loop)"}}}var Re="@waniwani/sdk",Ie="https://app.waniwani.ai",M=class{baseUrl;apiKey;constructor(t){this.baseUrl=(t?.baseUrl??process.env.WANIWANI_BASE_URL??Ie).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":Re},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 Ce(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=Object.entries(e.state).map(([r,o])=>{let a=Ce(o);return a?`\`${r}\` (${a})`:`\`${r}\``}).join(", ");t.push(` Known fields: ${n}.`)}return t.push("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."," If the response includes `interactive: false`, the widget is display-only:"," call the display tool, show the widget, then immediately call THIS flow tool again with",' `action: "continue"`. In that case, do NOT wait for the user'," to click or use the widget, and do NOT ask them to interact with it unless the"," description explicitly says otherwise."," Otherwise, present the widget result to the user. When the user makes a choice or interacts"," with the widget, call THIS flow tool again with:",' `action: "continue"`,'," `stateUpdates` = `{ [field]: <user's selection> }` plus any other fields the user mentioned.",' - `"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 be={action:W.enum(["start","continue"]).describe('"start" to begin the flow, "continue" to resume after a pause (interrupt or widget)'),stateUpdates:W.record(W.string(),W.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 oe(e){let{config:t,nodes:n,edges:r}=e,o=re(t),a=`${t.description}
5
+ ${o}`,i=e.store??new M,s=new Map;async function u(c,f,p){if(c.action==="start"){let d=r.get(x);if(!d)return{content:{status:"error",error:"No start edge"}};let y={...c.stateUpdates??{}},l=await R(d,y);return P(l,y,n,r,s,p)}if(c.action==="continue"){if(!f)return{content:{status:"error",error:"No session ID available for continue action."}};let d=await i.get(f);if(!d)return{content:{status:"error",error:"Flow state not found. The flow may have expired."}};let y=d.state,l=d.step;if(!l)return{content:{status:"error",error:"Flow state is missing the current step. The flow may have expired."}};let h={...y,...c.stateUpdates??{}};if(d.widgetId){let g=r.get(l);if(!g)return{content:{status:"error",error:`No edge from step "${l}"`}};let v=await R(g,h);return P(v,h,n,r,s,p)}return P(l,h,n,r,s,p)}return{content:{status:"error",error:`Unknown action: "${c.action}"`}}}return{id:t.id,title:t.title,description:a,async register(c){c.registerTool(t.id,{title:t.title,description:a,inputSchema:be,annotations:t.annotations},(async(f,p)=>{let y=p._meta??{},l=te(y),h=await u(f,l,y);return h.flowTokenContent&&l&&await i.set(l,h.flowTokenContent),{content:[{type:"text",text:JSON.stringify(h.content,null,2)}],_meta:y,...h.content.status==="error"?{isError:!0}:{}}}))}}}var I=class{nodes=new Map;edges=new Map;config;constructor(t){this.config=t}addNode(t,n){if(t===x||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}compile(t){return this.validate(),oe({config:this.config,nodes:new Map(this.nodes),edges:new Map(this.edges),store:t?.store})}validate(){if(!this.edges.has(x))throw new Error('Flow must have an entry point. Add an edge from START: .addEdge(START, "first_node")');let t=this.edges.get(x);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!==x&&!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 ie(e){return new I(e)}function se(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)}`,a={registerTool:(...c)=>{r.push(c)}};await e.register(a);let i=r[0]?.[2];if(!i)throw new Error(`Flow "${e.id}" did not register a handler`);let s={_meta:{sessionId:o}};async function u(c){return{...c,decodedState:n?await n.get(o):null}}return{async start(c){let f=await i({action:"start",...c?{stateUpdates:c}:{}},s);return u(se(f))},async continueWith(c){let f=await i({action:"continue",...c?{stateUpdates:c}:{}},s);return u(se(f))},async lastState(){return n?n.get(o):null}}}var A="text/html+skybridge",D="text/html;profile=mcp-app",ce=async(e,t)=>{let n=e.endsWith("/")?e.slice(0,-1):e;return await(await fetch(`${n}${t}`)).text()};function de(e){return{"openai/widgetDescription":e.description,"openai/widgetPrefersBorder":e.prefersBorder,"openai/widgetDomain":e.widgetDomain,...e.widgetCSP&&{"openai/widgetCSP":e.widgetCSP}}}function ue(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 L(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}}}}}function le(e){let{id:t,title:n,description:r,baseUrl:o,htmlPath:a,widgetDomain:i,prefersBorder:s=!0,autoHeight:u=!0}=e,c=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")&&(c={...c,connect_domains:[...c.connect_domains||[],`ws://${g}:*`,`wss://${g}:*`],resource_domains:[...c.resource_domains||[],`http://${g}:*`]})}catch{}let f=`ui://widgets/apps-sdk/${t}.html`,p=`ui://widgets/ext-apps/${t}.html`,d=null,y=()=>(d||(d=ce(o,a)),d),l=r;async function h(g){let v=await y();g.registerResource(`${t}-openai-widget`,f,{title:n,description:l,mimeType:A,_meta:{"openai/widgetDescription":l,"openai/widgetPrefersBorder":s}},async k=>({contents:[{uri:k.href,mimeType:A,text:v,_meta:de({description:l,prefersBorder:s,widgetDomain:i,widgetCSP:c})}]})),g.registerResource(`${t}-mcp-widget`,p,{title:n,description:l,mimeType:D,_meta:{ui:{prefersBorder:s}}},async k=>({contents:[{uri:k.href,mimeType:D,text:v,_meta:ue({description:l,prefersBorder:s,widgetCSP:c})}]}))}return{id:t,title:n,description:r,openaiUri:f,mcpUri:p,autoHeight:u,register:h}}function _e(e,t){let{resource:n,description:r,inputSchema:o,annotations:a,autoInjectResultText:i=!0}=e,s=e.id??n?.id,u=e.title??n?.title;if(!s)throw new Error("createTool: `id` is required when no resource is provided");if(!u)throw new Error("createTool: `title` is required when no resource is provided");let c=n?L({openaiTemplateUri:n.openaiUri,mcpTemplateUri:n.mcpUri,invoking:e.invoking??"Loading...",invoked:e.invoked??"Loaded",autoHeight:n.autoHeight}):void 0;return{id:s,title:u,description:r,async register(f){f.registerTool(s,{title:u,description:r,inputSchema:o,annotations:a,...c&&{_meta:c}},(async(p,d)=>{let l=d._meta??{},h=await t(p,{extra:{_meta:l}});return n&&h.data?{content:[{type:"text",text:h.text}],structuredContent:h.data,_meta:{...c,...l,...i===!1?{"waniwani/autoInjectResultText":!1}:{}}}:{content:[{type:"text",text:h.text}],...h.data?{structuredContent:h.data}:{},...i===!1?{_meta:{"waniwani/autoInjectResultText":!1}}:{}}}))}}}async function Me(e,t){await Promise.all(t.map(n=>n.register(e)))}var U=class extends Error{constructor(n,r){super(n);this.status=r;this.name="WaniWaniError"}};var Fe="@waniwani/sdk";function pe(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(a,i,s){let u=r(),c=`${t.replace(/\/$/,"")}${i}`,f={Authorization:`Bearer ${u}`,"X-WaniWani-SDK":Fe},p={method:a,headers:f};s!==void 0&&(f["Content-Type"]="application/json",p.body=JSON.stringify(s));let d=await fetch(c,p);if(!d.ok){let l=await d.text().catch(()=>"");throw new U(l||`KB API error: HTTP ${d.status}`,d.status)}return(await d.json()).data}return{async ingest(a){return o("POST","/api/mcp/kb/ingest",{files:a})},async search(a,i){return o("POST","/api/mcp/kb/search",{query:a,...i})},async sources(){return o("GET","/api/mcp/kb/sources")}}}var Pe="@waniwani/sdk";function O(e,t={}){let n=t.now??(()=>new Date),r=t.generateId??ge,o=De(e),a=N(e.meta),i=N(e.metadata),s=Ue(e,a),u=S(e.eventId)??r(),c=Ne(e.timestamp,n),f=S(e.source)??t.source??Pe,p=H(e)?{...e}:void 0,d={...i};return Object.keys(a).length>0&&(d.meta=a),p&&(d.rawLegacy=p),{id:u,type:"mcp.event",name:o,source:f,timestamp:c,correlation:s,properties:We(e,o),metadata:d,rawLegacy:p}}function ge(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?`evt_${crypto.randomUUID()}`:`evt_${Math.random().toString(36).slice(2,10)}_${Date.now().toString(36)}`}function We(e,t){if(!H(e))return N(e.properties);let n=Ae(e,t),r=N(e.properties);return{...n,...r}}function Ae(e,t){switch(t){case"tool.called":{let n={};return S(e.toolName)&&(n.name=e.toolName),S(e.toolType)&&(n.type=e.toolType),n}case"quote.succeeded":{let n={};return typeof e.quoteAmount=="number"&&(n.amount=e.quoteAmount),S(e.quoteCurrency)&&(n.currency=e.quoteCurrency),n}case"link.clicked":{let n={};return S(e.linkUrl)&&(n.url=e.linkUrl),n}case"purchase.completed":{let n={};return typeof e.purchaseAmount=="number"&&(n.amount=e.purchaseAmount),S(e.purchaseCurrency)&&(n.currency=e.purchaseCurrency),n}default:return{}}}function De(e){return H(e)?e.eventType:e.event}function Ue(e,t){let n=S(e.requestId)??F(t,["openai/requestId","requestId","mcp/requestId"]),r=S(e.sessionId)??F(t,["openai/sessionId","sessionId","conversationId","anthropic/sessionId"]),o=S(e.traceId)??F(t,["openai/traceId","traceId","mcp/traceId","openai/requestId","requestId"]),a=S(e.externalUserId)??F(t,["openai/userId","externalUserId","userId","actorId"]),i=S(e.correlationId)??F(t,["correlationId","openai/requestId"])??n,s={};return r&&(s.sessionId=r),o&&(s.traceId=o),n&&(s.requestId=n),i&&(s.correlationId=i),a&&(s.externalUserId=a),s}function Ne(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 F(e,t){for(let n of t){let r=e[n];if(typeof r=="string"&&r.trim().length>0)return r}}function N(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function S(e){if(typeof e=="string"&&e.trim().length!==0)return e}function H(e){return"eventType"in e}var Oe="/api/mcp/events/v2/batch";var fe="@waniwani/sdk",Be=new Set([401,403]),je=new Set([408,425,429,500,502,503,504]);function he(e){return new K(e)}var K=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=Le(t.baseUrl,t.endpointPath??Oe),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":fe},body:JSON.stringify(this.makeBatchRequest(t))})}catch(a){return{kind:"retryable",reason:He(a)}}if(Be.has(n.status))return{kind:"auth",status:n.status};if(je.has(n.status))return{kind:"retryable",reason:`HTTP ${n.status}`};if(!n.ok)return{kind:"permanent",reason:`HTTP ${n.status}`};let r=await $e(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:fe,version:this.sdkVersion??"0.0.0"},events:t}}classifyRejectedEvents(t,n){let r=new Map(t.map(i=>[i.id,i])),o=[],a=[];for(let i of n){let s=r.get(i.eventId);if(s){if(qe(i)){o.push(s);continue}a.push(s)}}return{retryable:o,permanent:a}}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 qe(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 $e(e){let t=await e.text();if(t)try{return JSON.parse(t)}catch{return}}function Le(e,t){let n=e.endsWith("/")?e:`${e}/`,r=t.startsWith("/")?t.slice(1):t;return`${n}${r}`}function He(e){return e instanceof Error?e.message:String(e)}function me(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 a=n?he({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(s,u){o();let c=O({event:"user.identified",externalUserId:s,properties:u});return a?.enqueue(c),{eventId:c.id}},async track(s){o();let u=O(s);return a?.enqueue(u),{eventId:u.id}},async flush(){o(),await a?.flush()},async shutdown(s){return o(),await a?.shutdown({timeoutMs:s?.timeoutMs??r.shutdownTimeoutMs})??{timedOut:!1,pendingEvents:0}}};return a&&Ke(i,r.shutdownTimeoutMs),i}function Ke(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 B(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},a=me(o),i=pe(o);return{...a,kb:i,_config:o}}function Ve(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 ze(e){let t={apiKey:e?.apiKey,baseUrl:e?.baseUrl},n;function r(){return n||(n=B(t)),n}return async function(a){let i;try{i=await a.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 s=r(),u=[];for(let c of i.events){let f=Ve(c),p=await s.track(f);u.push(p.eventId)}return await s.flush(),new Response(JSON.stringify({ok:!0,accepted:u.length}),{status:200,headers:{"Content-Type":"application/json"}})}catch(s){let u=s instanceof Error?s.message:"Unknown error";return new Response(JSON.stringify({error:u}),{status:500,headers:{"Content-Type":"application/json"}})}}}var j=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=Ye(this.config.baseUrl,"/api/mcp/widget-tokens"),o={};t&&(o.sessionId=t),n&&(o.traceId=n);try{let a=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify(o)});if(!a.ok)return null;let i=await a.json(),s=i.data&&typeof i.data=="object"?i.data:i,u=new Date(s.expiresAt).getTime();return!s.token||Number.isNaN(u)?null:(this.cached={token:s.token,expiresAt:u},s.token)}catch{return null}}};function Ye(e,t){return`${e.endsWith("/")?e.slice(0,-1):e}${t}`}var we="https://app.waniwani.ai";function Xe(e,t={}){let n=e;if(n.__waniwaniWrapped)return n;n.__waniwaniWrapped=!0;let r=t.client??B(t.config),o=t.injectWidgetToken!==!1,a=null;function i(){if(a)return a;let u=r._config.apiKey;return u?(a=new j({baseUrl:r._config.baseUrl??we,apiKey:u}),a):null}let s=e.registerTool.bind(e);return n.registerTool=((...u)=>{let[c,f,p]=u,d=typeof c=="string"&&c.trim().length>0?c:"unknown";if(typeof p!="function")return s(...u);let y=p;return s(c,f,async(h,g)=>{let v=performance.now(),k=e.server?.getClientVersion?.();try{let T=await y(h,g),b=Math.round(performance.now()-v);return await Te(r,ye(d,g,t,{durationMs:b,status:"ok"},k),t.onError),t.flushAfterToolCall&&await ke(r,t.onError),o&&await Je(T,i(),r._config.baseUrl??we,t.onError),Ge(T,g),T}catch(T){let b=Math.round(performance.now()-v);throw await Te(r,ye(d,g,t,{durationMs:b,status:"error",errorMessage:T instanceof Error?T.message:String(T)},k),t.onError),t.flushAfterToolCall&&await ke(r,t.onError),T}})}),n}async function Je(e,t,n,r){if(!C(e))return;C(e._meta)||(e._meta={});let o=e._meta,a={endpoint:`${n.replace(/\/$/,"")}/api/mcp/events/v2/batch`};if(t)try{let i=await t.getToken();i&&(a.token=i)}catch(i){r?.(z(i))}o.waniwani=a}function ye(e,t,n,r,o){let a=Ze(e,n.toolType),i=Se(t);return{event:"tool.called",properties:{name:e,type:a,...r??{}},meta:i,metadata:{source:"withWaniwani",...n.metadata??{},...o&&{clientInfo:o}}}}function Ze(e,t){return typeof t=="function"?t(e)??"other":t??"other"}function Se(e){if(!C(e))return;let t=e._meta;if(C(t))return t}function C(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}async function Te(e,t,n){try{await e.track(t)}catch(r){n?.(z(r))}}async function ke(e,t){try{await e.flush()}catch(n){t?.(z(n))}}function z(e){return e instanceof Error?e:new Error(String(e))}var V="waniwani/userLocation";function Ge(e,t){let n=Se(t);if(!n)return;let r=n[V];if(!r||!C(e))return;C(e._meta)||(e._meta={});let o=e._meta;o[V]||(o[V]=r)}export{E as END,x as START,I as StateGraph,ie as createFlow,ae as createFlowTestHarness,le as createResource,_e as createTool,ze as createTrackingRoute,q as detectPlatform,xe as isMCPApps,ve as isOpenAI,Me as registerTools,Xe as withWaniwani};
6
6
  //# sourceMappingURL=index.js.map