@waniwani/sdk 0.14.14 → 0.14.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat/embed.js +1 -1
- package/dist/chat/embed.js.map +1 -1
- package/dist/chat/index.js +1 -1
- package/dist/chat/index.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var y=class extends Error{constructor(t,r){super(t);this.status=r;this.name="WaniWaniError"}};import{existsSync as L,readFileSync as V}from"fs";import{resolve as K}from"path";var N="waniwani.json",m;function k(){if(m!==void 0)return m;try{let e=K(process.cwd(),N);if(!L(e))return m=null,null;let n=V(e,"utf-8");return m=JSON.parse(n),m}catch{return m=null,null}}var R="__waniwani_config__";function q(e){return globalThis[R]=e,e}function x(){return globalThis[R]}var z="@waniwani/sdk";function b(e){let{apiUrl:n,apiKey:t}=e;function r(){if(!t)throw new Error("WANIWANI_API_KEY is not set");return t}async function i(s,o,a){let h=r(),g=`${n.replace(/\/$/,"")}${o}`,u={Authorization:`Bearer ${h}`,"X-WaniWani-SDK":z},c={method:s,headers:u};a!==void 0&&(u["Content-Type"]="application/json",c.body=JSON.stringify(a));let p=await fetch(g,c);if(!p.ok){let j=await p.text().catch(()=>"");throw new y(j||`KB API error: HTTP ${p.status}`,p.status)}return(await p.json()).data}return{async ingest(s){return i("POST","/api/mcp/kb/ingest",{files:s})},async search(s,o){return i("POST","/api/mcp/kb/search",{query:s,...o})},async sources(){return i("GET","/api/mcp/kb/sources")}}}function v(e,n){for(let t of n){let r=e[t];if(typeof r=="string"&&r.length>0)return r}}var Y=["waniwani/sessionId","openai/sessionId","openai/session","sessionId","conversationId","mcp-session-id"],$=["waniwani/requestId","openai/requestId","requestId","mcp/requestId"],H=["waniwani/traceId","openai/traceId","traceId","mcp/traceId","openai/requestId","requestId"],X=["waniwani/userId","openai/userId","externalUserId","userId","actorId"],G=["correlationId","openai/requestId"];function C(e){return e?v(e,Y):void 0}function _(e){return e?v(e,$):void 0}function P(e){return e?v(e,H):void 0}function M(e){return e?v(e,X):void 0}function A(e){return e?v(e,G):void 0}var J=[{key:"openai/sessionId",source:"chatgpt"},{key:"openai/session",source:"chatgpt"}],Z=[{needle:"claude",source:"claude"}];function D(e,n){if(e){let r=e["waniwani/source"];if(typeof r=="string"&&r.length>0)return r;for(let{key:i,source:s}of J){let o=e[i];if(typeof o=="string"&&o.length>0)return s}}let t=n?.name;if(typeof t=="string"&&t.length>0){let r=t.toLowerCase();for(let{needle:i,source:s}of Z)if(r.includes(i))return s}}var Q="@waniwani/sdk";function I(e,n={}){let t=n.now??(()=>new Date),r=n.generateId??B,i=ne(e),s=T(e.meta),o=T(e.metadata),a=re(e,s),h=d(e.eventId)??r(),g=ie(e.timestamp,t),u=d(e.source)??D(s)??n.source??Q,c=w(e)?{...e}:void 0,p={...o};return Object.keys(s).length>0&&(p.meta=s),c&&(p.rawLegacy=c),{id:h,type:"mcp.event",name:i,source:u,timestamp:g,correlation:a,properties:ee(e,i),metadata:p,rawLegacy:c}}function B(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?`evt_${crypto.randomUUID()}`:`evt_${Math.random().toString(36).slice(2,10)}_${Date.now().toString(36)}`}function ee(e,n){if(!w(e))return T(e.properties);let t=te(e,n),r=T(e.properties);return{...t,...r}}function te(e,n){switch(n){case"tool.called":{let t={};return d(e.toolName)&&(t.name=e.toolName),d(e.toolType)&&(t.type=e.toolType),t}case"quote.succeeded":{let t={};return typeof e.quoteAmount=="number"&&(t.amount=e.quoteAmount),d(e.quoteCurrency)&&(t.currency=e.quoteCurrency),t}case"link.clicked":{let t={};return d(e.linkUrl)&&(t.url=e.linkUrl),t}case"purchase.completed":{let t={};return typeof e.purchaseAmount=="number"&&(t.amount=e.purchaseAmount),d(e.purchaseCurrency)&&(t.currency=e.purchaseCurrency),t}default:return{}}}function ne(e){return w(e)?e.eventType:e.event}function re(e,n){let t=d(e.requestId)??_(n),r=d(e.sessionId)??C(n),i=d(e.traceId)??P(n),s=d(e.externalUserId)??M(n),o=d(e.correlationId)??A(n)??t,a={};return r&&(a.sessionId=r),i&&(a.traceId=i),t&&(a.requestId=t),o&&(a.correlationId=o),s&&(a.externalUserId=s),a}function ie(e,n){if(e instanceof Date)return e.toISOString();if(typeof e=="string"){let t=new Date(e);if(!Number.isNaN(t.getTime()))return t.toISOString()}return n().toISOString()}function T(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function d(e){if(typeof e=="string"&&e.trim().length!==0)return e}function w(e){return"eventType"in e}function F(e){return{priceShown:({amount:n,currency:t,itemId:r,label:i,...s})=>e({event:"price_shown",properties:{amount:n,currency:t,itemId:r,label:i},...s}),pricesCompared:({options:n,...t})=>e({event:"prices_compared",properties:{options:n},...t}),optionSelected:({id:n,amount:t,currency:r,...i})=>e({event:"option_selected",properties:{id:n,amount:t,currency:r},...i}),lead:n=>{let{source:t,...r}=n??{};return e({event:"lead",properties:{source:t},...r})},converted:({amount:n,currency:t,occurredAt:r,...i})=>e({event:"converted",properties:{amount:n,currency:t,occurredAt:r},...i})}}var se="/api/mcp/events/v2/batch";var W="@waniwani/sdk",oe=new Set([401,403]),ae=new Set([408,425,429,500,502,503,504]);function O(e){return new S(e)}var S=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(n){this.endpointUrl=de(n.apiUrl,n.endpointPath??se),this.flushIntervalMs=n.flushIntervalMs??1e3,this.maxBatchSize=n.maxBatchSize??20,this.maxBufferSize=n.maxBufferSize??1e3,this.maxRetries=n.maxRetries??3,this.retryBaseDelayMs=n.retryBaseDelayMs??200,this.retryMaxDelayMs=n.retryMaxDelayMs??2e3,this.shutdownTimeoutMs=n.shutdownTimeoutMs??2e3,this.fetchFn=n.fetchFn??fetch,this.logger=n.logger??console,this.now=n.now??(()=>new Date),this.sleep=n.sleep??(t=>new Promise(r=>setTimeout(r,t))),this.apiKey=n.apiKey,this.sdkVersion=n.sdkVersion,this.flushIntervalMs>0&&(this.flushTimer=setInterval(()=>{this.flush()},this.flushIntervalMs))}enqueue(n){if(this.isStopped||this.isShuttingDown){this.logger.warn("[Waniwani] Tracking transport is stopped, dropping event %s",n.id);return}if(this.buffer.length>=this.maxBufferSize){let t=this.buffer.length-this.maxBufferSize+1;this.buffer.splice(0,t),this.logger.warn("[Waniwani] Tracking buffer overflow, dropped %d oldest event(s)",t)}if(this.buffer.push(n),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(n){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 t=n?.timeoutMs??this.shutdownTimeoutMs,r=this.flush();if(!Number.isFinite(t)||t<=0)return await r,this.isStopped=!0,{timedOut:!1,pendingEvents:this.pendingEvents()};let i=Symbol("shutdown-timeout");return await Promise.race([r.then(()=>"flushed"),this.sleep(t).then(()=>i)])===i?(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 n=this.buffer.splice(0,this.maxBatchSize);await this.sendBatchWithRetry(n)}}async sendBatchWithRetry(n){let t=0,r=n;for(;r.length>0&&!this.isStopped;){this.inFlightCount=r.length;let i=await this.sendBatchOnce(r);switch(this.inFlightCount=0,i.kind){case"success":return;case"auth":this.stopTransportForAuthFailure(i.status,r.length);return;case"permanent":this.logger.error("[Waniwani] Dropping %d event(s) after permanent failure: %s",r.length,i.reason);return;case"retryable":if(t>=this.maxRetries){this.logger.error("[Waniwani] Dropping %d event(s) after retry exhaustion: %s",r.length,i.reason);return}await this.sleep(this.backoffDelayMs(t)),t+=1;continue;case"partial":if(i.permanent.length>0&&this.logger.error("[Waniwani] Dropping %d event(s) rejected as permanent",i.permanent.length),i.retryable.length===0)return;if(t>=this.maxRetries){this.logger.error("[Waniwani] Dropping %d retryable event(s) after retry exhaustion",i.retryable.length);return}r=i.retryable,await this.sleep(this.backoffDelayMs(t)),t+=1;continue}}}async sendBatchOnce(n){let t;try{t=await this.fetchFn(this.endpointUrl,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,"X-WaniWani-SDK":W},body:JSON.stringify(this.makeBatchRequest(n))})}catch(s){return{kind:"retryable",reason:pe(s)}}if(oe.has(t.status))return{kind:"auth",status:t.status};if(ae.has(t.status))return{kind:"retryable",reason:`HTTP ${t.status}`};if(!t.ok)return{kind:"permanent",reason:`HTTP ${t.status}`};let r=await ue(t);if(!r?.rejected||r.rejected.length===0)return{kind:"success"};let i=this.classifyRejectedEvents(n,r.rejected);return i.retryable.length===0&&i.permanent.length===0?{kind:"success"}:{kind:"partial",retryable:i.retryable,permanent:i.permanent}}makeBatchRequest(n){return{sentAt:this.now().toISOString(),source:{sdk:W,version:this.sdkVersion??"0.0.0"},events:n}}classifyRejectedEvents(n,t){let r=new Map(n.map(o=>[o.id,o])),i=[],s=[];for(let o of t){let a=r.get(o.eventId);if(a){if(ce(o)){i.push(a);continue}s.push(a)}}return{retryable:i,permanent:s}}backoffDelayMs(n){let t=this.retryBaseDelayMs*2**n;return Math.min(t,this.retryMaxDelayMs)}stopTransportForAuthFailure(n,t){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)",n,t+r)}};function ce(e){if(e.retryable===!0)return!0;let n=e.code.toLowerCase();return n.includes("timeout")||n.includes("temporary")||n.includes("unavailable")||n.includes("rate_limit")||n.includes("transient")||n.includes("server")}async function ue(e){let n=await e.text();if(n)try{return JSON.parse(n)}catch{return}}function de(e,n){let t=e.endsWith("/")?e:`${e}/`,r=n.startsWith("/")?n.slice(1):n;return`${t}${r}`}function pe(e){return e instanceof Error?e.message:String(e)}function U(e){let{apiUrl:n,apiKey:t,tracking:r}=e;function i(){if(!t)throw new Error("WANIWANI_API_KEY is not set");return t}let s=t?O({apiUrl:n,apiKey:t,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;function o(u){i();let c=I(u);return!c.correlation.sessionId&&!c.correlation.externalUserId&&console.warn(`[waniwani] event "${c.name}" has no sessionId or externalUserId; the ingest API requires one and will reject it.`),s?.enqueue(c),{eventId:c.id}}let a=async u=>o(u),h=Object.assign(a,F(a)),g={async identify(u,c,p){i();let E=I({event:"user.identified",externalUserId:u,properties:c,meta:p});return s?.enqueue(E),{eventId:E.id}},track:h,async flush(){i(),await s?.flush()},async shutdown(u){return i(),await s?.shutdown({timeoutMs:u?.timeoutMs??r.shutdownTimeoutMs})??{timedOut:!1,pendingEvents:0}}};return s&&le(g,r.shutdownTimeoutMs),g}function le(e,n){if(typeof process>"u"||typeof process.once!="function"||typeof process.on!="function")return;let t=()=>{e.shutdown({timeoutMs:n})};process.once("beforeExit",t),process.once("SIGINT",t),process.once("SIGTERM",t)}function fe(e){let t=e??k()??x(),r=t?.apiUrl??"https://app.waniwani.ai",i=t?.apiKey??process.env.WANIWANI_API_KEY,s={endpointPath:t?.tracking?.endpointPath??"/api/mcp/events/v2/batch",flushIntervalMs:t?.tracking?.flushIntervalMs??1e3,maxBatchSize:t?.tracking?.maxBatchSize??20,maxBufferSize:t?.tracking?.maxBufferSize??1e3,maxRetries:t?.tracking?.maxRetries??3,retryBaseDelayMs:t?.tracking?.retryBaseDelayMs??200,retryMaxDelayMs:t?.tracking?.retryMaxDelayMs??2e3,shutdownTimeoutMs:t?.tracking?.shutdownTimeoutMs??2e3},o={apiUrl:r,apiKey:i,tracking:s},a=U(o),h=b(o);return{...a,kb:h,_config:o}}export{y as WaniWaniError,q as defineConfig,fe as waniwani};
|
|
1
|
+
var y=class extends Error{constructor(t,r){super(t);this.status=r;this.name="WaniWaniError"}};import{existsSync as L,readFileSync as V}from"fs";import{resolve as K}from"path";var N="waniwani.json",m;function k(){if(m!==void 0)return m;try{let e=K(process.cwd(),N);if(!L(e))return m=null,null;let n=V(e,"utf-8");return m=JSON.parse(n),m}catch{return m=null,null}}var R="__waniwani_config__";function q(e){return globalThis[R]=e,e}function x(){return globalThis[R]}var z="@waniwani/sdk";function b(e){let{apiUrl:n,apiKey:t}=e;function r(){if(!t)throw new Error("WANIWANI_API_KEY is not set");return t}async function i(s,o,a){let h=r(),g=`${n.replace(/\/$/,"")}${o}`,u={Authorization:`Bearer ${h}`,"X-WaniWani-SDK":z},c={method:s,headers:u};a!==void 0&&(u["Content-Type"]="application/json",c.body=JSON.stringify(a));let p=await fetch(g,c);if(!p.ok){let j=await p.text().catch(()=>"");throw new y(j||`KB API error: HTTP ${p.status}`,p.status)}return(await p.json()).data}return{async ingest(s){return i("POST","/api/mcp/kb/ingest",{files:s})},async search(s,o){return i("POST","/api/mcp/kb/search",{query:s,...o})},async sources(){return i("GET","/api/mcp/kb/sources")}}}function v(e,n){for(let t of n){let r=e[t];if(typeof r=="string"&&r.length>0)return r}}var Y=["waniwani/sessionId","openai/sessionId","openai/session","sessionId","conversationId","mcp-session-id"],$=["waniwani/requestId","openai/requestId","requestId","mcp/requestId"],H=["waniwani/traceId","openai/traceId","traceId","mcp/traceId","openai/requestId","requestId"],X=["waniwani/userId","openai/userId","externalUserId","userId","actorId"],G=["correlationId","openai/requestId"];function C(e){return e?v(e,Y):void 0}function _(e){return e?v(e,$):void 0}function P(e){return e?v(e,H):void 0}function M(e){return e?v(e,X):void 0}function A(e){return e?v(e,G):void 0}var J=[{key:"openai/sessionId",source:"chatgpt"},{key:"openai/session",source:"chatgpt"}],Z=[{needle:"claude",source:"claude"},{needle:"chatgpt",source:"chatgpt"},{needle:"openai",source:"chatgpt"},{needle:"gemini",source:"gemini"}];function D(e,n){if(e){let r=e["waniwani/source"];if(typeof r=="string"&&r.length>0)return r;for(let{key:i,source:s}of J){let o=e[i];if(typeof o=="string"&&o.length>0)return s}}let t=n?.name;if(typeof t=="string"&&t.length>0){let r=t.toLowerCase();for(let{needle:i,source:s}of Z)if(r.includes(i))return s}}var Q="@waniwani/sdk";function I(e,n={}){let t=n.now??(()=>new Date),r=n.generateId??B,i=ne(e),s=T(e.meta),o=T(e.metadata),a=re(e,s),h=d(e.eventId)??r(),g=ie(e.timestamp,t),u=d(e.source)??D(s)??n.source??Q,c=w(e)?{...e}:void 0,p={...o};return Object.keys(s).length>0&&(p.meta=s),c&&(p.rawLegacy=c),{id:h,type:"mcp.event",name:i,source:u,timestamp:g,correlation:a,properties:ee(e,i),metadata:p,rawLegacy:c}}function B(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?`evt_${crypto.randomUUID()}`:`evt_${Math.random().toString(36).slice(2,10)}_${Date.now().toString(36)}`}function ee(e,n){if(!w(e))return T(e.properties);let t=te(e,n),r=T(e.properties);return{...t,...r}}function te(e,n){switch(n){case"tool.called":{let t={};return d(e.toolName)&&(t.name=e.toolName),d(e.toolType)&&(t.type=e.toolType),t}case"quote.succeeded":{let t={};return typeof e.quoteAmount=="number"&&(t.amount=e.quoteAmount),d(e.quoteCurrency)&&(t.currency=e.quoteCurrency),t}case"link.clicked":{let t={};return d(e.linkUrl)&&(t.url=e.linkUrl),t}case"purchase.completed":{let t={};return typeof e.purchaseAmount=="number"&&(t.amount=e.purchaseAmount),d(e.purchaseCurrency)&&(t.currency=e.purchaseCurrency),t}default:return{}}}function ne(e){return w(e)?e.eventType:e.event}function re(e,n){let t=d(e.requestId)??_(n),r=d(e.sessionId)??C(n),i=d(e.traceId)??P(n),s=d(e.externalUserId)??M(n),o=d(e.correlationId)??A(n)??t,a={};return r&&(a.sessionId=r),i&&(a.traceId=i),t&&(a.requestId=t),o&&(a.correlationId=o),s&&(a.externalUserId=s),a}function ie(e,n){if(e instanceof Date)return e.toISOString();if(typeof e=="string"){let t=new Date(e);if(!Number.isNaN(t.getTime()))return t.toISOString()}return n().toISOString()}function T(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function d(e){if(typeof e=="string"&&e.trim().length!==0)return e}function w(e){return"eventType"in e}function F(e){return{priceShown:({amount:n,currency:t,itemId:r,label:i,...s})=>e({event:"price_shown",properties:{amount:n,currency:t,itemId:r,label:i},...s}),pricesCompared:({options:n,...t})=>e({event:"prices_compared",properties:{options:n},...t}),optionSelected:({id:n,amount:t,currency:r,...i})=>e({event:"option_selected",properties:{id:n,amount:t,currency:r},...i}),lead:n=>{let{source:t,...r}=n??{};return e({event:"lead",properties:{source:t},...r})},converted:({amount:n,currency:t,occurredAt:r,...i})=>e({event:"converted",properties:{amount:n,currency:t,occurredAt:r},...i})}}var se="/api/mcp/events/v2/batch";var W="@waniwani/sdk",oe=new Set([401,403]),ae=new Set([408,425,429,500,502,503,504]);function O(e){return new S(e)}var S=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(n){this.endpointUrl=de(n.apiUrl,n.endpointPath??se),this.flushIntervalMs=n.flushIntervalMs??1e3,this.maxBatchSize=n.maxBatchSize??20,this.maxBufferSize=n.maxBufferSize??1e3,this.maxRetries=n.maxRetries??3,this.retryBaseDelayMs=n.retryBaseDelayMs??200,this.retryMaxDelayMs=n.retryMaxDelayMs??2e3,this.shutdownTimeoutMs=n.shutdownTimeoutMs??2e3,this.fetchFn=n.fetchFn??fetch,this.logger=n.logger??console,this.now=n.now??(()=>new Date),this.sleep=n.sleep??(t=>new Promise(r=>setTimeout(r,t))),this.apiKey=n.apiKey,this.sdkVersion=n.sdkVersion,this.flushIntervalMs>0&&(this.flushTimer=setInterval(()=>{this.flush()},this.flushIntervalMs))}enqueue(n){if(this.isStopped||this.isShuttingDown){this.logger.warn("[Waniwani] Tracking transport is stopped, dropping event %s",n.id);return}if(this.buffer.length>=this.maxBufferSize){let t=this.buffer.length-this.maxBufferSize+1;this.buffer.splice(0,t),this.logger.warn("[Waniwani] Tracking buffer overflow, dropped %d oldest event(s)",t)}if(this.buffer.push(n),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(n){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 t=n?.timeoutMs??this.shutdownTimeoutMs,r=this.flush();if(!Number.isFinite(t)||t<=0)return await r,this.isStopped=!0,{timedOut:!1,pendingEvents:this.pendingEvents()};let i=Symbol("shutdown-timeout");return await Promise.race([r.then(()=>"flushed"),this.sleep(t).then(()=>i)])===i?(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 n=this.buffer.splice(0,this.maxBatchSize);await this.sendBatchWithRetry(n)}}async sendBatchWithRetry(n){let t=0,r=n;for(;r.length>0&&!this.isStopped;){this.inFlightCount=r.length;let i=await this.sendBatchOnce(r);switch(this.inFlightCount=0,i.kind){case"success":return;case"auth":this.stopTransportForAuthFailure(i.status,r.length);return;case"permanent":this.logger.error("[Waniwani] Dropping %d event(s) after permanent failure: %s",r.length,i.reason);return;case"retryable":if(t>=this.maxRetries){this.logger.error("[Waniwani] Dropping %d event(s) after retry exhaustion: %s",r.length,i.reason);return}await this.sleep(this.backoffDelayMs(t)),t+=1;continue;case"partial":if(i.permanent.length>0&&this.logger.error("[Waniwani] Dropping %d event(s) rejected as permanent",i.permanent.length),i.retryable.length===0)return;if(t>=this.maxRetries){this.logger.error("[Waniwani] Dropping %d retryable event(s) after retry exhaustion",i.retryable.length);return}r=i.retryable,await this.sleep(this.backoffDelayMs(t)),t+=1;continue}}}async sendBatchOnce(n){let t;try{t=await this.fetchFn(this.endpointUrl,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,"X-WaniWani-SDK":W},body:JSON.stringify(this.makeBatchRequest(n))})}catch(s){return{kind:"retryable",reason:pe(s)}}if(oe.has(t.status))return{kind:"auth",status:t.status};if(ae.has(t.status))return{kind:"retryable",reason:`HTTP ${t.status}`};if(!t.ok)return{kind:"permanent",reason:`HTTP ${t.status}`};let r=await ue(t);if(!r?.rejected||r.rejected.length===0)return{kind:"success"};let i=this.classifyRejectedEvents(n,r.rejected);return i.retryable.length===0&&i.permanent.length===0?{kind:"success"}:{kind:"partial",retryable:i.retryable,permanent:i.permanent}}makeBatchRequest(n){return{sentAt:this.now().toISOString(),source:{sdk:W,version:this.sdkVersion??"0.0.0"},events:n}}classifyRejectedEvents(n,t){let r=new Map(n.map(o=>[o.id,o])),i=[],s=[];for(let o of t){let a=r.get(o.eventId);if(a){if(ce(o)){i.push(a);continue}s.push(a)}}return{retryable:i,permanent:s}}backoffDelayMs(n){let t=this.retryBaseDelayMs*2**n;return Math.min(t,this.retryMaxDelayMs)}stopTransportForAuthFailure(n,t){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)",n,t+r)}};function ce(e){if(e.retryable===!0)return!0;let n=e.code.toLowerCase();return n.includes("timeout")||n.includes("temporary")||n.includes("unavailable")||n.includes("rate_limit")||n.includes("transient")||n.includes("server")}async function ue(e){let n=await e.text();if(n)try{return JSON.parse(n)}catch{return}}function de(e,n){let t=e.endsWith("/")?e:`${e}/`,r=n.startsWith("/")?n.slice(1):n;return`${t}${r}`}function pe(e){return e instanceof Error?e.message:String(e)}function U(e){let{apiUrl:n,apiKey:t,tracking:r}=e;function i(){if(!t)throw new Error("WANIWANI_API_KEY is not set");return t}let s=t?O({apiUrl:n,apiKey:t,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;function o(u){i();let c=I(u);return!c.correlation.sessionId&&!c.correlation.externalUserId&&console.warn(`[waniwani] event "${c.name}" has no sessionId or externalUserId; the ingest API requires one and will reject it.`),s?.enqueue(c),{eventId:c.id}}let a=async u=>o(u),h=Object.assign(a,F(a)),g={async identify(u,c,p){i();let E=I({event:"user.identified",externalUserId:u,properties:c,meta:p});return s?.enqueue(E),{eventId:E.id}},track:h,async flush(){i(),await s?.flush()},async shutdown(u){return i(),await s?.shutdown({timeoutMs:u?.timeoutMs??r.shutdownTimeoutMs})??{timedOut:!1,pendingEvents:0}}};return s&&le(g,r.shutdownTimeoutMs),g}function le(e,n){if(typeof process>"u"||typeof process.once!="function"||typeof process.on!="function")return;let t=()=>{e.shutdown({timeoutMs:n})};process.once("beforeExit",t),process.once("SIGINT",t),process.once("SIGTERM",t)}function fe(e){let t=e??k()??x(),r=t?.apiUrl??"https://app.waniwani.ai",i=t?.apiKey??process.env.WANIWANI_API_KEY,s={endpointPath:t?.tracking?.endpointPath??"/api/mcp/events/v2/batch",flushIntervalMs:t?.tracking?.flushIntervalMs??1e3,maxBatchSize:t?.tracking?.maxBatchSize??20,maxBufferSize:t?.tracking?.maxBufferSize??1e3,maxRetries:t?.tracking?.maxRetries??3,retryBaseDelayMs:t?.tracking?.retryBaseDelayMs??200,retryMaxDelayMs:t?.tracking?.retryMaxDelayMs??2e3,shutdownTimeoutMs:t?.tracking?.shutdownTimeoutMs??2e3},o={apiUrl:r,apiKey:i,tracking:s},a=U(o),h=b(o);return{...a,kb:h,_config:o}}export{y as WaniWaniError,q as defineConfig,fe as waniwani};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/error.ts","../src/project-config.ts","../src/kb/client.ts","../src/mcp/server/utils.ts","../src/tracking/mapper.ts","../src/tracking/revenue.ts","../src/tracking/transport.ts","../src/tracking/index.ts","../src/waniwani.ts"],"sourcesContent":["// 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","import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\n/**\n * Project-level configuration for Waniwani MCP projects.\n *\n * Mirrors the JSON Schema hosted at https://docs.waniwani.ai/waniwani.json.\n * The canonical config file is `waniwani.json` at the project root:\n *\n * ```json\n * {\n * \"$schema\": \"https://docs.waniwani.ai/waniwani.json\",\n * \"orgId\": \"...\",\n * \"projectId\": \"...\"\n * }\n * ```\n *\n * `waniwani()` and the CLI both read this file automatically — no\n * explicit import is required.\n */\nexport interface WaniWaniProjectConfig {\n\t/** URL of the JSON Schema for editor autocomplete. Ignored at runtime. */\n\t$schema?: string;\n\t/** Waniwani organization ID this project belongs to. */\n\torgId?: string;\n\t/** Waniwani MCP project ID. */\n\tprojectId?: string;\n\t/**\n\t * The base URL of the Waniwani API.\n\t * Defaults to `https://app.waniwani.ai`.\n\t */\n\tapiUrl?: string;\n\t/**\n\t * Local port the MCP listens on during `waniwani dev`. Overridden by\n\t * `--port`. Defaults to 3000.\n\t */\n\tdevPort?: number;\n}\n\n// ---------------------------------------------------------------------------\n// waniwani.json loader\n// ---------------------------------------------------------------------------\n\nconst CONFIG_FILENAME = \"waniwani.json\";\n\nlet _cached: WaniWaniProjectConfig | null | undefined;\n\n/**\n * Load `waniwani.json` from the current working directory.\n *\n * Returns `null` if the file doesn't exist or the runtime doesn't support\n * synchronous filesystem reads (e.g. edge / worker runtimes). Cached after\n * the first call.\n *\n * @internal\n */\nexport function loadProjectConfig(): WaniWaniProjectConfig | null {\n\tif (_cached !== undefined) {\n\t\treturn _cached;\n\t}\n\n\ttry {\n\t\tconst filePath = resolve(process.cwd(), CONFIG_FILENAME);\n\t\tif (!existsSync(filePath)) {\n\t\t\t_cached = null;\n\t\t\treturn null;\n\t\t}\n\t\tconst raw = readFileSync(filePath, \"utf-8\");\n\t\t_cached = JSON.parse(raw) as WaniWaniProjectConfig;\n\t\treturn _cached;\n\t} catch {\n\t\t_cached = null;\n\t\treturn null;\n\t}\n}\n\n/**\n * Reset the cached config. Test-only.\n *\n * @internal\n */\nexport function resetProjectConfigCache(): void {\n\t_cached = undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Legacy: defineConfig + globalThis registration\n// ---------------------------------------------------------------------------\n\nconst GLOBAL_KEY = \"__waniwani_config__\" as const;\n\n/**\n * Register a Waniwani project configuration on `globalThis`.\n *\n * @deprecated Create a `waniwani.json` at the project root instead. The SDK\n * and CLI both auto-load that file — no `defineConfig` call required.\n * See https://docs.waniwani.ai/waniwani.json for the schema.\n */\nexport function defineConfig(\n\tconfig: WaniWaniProjectConfig,\n): WaniWaniProjectConfig {\n\t(globalThis as Record<string, unknown>)[GLOBAL_KEY] = config;\n\treturn config;\n}\n\n/**\n * Retrieve the globally registered config (set by `defineConfig`).\n *\n * @deprecated Use `loadProjectConfig()` instead. `defineConfig` is going away.\n * @internal\n */\nexport function getGlobalConfig(): WaniWaniProjectConfig | undefined {\n\treturn (globalThis as Record<string, unknown>)[GLOBAL_KEY] as\n\t\t| WaniWaniProjectConfig\n\t\t| undefined;\n}\n","// KB Client — thin HTTP wrapper for knowledge base API\n\nimport { WaniWaniError } from \"../error.js\";\nimport type { InternalConfig } from \"../types.js\";\nimport type {\n\tKbClient,\n\tKbIngestFile,\n\tKbIngestResult,\n\tKbSearchOptions,\n\tKbSource,\n\tSearchResult,\n} from \"./types.js\";\n\nconst SDK_NAME = \"@waniwani/sdk\";\n\nexport function createKbClient(config: InternalConfig): KbClient {\n\tconst { apiUrl, apiKey } = config;\n\n\tfunction requireApiKey(): string {\n\t\tif (!apiKey) {\n\t\t\tthrow new Error(\"WANIWANI_API_KEY is not set\");\n\t\t}\n\t\treturn apiKey;\n\t}\n\n\tasync function request<T>(\n\t\tmethod: \"GET\" | \"POST\",\n\t\tpath: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tconst key = requireApiKey();\n\t\tconst url = `${apiUrl.replace(/\\/$/, \"\")}${path}`;\n\n\t\tconst headers: Record<string, string> = {\n\t\t\tAuthorization: `Bearer ${key}`,\n\t\t\t\"X-WaniWani-SDK\": SDK_NAME,\n\t\t};\n\n\t\tconst init: RequestInit = { method, headers };\n\n\t\tif (body !== undefined) {\n\t\t\theaders[\"Content-Type\"] = \"application/json\";\n\t\t\tinit.body = JSON.stringify(body);\n\t\t}\n\n\t\tconst response = await fetch(url, init);\n\n\t\tif (!response.ok) {\n\t\t\tconst text = await response.text().catch(() => \"\");\n\t\t\tthrow new WaniWaniError(\n\t\t\t\ttext || `KB API error: HTTP ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t);\n\t\t}\n\n\t\tconst json = (await response.json()) as { data: T };\n\t\treturn json.data;\n\t}\n\n\treturn {\n\t\tasync ingest(files: KbIngestFile[]): Promise<KbIngestResult> {\n\t\t\treturn request<KbIngestResult>(\"POST\", \"/api/mcp/kb/ingest\", {\n\t\t\t\tfiles,\n\t\t\t});\n\t\t},\n\n\t\tasync search(\n\t\t\tquery: string,\n\t\t\toptions?: KbSearchOptions,\n\t\t): Promise<SearchResult[]> {\n\t\t\treturn request<SearchResult[]>(\"POST\", \"/api/mcp/kb/search\", {\n\t\t\t\tquery,\n\t\t\t\t...options,\n\t\t\t});\n\t\t},\n\n\t\tasync sources(): Promise<KbSource[]> {\n\t\t\treturn request<KbSource[]>(\"GET\", \"/api/mcp/kb/sources\");\n\t\t},\n\t};\n}\n","// ============================================================================\n// Meta key extraction helpers\n// ============================================================================\n\n/** Pick the first non-empty string value from `meta` matching the given keys. */\nfunction pickFirst(\n\tmeta: Record<string, unknown>,\n\tkeys: readonly string[],\n): string | undefined {\n\tfor (const key of keys) {\n\t\tconst value = meta[key];\n\t\tif (typeof value === \"string\" && value.length > 0) {\n\t\t\treturn value;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n// --- Key lists (ordered by priority) ---\n\nconst SESSION_ID_KEYS = [\n\t\"waniwani/sessionId\",\n\t\"openai/sessionId\",\n\t\"openai/session\",\n\t\"sessionId\",\n\t\"conversationId\",\n\t\"mcp-session-id\",\n] as const;\n\nconst REQUEST_ID_KEYS = [\n\t\"waniwani/requestId\",\n\t\"openai/requestId\",\n\t\"requestId\",\n\t\"mcp/requestId\",\n] as const;\n\nconst TRACE_ID_KEYS = [\n\t\"waniwani/traceId\",\n\t\"openai/traceId\",\n\t\"traceId\",\n\t\"mcp/traceId\",\n\t\"openai/requestId\",\n\t\"requestId\",\n] as const;\n\nconst EXTERNAL_USER_ID_KEYS = [\n\t\"waniwani/userId\",\n\t\"openai/userId\",\n\t\"externalUserId\",\n\t\"userId\",\n\t\"actorId\",\n] as const;\n\nconst CORRELATION_ID_KEYS = [\"correlationId\", \"openai/requestId\"] as const;\n\nconst TURN_COUNT_KEYS = [\"waniwani/turnCount\"] as const;\n\n/** Meta key for flow execution path (nodesVisited, flowId). */\nexport const FLOW_META_KEY = \"waniwani/flow\" as const;\n\n// --- Extractors ---\n\nexport function extractSessionId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, SESSION_ID_KEYS) : undefined;\n}\n\nexport function extractRequestId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, REQUEST_ID_KEYS) : undefined;\n}\n\nexport function extractTraceId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, TRACE_ID_KEYS) : undefined;\n}\n\nexport function extractExternalUserId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, EXTERNAL_USER_ID_KEYS) : undefined;\n}\n\nexport function extractCorrelationId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, CORRELATION_ID_KEYS) : undefined;\n}\n\n/**\n * Number of user messages in the current chat session, as counted by the\n * Waniwani app before dispatching the MCP request. Useful for MCPs that\n * gate behavior on conversation length (e.g. compulsory email verification\n * after N turns) without each MCP having to track turn state itself.\n *\n * Forwarded as `waniwani/turnCount` (non-negative integer).\n */\nexport function extractTurnCount(\n\tmeta: Record<string, unknown> | undefined,\n): number | undefined {\n\tif (!meta) {\n\t\treturn undefined;\n\t}\n\tfor (const key of TURN_COUNT_KEYS) {\n\t\tconst value = meta[key];\n\t\tif (typeof value === \"number\" && Number.isFinite(value) && value >= 0) {\n\t\t\treturn value;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nconst SOURCE_SESSION_KEYS = [\n\t{ key: \"openai/sessionId\", source: \"chatgpt\" },\n\t{ key: \"openai/session\", source: \"chatgpt\" },\n] as const;\n\n/**\n * Client identifiers advertised via MCP `initialize` → `clientInfo.name` that\n * the SDK maps to a known source. Match is case-insensitive substring on the\n * advertised name, so this catches \"Claude\", \"Claude Code\", \"claude-ai\", etc.\n * without having to enumerate every surface.\n */\nconst CLIENT_INFO_NAME_SOURCES: ReadonlyArray<{\n\tneedle: string;\n\tsource: string;\n}> = [{ needle: \"claude\", source: \"claude\" }];\n\nexport type ExtractSourceClientInfo = {\n\tname?: string;\n\tversion?: string;\n};\n\nexport function extractSource(\n\tmeta: Record<string, unknown> | undefined,\n\tclientInfo?: ExtractSourceClientInfo,\n): string | undefined {\n\tif (meta) {\n\t\tconst explicit = meta[\"waniwani/source\"];\n\t\tif (typeof explicit === \"string\" && explicit.length > 0) {\n\t\t\treturn explicit;\n\t\t}\n\t\tfor (const { key, source } of SOURCE_SESSION_KEYS) {\n\t\t\tconst value = meta[key];\n\t\t\tif (typeof value === \"string\" && value.length > 0) {\n\t\t\t\treturn source;\n\t\t\t}\n\t\t}\n\t}\n\t// Fall back to MCP `initialize` clientInfo.name when no _meta key matches.\n\t// Claude surfaces (Code, Desktop, claude.ai connector, Anthropic API MCP\n\t// connector) don't expose a namespaced session id in _meta but do advertise\n\t// themselves in the handshake. Per-MCP-session clientInfo is captured by the\n\t// MCP transport and exposed via server.getClientVersion().\n\tconst name = clientInfo?.name;\n\tif (typeof name === \"string\" && name.length > 0) {\n\t\tconst lower = name.toLowerCase();\n\t\tfor (const { needle, source } of CLIENT_INFO_NAME_SOURCES) {\n\t\t\tif (lower.includes(needle)) {\n\t\t\t\treturn source;\n\t\t\t}\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * HTTP request headers that identify a known caller when neither `_meta` nor\n * `clientInfo` carries a source. This is the most robust Claude signal: every\n * Claude surface sends `User-Agent: Claude-User` and `x-anthropic-client` on\n * MCP HTTP requests, whereas the MCP transport carries no session id and the\n * `initialize` `clientInfo` may not be surfaced on stateless deployments.\n */\nfunction headerValue(\n\theaders: Record<string, unknown>,\n\tkey: string,\n): string | undefined {\n\t// HTTP header names are case-insensitive; the transport may preserve the\n\t// original casing, so match the key case-insensitively rather than assuming\n\t// it was lowercased.\n\tconst lowerKey = key.toLowerCase();\n\tlet raw = headers[key] ?? headers[lowerKey];\n\tif (raw === undefined) {\n\t\tfor (const headerKey of Object.keys(headers)) {\n\t\t\tif (headerKey.toLowerCase() === lowerKey) {\n\t\t\t\traw = headers[headerKey];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\tconst value = Array.isArray(raw) ? raw[0] : raw;\n\treturn typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nexport function extractSourceFromHeaders(\n\theaders: Record<string, unknown> | undefined,\n): string | undefined {\n\tif (!headers) {\n\t\treturn undefined;\n\t}\n\tconst userAgent = headerValue(headers, \"user-agent\");\n\tconst anthropicClient = headerValue(headers, \"x-anthropic-client\");\n\tif (\n\t\t(userAgent && /claude/i.test(userAgent)) ||\n\t\t(anthropicClient && /claude|anthropic/i.test(anthropicClient))\n\t) {\n\t\treturn \"claude\";\n\t}\n\treturn undefined;\n}\n","import {\n\textractCorrelationId,\n\textractExternalUserId,\n\textractRequestId,\n\textractSessionId,\n\textractSource,\n\textractTraceId,\n} from \"../mcp/server/utils.js\";\nimport type { EventType, LegacyTrackEvent, TrackInput } from \"./@types.js\";\nimport type { V2CorrelationIds, V2EventEnvelope } from \"./v2-types.js\";\n\nconst DEFAULT_SOURCE = \"@waniwani/sdk\";\n\nexport interface MapTrackEventOptions {\n\tnow?: () => Date;\n\tgenerateId?: () => string;\n\tsource?: string;\n}\n\nexport function mapTrackEventToV2(\n\tinput: TrackInput,\n\toptions: MapTrackEventOptions = {},\n): V2EventEnvelope {\n\tconst now = options.now ?? (() => new Date());\n\tconst generateId = options.generateId ?? createEventId;\n\tconst eventName = resolveEventName(input);\n\tconst meta = toRecord(input.meta);\n\tconst metadata = toRecord(input.metadata);\n\tconst correlation = resolveCorrelationIds(input, meta);\n\tconst eventId = takeNonEmptyString(input.eventId) ?? generateId();\n\tconst timestamp = normalizeTimestamp(input.timestamp, now);\n\tconst source =\n\t\ttakeNonEmptyString(input.source) ??\n\t\textractSource(meta) ??\n\t\toptions.source ??\n\t\tDEFAULT_SOURCE;\n\tconst rawLegacy = isLegacyTrackEvent(input) ? { ...input } : undefined;\n\n\tconst mappedMetadata: Record<string, unknown> = {\n\t\t...metadata,\n\t};\n\tif (Object.keys(meta).length > 0) {\n\t\tmappedMetadata.meta = meta;\n\t}\n\tif (rawLegacy) {\n\t\tmappedMetadata.rawLegacy = rawLegacy;\n\t}\n\n\treturn {\n\t\tid: eventId,\n\t\ttype: \"mcp.event\",\n\t\tname: eventName,\n\t\tsource,\n\t\ttimestamp,\n\t\tcorrelation,\n\t\tproperties: mapProperties(input, eventName),\n\t\tmetadata: mappedMetadata,\n\t\trawLegacy,\n\t};\n}\n\nexport function createEventId(): string {\n\tif (\n\t\ttypeof crypto !== \"undefined\" &&\n\t\ttypeof crypto.randomUUID === \"function\"\n\t) {\n\t\treturn `evt_${crypto.randomUUID()}`;\n\t}\n\n\treturn `evt_${Math.random().toString(36).slice(2, 10)}_${Date.now().toString(36)}`;\n}\n\nfunction mapProperties(\n\tinput: TrackInput,\n\teventName: EventType,\n): Record<string, unknown> {\n\tif (!isLegacyTrackEvent(input)) {\n\t\treturn toRecord(input.properties);\n\t}\n\n\tconst legacyProperties = mapLegacyProperties(input, eventName);\n\tconst explicitProperties = toRecord(input.properties);\n\treturn {\n\t\t...legacyProperties,\n\t\t...explicitProperties,\n\t};\n}\n\nfunction mapLegacyProperties(\n\tinput: LegacyTrackEvent,\n\teventName: EventType,\n): Record<string, unknown> {\n\tswitch (eventName) {\n\t\tcase \"tool.called\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (takeNonEmptyString(input.toolName)) {\n\t\t\t\tproperties.name = input.toolName;\n\t\t\t}\n\t\t\tif (takeNonEmptyString(input.toolType)) {\n\t\t\t\tproperties.type = input.toolType;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tcase \"quote.succeeded\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (typeof input.quoteAmount === \"number\") {\n\t\t\t\tproperties.amount = input.quoteAmount;\n\t\t\t}\n\t\t\tif (takeNonEmptyString(input.quoteCurrency)) {\n\t\t\t\tproperties.currency = input.quoteCurrency;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tcase \"link.clicked\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (takeNonEmptyString(input.linkUrl)) {\n\t\t\t\tproperties.url = input.linkUrl;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tcase \"purchase.completed\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (typeof input.purchaseAmount === \"number\") {\n\t\t\t\tproperties.amount = input.purchaseAmount;\n\t\t\t}\n\t\t\tif (takeNonEmptyString(input.purchaseCurrency)) {\n\t\t\t\tproperties.currency = input.purchaseCurrency;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tdefault:\n\t\t\treturn {};\n\t}\n}\n\nfunction resolveEventName(input: TrackInput): EventType {\n\tif (isLegacyTrackEvent(input)) {\n\t\treturn input.eventType;\n\t}\n\treturn input.event;\n}\n\nfunction resolveCorrelationIds(\n\tinput: TrackInput,\n\tmeta: Record<string, unknown>,\n): V2CorrelationIds {\n\tconst requestId =\n\t\ttakeNonEmptyString(input.requestId) ?? extractRequestId(meta);\n\n\tconst sessionId =\n\t\ttakeNonEmptyString(input.sessionId) ?? extractSessionId(meta);\n\n\tconst traceId = takeNonEmptyString(input.traceId) ?? extractTraceId(meta);\n\n\tconst externalUserId =\n\t\ttakeNonEmptyString(input.externalUserId) ?? extractExternalUserId(meta);\n\n\tconst correlationId =\n\t\ttakeNonEmptyString(input.correlationId) ??\n\t\textractCorrelationId(meta) ??\n\t\trequestId;\n\n\tconst correlation: V2CorrelationIds = {};\n\tif (sessionId) {\n\t\tcorrelation.sessionId = sessionId;\n\t}\n\tif (traceId) {\n\t\tcorrelation.traceId = traceId;\n\t}\n\tif (requestId) {\n\t\tcorrelation.requestId = requestId;\n\t}\n\tif (correlationId) {\n\t\tcorrelation.correlationId = correlationId;\n\t}\n\tif (externalUserId) {\n\t\tcorrelation.externalUserId = externalUserId;\n\t}\n\treturn correlation;\n}\n\nfunction normalizeTimestamp(\n\tinput: string | Date | undefined,\n\tnow: () => Date,\n): string {\n\tif (input instanceof Date) {\n\t\treturn input.toISOString();\n\t}\n\tif (typeof input === \"string\") {\n\t\tconst date = new Date(input);\n\t\tif (!Number.isNaN(date.getTime())) {\n\t\t\treturn date.toISOString();\n\t\t}\n\t}\n\treturn now().toISOString();\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\treturn {};\n\t}\n\treturn value as Record<string, unknown>;\n}\n\nfunction takeNonEmptyString(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") {\n\t\treturn undefined;\n\t}\n\tif (value.trim().length === 0) {\n\t\treturn undefined;\n\t}\n\treturn value;\n}\n\nfunction isLegacyTrackEvent(input: TrackInput): input is LegacyTrackEvent {\n\treturn \"eventType\" in input;\n}\n","import type { RevenueTrackingApi, TrackInput } from \"./@types.js\";\n\n/** Enqueue one event and resolve to its id. */\ntype Emit = (event: TrackInput) => Promise<{ eventId: string }>;\n\n/**\n * Build the flat `track.*` revenue helpers over an `emit` function. Shared\n * by the top-level client and the request-scoped client (which injects meta\n * into `emit`) so both expose the same revenue API (WAN-386).\n *\n * Each helper maps its flat input to a typed first-class revenue event; the\n * remaining tracking context (sessionId / externalUserId / meta) is forwarded\n * as-is. Identity (one of those, or meta-derived) is required by the ingest\n * API — see the warning emitted from the client's `emit`.\n */\nexport function createRevenueApi(emit: Emit): RevenueTrackingApi {\n\treturn {\n\t\tpriceShown: ({ amount, currency, itemId, label, ...context }) =>\n\t\t\temit({\n\t\t\t\tevent: \"price_shown\",\n\t\t\t\tproperties: { amount, currency, itemId, label },\n\t\t\t\t...context,\n\t\t\t}),\n\t\tpricesCompared: ({ options, ...context }) =>\n\t\t\temit({ event: \"prices_compared\", properties: { options }, ...context }),\n\t\toptionSelected: ({ id, amount, currency, ...context }) =>\n\t\t\temit({\n\t\t\t\tevent: \"option_selected\",\n\t\t\t\tproperties: { id, amount, currency },\n\t\t\t\t...context,\n\t\t\t}),\n\t\tlead: (input) => {\n\t\t\tconst { source, ...context } = input ?? {};\n\t\t\treturn emit({ event: \"lead\", properties: { source }, ...context });\n\t\t},\n\t\tconverted: ({ amount, currency, occurredAt, ...context }) =>\n\t\t\temit({\n\t\t\t\tevent: \"converted\",\n\t\t\t\tproperties: { amount, currency, occurredAt },\n\t\t\t\t...context,\n\t\t\t}),\n\t};\n}\n","import type {\n\tTrackingShutdownOptions,\n\tTrackingShutdownResult,\n} from \"./@types.js\";\nimport type {\n\tV2BatchRejectedEvent,\n\tV2BatchRequest,\n\tV2BatchResponse,\n\tV2EventEnvelope,\n} from \"./v2-types.js\";\n\ntype FetchFn = (\n\tinput: URL | RequestInfo,\n\tinit?: RequestInit,\n) => Promise<Response>;\n\nconst DEFAULT_ENDPOINT_PATH = \"/api/mcp/events/v2/batch\";\nconst DEFAULT_FLUSH_INTERVAL_MS = 1_000;\nconst DEFAULT_MAX_BATCH_SIZE = 20;\nconst DEFAULT_MAX_BUFFER_SIZE = 1_000;\nconst DEFAULT_MAX_RETRIES = 3;\nconst DEFAULT_RETRY_BASE_DELAY_MS = 200;\nconst DEFAULT_RETRY_MAX_DELAY_MS = 2_000;\nconst DEFAULT_SHUTDOWN_TIMEOUT_MS = 2_000;\nconst SDK_NAME = \"@waniwani/sdk\";\n\nconst AUTH_FAILURE_STATUS = new Set([401, 403]);\nconst RETRYABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504]);\n\ninterface Logger {\n\twarn: (message: string, ...args: unknown[]) => void;\n\terror: (message: string, ...args: unknown[]) => void;\n}\n\nexport interface V2TransportOptions {\n\tapiUrl: string;\n\tapiKey: string;\n\tendpointPath?: string;\n\tflushIntervalMs?: number;\n\tmaxBatchSize?: number;\n\tmaxBufferSize?: number;\n\tmaxRetries?: number;\n\tretryBaseDelayMs?: number;\n\tretryMaxDelayMs?: number;\n\tshutdownTimeoutMs?: number;\n\tsdkVersion?: string;\n\tfetchFn?: FetchFn;\n\tlogger?: Logger;\n\tnow?: () => Date;\n\tsleep?: (delayMs: number) => Promise<void>;\n}\n\nexport interface V2BatchTransport {\n\tenqueue: (event: V2EventEnvelope) => void;\n\tflush: () => Promise<void>;\n\tshutdown: (\n\t\toptions?: TrackingShutdownOptions,\n\t) => Promise<TrackingShutdownResult>;\n\tpendingEvents: () => number;\n}\n\ntype SendBatchResult =\n\t| { kind: \"success\" }\n\t| { kind: \"retryable\"; reason: string }\n\t| { kind: \"permanent\"; reason: string }\n\t| { kind: \"auth\"; status: number }\n\t| {\n\t\t\tkind: \"partial\";\n\t\t\tretryable: V2EventEnvelope[];\n\t\t\tpermanent: V2EventEnvelope[];\n\t };\n\nexport function createV2BatchTransport(\n\toptions: V2TransportOptions,\n): V2BatchTransport {\n\treturn new BatchingV2Transport(options);\n}\n\nclass BatchingV2Transport implements V2BatchTransport {\n\tprivate readonly endpointUrl: string;\n\tprivate readonly flushIntervalMs: number;\n\tprivate readonly maxBatchSize: number;\n\tprivate readonly maxBufferSize: number;\n\tprivate readonly maxRetries: number;\n\tprivate readonly retryBaseDelayMs: number;\n\tprivate readonly retryMaxDelayMs: number;\n\tprivate readonly shutdownTimeoutMs: number;\n\tprivate readonly sdkVersion?: string;\n\tprivate readonly fetchFn: FetchFn;\n\tprivate readonly logger: Logger;\n\tprivate readonly now: () => Date;\n\tprivate readonly sleep: (delayMs: number) => Promise<void>;\n\tprivate readonly apiKey: string;\n\n\tprivate readonly buffer: V2EventEnvelope[] = [];\n\tprivate flushTimer: ReturnType<typeof setInterval> | undefined;\n\tprivate flushScheduled = false;\n\tprivate flushScheduledTimer: ReturnType<typeof setTimeout> | undefined;\n\tprivate flushInFlight: Promise<void> | undefined;\n\tprivate inFlightCount = 0;\n\tprivate isStopped = false;\n\tprivate isShuttingDown = false;\n\n\tconstructor(options: V2TransportOptions) {\n\t\tthis.endpointUrl = joinUrl(\n\t\t\toptions.apiUrl,\n\t\t\toptions.endpointPath ?? DEFAULT_ENDPOINT_PATH,\n\t\t);\n\t\tthis.flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;\n\t\tthis.maxBatchSize = options.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;\n\t\tthis.maxBufferSize = options.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE;\n\t\tthis.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;\n\t\tthis.retryBaseDelayMs =\n\t\t\toptions.retryBaseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS;\n\t\tthis.retryMaxDelayMs =\n\t\t\toptions.retryMaxDelayMs ?? DEFAULT_RETRY_MAX_DELAY_MS;\n\t\tthis.shutdownTimeoutMs =\n\t\t\toptions.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;\n\t\tthis.fetchFn = options.fetchFn ?? fetch;\n\t\tthis.logger = options.logger ?? console;\n\t\tthis.now = options.now ?? (() => new Date());\n\t\tthis.sleep =\n\t\t\toptions.sleep ??\n\t\t\t((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)));\n\t\tthis.apiKey = options.apiKey;\n\t\tthis.sdkVersion = options.sdkVersion;\n\n\t\tif (this.flushIntervalMs > 0) {\n\t\t\tthis.flushTimer = setInterval(() => {\n\t\t\t\tvoid this.flush();\n\t\t\t}, this.flushIntervalMs);\n\t\t}\n\t}\n\n\tenqueue(event: V2EventEnvelope): void {\n\t\tif (this.isStopped || this.isShuttingDown) {\n\t\t\tthis.logger.warn(\n\t\t\t\t\"[Waniwani] Tracking transport is stopped, dropping event %s\",\n\t\t\t\tevent.id,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.buffer.length >= this.maxBufferSize) {\n\t\t\tconst dropCount = this.buffer.length - this.maxBufferSize + 1;\n\t\t\tthis.buffer.splice(0, dropCount);\n\t\t\tthis.logger.warn(\n\t\t\t\t\"[Waniwani] Tracking buffer overflow, dropped %d oldest event(s)\",\n\t\t\t\tdropCount,\n\t\t\t);\n\t\t}\n\n\t\tthis.buffer.push(event);\n\n\t\tif (this.buffer.length >= this.maxBatchSize) {\n\t\t\tvoid this.flush();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.scheduleMicroFlush();\n\t}\n\n\tpendingEvents(): number {\n\t\treturn this.buffer.length + this.inFlightCount;\n\t}\n\n\tasync flush(): Promise<void> {\n\t\tif (this.flushInFlight) {\n\t\t\treturn this.flushInFlight;\n\t\t}\n\t\tthis.flushInFlight = this.flushLoop().finally(() => {\n\t\t\tthis.flushInFlight = undefined;\n\t\t});\n\t\treturn this.flushInFlight;\n\t}\n\n\tasync shutdown(\n\t\toptions?: TrackingShutdownOptions,\n\t): Promise<TrackingShutdownResult> {\n\t\tthis.isShuttingDown = true;\n\t\tif (this.flushTimer) {\n\t\t\tclearInterval(this.flushTimer);\n\t\t\tthis.flushTimer = undefined;\n\t\t}\n\t\tif (this.flushScheduledTimer) {\n\t\t\tclearTimeout(this.flushScheduledTimer);\n\t\t\tthis.flushScheduledTimer = undefined;\n\t\t\tthis.flushScheduled = false;\n\t\t}\n\n\t\tconst timeoutMs = options?.timeoutMs ?? this.shutdownTimeoutMs;\n\t\tconst flushPromise = this.flush();\n\n\t\tif (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n\t\t\tawait flushPromise;\n\t\t\tthis.isStopped = true;\n\t\t\treturn { timedOut: false, pendingEvents: this.pendingEvents() };\n\t\t}\n\n\t\tconst timeoutSignal = Symbol(\"shutdown-timeout\");\n\t\tconst result = await Promise.race([\n\t\t\tflushPromise.then(() => \"flushed\" as const),\n\t\t\tthis.sleep(timeoutMs).then(() => timeoutSignal),\n\t\t]);\n\n\t\tif (result === timeoutSignal) {\n\t\t\tthis.isStopped = true;\n\t\t\treturn { timedOut: true, pendingEvents: this.pendingEvents() };\n\t\t}\n\n\t\tthis.isStopped = true;\n\t\treturn { timedOut: false, pendingEvents: this.pendingEvents() };\n\t}\n\n\tprivate scheduleMicroFlush(): void {\n\t\tif (this.flushScheduled) {\n\t\t\treturn;\n\t\t}\n\t\tthis.flushScheduled = true;\n\t\tthis.flushScheduledTimer = setTimeout(() => {\n\t\t\tthis.flushScheduledTimer = undefined;\n\t\t\tthis.flushScheduled = false;\n\t\t\tvoid this.flush();\n\t\t}, 0);\n\t}\n\n\tprivate async flushLoop(): Promise<void> {\n\t\twhile (this.buffer.length > 0 && !this.isStopped) {\n\t\t\tconst batch = this.buffer.splice(0, this.maxBatchSize);\n\t\t\tawait this.sendBatchWithRetry(batch);\n\t\t}\n\t}\n\n\tprivate async sendBatchWithRetry(batch: V2EventEnvelope[]): Promise<void> {\n\t\tlet attempt = 0;\n\t\tlet pendingBatch = batch;\n\n\t\twhile (pendingBatch.length > 0 && !this.isStopped) {\n\t\t\tthis.inFlightCount = pendingBatch.length;\n\t\t\tconst result = await this.sendBatchOnce(pendingBatch);\n\t\t\tthis.inFlightCount = 0;\n\n\t\t\tswitch (result.kind) {\n\t\t\t\tcase \"success\":\n\t\t\t\t\treturn;\n\t\t\t\tcase \"auth\":\n\t\t\t\t\tthis.stopTransportForAuthFailure(result.status, pendingBatch.length);\n\t\t\t\t\treturn;\n\t\t\t\tcase \"permanent\":\n\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\"[Waniwani] Dropping %d event(s) after permanent failure: %s\",\n\t\t\t\t\t\tpendingBatch.length,\n\t\t\t\t\t\tresult.reason,\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\tcase \"retryable\":\n\t\t\t\t\tif (attempt >= this.maxRetries) {\n\t\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\t\"[Waniwani] Dropping %d event(s) after retry exhaustion: %s\",\n\t\t\t\t\t\t\tpendingBatch.length,\n\t\t\t\t\t\t\tresult.reason,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tawait this.sleep(this.backoffDelayMs(attempt));\n\t\t\t\t\tattempt += 1;\n\t\t\t\t\tcontinue;\n\t\t\t\tcase \"partial\":\n\t\t\t\t\tif (result.permanent.length > 0) {\n\t\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\t\"[Waniwani] Dropping %d event(s) rejected as permanent\",\n\t\t\t\t\t\t\tresult.permanent.length,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (result.retryable.length === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (attempt >= this.maxRetries) {\n\t\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\t\"[Waniwani] Dropping %d retryable event(s) after retry exhaustion\",\n\t\t\t\t\t\t\tresult.retryable.length,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tpendingBatch = result.retryable;\n\t\t\t\t\tawait this.sleep(this.backoffDelayMs(attempt));\n\t\t\t\t\tattempt += 1;\n\t\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async sendBatchOnce(\n\t\tevents: V2EventEnvelope[],\n\t): Promise<SendBatchResult> {\n\t\tlet response: Response;\n\n\t\ttry {\n\t\t\tresponse = await this.fetchFn(this.endpointUrl, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\tAuthorization: `Bearer ${this.apiKey}`,\n\t\t\t\t\t\"X-WaniWani-SDK\": SDK_NAME,\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify(this.makeBatchRequest(events)),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tkind: \"retryable\",\n\t\t\t\treason: getErrorMessage(error),\n\t\t\t};\n\t\t}\n\n\t\tif (AUTH_FAILURE_STATUS.has(response.status)) {\n\t\t\treturn { kind: \"auth\", status: response.status };\n\t\t}\n\n\t\tif (RETRYABLE_STATUS.has(response.status)) {\n\t\t\treturn {\n\t\t\t\tkind: \"retryable\",\n\t\t\t\treason: `HTTP ${response.status}`,\n\t\t\t};\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\treturn {\n\t\t\t\tkind: \"permanent\",\n\t\t\t\treason: `HTTP ${response.status}`,\n\t\t\t};\n\t\t}\n\n\t\tconst data = await parseJsonResponse<V2BatchResponse>(response);\n\t\tif (!data?.rejected || data.rejected.length === 0) {\n\t\t\treturn { kind: \"success\" };\n\t\t}\n\n\t\tconst partial = this.classifyRejectedEvents(events, data.rejected);\n\t\tif (partial.retryable.length === 0 && partial.permanent.length === 0) {\n\t\t\treturn { kind: \"success\" };\n\t\t}\n\n\t\treturn {\n\t\t\tkind: \"partial\",\n\t\t\tretryable: partial.retryable,\n\t\t\tpermanent: partial.permanent,\n\t\t};\n\t}\n\n\tprivate makeBatchRequest(events: V2EventEnvelope[]): V2BatchRequest {\n\t\treturn {\n\t\t\tsentAt: this.now().toISOString(),\n\t\t\tsource: {\n\t\t\t\tsdk: SDK_NAME,\n\t\t\t\tversion: this.sdkVersion ?? \"0.0.0\",\n\t\t\t},\n\t\t\tevents,\n\t\t};\n\t}\n\n\tprivate classifyRejectedEvents(\n\t\tevents: V2EventEnvelope[],\n\t\trejected: V2BatchRejectedEvent[],\n\t): {\n\t\tretryable: V2EventEnvelope[];\n\t\tpermanent: V2EventEnvelope[];\n\t} {\n\t\tconst byId = new Map(events.map((event) => [event.id, event]));\n\t\tconst retryable: V2EventEnvelope[] = [];\n\t\tconst permanent: V2EventEnvelope[] = [];\n\n\t\tfor (const rejectedEvent of rejected) {\n\t\t\tconst event = byId.get(rejectedEvent.eventId);\n\t\t\tif (!event) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (isRetryableRejectedEvent(rejectedEvent)) {\n\t\t\t\tretryable.push(event);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpermanent.push(event);\n\t\t}\n\n\t\treturn { retryable, permanent };\n\t}\n\n\tprivate backoffDelayMs(attempt: number): number {\n\t\tconst rawDelay = this.retryBaseDelayMs * 2 ** attempt;\n\t\treturn Math.min(rawDelay, this.retryMaxDelayMs);\n\t}\n\n\tprivate stopTransportForAuthFailure(\n\t\tstatus: number,\n\t\trejectedCount: number,\n\t): void {\n\t\tthis.isStopped = true;\n\t\tconst buffered = this.buffer.length;\n\t\tthis.buffer.splice(0, buffered);\n\t\tthis.logger.error(\n\t\t\t\"[Waniwani] Auth failure (HTTP %d). Stopping tracking transport and dropping %d queued event(s)\",\n\t\t\tstatus,\n\t\t\trejectedCount + buffered,\n\t\t);\n\t}\n}\n\nfunction isRetryableRejectedEvent(\n\trejectedEvent: V2BatchRejectedEvent,\n): boolean {\n\tif (rejectedEvent.retryable === true) {\n\t\treturn true;\n\t}\n\tconst code = rejectedEvent.code.toLowerCase();\n\treturn (\n\t\tcode.includes(\"timeout\") ||\n\t\tcode.includes(\"temporary\") ||\n\t\tcode.includes(\"unavailable\") ||\n\t\tcode.includes(\"rate_limit\") ||\n\t\tcode.includes(\"transient\") ||\n\t\tcode.includes(\"server\")\n\t);\n}\n\nasync function parseJsonResponse<T>(\n\tresponse: Response,\n): Promise<T | undefined> {\n\tconst body = await response.text();\n\tif (!body) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\treturn JSON.parse(body) as T;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction joinUrl(apiUrl: string, endpointPath: string): string {\n\tconst normalizedBase = apiUrl.endsWith(\"/\") ? apiUrl : `${apiUrl}/`;\n\tconst normalizedPath = endpointPath.startsWith(\"/\")\n\t\t? endpointPath.slice(1)\n\t\t: endpointPath;\n\treturn `${normalizedBase}${normalizedPath}`;\n}\n\nfunction getErrorMessage(error: unknown): string {\n\tif (error instanceof Error) {\n\t\treturn error.message;\n\t}\n\treturn String(error);\n}\n","// Tracking Module\n\nimport type { InternalConfig } from \"../types.js\";\nimport type {\n\tTrackFn,\n\tTrackInput,\n\tTrackingClient,\n\tTrackingShutdownOptions,\n} from \"./@types.js\";\nimport { mapTrackEventToV2 } from \"./mapper.js\";\nimport { createRevenueApi } from \"./revenue.js\";\nimport { createV2BatchTransport } from \"./transport.js\";\n\n// Re-export types\nexport type {\n\tCallableTrack,\n\tComparedPriceOption,\n\tConvertedProperties,\n\tEventType,\n\tLeadProperties,\n\tLegacyTrackEvent,\n\tLinkClickedProperties,\n\tOptionSelectedProperties,\n\tPriceShownProperties,\n\tPricesComparedProperties,\n\tPurchaseCompletedProperties,\n\tQuoteSucceededProperties,\n\tRevenueConvertedInput,\n\tRevenueLeadInput,\n\tRevenueOptionSelectedInput,\n\tRevenuePriceShownInput,\n\tRevenuePricesComparedInput,\n\tRevenueTrackingApi,\n\tToolCalledProperties,\n\tTrackEvent,\n\tTrackFn,\n\tTrackInput,\n\tTrackingClient,\n\tTrackingConfig,\n\tTrackingShutdownOptions,\n\tTrackingShutdownResult,\n} from \"./@types.js\";\nexport { createEventId, mapTrackEventToV2 } from \"./mapper.js\";\nexport type {\n\tV2BatchRejectedEvent,\n\tV2BatchRequest,\n\tV2BatchResponse,\n\tV2CorrelationIds,\n\tV2EnvelopeType,\n\tV2EventEnvelope,\n} from \"./v2-types.js\";\n\nexport function createTrackingClient(config: InternalConfig): TrackingClient {\n\tconst { apiUrl, apiKey, tracking } = config;\n\n\tfunction requireApiKey(): string {\n\t\tif (!apiKey) {\n\t\t\tthrow new Error(\"WANIWANI_API_KEY is not set\");\n\t\t}\n\t\treturn apiKey;\n\t}\n\n\tconst transport = apiKey\n\t\t? createV2BatchTransport({\n\t\t\t\tapiUrl,\n\t\t\t\tapiKey,\n\t\t\t\tendpointPath: tracking.endpointPath,\n\t\t\t\tflushIntervalMs: tracking.flushIntervalMs,\n\t\t\t\tmaxBatchSize: tracking.maxBatchSize,\n\t\t\t\tmaxBufferSize: tracking.maxBufferSize,\n\t\t\t\tmaxRetries: tracking.maxRetries,\n\t\t\t\tretryBaseDelayMs: tracking.retryBaseDelayMs,\n\t\t\t\tretryMaxDelayMs: tracking.retryMaxDelayMs,\n\t\t\t\tshutdownTimeoutMs: tracking.shutdownTimeoutMs,\n\t\t\t})\n\t\t: undefined;\n\n\t// Single enqueue path shared by track(), track.* revenue helpers, and identify().\n\tfunction emit(event: TrackInput): { eventId: string } {\n\t\trequireApiKey();\n\t\tconst mappedEvent = mapTrackEventToV2(event);\n\t\t// Identity is required server-side (sessionId or externalUserId, possibly\n\t\t// derived from meta). Warn early — without it the ingest API rejects the\n\t\t// event even though enqueue returns an id here.\n\t\tif (\n\t\t\t!mappedEvent.correlation.sessionId &&\n\t\t\t!mappedEvent.correlation.externalUserId\n\t\t) {\n\t\t\tconsole.warn(\n\t\t\t\t`[waniwani] event \"${mappedEvent.name}\" has no sessionId or externalUserId; ` +\n\t\t\t\t\t\"the ingest API requires one and will reject it.\",\n\t\t\t);\n\t\t}\n\t\ttransport?.enqueue(mappedEvent);\n\t\treturn { eventId: mappedEvent.id };\n\t}\n\n\tconst trackOnce = async (event: TrackInput): Promise<{ eventId: string }> =>\n\t\temit(event);\n\t// Revenue helpers attach flat onto `track` (track.priceShown(), …).\n\tconst track: TrackFn = Object.assign(trackOnce, createRevenueApi(trackOnce));\n\n\tconst client: TrackingClient = {\n\t\tasync identify(\n\t\t\tuserId: string,\n\t\t\tproperties?: Record<string, unknown>,\n\t\t\tmeta?: Record<string, unknown>,\n\t\t): Promise<{ eventId: string }> {\n\t\t\trequireApiKey();\n\t\t\tconst mappedEvent = mapTrackEventToV2({\n\t\t\t\tevent: \"user.identified\",\n\t\t\t\texternalUserId: userId,\n\t\t\t\tproperties,\n\t\t\t\tmeta,\n\t\t\t});\n\t\t\ttransport?.enqueue(mappedEvent);\n\t\t\treturn { eventId: mappedEvent.id };\n\t\t},\n\t\ttrack,\n\t\tasync flush(): Promise<void> {\n\t\t\trequireApiKey();\n\t\t\tawait transport?.flush();\n\t\t},\n\t\tasync shutdown(options?: TrackingShutdownOptions) {\n\t\t\trequireApiKey();\n\t\t\treturn (\n\t\t\t\t(await transport?.shutdown({\n\t\t\t\t\ttimeoutMs: options?.timeoutMs ?? tracking.shutdownTimeoutMs,\n\t\t\t\t})) ?? { timedOut: false, pendingEvents: 0 }\n\t\t\t);\n\t\t},\n\t};\n\n\tif (transport) {\n\t\tattachShutdownHooks(client, tracking.shutdownTimeoutMs);\n\t}\n\treturn client;\n}\n\nfunction attachShutdownHooks(\n\tclient: TrackingClient,\n\tdefaultTimeoutMs: number,\n): void {\n\tif (\n\t\ttypeof process === \"undefined\" ||\n\t\ttypeof process.once !== \"function\" ||\n\t\ttypeof process.on !== \"function\"\n\t) {\n\t\treturn;\n\t}\n\n\tconst shutdown = () => {\n\t\tvoid client.shutdown({ timeoutMs: defaultTimeoutMs });\n\t};\n\n\tprocess.once(\"beforeExit\", shutdown);\n\tprocess.once(\"SIGINT\", shutdown);\n\tprocess.once(\"SIGTERM\", shutdown);\n}\n","// Waniwani SDK - Main Entry\n\nimport { createKbClient } from \"./kb/client.js\";\nimport {\n\tgetGlobalConfig,\n\tloadProjectConfig,\n\ttype WaniWaniProjectConfig,\n} from \"./project-config.js\";\nimport { createTrackingClient } from \"./tracking/index.js\";\nimport type { WaniWaniClient, WaniWaniConfig } from \"./types.js\";\n\n/**\n * Create a Waniwani SDK client\n *\n * @param config - Configuration options. When omitted, reads `waniwani.json`\n * from the current working directory, then falls back to env vars.\n * @returns A fully typed Waniwani client\n *\n * @example\n * ```typescript\n * import { waniwani } from \"@waniwani/sdk\";\n * import { toNextJsHandler } from \"@waniwani/sdk/next-js\";\n *\n * const wani = waniwani({ apiKey: \"...\" });\n *\n * // Next.js route handler\n * export const { GET, POST } = toNextJsHandler(wani, {\n * chat: { systemPrompt: \"You are a helpful assistant.\" },\n * });\n * ```\n */\nexport function waniwani(\n\tconfig?: WaniWaniConfig | WaniWaniProjectConfig,\n): WaniWaniClient {\n\tconst projectConfig = config ?? loadProjectConfig() ?? getGlobalConfig();\n\tconst effective = projectConfig as\n\t\t| (WaniWaniConfig & WaniWaniProjectConfig)\n\t\t| undefined;\n\n\tconst apiUrl = effective?.apiUrl ?? \"https://app.waniwani.ai\";\n\tconst apiKey = effective?.apiKey ?? process.env.WANIWANI_API_KEY;\n\tconst trackingConfig = {\n\t\tendpointPath:\n\t\t\teffective?.tracking?.endpointPath ?? \"/api/mcp/events/v2/batch\",\n\t\tflushIntervalMs: effective?.tracking?.flushIntervalMs ?? 1_000,\n\t\tmaxBatchSize: effective?.tracking?.maxBatchSize ?? 20,\n\t\tmaxBufferSize: effective?.tracking?.maxBufferSize ?? 1_000,\n\t\tmaxRetries: effective?.tracking?.maxRetries ?? 3,\n\t\tretryBaseDelayMs: effective?.tracking?.retryBaseDelayMs ?? 200,\n\t\tretryMaxDelayMs: effective?.tracking?.retryMaxDelayMs ?? 2_000,\n\t\tshutdownTimeoutMs: effective?.tracking?.shutdownTimeoutMs ?? 2_000,\n\t};\n\n\tconst internalConfig = { apiUrl, apiKey, tracking: trackingConfig };\n\n\t// Compose client from modules\n\tconst trackingClient = createTrackingClient(internalConfig);\n\tconst kbClient = createKbClient(internalConfig);\n\n\treturn {\n\t\t...trackingClient,\n\t\tkb: kbClient,\n\t\t_config: internalConfig,\n\t};\n}\n"],"mappings":"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","names":["WaniWaniError","message","status","existsSync","readFileSync","resolve","CONFIG_FILENAME","_cached","loadProjectConfig","filePath","raw","GLOBAL_KEY","defineConfig","config","getGlobalConfig","SDK_NAME","createKbClient","config","apiUrl","apiKey","requireApiKey","request","method","path","body","key","url","headers","init","response","text","WaniWaniError","files","query","options","pickFirst","meta","keys","key","value","SESSION_ID_KEYS","REQUEST_ID_KEYS","TRACE_ID_KEYS","EXTERNAL_USER_ID_KEYS","CORRELATION_ID_KEYS","extractSessionId","meta","pickFirst","SESSION_ID_KEYS","extractRequestId","REQUEST_ID_KEYS","extractTraceId","TRACE_ID_KEYS","extractExternalUserId","EXTERNAL_USER_ID_KEYS","extractCorrelationId","CORRELATION_ID_KEYS","SOURCE_SESSION_KEYS","CLIENT_INFO_NAME_SOURCES","extractSource","meta","clientInfo","explicit","key","source","value","name","lower","needle","DEFAULT_SOURCE","mapTrackEventToV2","input","options","now","generateId","createEventId","eventName","resolveEventName","meta","toRecord","metadata","correlation","resolveCorrelationIds","eventId","takeNonEmptyString","timestamp","normalizeTimestamp","source","extractSource","rawLegacy","isLegacyTrackEvent","mappedMetadata","mapProperties","legacyProperties","mapLegacyProperties","explicitProperties","properties","requestId","extractRequestId","sessionId","extractSessionId","traceId","extractTraceId","externalUserId","extractExternalUserId","correlationId","extractCorrelationId","date","value","createRevenueApi","emit","amount","currency","itemId","label","context","options","id","input","source","occurredAt","DEFAULT_ENDPOINT_PATH","SDK_NAME","AUTH_FAILURE_STATUS","RETRYABLE_STATUS","createV2BatchTransport","options","BatchingV2Transport","joinUrl","DEFAULT_ENDPOINT_PATH","delayMs","resolve","event","dropCount","timeoutMs","flushPromise","timeoutSignal","batch","attempt","pendingBatch","result","events","response","error","getErrorMessage","data","parseJsonResponse","partial","rejected","byId","retryable","permanent","rejectedEvent","isRetryableRejectedEvent","rawDelay","status","rejectedCount","buffered","code","body","apiUrl","endpointPath","normalizedBase","normalizedPath","createTrackingClient","config","apiUrl","apiKey","tracking","requireApiKey","transport","createV2BatchTransport","emit","event","mappedEvent","mapTrackEventToV2","trackOnce","track","createRevenueApi","client","userId","properties","meta","options","attachShutdownHooks","defaultTimeoutMs","shutdown","waniwani","config","effective","loadProjectConfig","getGlobalConfig","apiUrl","apiKey","trackingConfig","internalConfig","trackingClient","createTrackingClient","kbClient","createKbClient"]}
|
|
1
|
+
{"version":3,"sources":["../src/error.ts","../src/project-config.ts","../src/kb/client.ts","../src/mcp/server/utils.ts","../src/tracking/mapper.ts","../src/tracking/revenue.ts","../src/tracking/transport.ts","../src/tracking/index.ts","../src/waniwani.ts"],"sourcesContent":["// 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","import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\n/**\n * Project-level configuration for Waniwani MCP projects.\n *\n * Mirrors the JSON Schema hosted at https://docs.waniwani.ai/waniwani.json.\n * The canonical config file is `waniwani.json` at the project root:\n *\n * ```json\n * {\n * \"$schema\": \"https://docs.waniwani.ai/waniwani.json\",\n * \"orgId\": \"...\",\n * \"projectId\": \"...\"\n * }\n * ```\n *\n * `waniwani()` and the CLI both read this file automatically — no\n * explicit import is required.\n */\nexport interface WaniWaniProjectConfig {\n\t/** URL of the JSON Schema for editor autocomplete. Ignored at runtime. */\n\t$schema?: string;\n\t/** Waniwani organization ID this project belongs to. */\n\torgId?: string;\n\t/** Waniwani MCP project ID. */\n\tprojectId?: string;\n\t/**\n\t * The base URL of the Waniwani API.\n\t * Defaults to `https://app.waniwani.ai`.\n\t */\n\tapiUrl?: string;\n\t/**\n\t * Local port the MCP listens on during `waniwani dev`. Overridden by\n\t * `--port`. Defaults to 3000.\n\t */\n\tdevPort?: number;\n}\n\n// ---------------------------------------------------------------------------\n// waniwani.json loader\n// ---------------------------------------------------------------------------\n\nconst CONFIG_FILENAME = \"waniwani.json\";\n\nlet _cached: WaniWaniProjectConfig | null | undefined;\n\n/**\n * Load `waniwani.json` from the current working directory.\n *\n * Returns `null` if the file doesn't exist or the runtime doesn't support\n * synchronous filesystem reads (e.g. edge / worker runtimes). Cached after\n * the first call.\n *\n * @internal\n */\nexport function loadProjectConfig(): WaniWaniProjectConfig | null {\n\tif (_cached !== undefined) {\n\t\treturn _cached;\n\t}\n\n\ttry {\n\t\tconst filePath = resolve(process.cwd(), CONFIG_FILENAME);\n\t\tif (!existsSync(filePath)) {\n\t\t\t_cached = null;\n\t\t\treturn null;\n\t\t}\n\t\tconst raw = readFileSync(filePath, \"utf-8\");\n\t\t_cached = JSON.parse(raw) as WaniWaniProjectConfig;\n\t\treturn _cached;\n\t} catch {\n\t\t_cached = null;\n\t\treturn null;\n\t}\n}\n\n/**\n * Reset the cached config. Test-only.\n *\n * @internal\n */\nexport function resetProjectConfigCache(): void {\n\t_cached = undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Legacy: defineConfig + globalThis registration\n// ---------------------------------------------------------------------------\n\nconst GLOBAL_KEY = \"__waniwani_config__\" as const;\n\n/**\n * Register a Waniwani project configuration on `globalThis`.\n *\n * @deprecated Create a `waniwani.json` at the project root instead. The SDK\n * and CLI both auto-load that file — no `defineConfig` call required.\n * See https://docs.waniwani.ai/waniwani.json for the schema.\n */\nexport function defineConfig(\n\tconfig: WaniWaniProjectConfig,\n): WaniWaniProjectConfig {\n\t(globalThis as Record<string, unknown>)[GLOBAL_KEY] = config;\n\treturn config;\n}\n\n/**\n * Retrieve the globally registered config (set by `defineConfig`).\n *\n * @deprecated Use `loadProjectConfig()` instead. `defineConfig` is going away.\n * @internal\n */\nexport function getGlobalConfig(): WaniWaniProjectConfig | undefined {\n\treturn (globalThis as Record<string, unknown>)[GLOBAL_KEY] as\n\t\t| WaniWaniProjectConfig\n\t\t| undefined;\n}\n","// KB Client — thin HTTP wrapper for knowledge base API\n\nimport { WaniWaniError } from \"../error.js\";\nimport type { InternalConfig } from \"../types.js\";\nimport type {\n\tKbClient,\n\tKbIngestFile,\n\tKbIngestResult,\n\tKbSearchOptions,\n\tKbSource,\n\tSearchResult,\n} from \"./types.js\";\n\nconst SDK_NAME = \"@waniwani/sdk\";\n\nexport function createKbClient(config: InternalConfig): KbClient {\n\tconst { apiUrl, apiKey } = config;\n\n\tfunction requireApiKey(): string {\n\t\tif (!apiKey) {\n\t\t\tthrow new Error(\"WANIWANI_API_KEY is not set\");\n\t\t}\n\t\treturn apiKey;\n\t}\n\n\tasync function request<T>(\n\t\tmethod: \"GET\" | \"POST\",\n\t\tpath: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tconst key = requireApiKey();\n\t\tconst url = `${apiUrl.replace(/\\/$/, \"\")}${path}`;\n\n\t\tconst headers: Record<string, string> = {\n\t\t\tAuthorization: `Bearer ${key}`,\n\t\t\t\"X-WaniWani-SDK\": SDK_NAME,\n\t\t};\n\n\t\tconst init: RequestInit = { method, headers };\n\n\t\tif (body !== undefined) {\n\t\t\theaders[\"Content-Type\"] = \"application/json\";\n\t\t\tinit.body = JSON.stringify(body);\n\t\t}\n\n\t\tconst response = await fetch(url, init);\n\n\t\tif (!response.ok) {\n\t\t\tconst text = await response.text().catch(() => \"\");\n\t\t\tthrow new WaniWaniError(\n\t\t\t\ttext || `KB API error: HTTP ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t);\n\t\t}\n\n\t\tconst json = (await response.json()) as { data: T };\n\t\treturn json.data;\n\t}\n\n\treturn {\n\t\tasync ingest(files: KbIngestFile[]): Promise<KbIngestResult> {\n\t\t\treturn request<KbIngestResult>(\"POST\", \"/api/mcp/kb/ingest\", {\n\t\t\t\tfiles,\n\t\t\t});\n\t\t},\n\n\t\tasync search(\n\t\t\tquery: string,\n\t\t\toptions?: KbSearchOptions,\n\t\t): Promise<SearchResult[]> {\n\t\t\treturn request<SearchResult[]>(\"POST\", \"/api/mcp/kb/search\", {\n\t\t\t\tquery,\n\t\t\t\t...options,\n\t\t\t});\n\t\t},\n\n\t\tasync sources(): Promise<KbSource[]> {\n\t\t\treturn request<KbSource[]>(\"GET\", \"/api/mcp/kb/sources\");\n\t\t},\n\t};\n}\n","// ============================================================================\n// Meta key extraction helpers\n// ============================================================================\n\n/** Pick the first non-empty string value from `meta` matching the given keys. */\nfunction pickFirst(\n\tmeta: Record<string, unknown>,\n\tkeys: readonly string[],\n): string | undefined {\n\tfor (const key of keys) {\n\t\tconst value = meta[key];\n\t\tif (typeof value === \"string\" && value.length > 0) {\n\t\t\treturn value;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n// --- Key lists (ordered by priority) ---\n\nconst SESSION_ID_KEYS = [\n\t\"waniwani/sessionId\",\n\t\"openai/sessionId\",\n\t\"openai/session\",\n\t\"sessionId\",\n\t\"conversationId\",\n\t\"mcp-session-id\",\n] as const;\n\nconst REQUEST_ID_KEYS = [\n\t\"waniwani/requestId\",\n\t\"openai/requestId\",\n\t\"requestId\",\n\t\"mcp/requestId\",\n] as const;\n\nconst TRACE_ID_KEYS = [\n\t\"waniwani/traceId\",\n\t\"openai/traceId\",\n\t\"traceId\",\n\t\"mcp/traceId\",\n\t\"openai/requestId\",\n\t\"requestId\",\n] as const;\n\nconst EXTERNAL_USER_ID_KEYS = [\n\t\"waniwani/userId\",\n\t\"openai/userId\",\n\t\"externalUserId\",\n\t\"userId\",\n\t\"actorId\",\n] as const;\n\nconst CORRELATION_ID_KEYS = [\"correlationId\", \"openai/requestId\"] as const;\n\nconst TURN_COUNT_KEYS = [\"waniwani/turnCount\"] as const;\n\n/** Meta key for flow execution path (nodesVisited, flowId). */\nexport const FLOW_META_KEY = \"waniwani/flow\" as const;\n\n// --- Extractors ---\n\nexport function extractSessionId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, SESSION_ID_KEYS) : undefined;\n}\n\nexport function extractRequestId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, REQUEST_ID_KEYS) : undefined;\n}\n\nexport function extractTraceId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, TRACE_ID_KEYS) : undefined;\n}\n\nexport function extractExternalUserId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, EXTERNAL_USER_ID_KEYS) : undefined;\n}\n\nexport function extractCorrelationId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, CORRELATION_ID_KEYS) : undefined;\n}\n\n/**\n * Number of user messages in the current chat session, as counted by the\n * Waniwani app before dispatching the MCP request. Useful for MCPs that\n * gate behavior on conversation length (e.g. compulsory email verification\n * after N turns) without each MCP having to track turn state itself.\n *\n * Forwarded as `waniwani/turnCount` (non-negative integer).\n */\nexport function extractTurnCount(\n\tmeta: Record<string, unknown> | undefined,\n): number | undefined {\n\tif (!meta) {\n\t\treturn undefined;\n\t}\n\tfor (const key of TURN_COUNT_KEYS) {\n\t\tconst value = meta[key];\n\t\tif (typeof value === \"number\" && Number.isFinite(value) && value >= 0) {\n\t\t\treturn value;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nconst SOURCE_SESSION_KEYS = [\n\t{ key: \"openai/sessionId\", source: \"chatgpt\" },\n\t{ key: \"openai/session\", source: \"chatgpt\" },\n] as const;\n\n/**\n * Client identifiers advertised via MCP `initialize` → `clientInfo.name` that\n * the SDK maps to a known source. Match is case-insensitive substring on the\n * advertised name, so this catches \"Claude\", \"Claude Code\", \"claude-ai\", etc.\n * without having to enumerate every surface.\n *\n * ChatGPT is primarily identified by its `openai/*` session key (see\n * SOURCE_SESSION_KEYS), but a session without that key still resolves here from\n * the advertised client name so assistant traffic is attributed consistently.\n * First match wins, so order the needles most-specific first.\n */\nconst CLIENT_INFO_NAME_SOURCES: ReadonlyArray<{\n\tneedle: string;\n\tsource: string;\n}> = [\n\t{ needle: \"claude\", source: \"claude\" },\n\t{ needle: \"chatgpt\", source: \"chatgpt\" },\n\t{ needle: \"openai\", source: \"chatgpt\" },\n\t{ needle: \"gemini\", source: \"gemini\" },\n];\n\nexport type ExtractSourceClientInfo = {\n\tname?: string;\n\tversion?: string;\n};\n\nexport function extractSource(\n\tmeta: Record<string, unknown> | undefined,\n\tclientInfo?: ExtractSourceClientInfo,\n): string | undefined {\n\tif (meta) {\n\t\tconst explicit = meta[\"waniwani/source\"];\n\t\tif (typeof explicit === \"string\" && explicit.length > 0) {\n\t\t\treturn explicit;\n\t\t}\n\t\tfor (const { key, source } of SOURCE_SESSION_KEYS) {\n\t\t\tconst value = meta[key];\n\t\t\tif (typeof value === \"string\" && value.length > 0) {\n\t\t\t\treturn source;\n\t\t\t}\n\t\t}\n\t}\n\t// Fall back to MCP `initialize` clientInfo.name when no _meta key matches.\n\t// Claude surfaces (Code, Desktop, claude.ai connector, Anthropic API MCP\n\t// connector) don't expose a namespaced session id in _meta but do advertise\n\t// themselves in the handshake. Per-MCP-session clientInfo is captured by the\n\t// MCP transport and exposed via server.getClientVersion().\n\tconst name = clientInfo?.name;\n\tif (typeof name === \"string\" && name.length > 0) {\n\t\tconst lower = name.toLowerCase();\n\t\tfor (const { needle, source } of CLIENT_INFO_NAME_SOURCES) {\n\t\t\tif (lower.includes(needle)) {\n\t\t\t\treturn source;\n\t\t\t}\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * HTTP request headers that identify a known caller when neither `_meta` nor\n * `clientInfo` carries a source. This is the most robust Claude signal: every\n * Claude surface sends `User-Agent: Claude-User` and `x-anthropic-client` on\n * MCP HTTP requests, whereas the MCP transport carries no session id and the\n * `initialize` `clientInfo` may not be surfaced on stateless deployments.\n */\nfunction headerValue(\n\theaders: Record<string, unknown>,\n\tkey: string,\n): string | undefined {\n\t// HTTP header names are case-insensitive; the transport may preserve the\n\t// original casing, so match the key case-insensitively rather than assuming\n\t// it was lowercased.\n\tconst lowerKey = key.toLowerCase();\n\tlet raw = headers[key] ?? headers[lowerKey];\n\tif (raw === undefined) {\n\t\tfor (const headerKey of Object.keys(headers)) {\n\t\t\tif (headerKey.toLowerCase() === lowerKey) {\n\t\t\t\traw = headers[headerKey];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\tconst value = Array.isArray(raw) ? raw[0] : raw;\n\treturn typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nexport function extractSourceFromHeaders(\n\theaders: Record<string, unknown> | undefined,\n): string | undefined {\n\tif (!headers) {\n\t\treturn undefined;\n\t}\n\tconst userAgent = headerValue(headers, \"user-agent\");\n\tconst anthropicClient = headerValue(headers, \"x-anthropic-client\");\n\tif (\n\t\t(userAgent && /claude/i.test(userAgent)) ||\n\t\t(anthropicClient && /claude|anthropic/i.test(anthropicClient))\n\t) {\n\t\treturn \"claude\";\n\t}\n\t// ChatGPT / OpenAI surfaces without an `openai/*` session key still send an\n\t// OpenAI user-agent (e.g. `ChatGPT-User`), so attribute them here too.\n\tif (userAgent && /chatgpt|openai/i.test(userAgent)) {\n\t\treturn \"chatgpt\";\n\t}\n\treturn undefined;\n}\n","import {\n\textractCorrelationId,\n\textractExternalUserId,\n\textractRequestId,\n\textractSessionId,\n\textractSource,\n\textractTraceId,\n} from \"../mcp/server/utils.js\";\nimport type { EventType, LegacyTrackEvent, TrackInput } from \"./@types.js\";\nimport type { V2CorrelationIds, V2EventEnvelope } from \"./v2-types.js\";\n\nconst DEFAULT_SOURCE = \"@waniwani/sdk\";\n\nexport interface MapTrackEventOptions {\n\tnow?: () => Date;\n\tgenerateId?: () => string;\n\tsource?: string;\n}\n\nexport function mapTrackEventToV2(\n\tinput: TrackInput,\n\toptions: MapTrackEventOptions = {},\n): V2EventEnvelope {\n\tconst now = options.now ?? (() => new Date());\n\tconst generateId = options.generateId ?? createEventId;\n\tconst eventName = resolveEventName(input);\n\tconst meta = toRecord(input.meta);\n\tconst metadata = toRecord(input.metadata);\n\tconst correlation = resolveCorrelationIds(input, meta);\n\tconst eventId = takeNonEmptyString(input.eventId) ?? generateId();\n\tconst timestamp = normalizeTimestamp(input.timestamp, now);\n\tconst source =\n\t\ttakeNonEmptyString(input.source) ??\n\t\textractSource(meta) ??\n\t\toptions.source ??\n\t\tDEFAULT_SOURCE;\n\tconst rawLegacy = isLegacyTrackEvent(input) ? { ...input } : undefined;\n\n\tconst mappedMetadata: Record<string, unknown> = {\n\t\t...metadata,\n\t};\n\tif (Object.keys(meta).length > 0) {\n\t\tmappedMetadata.meta = meta;\n\t}\n\tif (rawLegacy) {\n\t\tmappedMetadata.rawLegacy = rawLegacy;\n\t}\n\n\treturn {\n\t\tid: eventId,\n\t\ttype: \"mcp.event\",\n\t\tname: eventName,\n\t\tsource,\n\t\ttimestamp,\n\t\tcorrelation,\n\t\tproperties: mapProperties(input, eventName),\n\t\tmetadata: mappedMetadata,\n\t\trawLegacy,\n\t};\n}\n\nexport function createEventId(): string {\n\tif (\n\t\ttypeof crypto !== \"undefined\" &&\n\t\ttypeof crypto.randomUUID === \"function\"\n\t) {\n\t\treturn `evt_${crypto.randomUUID()}`;\n\t}\n\n\treturn `evt_${Math.random().toString(36).slice(2, 10)}_${Date.now().toString(36)}`;\n}\n\nfunction mapProperties(\n\tinput: TrackInput,\n\teventName: EventType,\n): Record<string, unknown> {\n\tif (!isLegacyTrackEvent(input)) {\n\t\treturn toRecord(input.properties);\n\t}\n\n\tconst legacyProperties = mapLegacyProperties(input, eventName);\n\tconst explicitProperties = toRecord(input.properties);\n\treturn {\n\t\t...legacyProperties,\n\t\t...explicitProperties,\n\t};\n}\n\nfunction mapLegacyProperties(\n\tinput: LegacyTrackEvent,\n\teventName: EventType,\n): Record<string, unknown> {\n\tswitch (eventName) {\n\t\tcase \"tool.called\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (takeNonEmptyString(input.toolName)) {\n\t\t\t\tproperties.name = input.toolName;\n\t\t\t}\n\t\t\tif (takeNonEmptyString(input.toolType)) {\n\t\t\t\tproperties.type = input.toolType;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tcase \"quote.succeeded\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (typeof input.quoteAmount === \"number\") {\n\t\t\t\tproperties.amount = input.quoteAmount;\n\t\t\t}\n\t\t\tif (takeNonEmptyString(input.quoteCurrency)) {\n\t\t\t\tproperties.currency = input.quoteCurrency;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tcase \"link.clicked\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (takeNonEmptyString(input.linkUrl)) {\n\t\t\t\tproperties.url = input.linkUrl;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tcase \"purchase.completed\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (typeof input.purchaseAmount === \"number\") {\n\t\t\t\tproperties.amount = input.purchaseAmount;\n\t\t\t}\n\t\t\tif (takeNonEmptyString(input.purchaseCurrency)) {\n\t\t\t\tproperties.currency = input.purchaseCurrency;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tdefault:\n\t\t\treturn {};\n\t}\n}\n\nfunction resolveEventName(input: TrackInput): EventType {\n\tif (isLegacyTrackEvent(input)) {\n\t\treturn input.eventType;\n\t}\n\treturn input.event;\n}\n\nfunction resolveCorrelationIds(\n\tinput: TrackInput,\n\tmeta: Record<string, unknown>,\n): V2CorrelationIds {\n\tconst requestId =\n\t\ttakeNonEmptyString(input.requestId) ?? extractRequestId(meta);\n\n\tconst sessionId =\n\t\ttakeNonEmptyString(input.sessionId) ?? extractSessionId(meta);\n\n\tconst traceId = takeNonEmptyString(input.traceId) ?? extractTraceId(meta);\n\n\tconst externalUserId =\n\t\ttakeNonEmptyString(input.externalUserId) ?? extractExternalUserId(meta);\n\n\tconst correlationId =\n\t\ttakeNonEmptyString(input.correlationId) ??\n\t\textractCorrelationId(meta) ??\n\t\trequestId;\n\n\tconst correlation: V2CorrelationIds = {};\n\tif (sessionId) {\n\t\tcorrelation.sessionId = sessionId;\n\t}\n\tif (traceId) {\n\t\tcorrelation.traceId = traceId;\n\t}\n\tif (requestId) {\n\t\tcorrelation.requestId = requestId;\n\t}\n\tif (correlationId) {\n\t\tcorrelation.correlationId = correlationId;\n\t}\n\tif (externalUserId) {\n\t\tcorrelation.externalUserId = externalUserId;\n\t}\n\treturn correlation;\n}\n\nfunction normalizeTimestamp(\n\tinput: string | Date | undefined,\n\tnow: () => Date,\n): string {\n\tif (input instanceof Date) {\n\t\treturn input.toISOString();\n\t}\n\tif (typeof input === \"string\") {\n\t\tconst date = new Date(input);\n\t\tif (!Number.isNaN(date.getTime())) {\n\t\t\treturn date.toISOString();\n\t\t}\n\t}\n\treturn now().toISOString();\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\treturn {};\n\t}\n\treturn value as Record<string, unknown>;\n}\n\nfunction takeNonEmptyString(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") {\n\t\treturn undefined;\n\t}\n\tif (value.trim().length === 0) {\n\t\treturn undefined;\n\t}\n\treturn value;\n}\n\nfunction isLegacyTrackEvent(input: TrackInput): input is LegacyTrackEvent {\n\treturn \"eventType\" in input;\n}\n","import type { RevenueTrackingApi, TrackInput } from \"./@types.js\";\n\n/** Enqueue one event and resolve to its id. */\ntype Emit = (event: TrackInput) => Promise<{ eventId: string }>;\n\n/**\n * Build the flat `track.*` revenue helpers over an `emit` function. Shared\n * by the top-level client and the request-scoped client (which injects meta\n * into `emit`) so both expose the same revenue API (WAN-386).\n *\n * Each helper maps its flat input to a typed first-class revenue event; the\n * remaining tracking context (sessionId / externalUserId / meta) is forwarded\n * as-is. Identity (one of those, or meta-derived) is required by the ingest\n * API — see the warning emitted from the client's `emit`.\n */\nexport function createRevenueApi(emit: Emit): RevenueTrackingApi {\n\treturn {\n\t\tpriceShown: ({ amount, currency, itemId, label, ...context }) =>\n\t\t\temit({\n\t\t\t\tevent: \"price_shown\",\n\t\t\t\tproperties: { amount, currency, itemId, label },\n\t\t\t\t...context,\n\t\t\t}),\n\t\tpricesCompared: ({ options, ...context }) =>\n\t\t\temit({ event: \"prices_compared\", properties: { options }, ...context }),\n\t\toptionSelected: ({ id, amount, currency, ...context }) =>\n\t\t\temit({\n\t\t\t\tevent: \"option_selected\",\n\t\t\t\tproperties: { id, amount, currency },\n\t\t\t\t...context,\n\t\t\t}),\n\t\tlead: (input) => {\n\t\t\tconst { source, ...context } = input ?? {};\n\t\t\treturn emit({ event: \"lead\", properties: { source }, ...context });\n\t\t},\n\t\tconverted: ({ amount, currency, occurredAt, ...context }) =>\n\t\t\temit({\n\t\t\t\tevent: \"converted\",\n\t\t\t\tproperties: { amount, currency, occurredAt },\n\t\t\t\t...context,\n\t\t\t}),\n\t};\n}\n","import type {\n\tTrackingShutdownOptions,\n\tTrackingShutdownResult,\n} from \"./@types.js\";\nimport type {\n\tV2BatchRejectedEvent,\n\tV2BatchRequest,\n\tV2BatchResponse,\n\tV2EventEnvelope,\n} from \"./v2-types.js\";\n\ntype FetchFn = (\n\tinput: URL | RequestInfo,\n\tinit?: RequestInit,\n) => Promise<Response>;\n\nconst DEFAULT_ENDPOINT_PATH = \"/api/mcp/events/v2/batch\";\nconst DEFAULT_FLUSH_INTERVAL_MS = 1_000;\nconst DEFAULT_MAX_BATCH_SIZE = 20;\nconst DEFAULT_MAX_BUFFER_SIZE = 1_000;\nconst DEFAULT_MAX_RETRIES = 3;\nconst DEFAULT_RETRY_BASE_DELAY_MS = 200;\nconst DEFAULT_RETRY_MAX_DELAY_MS = 2_000;\nconst DEFAULT_SHUTDOWN_TIMEOUT_MS = 2_000;\nconst SDK_NAME = \"@waniwani/sdk\";\n\nconst AUTH_FAILURE_STATUS = new Set([401, 403]);\nconst RETRYABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504]);\n\ninterface Logger {\n\twarn: (message: string, ...args: unknown[]) => void;\n\terror: (message: string, ...args: unknown[]) => void;\n}\n\nexport interface V2TransportOptions {\n\tapiUrl: string;\n\tapiKey: string;\n\tendpointPath?: string;\n\tflushIntervalMs?: number;\n\tmaxBatchSize?: number;\n\tmaxBufferSize?: number;\n\tmaxRetries?: number;\n\tretryBaseDelayMs?: number;\n\tretryMaxDelayMs?: number;\n\tshutdownTimeoutMs?: number;\n\tsdkVersion?: string;\n\tfetchFn?: FetchFn;\n\tlogger?: Logger;\n\tnow?: () => Date;\n\tsleep?: (delayMs: number) => Promise<void>;\n}\n\nexport interface V2BatchTransport {\n\tenqueue: (event: V2EventEnvelope) => void;\n\tflush: () => Promise<void>;\n\tshutdown: (\n\t\toptions?: TrackingShutdownOptions,\n\t) => Promise<TrackingShutdownResult>;\n\tpendingEvents: () => number;\n}\n\ntype SendBatchResult =\n\t| { kind: \"success\" }\n\t| { kind: \"retryable\"; reason: string }\n\t| { kind: \"permanent\"; reason: string }\n\t| { kind: \"auth\"; status: number }\n\t| {\n\t\t\tkind: \"partial\";\n\t\t\tretryable: V2EventEnvelope[];\n\t\t\tpermanent: V2EventEnvelope[];\n\t };\n\nexport function createV2BatchTransport(\n\toptions: V2TransportOptions,\n): V2BatchTransport {\n\treturn new BatchingV2Transport(options);\n}\n\nclass BatchingV2Transport implements V2BatchTransport {\n\tprivate readonly endpointUrl: string;\n\tprivate readonly flushIntervalMs: number;\n\tprivate readonly maxBatchSize: number;\n\tprivate readonly maxBufferSize: number;\n\tprivate readonly maxRetries: number;\n\tprivate readonly retryBaseDelayMs: number;\n\tprivate readonly retryMaxDelayMs: number;\n\tprivate readonly shutdownTimeoutMs: number;\n\tprivate readonly sdkVersion?: string;\n\tprivate readonly fetchFn: FetchFn;\n\tprivate readonly logger: Logger;\n\tprivate readonly now: () => Date;\n\tprivate readonly sleep: (delayMs: number) => Promise<void>;\n\tprivate readonly apiKey: string;\n\n\tprivate readonly buffer: V2EventEnvelope[] = [];\n\tprivate flushTimer: ReturnType<typeof setInterval> | undefined;\n\tprivate flushScheduled = false;\n\tprivate flushScheduledTimer: ReturnType<typeof setTimeout> | undefined;\n\tprivate flushInFlight: Promise<void> | undefined;\n\tprivate inFlightCount = 0;\n\tprivate isStopped = false;\n\tprivate isShuttingDown = false;\n\n\tconstructor(options: V2TransportOptions) {\n\t\tthis.endpointUrl = joinUrl(\n\t\t\toptions.apiUrl,\n\t\t\toptions.endpointPath ?? DEFAULT_ENDPOINT_PATH,\n\t\t);\n\t\tthis.flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;\n\t\tthis.maxBatchSize = options.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;\n\t\tthis.maxBufferSize = options.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE;\n\t\tthis.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;\n\t\tthis.retryBaseDelayMs =\n\t\t\toptions.retryBaseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS;\n\t\tthis.retryMaxDelayMs =\n\t\t\toptions.retryMaxDelayMs ?? DEFAULT_RETRY_MAX_DELAY_MS;\n\t\tthis.shutdownTimeoutMs =\n\t\t\toptions.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;\n\t\tthis.fetchFn = options.fetchFn ?? fetch;\n\t\tthis.logger = options.logger ?? console;\n\t\tthis.now = options.now ?? (() => new Date());\n\t\tthis.sleep =\n\t\t\toptions.sleep ??\n\t\t\t((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)));\n\t\tthis.apiKey = options.apiKey;\n\t\tthis.sdkVersion = options.sdkVersion;\n\n\t\tif (this.flushIntervalMs > 0) {\n\t\t\tthis.flushTimer = setInterval(() => {\n\t\t\t\tvoid this.flush();\n\t\t\t}, this.flushIntervalMs);\n\t\t}\n\t}\n\n\tenqueue(event: V2EventEnvelope): void {\n\t\tif (this.isStopped || this.isShuttingDown) {\n\t\t\tthis.logger.warn(\n\t\t\t\t\"[Waniwani] Tracking transport is stopped, dropping event %s\",\n\t\t\t\tevent.id,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.buffer.length >= this.maxBufferSize) {\n\t\t\tconst dropCount = this.buffer.length - this.maxBufferSize + 1;\n\t\t\tthis.buffer.splice(0, dropCount);\n\t\t\tthis.logger.warn(\n\t\t\t\t\"[Waniwani] Tracking buffer overflow, dropped %d oldest event(s)\",\n\t\t\t\tdropCount,\n\t\t\t);\n\t\t}\n\n\t\tthis.buffer.push(event);\n\n\t\tif (this.buffer.length >= this.maxBatchSize) {\n\t\t\tvoid this.flush();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.scheduleMicroFlush();\n\t}\n\n\tpendingEvents(): number {\n\t\treturn this.buffer.length + this.inFlightCount;\n\t}\n\n\tasync flush(): Promise<void> {\n\t\tif (this.flushInFlight) {\n\t\t\treturn this.flushInFlight;\n\t\t}\n\t\tthis.flushInFlight = this.flushLoop().finally(() => {\n\t\t\tthis.flushInFlight = undefined;\n\t\t});\n\t\treturn this.flushInFlight;\n\t}\n\n\tasync shutdown(\n\t\toptions?: TrackingShutdownOptions,\n\t): Promise<TrackingShutdownResult> {\n\t\tthis.isShuttingDown = true;\n\t\tif (this.flushTimer) {\n\t\t\tclearInterval(this.flushTimer);\n\t\t\tthis.flushTimer = undefined;\n\t\t}\n\t\tif (this.flushScheduledTimer) {\n\t\t\tclearTimeout(this.flushScheduledTimer);\n\t\t\tthis.flushScheduledTimer = undefined;\n\t\t\tthis.flushScheduled = false;\n\t\t}\n\n\t\tconst timeoutMs = options?.timeoutMs ?? this.shutdownTimeoutMs;\n\t\tconst flushPromise = this.flush();\n\n\t\tif (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n\t\t\tawait flushPromise;\n\t\t\tthis.isStopped = true;\n\t\t\treturn { timedOut: false, pendingEvents: this.pendingEvents() };\n\t\t}\n\n\t\tconst timeoutSignal = Symbol(\"shutdown-timeout\");\n\t\tconst result = await Promise.race([\n\t\t\tflushPromise.then(() => \"flushed\" as const),\n\t\t\tthis.sleep(timeoutMs).then(() => timeoutSignal),\n\t\t]);\n\n\t\tif (result === timeoutSignal) {\n\t\t\tthis.isStopped = true;\n\t\t\treturn { timedOut: true, pendingEvents: this.pendingEvents() };\n\t\t}\n\n\t\tthis.isStopped = true;\n\t\treturn { timedOut: false, pendingEvents: this.pendingEvents() };\n\t}\n\n\tprivate scheduleMicroFlush(): void {\n\t\tif (this.flushScheduled) {\n\t\t\treturn;\n\t\t}\n\t\tthis.flushScheduled = true;\n\t\tthis.flushScheduledTimer = setTimeout(() => {\n\t\t\tthis.flushScheduledTimer = undefined;\n\t\t\tthis.flushScheduled = false;\n\t\t\tvoid this.flush();\n\t\t}, 0);\n\t}\n\n\tprivate async flushLoop(): Promise<void> {\n\t\twhile (this.buffer.length > 0 && !this.isStopped) {\n\t\t\tconst batch = this.buffer.splice(0, this.maxBatchSize);\n\t\t\tawait this.sendBatchWithRetry(batch);\n\t\t}\n\t}\n\n\tprivate async sendBatchWithRetry(batch: V2EventEnvelope[]): Promise<void> {\n\t\tlet attempt = 0;\n\t\tlet pendingBatch = batch;\n\n\t\twhile (pendingBatch.length > 0 && !this.isStopped) {\n\t\t\tthis.inFlightCount = pendingBatch.length;\n\t\t\tconst result = await this.sendBatchOnce(pendingBatch);\n\t\t\tthis.inFlightCount = 0;\n\n\t\t\tswitch (result.kind) {\n\t\t\t\tcase \"success\":\n\t\t\t\t\treturn;\n\t\t\t\tcase \"auth\":\n\t\t\t\t\tthis.stopTransportForAuthFailure(result.status, pendingBatch.length);\n\t\t\t\t\treturn;\n\t\t\t\tcase \"permanent\":\n\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\"[Waniwani] Dropping %d event(s) after permanent failure: %s\",\n\t\t\t\t\t\tpendingBatch.length,\n\t\t\t\t\t\tresult.reason,\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\tcase \"retryable\":\n\t\t\t\t\tif (attempt >= this.maxRetries) {\n\t\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\t\"[Waniwani] Dropping %d event(s) after retry exhaustion: %s\",\n\t\t\t\t\t\t\tpendingBatch.length,\n\t\t\t\t\t\t\tresult.reason,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tawait this.sleep(this.backoffDelayMs(attempt));\n\t\t\t\t\tattempt += 1;\n\t\t\t\t\tcontinue;\n\t\t\t\tcase \"partial\":\n\t\t\t\t\tif (result.permanent.length > 0) {\n\t\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\t\"[Waniwani] Dropping %d event(s) rejected as permanent\",\n\t\t\t\t\t\t\tresult.permanent.length,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (result.retryable.length === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (attempt >= this.maxRetries) {\n\t\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\t\"[Waniwani] Dropping %d retryable event(s) after retry exhaustion\",\n\t\t\t\t\t\t\tresult.retryable.length,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tpendingBatch = result.retryable;\n\t\t\t\t\tawait this.sleep(this.backoffDelayMs(attempt));\n\t\t\t\t\tattempt += 1;\n\t\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async sendBatchOnce(\n\t\tevents: V2EventEnvelope[],\n\t): Promise<SendBatchResult> {\n\t\tlet response: Response;\n\n\t\ttry {\n\t\t\tresponse = await this.fetchFn(this.endpointUrl, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\tAuthorization: `Bearer ${this.apiKey}`,\n\t\t\t\t\t\"X-WaniWani-SDK\": SDK_NAME,\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify(this.makeBatchRequest(events)),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tkind: \"retryable\",\n\t\t\t\treason: getErrorMessage(error),\n\t\t\t};\n\t\t}\n\n\t\tif (AUTH_FAILURE_STATUS.has(response.status)) {\n\t\t\treturn { kind: \"auth\", status: response.status };\n\t\t}\n\n\t\tif (RETRYABLE_STATUS.has(response.status)) {\n\t\t\treturn {\n\t\t\t\tkind: \"retryable\",\n\t\t\t\treason: `HTTP ${response.status}`,\n\t\t\t};\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\treturn {\n\t\t\t\tkind: \"permanent\",\n\t\t\t\treason: `HTTP ${response.status}`,\n\t\t\t};\n\t\t}\n\n\t\tconst data = await parseJsonResponse<V2BatchResponse>(response);\n\t\tif (!data?.rejected || data.rejected.length === 0) {\n\t\t\treturn { kind: \"success\" };\n\t\t}\n\n\t\tconst partial = this.classifyRejectedEvents(events, data.rejected);\n\t\tif (partial.retryable.length === 0 && partial.permanent.length === 0) {\n\t\t\treturn { kind: \"success\" };\n\t\t}\n\n\t\treturn {\n\t\t\tkind: \"partial\",\n\t\t\tretryable: partial.retryable,\n\t\t\tpermanent: partial.permanent,\n\t\t};\n\t}\n\n\tprivate makeBatchRequest(events: V2EventEnvelope[]): V2BatchRequest {\n\t\treturn {\n\t\t\tsentAt: this.now().toISOString(),\n\t\t\tsource: {\n\t\t\t\tsdk: SDK_NAME,\n\t\t\t\tversion: this.sdkVersion ?? \"0.0.0\",\n\t\t\t},\n\t\t\tevents,\n\t\t};\n\t}\n\n\tprivate classifyRejectedEvents(\n\t\tevents: V2EventEnvelope[],\n\t\trejected: V2BatchRejectedEvent[],\n\t): {\n\t\tretryable: V2EventEnvelope[];\n\t\tpermanent: V2EventEnvelope[];\n\t} {\n\t\tconst byId = new Map(events.map((event) => [event.id, event]));\n\t\tconst retryable: V2EventEnvelope[] = [];\n\t\tconst permanent: V2EventEnvelope[] = [];\n\n\t\tfor (const rejectedEvent of rejected) {\n\t\t\tconst event = byId.get(rejectedEvent.eventId);\n\t\t\tif (!event) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (isRetryableRejectedEvent(rejectedEvent)) {\n\t\t\t\tretryable.push(event);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpermanent.push(event);\n\t\t}\n\n\t\treturn { retryable, permanent };\n\t}\n\n\tprivate backoffDelayMs(attempt: number): number {\n\t\tconst rawDelay = this.retryBaseDelayMs * 2 ** attempt;\n\t\treturn Math.min(rawDelay, this.retryMaxDelayMs);\n\t}\n\n\tprivate stopTransportForAuthFailure(\n\t\tstatus: number,\n\t\trejectedCount: number,\n\t): void {\n\t\tthis.isStopped = true;\n\t\tconst buffered = this.buffer.length;\n\t\tthis.buffer.splice(0, buffered);\n\t\tthis.logger.error(\n\t\t\t\"[Waniwani] Auth failure (HTTP %d). Stopping tracking transport and dropping %d queued event(s)\",\n\t\t\tstatus,\n\t\t\trejectedCount + buffered,\n\t\t);\n\t}\n}\n\nfunction isRetryableRejectedEvent(\n\trejectedEvent: V2BatchRejectedEvent,\n): boolean {\n\tif (rejectedEvent.retryable === true) {\n\t\treturn true;\n\t}\n\tconst code = rejectedEvent.code.toLowerCase();\n\treturn (\n\t\tcode.includes(\"timeout\") ||\n\t\tcode.includes(\"temporary\") ||\n\t\tcode.includes(\"unavailable\") ||\n\t\tcode.includes(\"rate_limit\") ||\n\t\tcode.includes(\"transient\") ||\n\t\tcode.includes(\"server\")\n\t);\n}\n\nasync function parseJsonResponse<T>(\n\tresponse: Response,\n): Promise<T | undefined> {\n\tconst body = await response.text();\n\tif (!body) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\treturn JSON.parse(body) as T;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction joinUrl(apiUrl: string, endpointPath: string): string {\n\tconst normalizedBase = apiUrl.endsWith(\"/\") ? apiUrl : `${apiUrl}/`;\n\tconst normalizedPath = endpointPath.startsWith(\"/\")\n\t\t? endpointPath.slice(1)\n\t\t: endpointPath;\n\treturn `${normalizedBase}${normalizedPath}`;\n}\n\nfunction getErrorMessage(error: unknown): string {\n\tif (error instanceof Error) {\n\t\treturn error.message;\n\t}\n\treturn String(error);\n}\n","// Tracking Module\n\nimport type { InternalConfig } from \"../types.js\";\nimport type {\n\tTrackFn,\n\tTrackInput,\n\tTrackingClient,\n\tTrackingShutdownOptions,\n} from \"./@types.js\";\nimport { mapTrackEventToV2 } from \"./mapper.js\";\nimport { createRevenueApi } from \"./revenue.js\";\nimport { createV2BatchTransport } from \"./transport.js\";\n\n// Re-export types\nexport type {\n\tCallableTrack,\n\tComparedPriceOption,\n\tConvertedProperties,\n\tEventType,\n\tLeadProperties,\n\tLegacyTrackEvent,\n\tLinkClickedProperties,\n\tOptionSelectedProperties,\n\tPriceShownProperties,\n\tPricesComparedProperties,\n\tPurchaseCompletedProperties,\n\tQuoteSucceededProperties,\n\tRevenueConvertedInput,\n\tRevenueLeadInput,\n\tRevenueOptionSelectedInput,\n\tRevenuePriceShownInput,\n\tRevenuePricesComparedInput,\n\tRevenueTrackingApi,\n\tToolCalledProperties,\n\tTrackEvent,\n\tTrackFn,\n\tTrackInput,\n\tTrackingClient,\n\tTrackingConfig,\n\tTrackingShutdownOptions,\n\tTrackingShutdownResult,\n} from \"./@types.js\";\nexport { createEventId, mapTrackEventToV2 } from \"./mapper.js\";\nexport type {\n\tV2BatchRejectedEvent,\n\tV2BatchRequest,\n\tV2BatchResponse,\n\tV2CorrelationIds,\n\tV2EnvelopeType,\n\tV2EventEnvelope,\n} from \"./v2-types.js\";\n\nexport function createTrackingClient(config: InternalConfig): TrackingClient {\n\tconst { apiUrl, apiKey, tracking } = config;\n\n\tfunction requireApiKey(): string {\n\t\tif (!apiKey) {\n\t\t\tthrow new Error(\"WANIWANI_API_KEY is not set\");\n\t\t}\n\t\treturn apiKey;\n\t}\n\n\tconst transport = apiKey\n\t\t? createV2BatchTransport({\n\t\t\t\tapiUrl,\n\t\t\t\tapiKey,\n\t\t\t\tendpointPath: tracking.endpointPath,\n\t\t\t\tflushIntervalMs: tracking.flushIntervalMs,\n\t\t\t\tmaxBatchSize: tracking.maxBatchSize,\n\t\t\t\tmaxBufferSize: tracking.maxBufferSize,\n\t\t\t\tmaxRetries: tracking.maxRetries,\n\t\t\t\tretryBaseDelayMs: tracking.retryBaseDelayMs,\n\t\t\t\tretryMaxDelayMs: tracking.retryMaxDelayMs,\n\t\t\t\tshutdownTimeoutMs: tracking.shutdownTimeoutMs,\n\t\t\t})\n\t\t: undefined;\n\n\t// Single enqueue path shared by track(), track.* revenue helpers, and identify().\n\tfunction emit(event: TrackInput): { eventId: string } {\n\t\trequireApiKey();\n\t\tconst mappedEvent = mapTrackEventToV2(event);\n\t\t// Identity is required server-side (sessionId or externalUserId, possibly\n\t\t// derived from meta). Warn early — without it the ingest API rejects the\n\t\t// event even though enqueue returns an id here.\n\t\tif (\n\t\t\t!mappedEvent.correlation.sessionId &&\n\t\t\t!mappedEvent.correlation.externalUserId\n\t\t) {\n\t\t\tconsole.warn(\n\t\t\t\t`[waniwani] event \"${mappedEvent.name}\" has no sessionId or externalUserId; ` +\n\t\t\t\t\t\"the ingest API requires one and will reject it.\",\n\t\t\t);\n\t\t}\n\t\ttransport?.enqueue(mappedEvent);\n\t\treturn { eventId: mappedEvent.id };\n\t}\n\n\tconst trackOnce = async (event: TrackInput): Promise<{ eventId: string }> =>\n\t\temit(event);\n\t// Revenue helpers attach flat onto `track` (track.priceShown(), …).\n\tconst track: TrackFn = Object.assign(trackOnce, createRevenueApi(trackOnce));\n\n\tconst client: TrackingClient = {\n\t\tasync identify(\n\t\t\tuserId: string,\n\t\t\tproperties?: Record<string, unknown>,\n\t\t\tmeta?: Record<string, unknown>,\n\t\t): Promise<{ eventId: string }> {\n\t\t\trequireApiKey();\n\t\t\tconst mappedEvent = mapTrackEventToV2({\n\t\t\t\tevent: \"user.identified\",\n\t\t\t\texternalUserId: userId,\n\t\t\t\tproperties,\n\t\t\t\tmeta,\n\t\t\t});\n\t\t\ttransport?.enqueue(mappedEvent);\n\t\t\treturn { eventId: mappedEvent.id };\n\t\t},\n\t\ttrack,\n\t\tasync flush(): Promise<void> {\n\t\t\trequireApiKey();\n\t\t\tawait transport?.flush();\n\t\t},\n\t\tasync shutdown(options?: TrackingShutdownOptions) {\n\t\t\trequireApiKey();\n\t\t\treturn (\n\t\t\t\t(await transport?.shutdown({\n\t\t\t\t\ttimeoutMs: options?.timeoutMs ?? tracking.shutdownTimeoutMs,\n\t\t\t\t})) ?? { timedOut: false, pendingEvents: 0 }\n\t\t\t);\n\t\t},\n\t};\n\n\tif (transport) {\n\t\tattachShutdownHooks(client, tracking.shutdownTimeoutMs);\n\t}\n\treturn client;\n}\n\nfunction attachShutdownHooks(\n\tclient: TrackingClient,\n\tdefaultTimeoutMs: number,\n): void {\n\tif (\n\t\ttypeof process === \"undefined\" ||\n\t\ttypeof process.once !== \"function\" ||\n\t\ttypeof process.on !== \"function\"\n\t) {\n\t\treturn;\n\t}\n\n\tconst shutdown = () => {\n\t\tvoid client.shutdown({ timeoutMs: defaultTimeoutMs });\n\t};\n\n\tprocess.once(\"beforeExit\", shutdown);\n\tprocess.once(\"SIGINT\", shutdown);\n\tprocess.once(\"SIGTERM\", shutdown);\n}\n","// Waniwani SDK - Main Entry\n\nimport { createKbClient } from \"./kb/client.js\";\nimport {\n\tgetGlobalConfig,\n\tloadProjectConfig,\n\ttype WaniWaniProjectConfig,\n} from \"./project-config.js\";\nimport { createTrackingClient } from \"./tracking/index.js\";\nimport type { WaniWaniClient, WaniWaniConfig } from \"./types.js\";\n\n/**\n * Create a Waniwani SDK client\n *\n * @param config - Configuration options. When omitted, reads `waniwani.json`\n * from the current working directory, then falls back to env vars.\n * @returns A fully typed Waniwani client\n *\n * @example\n * ```typescript\n * import { waniwani } from \"@waniwani/sdk\";\n * import { toNextJsHandler } from \"@waniwani/sdk/next-js\";\n *\n * const wani = waniwani({ apiKey: \"...\" });\n *\n * // Next.js route handler\n * export const { GET, POST } = toNextJsHandler(wani, {\n * chat: { systemPrompt: \"You are a helpful assistant.\" },\n * });\n * ```\n */\nexport function waniwani(\n\tconfig?: WaniWaniConfig | WaniWaniProjectConfig,\n): WaniWaniClient {\n\tconst projectConfig = config ?? loadProjectConfig() ?? getGlobalConfig();\n\tconst effective = projectConfig as\n\t\t| (WaniWaniConfig & WaniWaniProjectConfig)\n\t\t| undefined;\n\n\tconst apiUrl = effective?.apiUrl ?? \"https://app.waniwani.ai\";\n\tconst apiKey = effective?.apiKey ?? process.env.WANIWANI_API_KEY;\n\tconst trackingConfig = {\n\t\tendpointPath:\n\t\t\teffective?.tracking?.endpointPath ?? \"/api/mcp/events/v2/batch\",\n\t\tflushIntervalMs: effective?.tracking?.flushIntervalMs ?? 1_000,\n\t\tmaxBatchSize: effective?.tracking?.maxBatchSize ?? 20,\n\t\tmaxBufferSize: effective?.tracking?.maxBufferSize ?? 1_000,\n\t\tmaxRetries: effective?.tracking?.maxRetries ?? 3,\n\t\tretryBaseDelayMs: effective?.tracking?.retryBaseDelayMs ?? 200,\n\t\tretryMaxDelayMs: effective?.tracking?.retryMaxDelayMs ?? 2_000,\n\t\tshutdownTimeoutMs: effective?.tracking?.shutdownTimeoutMs ?? 2_000,\n\t};\n\n\tconst internalConfig = { apiUrl, apiKey, tracking: trackingConfig };\n\n\t// Compose client from modules\n\tconst trackingClient = createTrackingClient(internalConfig);\n\tconst kbClient = createKbClient(internalConfig);\n\n\treturn {\n\t\t...trackingClient,\n\t\tkb: kbClient,\n\t\t_config: internalConfig,\n\t};\n}\n"],"mappings":"AAEO,IAAMA,EAAN,cAA4B,KAAM,CACxC,YACCC,EACOC,EACN,CACD,MAAMD,CAAO,EAFN,YAAAC,EAGP,KAAK,KAAO,eACb,CACD,ECVA,OAAS,cAAAC,EAAY,gBAAAC,MAAoB,KACzC,OAAS,WAAAC,MAAe,OA0CxB,IAAMC,EAAkB,gBAEpBC,EAWG,SAASC,GAAkD,CACjE,GAAID,IAAY,OACf,OAAOA,EAGR,GAAI,CACH,IAAME,EAAWJ,EAAQ,QAAQ,IAAI,EAAGC,CAAe,EACvD,GAAI,CAACH,EAAWM,CAAQ,EACvB,OAAAF,EAAU,KACH,KAER,IAAMG,EAAMN,EAAaK,EAAU,OAAO,EAC1C,OAAAF,EAAU,KAAK,MAAMG,CAAG,EACjBH,CACR,MAAQ,CACP,OAAAA,EAAU,KACH,IACR,CACD,CAeA,IAAMI,EAAa,sBASZ,SAASC,EACfC,EACwB,CACxB,OAAC,WAAuCF,CAAU,EAAIE,EAC/CA,CACR,CAQO,SAASC,GAAqD,CACpE,OAAQ,WAAuCH,CAAU,CAG1D,CCtGA,IAAMI,EAAW,gBAEV,SAASC,EAAeC,EAAkC,CAChE,GAAM,CAAE,OAAAC,EAAQ,OAAAC,CAAO,EAAIF,EAE3B,SAASG,GAAwB,CAChC,GAAI,CAACD,EACJ,MAAM,IAAI,MAAM,6BAA6B,EAE9C,OAAOA,CACR,CAEA,eAAeE,EACdC,EACAC,EACAC,EACa,CACb,IAAMC,EAAML,EAAc,EACpBM,EAAM,GAAGR,EAAO,QAAQ,MAAO,EAAE,CAAC,GAAGK,CAAI,GAEzCI,EAAkC,CACvC,cAAe,UAAUF,CAAG,GAC5B,iBAAkBV,CACnB,EAEMa,EAAoB,CAAE,OAAAN,EAAQ,QAAAK,CAAQ,EAExCH,IAAS,SACZG,EAAQ,cAAc,EAAI,mBAC1BC,EAAK,KAAO,KAAK,UAAUJ,CAAI,GAGhC,IAAMK,EAAW,MAAM,MAAMH,EAAKE,CAAI,EAEtC,GAAI,CAACC,EAAS,GAAI,CACjB,IAAMC,EAAO,MAAMD,EAAS,KAAK,EAAE,MAAM,IAAM,EAAE,EACjD,MAAM,IAAIE,EACTD,GAAQ,sBAAsBD,EAAS,MAAM,GAC7CA,EAAS,MACV,CACD,CAGA,OADc,MAAMA,EAAS,KAAK,GACtB,IACb,CAEA,MAAO,CACN,MAAM,OAAOG,EAAgD,CAC5D,OAAOX,EAAwB,OAAQ,qBAAsB,CAC5D,MAAAW,CACD,CAAC,CACF,EAEA,MAAM,OACLC,EACAC,EAC0B,CAC1B,OAAOb,EAAwB,OAAQ,qBAAsB,CAC5D,MAAAY,EACA,GAAGC,CACJ,CAAC,CACF,EAEA,MAAM,SAA+B,CACpC,OAAOb,EAAoB,MAAO,qBAAqB,CACxD,CACD,CACD,CC3EA,SAASc,EACRC,EACAC,EACqB,CACrB,QAAWC,KAAOD,EAAM,CACvB,IAAME,EAAQH,EAAKE,CAAG,EACtB,GAAI,OAAOC,GAAU,UAAYA,EAAM,OAAS,EAC/C,OAAOA,CAET,CAED,CAIA,IAAMC,EAAkB,CACvB,qBACA,mBACA,iBACA,YACA,iBACA,gBACD,EAEMC,EAAkB,CACvB,qBACA,mBACA,YACA,eACD,EAEMC,EAAgB,CACrB,mBACA,iBACA,UACA,cACA,mBACA,WACD,EAEMC,EAAwB,CAC7B,kBACA,gBACA,iBACA,SACA,SACD,EAEMC,EAAsB,CAAC,gBAAiB,kBAAkB,EASzD,SAASC,EACfC,EACqB,CACrB,OAAOA,EAAOC,EAAUD,EAAME,CAAe,EAAI,MAClD,CAEO,SAASC,EACfH,EACqB,CACrB,OAAOA,EAAOC,EAAUD,EAAMI,CAAe,EAAI,MAClD,CAEO,SAASC,EACfL,EACqB,CACrB,OAAOA,EAAOC,EAAUD,EAAMM,CAAa,EAAI,MAChD,CAEO,SAASC,EACfP,EACqB,CACrB,OAAOA,EAAOC,EAAUD,EAAMQ,CAAqB,EAAI,MACxD,CAEO,SAASC,EACfT,EACqB,CACrB,OAAOA,EAAOC,EAAUD,EAAMU,CAAmB,EAAI,MACtD,CAyBA,IAAMC,EAAsB,CAC3B,CAAE,IAAK,mBAAoB,OAAQ,SAAU,EAC7C,CAAE,IAAK,iBAAkB,OAAQ,SAAU,CAC5C,EAaMC,EAGD,CACJ,CAAE,OAAQ,SAAU,OAAQ,QAAS,EACrC,CAAE,OAAQ,UAAW,OAAQ,SAAU,EACvC,CAAE,OAAQ,SAAU,OAAQ,SAAU,EACtC,CAAE,OAAQ,SAAU,OAAQ,QAAS,CACtC,EAOO,SAASC,EACfC,EACAC,EACqB,CACrB,GAAID,EAAM,CACT,IAAME,EAAWF,EAAK,iBAAiB,EACvC,GAAI,OAAOE,GAAa,UAAYA,EAAS,OAAS,EACrD,OAAOA,EAER,OAAW,CAAE,IAAAC,EAAK,OAAAC,CAAO,IAAKP,EAAqB,CAClD,IAAMQ,EAAQL,EAAKG,CAAG,EACtB,GAAI,OAAOE,GAAU,UAAYA,EAAM,OAAS,EAC/C,OAAOD,CAET,CACD,CAMA,IAAME,EAAOL,GAAY,KACzB,GAAI,OAAOK,GAAS,UAAYA,EAAK,OAAS,EAAG,CAChD,IAAMC,EAAQD,EAAK,YAAY,EAC/B,OAAW,CAAE,OAAAE,EAAQ,OAAAJ,CAAO,IAAKN,EAChC,GAAIS,EAAM,SAASC,CAAM,EACxB,OAAOJ,CAGV,CAED,CCtKA,IAAMK,EAAiB,gBAQhB,SAASC,EACfC,EACAC,EAAgC,CAAC,EACf,CAClB,IAAMC,EAAMD,EAAQ,MAAQ,IAAM,IAAI,MAChCE,EAAaF,EAAQ,YAAcG,EACnCC,EAAYC,GAAiBN,CAAK,EAClCO,EAAOC,EAASR,EAAM,IAAI,EAC1BS,EAAWD,EAASR,EAAM,QAAQ,EAClCU,EAAcC,GAAsBX,EAAOO,CAAI,EAC/CK,EAAUC,EAAmBb,EAAM,OAAO,GAAKG,EAAW,EAC1DW,EAAYC,GAAmBf,EAAM,UAAWE,CAAG,EACnDc,EACLH,EAAmBb,EAAM,MAAM,GAC/BiB,EAAcV,CAAI,GAClBN,EAAQ,QACRH,EACKoB,EAAYC,EAAmBnB,CAAK,EAAI,CAAE,GAAGA,CAAM,EAAI,OAEvDoB,EAA0C,CAC/C,GAAGX,CACJ,EACA,OAAI,OAAO,KAAKF,CAAI,EAAE,OAAS,IAC9Ba,EAAe,KAAOb,GAEnBW,IACHE,EAAe,UAAYF,GAGrB,CACN,GAAIN,EACJ,KAAM,YACN,KAAMP,EACN,OAAAW,EACA,UAAAF,EACA,YAAAJ,EACA,WAAYW,GAAcrB,EAAOK,CAAS,EAC1C,SAAUe,EACV,UAAAF,CACD,CACD,CAEO,SAASd,GAAwB,CACvC,OACC,OAAO,OAAW,KAClB,OAAO,OAAO,YAAe,WAEtB,OAAO,OAAO,WAAW,CAAC,GAG3B,OAAO,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,EAAG,EAAE,CAAC,IAAI,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,EACjF,CAEA,SAASiB,GACRrB,EACAK,EAC0B,CAC1B,GAAI,CAACc,EAAmBnB,CAAK,EAC5B,OAAOQ,EAASR,EAAM,UAAU,EAGjC,IAAMsB,EAAmBC,GAAoBvB,EAAOK,CAAS,EACvDmB,EAAqBhB,EAASR,EAAM,UAAU,EACpD,MAAO,CACN,GAAGsB,EACH,GAAGE,CACJ,CACD,CAEA,SAASD,GACRvB,EACAK,EAC0B,CAC1B,OAAQA,EAAW,CAClB,IAAK,cAAe,CACnB,IAAMoB,EAAsC,CAAC,EAC7C,OAAIZ,EAAmBb,EAAM,QAAQ,IACpCyB,EAAW,KAAOzB,EAAM,UAErBa,EAAmBb,EAAM,QAAQ,IACpCyB,EAAW,KAAOzB,EAAM,UAElByB,CACR,CACA,IAAK,kBAAmB,CACvB,IAAMA,EAAsC,CAAC,EAC7C,OAAI,OAAOzB,EAAM,aAAgB,WAChCyB,EAAW,OAASzB,EAAM,aAEvBa,EAAmBb,EAAM,aAAa,IACzCyB,EAAW,SAAWzB,EAAM,eAEtByB,CACR,CACA,IAAK,eAAgB,CACpB,IAAMA,EAAsC,CAAC,EAC7C,OAAIZ,EAAmBb,EAAM,OAAO,IACnCyB,EAAW,IAAMzB,EAAM,SAEjByB,CACR,CACA,IAAK,qBAAsB,CAC1B,IAAMA,EAAsC,CAAC,EAC7C,OAAI,OAAOzB,EAAM,gBAAmB,WACnCyB,EAAW,OAASzB,EAAM,gBAEvBa,EAAmBb,EAAM,gBAAgB,IAC5CyB,EAAW,SAAWzB,EAAM,kBAEtByB,CACR,CACA,QACC,MAAO,CAAC,CACV,CACD,CAEA,SAASnB,GAAiBN,EAA8B,CACvD,OAAImB,EAAmBnB,CAAK,EACpBA,EAAM,UAEPA,EAAM,KACd,CAEA,SAASW,GACRX,EACAO,EACmB,CACnB,IAAMmB,EACLb,EAAmBb,EAAM,SAAS,GAAK2B,EAAiBpB,CAAI,EAEvDqB,EACLf,EAAmBb,EAAM,SAAS,GAAK6B,EAAiBtB,CAAI,EAEvDuB,EAAUjB,EAAmBb,EAAM,OAAO,GAAK+B,EAAexB,CAAI,EAElEyB,EACLnB,EAAmBb,EAAM,cAAc,GAAKiC,EAAsB1B,CAAI,EAEjE2B,EACLrB,EAAmBb,EAAM,aAAa,GACtCmC,EAAqB5B,CAAI,GACzBmB,EAEKhB,EAAgC,CAAC,EACvC,OAAIkB,IACHlB,EAAY,UAAYkB,GAErBE,IACHpB,EAAY,QAAUoB,GAEnBJ,IACHhB,EAAY,UAAYgB,GAErBQ,IACHxB,EAAY,cAAgBwB,GAEzBF,IACHtB,EAAY,eAAiBsB,GAEvBtB,CACR,CAEA,SAASK,GACRf,EACAE,EACS,CACT,GAAIF,aAAiB,KACpB,OAAOA,EAAM,YAAY,EAE1B,GAAI,OAAOA,GAAU,SAAU,CAC9B,IAAMoC,EAAO,IAAI,KAAKpC,CAAK,EAC3B,GAAI,CAAC,OAAO,MAAMoC,EAAK,QAAQ,CAAC,EAC/B,OAAOA,EAAK,YAAY,CAE1B,CACA,OAAOlC,EAAI,EAAE,YAAY,CAC1B,CAEA,SAASM,EAAS6B,EAAyC,CAC1D,MAAI,CAACA,GAAS,OAAOA,GAAU,UAAY,MAAM,QAAQA,CAAK,EACtD,CAAC,EAEFA,CACR,CAEA,SAASxB,EAAmBwB,EAAoC,CAC/D,GAAI,OAAOA,GAAU,UAGjBA,EAAM,KAAK,EAAE,SAAW,EAG5B,OAAOA,CACR,CAEA,SAASlB,EAAmBnB,EAA8C,CACzE,MAAO,cAAeA,CACvB,CCzMO,SAASsC,EAAiBC,EAAgC,CAChE,MAAO,CACN,WAAY,CAAC,CAAE,OAAAC,EAAQ,SAAAC,EAAU,OAAAC,EAAQ,MAAAC,EAAO,GAAGC,CAAQ,IAC1DL,EAAK,CACJ,MAAO,cACP,WAAY,CAAE,OAAAC,EAAQ,SAAAC,EAAU,OAAAC,EAAQ,MAAAC,CAAM,EAC9C,GAAGC,CACJ,CAAC,EACF,eAAgB,CAAC,CAAE,QAAAC,EAAS,GAAGD,CAAQ,IACtCL,EAAK,CAAE,MAAO,kBAAmB,WAAY,CAAE,QAAAM,CAAQ,EAAG,GAAGD,CAAQ,CAAC,EACvE,eAAgB,CAAC,CAAE,GAAAE,EAAI,OAAAN,EAAQ,SAAAC,EAAU,GAAGG,CAAQ,IACnDL,EAAK,CACJ,MAAO,kBACP,WAAY,CAAE,GAAAO,EAAI,OAAAN,EAAQ,SAAAC,CAAS,EACnC,GAAGG,CACJ,CAAC,EACF,KAAOG,GAAU,CAChB,GAAM,CAAE,OAAAC,EAAQ,GAAGJ,CAAQ,EAAIG,GAAS,CAAC,EACzC,OAAOR,EAAK,CAAE,MAAO,OAAQ,WAAY,CAAE,OAAAS,CAAO,EAAG,GAAGJ,CAAQ,CAAC,CAClE,EACA,UAAW,CAAC,CAAE,OAAAJ,EAAQ,SAAAC,EAAU,WAAAQ,EAAY,GAAGL,CAAQ,IACtDL,EAAK,CACJ,MAAO,YACP,WAAY,CAAE,OAAAC,EAAQ,SAAAC,EAAU,WAAAQ,CAAW,EAC3C,GAAGL,CACJ,CAAC,CACH,CACD,CC1BA,IAAMM,GAAwB,2BAQ9B,IAAMC,EAAW,gBAEXC,GAAsB,IAAI,IAAI,CAAC,IAAK,GAAG,CAAC,EACxCC,GAAmB,IAAI,IAAI,CAAC,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,GAAG,CAAC,EA6C7D,SAASC,EACfC,EACmB,CACnB,OAAO,IAAIC,EAAoBD,CAAO,CACvC,CAEA,IAAMC,EAAN,KAAsD,CACpC,YACA,gBACA,aACA,cACA,WACA,iBACA,gBACA,kBACA,WACA,QACA,OACA,IACA,MACA,OAEA,OAA4B,CAAC,EACtC,WACA,eAAiB,GACjB,oBACA,cACA,cAAgB,EAChB,UAAY,GACZ,eAAiB,GAEzB,YAAYD,EAA6B,CACxC,KAAK,YAAcE,GAClBF,EAAQ,OACRA,EAAQ,cAAgBG,EACzB,EACA,KAAK,gBAAkBH,EAAQ,iBAAmB,IAClD,KAAK,aAAeA,EAAQ,cAAgB,GAC5C,KAAK,cAAgBA,EAAQ,eAAiB,IAC9C,KAAK,WAAaA,EAAQ,YAAc,EACxC,KAAK,iBACJA,EAAQ,kBAAoB,IAC7B,KAAK,gBACJA,EAAQ,iBAAmB,IAC5B,KAAK,kBACJA,EAAQ,mBAAqB,IAC9B,KAAK,QAAUA,EAAQ,SAAW,MAClC,KAAK,OAASA,EAAQ,QAAU,QAChC,KAAK,IAAMA,EAAQ,MAAQ,IAAM,IAAI,MACrC,KAAK,MACJA,EAAQ,QACNI,GAAY,IAAI,QAASC,GAAY,WAAWA,EAASD,CAAO,CAAC,GACpE,KAAK,OAASJ,EAAQ,OACtB,KAAK,WAAaA,EAAQ,WAEtB,KAAK,gBAAkB,IAC1B,KAAK,WAAa,YAAY,IAAM,CAC9B,KAAK,MAAM,CACjB,EAAG,KAAK,eAAe,EAEzB,CAEA,QAAQM,EAA8B,CACrC,GAAI,KAAK,WAAa,KAAK,eAAgB,CAC1C,KAAK,OAAO,KACX,8DACAA,EAAM,EACP,EACA,MACD,CAEA,GAAI,KAAK,OAAO,QAAU,KAAK,cAAe,CAC7C,IAAMC,EAAY,KAAK,OAAO,OAAS,KAAK,cAAgB,EAC5D,KAAK,OAAO,OAAO,EAAGA,CAAS,EAC/B,KAAK,OAAO,KACX,kEACAA,CACD,CACD,CAIA,GAFA,KAAK,OAAO,KAAKD,CAAK,EAElB,KAAK,OAAO,QAAU,KAAK,aAAc,CACvC,KAAK,MAAM,EAChB,MACD,CAEA,KAAK,mBAAmB,CACzB,CAEA,eAAwB,CACvB,OAAO,KAAK,OAAO,OAAS,KAAK,aAClC,CAEA,MAAM,OAAuB,CAC5B,OAAI,KAAK,cACD,KAAK,eAEb,KAAK,cAAgB,KAAK,UAAU,EAAE,QAAQ,IAAM,CACnD,KAAK,cAAgB,MACtB,CAAC,EACM,KAAK,cACb,CAEA,MAAM,SACLN,EACkC,CAClC,KAAK,eAAiB,GAClB,KAAK,aACR,cAAc,KAAK,UAAU,EAC7B,KAAK,WAAa,QAEf,KAAK,sBACR,aAAa,KAAK,mBAAmB,EACrC,KAAK,oBAAsB,OAC3B,KAAK,eAAiB,IAGvB,IAAMQ,EAAYR,GAAS,WAAa,KAAK,kBACvCS,EAAe,KAAK,MAAM,EAEhC,GAAI,CAAC,OAAO,SAASD,CAAS,GAAKA,GAAa,EAC/C,aAAMC,EACN,KAAK,UAAY,GACV,CAAE,SAAU,GAAO,cAAe,KAAK,cAAc,CAAE,EAG/D,IAAMC,EAAgB,OAAO,kBAAkB,EAM/C,OALe,MAAM,QAAQ,KAAK,CACjCD,EAAa,KAAK,IAAM,SAAkB,EAC1C,KAAK,MAAMD,CAAS,EAAE,KAAK,IAAME,CAAa,CAC/C,CAAC,IAEcA,GACd,KAAK,UAAY,GACV,CAAE,SAAU,GAAM,cAAe,KAAK,cAAc,CAAE,IAG9D,KAAK,UAAY,GACV,CAAE,SAAU,GAAO,cAAe,KAAK,cAAc,CAAE,EAC/D,CAEQ,oBAA2B,CAC9B,KAAK,iBAGT,KAAK,eAAiB,GACtB,KAAK,oBAAsB,WAAW,IAAM,CAC3C,KAAK,oBAAsB,OAC3B,KAAK,eAAiB,GACjB,KAAK,MAAM,CACjB,EAAG,CAAC,EACL,CAEA,MAAc,WAA2B,CACxC,KAAO,KAAK,OAAO,OAAS,GAAK,CAAC,KAAK,WAAW,CACjD,IAAMC,EAAQ,KAAK,OAAO,OAAO,EAAG,KAAK,YAAY,EACrD,MAAM,KAAK,mBAAmBA,CAAK,CACpC,CACD,CAEA,MAAc,mBAAmBA,EAAyC,CACzE,IAAIC,EAAU,EACVC,EAAeF,EAEnB,KAAOE,EAAa,OAAS,GAAK,CAAC,KAAK,WAAW,CAClD,KAAK,cAAgBA,EAAa,OAClC,IAAMC,EAAS,MAAM,KAAK,cAAcD,CAAY,EAGpD,OAFA,KAAK,cAAgB,EAEbC,EAAO,KAAM,CACpB,IAAK,UACJ,OACD,IAAK,OACJ,KAAK,4BAA4BA,EAAO,OAAQD,EAAa,MAAM,EACnE,OACD,IAAK,YACJ,KAAK,OAAO,MACX,8DACAA,EAAa,OACbC,EAAO,MACR,EACA,OACD,IAAK,YACJ,GAAIF,GAAW,KAAK,WAAY,CAC/B,KAAK,OAAO,MACX,6DACAC,EAAa,OACbC,EAAO,MACR,EACA,MACD,CACA,MAAM,KAAK,MAAM,KAAK,eAAeF,CAAO,CAAC,EAC7CA,GAAW,EACX,SACD,IAAK,UAOJ,GANIE,EAAO,UAAU,OAAS,GAC7B,KAAK,OAAO,MACX,wDACAA,EAAO,UAAU,MAClB,EAEGA,EAAO,UAAU,SAAW,EAC/B,OAED,GAAIF,GAAW,KAAK,WAAY,CAC/B,KAAK,OAAO,MACX,mEACAE,EAAO,UAAU,MAClB,EACA,MACD,CACAD,EAAeC,EAAO,UACtB,MAAM,KAAK,MAAM,KAAK,eAAeF,CAAO,CAAC,EAC7CA,GAAW,EACX,QACF,CACD,CACD,CAEA,MAAc,cACbG,EAC2B,CAC3B,IAAIC,EAEJ,GAAI,CACHA,EAAW,MAAM,KAAK,QAAQ,KAAK,YAAa,CAC/C,OAAQ,OACR,QAAS,CACR,eAAgB,mBAChB,cAAe,UAAU,KAAK,MAAM,GACpC,iBAAkBpB,CACnB,EACA,KAAM,KAAK,UAAU,KAAK,iBAAiBmB,CAAM,CAAC,CACnD,CAAC,CACF,OAASE,EAAO,CACf,MAAO,CACN,KAAM,YACN,OAAQC,GAAgBD,CAAK,CAC9B,CACD,CAEA,GAAIpB,GAAoB,IAAImB,EAAS,MAAM,EAC1C,MAAO,CAAE,KAAM,OAAQ,OAAQA,EAAS,MAAO,EAGhD,GAAIlB,GAAiB,IAAIkB,EAAS,MAAM,EACvC,MAAO,CACN,KAAM,YACN,OAAQ,QAAQA,EAAS,MAAM,EAChC,EAGD,GAAI,CAACA,EAAS,GACb,MAAO,CACN,KAAM,YACN,OAAQ,QAAQA,EAAS,MAAM,EAChC,EAGD,IAAMG,EAAO,MAAMC,GAAmCJ,CAAQ,EAC9D,GAAI,CAACG,GAAM,UAAYA,EAAK,SAAS,SAAW,EAC/C,MAAO,CAAE,KAAM,SAAU,EAG1B,IAAME,EAAU,KAAK,uBAAuBN,EAAQI,EAAK,QAAQ,EACjE,OAAIE,EAAQ,UAAU,SAAW,GAAKA,EAAQ,UAAU,SAAW,EAC3D,CAAE,KAAM,SAAU,EAGnB,CACN,KAAM,UACN,UAAWA,EAAQ,UACnB,UAAWA,EAAQ,SACpB,CACD,CAEQ,iBAAiBN,EAA2C,CACnE,MAAO,CACN,OAAQ,KAAK,IAAI,EAAE,YAAY,EAC/B,OAAQ,CACP,IAAKnB,EACL,QAAS,KAAK,YAAc,OAC7B,EACA,OAAAmB,CACD,CACD,CAEQ,uBACPA,EACAO,EAIC,CACD,IAAMC,EAAO,IAAI,IAAIR,EAAO,IAAKT,GAAU,CAACA,EAAM,GAAIA,CAAK,CAAC,CAAC,EACvDkB,EAA+B,CAAC,EAChCC,EAA+B,CAAC,EAEtC,QAAWC,KAAiBJ,EAAU,CACrC,IAAMhB,EAAQiB,EAAK,IAAIG,EAAc,OAAO,EAC5C,GAAKpB,EAGL,IAAIqB,GAAyBD,CAAa,EAAG,CAC5CF,EAAU,KAAKlB,CAAK,EACpB,QACD,CACAmB,EAAU,KAAKnB,CAAK,EACrB,CAEA,MAAO,CAAE,UAAAkB,EAAW,UAAAC,CAAU,CAC/B,CAEQ,eAAeb,EAAyB,CAC/C,IAAMgB,EAAW,KAAK,iBAAmB,GAAKhB,EAC9C,OAAO,KAAK,IAAIgB,EAAU,KAAK,eAAe,CAC/C,CAEQ,4BACPC,EACAC,EACO,CACP,KAAK,UAAY,GACjB,IAAMC,EAAW,KAAK,OAAO,OAC7B,KAAK,OAAO,OAAO,EAAGA,CAAQ,EAC9B,KAAK,OAAO,MACX,iGACAF,EACAC,EAAgBC,CACjB,CACD,CACD,EAEA,SAASJ,GACRD,EACU,CACV,GAAIA,EAAc,YAAc,GAC/B,MAAO,GAER,IAAMM,EAAON,EAAc,KAAK,YAAY,EAC5C,OACCM,EAAK,SAAS,SAAS,GACvBA,EAAK,SAAS,WAAW,GACzBA,EAAK,SAAS,aAAa,GAC3BA,EAAK,SAAS,YAAY,GAC1BA,EAAK,SAAS,WAAW,GACzBA,EAAK,SAAS,QAAQ,CAExB,CAEA,eAAeZ,GACdJ,EACyB,CACzB,IAAMiB,EAAO,MAAMjB,EAAS,KAAK,EACjC,GAAKiB,EAGL,GAAI,CACH,OAAO,KAAK,MAAMA,CAAI,CACvB,MAAQ,CACP,MACD,CACD,CAEA,SAAS/B,GAAQgC,EAAgBC,EAA8B,CAC9D,IAAMC,EAAiBF,EAAO,SAAS,GAAG,EAAIA,EAAS,GAAGA,CAAM,IAC1DG,EAAiBF,EAAa,WAAW,GAAG,EAC/CA,EAAa,MAAM,CAAC,EACpBA,EACH,MAAO,GAAGC,CAAc,GAAGC,CAAc,EAC1C,CAEA,SAASnB,GAAgBD,EAAwB,CAChD,OAAIA,aAAiB,MACbA,EAAM,QAEP,OAAOA,CAAK,CACpB,CC9YO,SAASqB,EAAqBC,EAAwC,CAC5E,GAAM,CAAE,OAAAC,EAAQ,OAAAC,EAAQ,SAAAC,CAAS,EAAIH,EAErC,SAASI,GAAwB,CAChC,GAAI,CAACF,EACJ,MAAM,IAAI,MAAM,6BAA6B,EAE9C,OAAOA,CACR,CAEA,IAAMG,EAAYH,EACfI,EAAuB,CACvB,OAAAL,EACA,OAAAC,EACA,aAAcC,EAAS,aACvB,gBAAiBA,EAAS,gBAC1B,aAAcA,EAAS,aACvB,cAAeA,EAAS,cACxB,WAAYA,EAAS,WACrB,iBAAkBA,EAAS,iBAC3B,gBAAiBA,EAAS,gBAC1B,kBAAmBA,EAAS,iBAC7B,CAAC,EACA,OAGH,SAASI,EAAKC,EAAwC,CACrDJ,EAAc,EACd,IAAMK,EAAcC,EAAkBF,CAAK,EAI3C,MACC,CAACC,EAAY,YAAY,WACzB,CAACA,EAAY,YAAY,gBAEzB,QAAQ,KACP,qBAAqBA,EAAY,IAAI,uFAEtC,EAEDJ,GAAW,QAAQI,CAAW,EACvB,CAAE,QAASA,EAAY,EAAG,CAClC,CAEA,IAAME,EAAY,MAAOH,GACxBD,EAAKC,CAAK,EAELI,EAAiB,OAAO,OAAOD,EAAWE,EAAiBF,CAAS,CAAC,EAErEG,EAAyB,CAC9B,MAAM,SACLC,EACAC,EACAC,EAC+B,CAC/Bb,EAAc,EACd,IAAMK,EAAcC,EAAkB,CACrC,MAAO,kBACP,eAAgBK,EAChB,WAAAC,EACA,KAAAC,CACD,CAAC,EACD,OAAAZ,GAAW,QAAQI,CAAW,EACvB,CAAE,QAASA,EAAY,EAAG,CAClC,EACA,MAAAG,EACA,MAAM,OAAuB,CAC5BR,EAAc,EACd,MAAMC,GAAW,MAAM,CACxB,EACA,MAAM,SAASa,EAAmC,CACjD,OAAAd,EAAc,EAEZ,MAAMC,GAAW,SAAS,CAC1B,UAAWa,GAAS,WAAaf,EAAS,iBAC3C,CAAC,GAAM,CAAE,SAAU,GAAO,cAAe,CAAE,CAE7C,CACD,EAEA,OAAIE,GACHc,GAAoBL,EAAQX,EAAS,iBAAiB,EAEhDW,CACR,CAEA,SAASK,GACRL,EACAM,EACO,CACP,GACC,OAAO,QAAY,KACnB,OAAO,QAAQ,MAAS,YACxB,OAAO,QAAQ,IAAO,WAEtB,OAGD,IAAMC,EAAW,IAAM,CACjBP,EAAO,SAAS,CAAE,UAAWM,CAAiB,CAAC,CACrD,EAEA,QAAQ,KAAK,aAAcC,CAAQ,EACnC,QAAQ,KAAK,SAAUA,CAAQ,EAC/B,QAAQ,KAAK,UAAWA,CAAQ,CACjC,CC/HO,SAASC,GACfC,EACiB,CAEjB,IAAMC,EADgBD,GAAUE,EAAkB,GAAKC,EAAgB,EAKjEC,EAASH,GAAW,QAAU,0BAC9BI,EAASJ,GAAW,QAAU,QAAQ,IAAI,iBAC1CK,EAAiB,CACtB,aACCL,GAAW,UAAU,cAAgB,2BACtC,gBAAiBA,GAAW,UAAU,iBAAmB,IACzD,aAAcA,GAAW,UAAU,cAAgB,GACnD,cAAeA,GAAW,UAAU,eAAiB,IACrD,WAAYA,GAAW,UAAU,YAAc,EAC/C,iBAAkBA,GAAW,UAAU,kBAAoB,IAC3D,gBAAiBA,GAAW,UAAU,iBAAmB,IACzD,kBAAmBA,GAAW,UAAU,mBAAqB,GAC9D,EAEMM,EAAiB,CAAE,OAAAH,EAAQ,OAAAC,EAAQ,SAAUC,CAAe,EAG5DE,EAAiBC,EAAqBF,CAAc,EACpDG,EAAWC,EAAeJ,CAAc,EAE9C,MAAO,CACN,GAAGC,EACH,GAAIE,EACJ,QAASH,CACV,CACD","names":["WaniWaniError","message","status","existsSync","readFileSync","resolve","CONFIG_FILENAME","_cached","loadProjectConfig","filePath","raw","GLOBAL_KEY","defineConfig","config","getGlobalConfig","SDK_NAME","createKbClient","config","apiUrl","apiKey","requireApiKey","request","method","path","body","key","url","headers","init","response","text","WaniWaniError","files","query","options","pickFirst","meta","keys","key","value","SESSION_ID_KEYS","REQUEST_ID_KEYS","TRACE_ID_KEYS","EXTERNAL_USER_ID_KEYS","CORRELATION_ID_KEYS","extractSessionId","meta","pickFirst","SESSION_ID_KEYS","extractRequestId","REQUEST_ID_KEYS","extractTraceId","TRACE_ID_KEYS","extractExternalUserId","EXTERNAL_USER_ID_KEYS","extractCorrelationId","CORRELATION_ID_KEYS","SOURCE_SESSION_KEYS","CLIENT_INFO_NAME_SOURCES","extractSource","meta","clientInfo","explicit","key","source","value","name","lower","needle","DEFAULT_SOURCE","mapTrackEventToV2","input","options","now","generateId","createEventId","eventName","resolveEventName","meta","toRecord","metadata","correlation","resolveCorrelationIds","eventId","takeNonEmptyString","timestamp","normalizeTimestamp","source","extractSource","rawLegacy","isLegacyTrackEvent","mappedMetadata","mapProperties","legacyProperties","mapLegacyProperties","explicitProperties","properties","requestId","extractRequestId","sessionId","extractSessionId","traceId","extractTraceId","externalUserId","extractExternalUserId","correlationId","extractCorrelationId","date","value","createRevenueApi","emit","amount","currency","itemId","label","context","options","id","input","source","occurredAt","DEFAULT_ENDPOINT_PATH","SDK_NAME","AUTH_FAILURE_STATUS","RETRYABLE_STATUS","createV2BatchTransport","options","BatchingV2Transport","joinUrl","DEFAULT_ENDPOINT_PATH","delayMs","resolve","event","dropCount","timeoutMs","flushPromise","timeoutSignal","batch","attempt","pendingBatch","result","events","response","error","getErrorMessage","data","parseJsonResponse","partial","rejected","byId","retryable","permanent","rejectedEvent","isRetryableRejectedEvent","rawDelay","status","rejectedCount","buffered","code","body","apiUrl","endpointPath","normalizedBase","normalizedPath","createTrackingClient","config","apiUrl","apiKey","tracking","requireApiKey","transport","createV2BatchTransport","emit","event","mappedEvent","mapTrackEventToV2","trackOnce","track","createRevenueApi","client","userId","properties","meta","options","attachShutdownHooks","defaultTimeoutMs","shutdown","waniwani","config","effective","loadProjectConfig","getGlobalConfig","apiUrl","apiKey","trackingConfig","internalConfig","trackingClient","createTrackingClient","kbClient","createKbClient"]}
|
package/dist/mcp/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var _="__start__",F="__end__",be=Symbol.for("waniwani.flow.interrupt"),Ce=Symbol.for("waniwani.flow.widget");function _e(e,t){let n=t?.context,o=[];for(let[r,i]of Object.entries(e))if(typeof i=="object"&&i!==null&&"question"in i){let s=i;o.push({question:s.question,field:r,suggestions:s.suggestions,context:s.context,validate:s.validate})}return{__type:be,questions:o,context:n}}function Fe(e,t){let n=typeof e=="object"&&e!==null&&"tool"in e,o=n?e.tool:e,r=n?e:t??{};if(r.description!==void 0){let p=typeof o=="string"?o:o.id;throw new Error(`showWidget("${p}", { description }) is no longer supported. The engine now emits a standardized instruction telling the AI to call the widget tool. Return any per-widget description from the "${p}" tool's own response instead.`)}let{data:i,interactive:s,field:a}=r;return{__type:Ce,tool:typeof o=="string"?o:o.id,...i!==void 0?{data:i}:{},...s!==void 0?{interactive:s}:{},...a!==void 0?{field:a}:{}}}function Pe(e){return typeof e=="object"&&e!==null&&"__type"in e&&e.__type===be}function Ne(e){return typeof e=="object"&&e!==null&&"__type"in e&&e.__type===Ce}import{z as W}from"zod";function G(e){return{priceShown:({amount:t,currency:n,itemId:o,label:r,...i})=>e({event:"price_shown",properties:{amount:t,currency:n,itemId:o,label:r},...i}),pricesCompared:({options:t,...n})=>e({event:"prices_compared",properties:{options:t},...n}),optionSelected:({id:t,amount:n,currency:o,...r})=>e({event:"option_selected",properties:{id:t,amount:n,currency:o},...r}),lead:t=>{let{source:n,...o}=t??{};return e({event:"lead",properties:{source:n},...o})},converted:({amount:t,currency:n,occurredAt:o,...r})=>e({event:"converted",properties:{amount:t,currency:n,occurredAt:o},...r})}}var le="waniwani/client";function Z(e){if(typeof e=="object"&&e!==null)return e[le]}function Ae(e,t,n){let o=r=>e.track({...r,meta:{...t,...r.meta}});return{track:Object.assign(o,G(o)),identify(r,i){return e.identify(r,i,t)},kb:e.kb,_config:n}}function q(e,t){for(let n of t){let o=e[n];if(typeof o=="string"&&o.length>0)return o}}var Ft=["waniwani/sessionId","openai/sessionId","openai/session","sessionId","conversationId","mcp-session-id"],Pt=["waniwani/requestId","openai/requestId","requestId","mcp/requestId"],Nt=["waniwani/traceId","openai/traceId","traceId","mcp/traceId","openai/requestId","requestId"],At=["waniwani/userId","openai/userId","externalUserId","userId","actorId"],Wt=["correlationId","openai/requestId"];var D="waniwani/flow";function P(e){return e?q(e,Ft):void 0}function Me(e){return e?q(e,Pt):void 0}function Ue(e){return e?q(e,Nt):void 0}function Oe(e){return e?q(e,At):void 0}function De(e){return e?q(e,Wt):void 0}var Mt=[{key:"openai/sessionId",source:"chatgpt"},{key:"openai/session",source:"chatgpt"}],Ut=[{needle:"claude",source:"claude"}];function N(e,t){if(e){let o=e["waniwani/source"];if(typeof o=="string"&&o.length>0)return o;for(let{key:r,source:i}of Mt){let s=e[r];if(typeof s=="string"&&s.length>0)return i}}let n=t?.name;if(typeof n=="string"&&n.length>0){let o=n.toLowerCase();for(let{needle:r,source:i}of Ut)if(o.includes(r))return i}}function We(e,t){let n=t.toLowerCase(),o=e[t]??e[n];if(o===void 0){for(let i of Object.keys(e))if(i.toLowerCase()===n){o=e[i];break}}let r=Array.isArray(o)?o[0]:o;return typeof r=="string"&&r.length>0?r:void 0}function Ke(e){if(!e)return;let t=We(e,"user-agent"),n=We(e,"x-anthropic-client");if(t&&/claude/i.test(t)||n&&/claude|anthropic/i.test(n))return"claude"}function je(e){let t=e._zod?.def;return t?.type==="object"&&t.shape?t.shape:null}function H(e,t){let n=t.split("."),o=e;for(let r of n){if(o==null||typeof o!="object")return;o=o[r]}return o}function Ot(e,t,n){let o=t.split("."),r=o.pop();if(!r)return;let i=e;for(let s of o)(i[s]==null||typeof i[s]!="object"||Array.isArray(i[s]))&&(i[s]={}),i=i[s];i[r]=n}function Le(e,t){let n=t.split("."),o=n.pop();if(!o)return;let r=e;for(let i of n){if(r==null||typeof r!="object")return;r=r[i]}r!=null&&typeof r=="object"&&delete r[o]}function J(e){let t={};for(let[n,o]of Object.entries(e))n.includes(".")?Ot(t,n,o):o!==null&&typeof o=="object"&&!Array.isArray(o)?t=A(t,{[n]:o}):t[n]=o;return t}function A(e,t){let n={...e};for(let[o,r]of Object.entries(t))r!==null&&typeof r=="object"&&!Array.isArray(r)&&n[o]!==null&&typeof n[o]=="object"&&!Array.isArray(n[o])?n[o]=A(n[o],r):n[o]=r;return n}function qe(e){return e._zod?.def}function Dt(e){let t=e,n=!1;for(let o=0;o<8;o++){let r=qe(t);if(!r?.innerType)break;if(r.type==="optional"||r.type==="nullable"||r.type==="default"){n=!0,t=r.innerType;continue}break}return{inner:t,optional:n}}function $e(e){let{inner:t,optional:n}=Dt(e),o=t.description??e.description??void 0,r=qe(t),i=r?.type,s={type:"unknown",...o?{description:o}:{},...n?{optional:!0}:{}};return i==="enum"&&r?.entries?{...s,type:"enum",values:Object.keys(r.entries)}:i==="string"||i==="number"||i==="boolean"||i==="object"||i==="array"?{...s,type:i}:s}function ue(e,t){if(!e||!t)return;if(t.includes(".")){let o=t.indexOf("."),r=t.slice(0,o),i=t.slice(o+1),s=e[r];if(!s)return;let p=je(s)?.[i];return p?$e(p):void 0}let n=e[t];if(n)return $e(n)}function pe(e){return e!=null&&e!==""}async function U(e,t){return e.type==="direct"?e.to:e.condition(t)}function He(e,t,n,o,r){if(e.every(c=>pe(H(o,c.field))))return null;let i=e.filter(c=>!pe(H(o,c.field))).map(c=>{let d=ue(r,c.field);return d?{...c,fieldSchema:d}:c}),s=i.length===1,a=i[0];return{content:s&&a?{status:"interrupt",question:a.question,field:a.field,...a.suggestions?{suggestions:a.suggestions}:{},...a.fieldSchema?{fieldSchema:a.fieldSchema}:{},...a.context||t?{context:a.context??t}:{}}:{status:"interrupt",questions:i,...t?{context:t}:{}},flowTokenContent:{step:n,state:o,...s&&a?{field:a.field}:{}}}}async function B(e,t,n,o,r,i,s,a,p){let c=e,d={...t},l=[],u=50,R=0;for(;R++<u;){if(c===F)return{content:{status:"complete"},flowTokenContent:{state:d},nodesVisited:l};let f=n.get(c);if(!f)return{content:{status:"error",error:`Unknown node: "${c}"`},nodesVisited:l};a?.get(c)?.hideFromFunnel||l.push({id:c,at:new Date().toISOString()});try{let g=await f({state:d,meta:i,interrupt:_e,showWidget:Fe,waniwani:s});if(Pe(g)){for(let S of g.questions)S.validate&&r.set(`${c}:${S.field}`,S.validate);let y=He(g.questions,g.context,c,d,p);if(y)return{...y,nodesVisited:l};for(let S of g.questions){let x=r.get(`${c}:${S.field}`);if(x)try{let E=H(d,S.field),b=await x(E);b&&typeof b=="object"&&(d=A(d,b))}catch(E){let b=E instanceof Error?E.message:String(E);Le(d,S.field);let M=g.questions.map(O=>O.field===S.field?{...O,context:O.context?`ERROR: ${b}
|
|
1
|
+
var _="__start__",F="__end__",be=Symbol.for("waniwani.flow.interrupt"),Ce=Symbol.for("waniwani.flow.widget");function _e(e,t){let n=t?.context,o=[];for(let[r,i]of Object.entries(e))if(typeof i=="object"&&i!==null&&"question"in i){let s=i;o.push({question:s.question,field:r,suggestions:s.suggestions,context:s.context,validate:s.validate})}return{__type:be,questions:o,context:n}}function Fe(e,t){let n=typeof e=="object"&&e!==null&&"tool"in e,o=n?e.tool:e,r=n?e:t??{};if(r.description!==void 0){let p=typeof o=="string"?o:o.id;throw new Error(`showWidget("${p}", { description }) is no longer supported. The engine now emits a standardized instruction telling the AI to call the widget tool. Return any per-widget description from the "${p}" tool's own response instead.`)}let{data:i,interactive:s,field:a}=r;return{__type:Ce,tool:typeof o=="string"?o:o.id,...i!==void 0?{data:i}:{},...s!==void 0?{interactive:s}:{},...a!==void 0?{field:a}:{}}}function Pe(e){return typeof e=="object"&&e!==null&&"__type"in e&&e.__type===be}function Ne(e){return typeof e=="object"&&e!==null&&"__type"in e&&e.__type===Ce}import{z as W}from"zod";function G(e){return{priceShown:({amount:t,currency:n,itemId:o,label:r,...i})=>e({event:"price_shown",properties:{amount:t,currency:n,itemId:o,label:r},...i}),pricesCompared:({options:t,...n})=>e({event:"prices_compared",properties:{options:t},...n}),optionSelected:({id:t,amount:n,currency:o,...r})=>e({event:"option_selected",properties:{id:t,amount:n,currency:o},...r}),lead:t=>{let{source:n,...o}=t??{};return e({event:"lead",properties:{source:n},...o})},converted:({amount:t,currency:n,occurredAt:o,...r})=>e({event:"converted",properties:{amount:t,currency:n,occurredAt:o},...r})}}var le="waniwani/client";function Z(e){if(typeof e=="object"&&e!==null)return e[le]}function Ae(e,t,n){let o=r=>e.track({...r,meta:{...t,...r.meta}});return{track:Object.assign(o,G(o)),identify(r,i){return e.identify(r,i,t)},kb:e.kb,_config:n}}function q(e,t){for(let n of t){let o=e[n];if(typeof o=="string"&&o.length>0)return o}}var Ft=["waniwani/sessionId","openai/sessionId","openai/session","sessionId","conversationId","mcp-session-id"],Pt=["waniwani/requestId","openai/requestId","requestId","mcp/requestId"],Nt=["waniwani/traceId","openai/traceId","traceId","mcp/traceId","openai/requestId","requestId"],At=["waniwani/userId","openai/userId","externalUserId","userId","actorId"],Wt=["correlationId","openai/requestId"];var D="waniwani/flow";function P(e){return e?q(e,Ft):void 0}function Me(e){return e?q(e,Pt):void 0}function Ue(e){return e?q(e,Nt):void 0}function Oe(e){return e?q(e,At):void 0}function De(e){return e?q(e,Wt):void 0}var Mt=[{key:"openai/sessionId",source:"chatgpt"},{key:"openai/session",source:"chatgpt"}],Ut=[{needle:"claude",source:"claude"},{needle:"chatgpt",source:"chatgpt"},{needle:"openai",source:"chatgpt"},{needle:"gemini",source:"gemini"}];function N(e,t){if(e){let o=e["waniwani/source"];if(typeof o=="string"&&o.length>0)return o;for(let{key:r,source:i}of Mt){let s=e[r];if(typeof s=="string"&&s.length>0)return i}}let n=t?.name;if(typeof n=="string"&&n.length>0){let o=n.toLowerCase();for(let{needle:r,source:i}of Ut)if(o.includes(r))return i}}function We(e,t){let n=t.toLowerCase(),o=e[t]??e[n];if(o===void 0){for(let i of Object.keys(e))if(i.toLowerCase()===n){o=e[i];break}}let r=Array.isArray(o)?o[0]:o;return typeof r=="string"&&r.length>0?r:void 0}function Ke(e){if(!e)return;let t=We(e,"user-agent"),n=We(e,"x-anthropic-client");if(t&&/claude/i.test(t)||n&&/claude|anthropic/i.test(n))return"claude";if(t&&/chatgpt|openai/i.test(t))return"chatgpt"}function je(e){let t=e._zod?.def;return t?.type==="object"&&t.shape?t.shape:null}function H(e,t){let n=t.split("."),o=e;for(let r of n){if(o==null||typeof o!="object")return;o=o[r]}return o}function Ot(e,t,n){let o=t.split("."),r=o.pop();if(!r)return;let i=e;for(let s of o)(i[s]==null||typeof i[s]!="object"||Array.isArray(i[s]))&&(i[s]={}),i=i[s];i[r]=n}function Le(e,t){let n=t.split("."),o=n.pop();if(!o)return;let r=e;for(let i of n){if(r==null||typeof r!="object")return;r=r[i]}r!=null&&typeof r=="object"&&delete r[o]}function J(e){let t={};for(let[n,o]of Object.entries(e))n.includes(".")?Ot(t,n,o):o!==null&&typeof o=="object"&&!Array.isArray(o)?t=A(t,{[n]:o}):t[n]=o;return t}function A(e,t){let n={...e};for(let[o,r]of Object.entries(t))r!==null&&typeof r=="object"&&!Array.isArray(r)&&n[o]!==null&&typeof n[o]=="object"&&!Array.isArray(n[o])?n[o]=A(n[o],r):n[o]=r;return n}function qe(e){return e._zod?.def}function Dt(e){let t=e,n=!1;for(let o=0;o<8;o++){let r=qe(t);if(!r?.innerType)break;if(r.type==="optional"||r.type==="nullable"||r.type==="default"){n=!0,t=r.innerType;continue}break}return{inner:t,optional:n}}function $e(e){let{inner:t,optional:n}=Dt(e),o=t.description??e.description??void 0,r=qe(t),i=r?.type,s={type:"unknown",...o?{description:o}:{},...n?{optional:!0}:{}};return i==="enum"&&r?.entries?{...s,type:"enum",values:Object.keys(r.entries)}:i==="string"||i==="number"||i==="boolean"||i==="object"||i==="array"?{...s,type:i}:s}function ue(e,t){if(!e||!t)return;if(t.includes(".")){let o=t.indexOf("."),r=t.slice(0,o),i=t.slice(o+1),s=e[r];if(!s)return;let p=je(s)?.[i];return p?$e(p):void 0}let n=e[t];if(n)return $e(n)}function pe(e){return e!=null&&e!==""}async function U(e,t){return e.type==="direct"?e.to:e.condition(t)}function He(e,t,n,o,r){if(e.every(c=>pe(H(o,c.field))))return null;let i=e.filter(c=>!pe(H(o,c.field))).map(c=>{let d=ue(r,c.field);return d?{...c,fieldSchema:d}:c}),s=i.length===1,a=i[0];return{content:s&&a?{status:"interrupt",question:a.question,field:a.field,...a.suggestions?{suggestions:a.suggestions}:{},...a.fieldSchema?{fieldSchema:a.fieldSchema}:{},...a.context||t?{context:a.context??t}:{}}:{status:"interrupt",questions:i,...t?{context:t}:{}},flowTokenContent:{step:n,state:o,...s&&a?{field:a.field}:{}}}}async function B(e,t,n,o,r,i,s,a,p){let c=e,d={...t},l=[],u=50,R=0;for(;R++<u;){if(c===F)return{content:{status:"complete"},flowTokenContent:{state:d},nodesVisited:l};let f=n.get(c);if(!f)return{content:{status:"error",error:`Unknown node: "${c}"`},nodesVisited:l};a?.get(c)?.hideFromFunnel||l.push({id:c,at:new Date().toISOString()});try{let g=await f({state:d,meta:i,interrupt:_e,showWidget:Fe,waniwani:s});if(Pe(g)){for(let S of g.questions)S.validate&&r.set(`${c}:${S.field}`,S.validate);let y=He(g.questions,g.context,c,d,p);if(y)return{...y,nodesVisited:l};for(let S of g.questions){let x=r.get(`${c}:${S.field}`);if(x)try{let E=H(d,S.field),b=await x(E);b&&typeof b=="object"&&(d=A(d,b))}catch(E){let b=E instanceof Error?E.message:String(E);Le(d,S.field);let M=g.questions.map(O=>O.field===S.field?{...O,context:O.context?`ERROR: ${b}
|
|
2
2
|
|
|
3
3
|
${O.context}`:`ERROR: ${b}`}:O),Y=He(M,g.context,c,d,p);if(Y)return{...Y,nodesVisited:l};break}}let w=o.get(c);if(!w)return{content:{status:"error",error:`No outgoing edge from node "${c}"`},nodesVisited:l};c=await U(w,d);continue}if(Ne(g)){let y=g.field;if(y&&pe(H(d,y))){let x=o.get(c);if(!x)return{content:{status:"error",error:`No outgoing edge from node "${c}"`},nodesVisited:l};c=await U(x,d);continue}let w=y?ue(p,y):void 0,S=g.interactive!==!1;return{content:{status:"widget",tool:g.tool,...g.data!==void 0?{data:g.data}:{},description:S?`IMPORTANT: You MUST now call the ${g.tool} tool to display the widget. Do NOT skip this step`:`IMPORTANT \u2014 two ordered steps. STEP 1: call the ${g.tool} tool RIGHT NOW to display this widget to the user. This renders UI the user must see \u2014 do NOT skip it and do NOT jump straight to action:"continue". STEP 2: ONLY AFTER ${g.tool} has been called, call this flow again with action:"continue".`,interactive:S,...y?{field:y}:{},...w?{fieldSchema:w}:{}},flowTokenContent:{step:c,state:d,field:y,widgetId:g.tool},nodesVisited:l}}d=A(d,g);let I=o.get(c);if(!I)return{content:{status:"error",error:`No outgoing edge from node "${c}"`},nodesVisited:l};c=await U(I,d)}catch(T){return{content:{status:"error",error:T instanceof Error?T.message:String(T)},flowTokenContent:{step:c,state:d},nodesVisited:l}}}return{content:{status:"error",error:"Flow exceeded maximum iterations (possible infinite loop)"},nodesVisited:l}}function fe(e){return typeof e=="object"&&e!==null&&e.__ww_enc===1&&typeof e.ct=="string"&&typeof e.iv=="string"}var Be=new Map;async function Ve(e){let t=Be.get(e);if(t)return t;let n=Buffer.from(e,"base64");if(n.length!==32)throw new Error("[Waniwani KV] WANIWANI_ENCRYPTION_KEY must be a base64-encoded 32-byte (256-bit) key.");let o=await globalThis.crypto.subtle.importKey("raw",n,{name:"AES-GCM"},!1,["encrypt","decrypt"]);return Be.set(e,o),o}async function ze(e,t){let n=await Ve(t),o=globalThis.crypto.getRandomValues(new Uint8Array(12)),r=new TextEncoder().encode(JSON.stringify(e)),i=await globalThis.crypto.subtle.encrypt({name:"AES-GCM",iv:o},n,r);return{__ww_enc:1,ct:Buffer.from(i).toString("base64"),iv:Buffer.from(o).toString("base64")}}async function Ye(e,t){let n=await Ve(t),o=Buffer.from(e.ct,"base64"),r=Buffer.from(e.iv,"base64"),i;try{i=await globalThis.crypto.subtle.decrypt({name:"AES-GCM",iv:r},n,o)}catch{throw new Error("[Waniwani KV] Decryption failed. The encryption key may be incorrect or the data may be corrupted.")}return JSON.parse(new TextDecoder().decode(i))}var Kt={debug:0,warn:1,error:2,none:3};function jt(){let e=process.env.WANIWANI_LOG_LEVEL;return e&&e in Kt?e:process.env.WANIWANI_DEBUG?"debug":"none"}function K(e,t){return t??jt()==="debug"?(...o)=>console.log(`[waniwani:${e}]`,...o):()=>{}}var Lt="@waniwani/sdk",$t="https://app.waniwani.ai",qt=K("kv"),j=class{get baseUrl(){return(process.env.WANIWANI_API_URL??$t).replace(/\/$/,"")}get apiKey(){return process.env.WANIWANI_API_KEY}get encryptionKey(){return process.env.WANIWANI_ENCRYPTION_KEY}async get(t){if(!this.apiKey)throw new Error("[Waniwani KV] No API key configured. Set WANIWANI_API_KEY env var.");let n=await this.request("/api/mcp/redis/get",{key:t});if(n==null)return null;if(fe(n)){if(!this.encryptionKey)throw new Error("[Waniwani KV] Encrypted data found but WANIWANI_ENCRYPTION_KEY is not set.");return Ye(n,this.encryptionKey)}return n}async set(t,n,o){if(!this.apiKey)throw new Error("[Waniwani KV] No API key configured. Set WANIWANI_API_KEY env var.");let r=this.encryptionKey;qt(`set "${t}" \u2014 encryption ${r?"enabled":"disabled (no WANIWANI_ENCRYPTION_KEY)"}`);let i=r?await ze(n,r):n;await this.request("/api/mcp/redis/set",{key:t,value:i,ttlSeconds:o?.ttlSeconds})}async delete(t){if(this.apiKey)try{await this.request("/api/mcp/redis/delete",{key:t})}catch(n){console.error("[Waniwani KV] delete failed for key:",t,n)}}async request(t,n){let o=`${this.baseUrl}${t}`,r=await fetch(o,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json","X-WaniWani-SDK":Lt},body:JSON.stringify(n)});if(!r.ok){let s=await r.text().catch(()=>"");throw new Error(s||`KV store API error: HTTP ${r.status}`)}return(await r.json()).data}};var X=class{map=new Map;async get(t){return this.map.get(t)??null}async set(t,n,o){this.map.set(t,n)}async delete(t){this.map.delete(t)}};var V=class{store=new j;get(t){return this.store.get(t)}set(t,n,o){return this.store.set(t,n,o)}delete(t){return this.store.delete(t)}};function Ht(e){let t=e.toString();return t.includes("showWidget")?"widget":t.includes("interrupt")?"interrupt":"action"}function Ge(e,t,n,o){let r=[];for(let[s,a]of t){let p=o.get(s);r.push({id:s,type:Ht(a),label:p?.label??s,...p?.hideFromFunnel?{hideFromFunnel:!0}:{}})}let i=[];for(let[s,a]of n)a.type==="direct"?i.push({from:s,to:a.to,type:"direct"}):i.push({from:s,to:a.targets,type:"conditional"});return{flowId:e.id,title:e.title,nodes:r,edges:i}}import{z as v}from"zod";var Ze=v.object({type:v.enum(["enum","string","number","boolean","object","array","unknown"]),values:v.array(v.string()).optional(),description:v.string().optional(),optional:v.boolean().optional()}).describe("JIT schema fragment for a state field \u2014 type, allowed values, and description."),Bt=v.object({question:v.string(),field:v.string(),suggestions:v.array(v.string()).optional(),context:v.string().optional(),fieldSchema:Ze.optional()}).describe("One question within a multi-question interrupt."),ge={status:v.enum(["interrupt","widget","complete","error"]).describe("Current flow status and the next action the assistant should take."),question:v.string().optional().describe("Single question to ask the user when status is interrupt."),field:v.string().optional().describe("State field to fill with the user's answer on the next continue call."),fieldSchema:Ze.optional().describe("JIT schema fragment for the single-question shorthand."),suggestions:v.array(v.string()).optional().describe("Suggested answers for the single-question shorthand."),questions:v.array(Bt).optional().describe("Multiple questions to ask the user when status is interrupt."),context:v.string().optional().describe("Private instruction context for the assistant."),tool:v.string().optional().describe("Widget tool to call when status is widget."),data:v.record(v.string(),v.unknown()).optional().describe("Input data to pass to the widget tool."),description:v.string().optional().describe("Instruction for rendering the requested widget."),interactive:v.boolean().optional().describe("Whether the widget requires user interaction before continuing."),sessionId:v.string().optional().describe("Session identifier to pass on future continue and reset calls."),error:v.string().optional().describe("Error message when status is error.")};function Je(e){let t=["","## FLOW EXECUTION PROTOCOL","","This tool implements a multi-step conversational flow. Follow this protocol exactly:","",'1. Call with `action: "start"` to begin and include `intent`.'," `intent` must be a brief summary of the user's goal for this flow."," Do NOT invent missing intent."," Optionally include `context` \u2014 the situation or environment that led the user to start"," this flow (e.g. what page they are on, what they were doing, or what triggered the request)."," Only provide `context` when there is genuinely relevant situational information. Do NOT invent missing context."," If the user's message already contains answers to likely questions,"," extract them into `stateUpdates` as `{ field: value }` pairs (see the `stateUpdates` schema"," for the list of writable fields). 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."];return e.omitIntentPII&&t.push(" Do NOT include PII in `intent` or `context` \u2014 no names, emails, phones, addresses, IDs, ages, or birthdates.",' Summarize the goal abstractly (e.g. "user wants a quote", not "Jane Doe wants a quote").'),t.push(" For grouped fields (z.object state), use dot-notation keys in `stateUpdates`:",' e.g. `{ "driver.name": "John", "driver.license": "ABC123" }`.',"2. The response JSON `status` field tells you what to do next:",' - `"interrupt"`: Pause and ask the user. Two forms:'," a. Single question: `{ question, field, fieldSchema?, context? }` \u2014 ask `question`, store answer in `field`."," b. Multi-question: `{ questions: [{question, field, fieldSchema?}, ...], context? }` \u2014 ask ALL questions"," in one conversational message, collect all answers."," `fieldSchema` (when present) describes the expected value: `{ type, values?, description?, optional? }`.",' Use it to validate before sending \u2014 match enum `values` exactly, coerce strings to numbers where `type: "number"`.'," `context` (if present) is hidden AI instructions \u2014 use to shape your response, do NOT show verbatim."," Then call again with:",' `action: "continue"`,'," `stateUpdates` = answers keyed by their `field` names, plus any other fields the user mentioned.",' - `"widget"`: The flow wants to show a UI widget. Call the tool named in the `tool`'," field, passing the `data` object as the tool's input."," Check the `interactive` field in the response:"," \u2022 `interactive: true` \u2014 The widget requires user interaction. After calling the display tool,"," STOP and WAIT for the user to interact with the widget. Do NOT call this flow tool again"," until the user has responded. When they do, call with:",' `action: "continue"`,'," `stateUpdates` = `{ [field]: <user's selection> }` plus any other fields the user mentioned."," \u2022 `interactive: false` \u2014 The widget is display-only. You MUST STILL call the display tool"," FIRST to render it for the user \u2014 this is a required, user-visible step. Do NOT skip it",' and do NOT jump straight to `action: "continue"`. ONLY AFTER you have called the display',' tool, call THIS flow tool again with `action: "continue"`. Do NOT wait for user interaction.',' - `"complete"`: The flow is done. Present the result to the user.',' - `"error"`: Something went wrong. Show the `error` message.',"","3. Do NOT invent state values. Only use `stateUpdates` for information the user explicitly provided.","4. Include only the fields the user actually answered in `stateUpdates` \u2014 do NOT guess missing ones."," If the user did not answer all pending questions, the engine will re-prompt for the remaining ones."," If the user mentioned values for other known fields, include those too \u2014"," they will be applied immediately and those steps will be auto-skipped.","5. CORRECTION: If the user wants to CHANGE a previously-answered field",' (e.g. "actually my email is X" or "go back and change my country"),',' call with `action: "reset"` and `stateUpdates` containing the corrected field(s).'," The engine will restart the flow from the beginning with all existing answers preserved"," plus your corrections. Steps with filled answers will be auto-skipped."," The flow may take a different path if the corrected value affects routing.",' Do NOT use "reset" for the CURRENT question \u2014 use "continue" for that.',"6. If the response includes a `sessionId`, you MUST pass it back as `sessionId`",' in every subsequent "continue" and "reset" call for this flow.'),t.join(`
|
|
4
4
|
`)}function we(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}function Xe(e){let t=Qe(e),n=we(t.waniwani)?t.waniwani:{};return e.meta({...t,waniwani:{...n,redacted:!0}})}function Vt(e){let n=Qe(e).waniwani;return we(n)&&n.redacted===!0}function Qe(e){let t=e.meta;if(typeof t!="function")return{};let n=t.call(e);return we(n)?n:{}}function et(e){if(!e)return[];let t=[];for(let[n,o]of Object.entries(e))Vt(o)&&t.push(n);return t}var Q="waniwani/redactedStateUpdateFields";function zt(e){let t=e.omitIntentPII?" Do not include PII (names, emails, phones, addresses, IDs, ages, birthdates) \u2014 summarize abstractly.":"",o=e.state&&Object.keys(e.state).length>0?W.object(e.state).partial().passthrough():W.record(W.string(),W.unknown());return{action:W.enum(["start","continue","reset"]).describe('"start" to begin the flow, "continue" to resume after a pause (interrupt or widget), "reset" to restart from the beginning with a correction to a previously-collected field'),intent:W.string().optional().describe(`Required when action is "start". Provide a brief summary of the user's goal for this flow. Do not invent missing intent.${t}`),context:W.string().optional().describe(`Optional when action is "start". Describe the situation or environment that led the user to start this flow \u2014 e.g. what page they are on, what they were doing, or what triggered the request. Do not invent missing context.${t}`),stateUpdates:o.optional().describe(`State field values to set before processing the next node. Pass the user's answer (keyed by the field name from the response) and any other values the user mentioned. For nested state fields, use dot-paths like "driver.name".`),sessionId:W.string().optional().describe('Session identifier. If the response includes a `sessionId`, pass it back on every subsequent "continue" and "reset" call for this flow.')}}function Yt(e){if(process.env.WANIWANI_API_KEY)return new V;throw new Error(`[waniwani] createFlow "${e}": no flow store configured. Pass { store } to .compile() \u2014 use MemoryKvStore from "@waniwani/sdk/mcp" for local development, or plug in a Redis/Upstash/Cloudflare KV adapter for production. Alternatively, set WANIWANI_API_KEY to use hosted flow state on app.waniwani.ai.`)}function tt(e){let{config:t,nodes:n,edges:o}=e,r=zt(t),i=Ge(t,n,o,e.nodeOptions),s=Je(t),a=`${t.description}
|