@waniwani/sdk 0.12.0 → 0.12.2
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/index.d.ts +46 -69
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/legacy/index.d.ts +87 -15
- package/dist/legacy/index.js +13 -13
- package/dist/legacy/index.js.map +1 -1
- package/dist/legacy/mcp/react.d.ts +87 -15
- package/dist/legacy/mcp/react.js +7 -7
- package/dist/legacy/mcp/react.js.map +1 -1
- package/dist/mcp/index.js +5 -5
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/react.d.ts +87 -15
- package/dist/mcp/react.js +7 -7
- package/dist/mcp/react.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -59,6 +59,50 @@ interface KbClient {
|
|
|
59
59
|
sources(): Promise<KbSource[]>;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Project-level configuration for WaniWani MCP projects.
|
|
64
|
+
*
|
|
65
|
+
* Mirrors the JSON Schema hosted at https://docs.waniwani.ai/waniwani.json.
|
|
66
|
+
* The canonical config file is `waniwani.json` at the project root:
|
|
67
|
+
*
|
|
68
|
+
* ```json
|
|
69
|
+
* {
|
|
70
|
+
* "$schema": "https://docs.waniwani.ai/waniwani.json",
|
|
71
|
+
* "orgId": "...",
|
|
72
|
+
* "projectId": "..."
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* `waniwani()` and the CLI both read this file automatically — no
|
|
77
|
+
* explicit import is required.
|
|
78
|
+
*/
|
|
79
|
+
interface WaniWaniProjectConfig {
|
|
80
|
+
/** URL of the JSON Schema for editor autocomplete. Ignored at runtime. */
|
|
81
|
+
$schema?: string;
|
|
82
|
+
/** WaniWani organization ID this project belongs to. */
|
|
83
|
+
orgId?: string;
|
|
84
|
+
/** WaniWani MCP project ID. */
|
|
85
|
+
projectId?: string;
|
|
86
|
+
/**
|
|
87
|
+
* The base URL of the WaniWani API.
|
|
88
|
+
* Defaults to `https://app.waniwani.ai`.
|
|
89
|
+
*/
|
|
90
|
+
apiUrl?: string;
|
|
91
|
+
/**
|
|
92
|
+
* Local port the MCP listens on during `waniwani dev`. Overridden by
|
|
93
|
+
* `--port`. Defaults to 3000.
|
|
94
|
+
*/
|
|
95
|
+
devPort?: number;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Register a WaniWani project configuration on `globalThis`.
|
|
99
|
+
*
|
|
100
|
+
* @deprecated Create a `waniwani.json` at the project root instead. The SDK
|
|
101
|
+
* and CLI both auto-load that file — no `defineConfig` call required.
|
|
102
|
+
* See https://docs.waniwani.ai/waniwani.json for the schema.
|
|
103
|
+
*/
|
|
104
|
+
declare function defineConfig(config: WaniWaniProjectConfig): WaniWaniProjectConfig;
|
|
105
|
+
|
|
62
106
|
type EventType = "session.started" | "tool.called" | "quote.requested" | "quote.succeeded" | "quote.failed" | "link.clicked" | "purchase.completed" | "widget_render" | "widget_click" | "widget_link_click" | "widget_error" | "widget_scroll" | "widget_form_field" | "widget_form_submit" | "user.identified";
|
|
63
107
|
interface ToolCalledProperties {
|
|
64
108
|
name?: string;
|
|
@@ -196,73 +240,6 @@ interface TrackingClient {
|
|
|
196
240
|
shutdown: (options?: TrackingShutdownOptions) => Promise<TrackingShutdownResult>;
|
|
197
241
|
}
|
|
198
242
|
|
|
199
|
-
/**
|
|
200
|
-
* Project-level configuration for WaniWani MCP projects.
|
|
201
|
-
*
|
|
202
|
-
* This is the single source of truth for both CLI tools (`waniwani eval`,
|
|
203
|
-
* `waniwani embed`, etc.) and the runtime SDK client (`waniwani()`).
|
|
204
|
-
*
|
|
205
|
-
* Create a `waniwani.config.ts` at the project root:
|
|
206
|
-
* ```ts
|
|
207
|
-
* import { defineConfig } from "@waniwani/sdk";
|
|
208
|
-
*
|
|
209
|
-
* export default defineConfig({
|
|
210
|
-
* apiKey: process.env.WANIWANI_API_KEY,
|
|
211
|
-
* evals: {
|
|
212
|
-
* mcpServerUrl: "http://localhost:3001",
|
|
213
|
-
* },
|
|
214
|
-
* });
|
|
215
|
-
* ```
|
|
216
|
-
*
|
|
217
|
-
* Then import it as a side-effect to register the config globally:
|
|
218
|
-
* ```ts
|
|
219
|
-
* import "./waniwani.config";
|
|
220
|
-
* import { waniwani } from "@waniwani/sdk";
|
|
221
|
-
*
|
|
222
|
-
* const wani = waniwani(); // picks up config from defineConfig
|
|
223
|
-
* ```
|
|
224
|
-
*/
|
|
225
|
-
interface WaniWaniProjectConfig {
|
|
226
|
-
/**
|
|
227
|
-
* Your MCP environment API key.
|
|
228
|
-
* Defaults to `process.env.WANIWANI_API_KEY` if not provided.
|
|
229
|
-
*/
|
|
230
|
-
apiKey?: string;
|
|
231
|
-
/**
|
|
232
|
-
* The base URL of the WaniWani API.
|
|
233
|
-
* Defaults to `https://app.waniwani.ai`.
|
|
234
|
-
*/
|
|
235
|
-
apiUrl?: string;
|
|
236
|
-
/** Tracking transport behavior. */
|
|
237
|
-
tracking?: TrackingConfig;
|
|
238
|
-
evals?: {
|
|
239
|
-
/** Path to the evals directory (relative to project root).
|
|
240
|
-
*
|
|
241
|
-
* @default ./evals */
|
|
242
|
-
dir?: string;
|
|
243
|
-
/** MCP server URL to test against. */
|
|
244
|
-
mcpServerUrl: string;
|
|
245
|
-
};
|
|
246
|
-
knowledgeBase?: {
|
|
247
|
-
/** Path to the knowledge base directory (relative to project root).
|
|
248
|
-
*
|
|
249
|
-
* @default ./knowledge-base
|
|
250
|
-
*/
|
|
251
|
-
dir?: string;
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Define and register a WaniWani project configuration.
|
|
256
|
-
*
|
|
257
|
-
* Calling this stores the config on `globalThis` so that
|
|
258
|
-
* `waniwani()` and `withWaniwani()` can read from it automatically
|
|
259
|
-
* when no explicit config is passed — even across different SDK
|
|
260
|
-
* entry points (`@waniwani/sdk`, `@waniwani/sdk/mcp`, etc.).
|
|
261
|
-
*
|
|
262
|
-
* The config is also returned for direct use.
|
|
263
|
-
*/
|
|
264
|
-
declare function defineConfig(config: WaniWaniProjectConfig): WaniWaniProjectConfig;
|
|
265
|
-
|
|
266
243
|
interface WaniWaniConfig {
|
|
267
244
|
/**
|
|
268
245
|
* Your MCP environment API key
|
|
@@ -347,8 +324,8 @@ interface V2BatchResponse {
|
|
|
347
324
|
/**
|
|
348
325
|
* Create a WaniWani SDK client
|
|
349
326
|
*
|
|
350
|
-
* @param config - Configuration options. When omitted, reads
|
|
351
|
-
*
|
|
327
|
+
* @param config - Configuration options. When omitted, reads `waniwani.json`
|
|
328
|
+
* from the current working directory, then falls back to env vars.
|
|
352
329
|
* @returns A fully typed WaniWani client
|
|
353
330
|
*
|
|
354
331
|
* @example
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var m=class extends Error{constructor(n,r){super(n);this.status=r;this.name="WaniWaniError"}};var P="__waniwani_config__";function W(e){return globalThis[P]=e,e}var U="@waniwani/sdk";function k(e){let{apiUrl:t,apiKey:n}=e;function r(){if(!n)throw new Error("WANIWANI_API_KEY is not set");return n}async function i(s,a,o){let d=r(),g=`${t.replace(/\/$/,"")}${a}`,f={Authorization:`Bearer ${d}`,"X-WaniWani-SDK":U},h={method:s,headers:f};o!==void 0&&(f["Content-Type"]="application/json",h.body=JSON.stringify(o));let u=await fetch(g,h);if(!u.ok){let B=await u.text().catch(()=>"");throw new m(B||`KB API error: HTTP ${u.status}`,u.status)}return(await u.json()).data}return{async ingest(s){return i("POST","/api/mcp/kb/ingest",{files:s})},async search(s,a){return i("POST","/api/mcp/kb/search",{query:s,...a})},async sources(){return i("GET","/api/mcp/kb/sources")}}}function y(e,t){for(let n of t){let r=e[n];if(typeof r=="string"&&r.length>0)return r}}var F=["waniwani/sessionId","openai/sessionId","openai/session","sessionId","conversationId","mcp-session-id"],V=["waniwani/requestId","openai/requestId","requestId","mcp/requestId"],L=["waniwani/traceId","openai/traceId","traceId","mcp/traceId","openai/requestId","requestId"],K=["waniwani/userId","openai/userId","externalUserId","userId","actorId"],j=["correlationId","openai/requestId"];function S(e){return e?y(e,F):void 0}function w(e){return e?y(e,V):void 0}function R(e){return e?y(e,L):void 0}function b(e){return e?y(e,K):void 0}function x(e){return e?y(e,j):void 0}var O=[{key:"waniwani/sessionId",source:"chatbar"},{key:"openai/sessionId",source:"chatgpt"},{key:"openai/session",source:"chatgpt"}];function _(e){if(!e)return;let t=e["waniwani/source"];if(typeof t=="string"&&t.length>0)return t;for(let{key:n,source:r}of O){let i=e[n];if(typeof i=="string"&&i.length>0)return r}}var q="@waniwani/sdk";function v(e,t={}){let n=t.now??(()=>new Date),r=t.generateId??M,i=Y(e),s=T(e.meta),a=T(e.metadata),o=$(e,s),d=c(e.eventId)??r(),g=H(e.timestamp,n),f=c(e.source)??_(s)??t.source??q,h=E(e)?{...e}:void 0,u={...a};return Object.keys(s).length>0&&(u.meta=s),h&&(u.rawLegacy=h),{id:d,type:"mcp.event",name:i,source:f,timestamp:g,correlation:o,properties:N(e,i),metadata:u,rawLegacy:h}}function M(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?`evt_${crypto.randomUUID()}`:`evt_${Math.random().toString(36).slice(2,10)}_${Date.now().toString(36)}`}function N(e,t){if(!E(e))return T(e.properties);let n=z(e,t),r=T(e.properties);return{...n,...r}}function z(e,t){switch(t){case"tool.called":{let n={};return c(e.toolName)&&(n.name=e.toolName),c(e.toolType)&&(n.type=e.toolType),n}case"quote.succeeded":{let n={};return typeof e.quoteAmount=="number"&&(n.amount=e.quoteAmount),c(e.quoteCurrency)&&(n.currency=e.quoteCurrency),n}case"link.clicked":{let n={};return c(e.linkUrl)&&(n.url=e.linkUrl),n}case"purchase.completed":{let n={};return typeof e.purchaseAmount=="number"&&(n.amount=e.purchaseAmount),c(e.purchaseCurrency)&&(n.currency=e.purchaseCurrency),n}default:return{}}}function Y(e){return E(e)?e.eventType:e.event}function $(e,t){let n=c(e.requestId)??w(t),r=c(e.sessionId)??S(t),i=c(e.traceId)??R(t),s=c(e.externalUserId)??b(t),a=c(e.correlationId)??x(t)??n,o={};return r&&(o.sessionId=r),i&&(o.traceId=i),n&&(o.requestId=n),a&&(o.correlationId=a),s&&(o.externalUserId=s),o}function H(e,t){if(e instanceof Date)return e.toISOString();if(typeof e=="string"){let n=new Date(e);if(!Number.isNaN(n.getTime()))return n.toISOString()}return t().toISOString()}function T(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function c(e){if(typeof e=="string"&&e.trim().length!==0)return e}function E(e){return"eventType"in e}var X="/api/mcp/events/v2/batch";var C="@waniwani/sdk",G=new Set([401,403]),J=new Set([408,425,429,500,502,503,504]);function D(e){return new I(e)}var I=class{endpointUrl;flushIntervalMs;maxBatchSize;maxBufferSize;maxRetries;retryBaseDelayMs;retryMaxDelayMs;shutdownTimeoutMs;sdkVersion;fetchFn;logger;now;sleep;apiKey;buffer=[];flushTimer;flushScheduled=!1;flushScheduledTimer;flushInFlight;inFlightCount=0;isStopped=!1;isShuttingDown=!1;constructor(t){this.endpointUrl=ee(t.apiUrl,t.endpointPath??X),this.flushIntervalMs=t.flushIntervalMs??1e3,this.maxBatchSize=t.maxBatchSize??20,this.maxBufferSize=t.maxBufferSize??1e3,this.maxRetries=t.maxRetries??3,this.retryBaseDelayMs=t.retryBaseDelayMs??200,this.retryMaxDelayMs=t.retryMaxDelayMs??2e3,this.shutdownTimeoutMs=t.shutdownTimeoutMs??2e3,this.fetchFn=t.fetchFn??fetch,this.logger=t.logger??console,this.now=t.now??(()=>new Date),this.sleep=t.sleep??(n=>new Promise(r=>setTimeout(r,n))),this.apiKey=t.apiKey,this.sdkVersion=t.sdkVersion,this.flushIntervalMs>0&&(this.flushTimer=setInterval(()=>{this.flush()},this.flushIntervalMs))}enqueue(t){if(this.isStopped||this.isShuttingDown){this.logger.warn("[WaniWani] Tracking transport is stopped, dropping event %s",t.id);return}if(this.buffer.length>=this.maxBufferSize){let n=this.buffer.length-this.maxBufferSize+1;this.buffer.splice(0,n),this.logger.warn("[WaniWani] Tracking buffer overflow, dropped %d oldest event(s)",n)}if(this.buffer.push(t),this.buffer.length>=this.maxBatchSize){this.flush();return}this.scheduleMicroFlush()}pendingEvents(){return this.buffer.length+this.inFlightCount}async flush(){return this.flushInFlight?this.flushInFlight:(this.flushInFlight=this.flushLoop().finally(()=>{this.flushInFlight=void 0}),this.flushInFlight)}async shutdown(t){this.isShuttingDown=!0,this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=void 0),this.flushScheduledTimer&&(clearTimeout(this.flushScheduledTimer),this.flushScheduledTimer=void 0,this.flushScheduled=!1);let n=t?.timeoutMs??this.shutdownTimeoutMs,r=this.flush();if(!Number.isFinite(n)||n<=0)return await r,this.isStopped=!0,{timedOut:!1,pendingEvents:this.pendingEvents()};let i=Symbol("shutdown-timeout");return await Promise.race([r.then(()=>"flushed"),this.sleep(n).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 t=this.buffer.splice(0,this.maxBatchSize);await this.sendBatchWithRetry(t)}}async sendBatchWithRetry(t){let n=0,r=t;for(;r.length>0&&!this.isStopped;){this.inFlightCount=r.length;let 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(n>=this.maxRetries){this.logger.error("[WaniWani] Dropping %d event(s) after retry exhaustion: %s",r.length,i.reason);return}await this.sleep(this.backoffDelayMs(n)),n+=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(n>=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(n)),n+=1;continue}}}async sendBatchOnce(t){let n;try{n=await this.fetchFn(this.endpointUrl,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,"X-WaniWani-SDK":C},body:JSON.stringify(this.makeBatchRequest(t))})}catch(s){return{kind:"retryable",reason:te(s)}}if(G.has(n.status))return{kind:"auth",status:n.status};if(J.has(n.status))return{kind:"retryable",reason:`HTTP ${n.status}`};if(!n.ok)return{kind:"permanent",reason:`HTTP ${n.status}`};let r=await Q(n);if(!r?.rejected||r.rejected.length===0)return{kind:"success"};let i=this.classifyRejectedEvents(t,r.rejected);return i.retryable.length===0&&i.permanent.length===0?{kind:"success"}:{kind:"partial",retryable:i.retryable,permanent:i.permanent}}makeBatchRequest(t){return{sentAt:this.now().toISOString(),source:{sdk:C,version:this.sdkVersion??"0.0.0"},events:t}}classifyRejectedEvents(t,n){let r=new Map(t.map(a=>[a.id,a])),i=[],s=[];for(let a of n){let o=r.get(a.eventId);if(o){if(Z(a)){i.push(o);continue}s.push(o)}}return{retryable:i,permanent:s}}backoffDelayMs(t){let n=this.retryBaseDelayMs*2**t;return Math.min(n,this.retryMaxDelayMs)}stopTransportForAuthFailure(t,n){this.isStopped=!0;let r=this.buffer.length;this.buffer.splice(0,r),this.logger.error("[WaniWani] Auth failure (HTTP %d). Stopping tracking transport and dropping %d queued event(s)",t,n+r)}};function Z(e){if(e.retryable===!0)return!0;let t=e.code.toLowerCase();return t.includes("timeout")||t.includes("temporary")||t.includes("unavailable")||t.includes("rate_limit")||t.includes("transient")||t.includes("server")}async function Q(e){let t=await e.text();if(t)try{return JSON.parse(t)}catch{return}}function ee(e,t){let n=e.endsWith("/")?e:`${e}/`,r=t.startsWith("/")?t.slice(1):t;return`${n}${r}`}function te(e){return e instanceof Error?e.message:String(e)}function A(e){let{apiUrl:t,apiKey:n,tracking:r}=e;function i(){if(!n)throw new Error("WANIWANI_API_KEY is not set");return n}let s=n?D({apiUrl:t,apiKey:n,endpointPath:r.endpointPath,flushIntervalMs:r.flushIntervalMs,maxBatchSize:r.maxBatchSize,maxBufferSize:r.maxBufferSize,maxRetries:r.maxRetries,retryBaseDelayMs:r.retryBaseDelayMs,retryMaxDelayMs:r.retryMaxDelayMs,shutdownTimeoutMs:r.shutdownTimeoutMs}):void 0,a={async identify(o,d,g){i();let f=v({event:"user.identified",externalUserId:o,properties:d,meta:g});return s?.enqueue(f),{eventId:f.id}},async track(o){i();let d=v(o);return s?.enqueue(d),{eventId:d.id}},async flush(){i(),await s?.flush()},async shutdown(o){return i(),await s?.shutdown({timeoutMs:o?.timeoutMs??r.shutdownTimeoutMs})??{timedOut:!1,pendingEvents:0}}};return s&&ne(a,r.shutdownTimeoutMs),a}function ne(e,t){if(typeof process>"u"||typeof process.once!="function"||typeof process.on!="function")return;let n=()=>{e.shutdown({timeoutMs:t})};process.once("beforeExit",n),process.once("SIGINT",n),process.once("SIGTERM",n)}function re(e){let t=e,n=t?.apiUrl??"https://app.waniwani.ai",r=t?.apiKey??process.env.WANIWANI_API_KEY,i={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},s={apiUrl:n,apiKey:r,tracking:i},a=A(s),o=k(s);return{...a,kb:o,_config:s}}export{m as WaniWaniError,W as defineConfig,re as waniwani};
|
|
1
|
+
var y=class extends Error{constructor(t,r){super(t);this.status=r;this.name="WaniWaniError"}};import{existsSync as j,readFileSync as V}from"fs";import{resolve as L}from"path";var O="waniwani.json",g;function w(){if(g!==void 0)return g;try{let e=L(process.cwd(),O);if(!j(e))return g=null,null;let n=V(e,"utf-8");return g=JSON.parse(n),g}catch{return g=null,null}}var k="__waniwani_config__";function K(e){return globalThis[k]=e,e}function R(){return globalThis[k]}var N="@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,a,o){let u=r(),m=`${n.replace(/\/$/,"")}${a}`,f={Authorization:`Bearer ${u}`,"X-WaniWani-SDK":N},h={method:s,headers:f};o!==void 0&&(f["Content-Type"]="application/json",h.body=JSON.stringify(o));let d=await fetch(m,h);if(!d.ok){let U=await d.text().catch(()=>"");throw new y(U||`KB API error: HTTP ${d.status}`,d.status)}return(await d.json()).data}return{async ingest(s){return i("POST","/api/mcp/kb/ingest",{files:s})},async search(s,a){return i("POST","/api/mcp/kb/search",{query:s,...a})},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 q=["waniwani/sessionId","openai/sessionId","openai/session","sessionId","conversationId","mcp-session-id"],z=["waniwani/requestId","openai/requestId","requestId","mcp/requestId"],Y=["waniwani/traceId","openai/traceId","traceId","mcp/traceId","openai/requestId","requestId"],$=["waniwani/userId","openai/userId","externalUserId","userId","actorId"],H=["correlationId","openai/requestId"];function x(e){return e?v(e,q):void 0}function _(e){return e?v(e,z):void 0}function M(e){return e?v(e,Y):void 0}function C(e){return e?v(e,$):void 0}function D(e){return e?v(e,H):void 0}var X=[{key:"waniwani/sessionId",source:"chatbar"},{key:"openai/sessionId",source:"chatgpt"},{key:"openai/session",source:"chatgpt"}];function W(e){if(!e)return;let n=e["waniwani/source"];if(typeof n=="string"&&n.length>0)return n;for(let{key:t,source:r}of X){let i=e[t];if(typeof i=="string"&&i.length>0)return r}}var G="@waniwani/sdk";function E(e,n={}){let t=n.now??(()=>new Date),r=n.generateId??A,i=Q(e),s=T(e.meta),a=T(e.metadata),o=ee(e,s),u=c(e.eventId)??r(),m=te(e.timestamp,t),f=c(e.source)??W(s)??n.source??G,h=I(e)?{...e}:void 0,d={...a};return Object.keys(s).length>0&&(d.meta=s),h&&(d.rawLegacy=h),{id:u,type:"mcp.event",name:i,source:f,timestamp:m,correlation:o,properties:J(e,i),metadata:d,rawLegacy:h}}function A(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?`evt_${crypto.randomUUID()}`:`evt_${Math.random().toString(36).slice(2,10)}_${Date.now().toString(36)}`}function J(e,n){if(!I(e))return T(e.properties);let t=Z(e,n),r=T(e.properties);return{...t,...r}}function Z(e,n){switch(n){case"tool.called":{let t={};return c(e.toolName)&&(t.name=e.toolName),c(e.toolType)&&(t.type=e.toolType),t}case"quote.succeeded":{let t={};return typeof e.quoteAmount=="number"&&(t.amount=e.quoteAmount),c(e.quoteCurrency)&&(t.currency=e.quoteCurrency),t}case"link.clicked":{let t={};return c(e.linkUrl)&&(t.url=e.linkUrl),t}case"purchase.completed":{let t={};return typeof e.purchaseAmount=="number"&&(t.amount=e.purchaseAmount),c(e.purchaseCurrency)&&(t.currency=e.purchaseCurrency),t}default:return{}}}function Q(e){return I(e)?e.eventType:e.event}function ee(e,n){let t=c(e.requestId)??_(n),r=c(e.sessionId)??x(n),i=c(e.traceId)??M(n),s=c(e.externalUserId)??C(n),a=c(e.correlationId)??D(n)??t,o={};return r&&(o.sessionId=r),i&&(o.traceId=i),t&&(o.requestId=t),a&&(o.correlationId=a),s&&(o.externalUserId=s),o}function te(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 c(e){if(typeof e=="string"&&e.trim().length!==0)return e}function I(e){return"eventType"in e}var ne="/api/mcp/events/v2/batch";var P="@waniwani/sdk",re=new Set([401,403]),ie=new Set([408,425,429,500,502,503,504]);function B(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=ae(n.apiUrl,n.endpointPath??ne),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":P},body:JSON.stringify(this.makeBatchRequest(n))})}catch(s){return{kind:"retryable",reason:ce(s)}}if(re.has(t.status))return{kind:"auth",status:t.status};if(ie.has(t.status))return{kind:"retryable",reason:`HTTP ${t.status}`};if(!t.ok)return{kind:"permanent",reason:`HTTP ${t.status}`};let r=await oe(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:P,version:this.sdkVersion??"0.0.0"},events:n}}classifyRejectedEvents(n,t){let r=new Map(n.map(a=>[a.id,a])),i=[],s=[];for(let a of t){let o=r.get(a.eventId);if(o){if(se(a)){i.push(o);continue}s.push(o)}}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 se(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 oe(e){let n=await e.text();if(n)try{return JSON.parse(n)}catch{return}}function ae(e,n){let t=e.endsWith("/")?e:`${e}/`,r=n.startsWith("/")?n.slice(1):n;return`${t}${r}`}function ce(e){return e instanceof Error?e.message:String(e)}function F(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?B({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,a={async identify(o,u,m){i();let f=E({event:"user.identified",externalUserId:o,properties:u,meta:m});return s?.enqueue(f),{eventId:f.id}},async track(o){i();let u=E(o);return s?.enqueue(u),{eventId:u.id}},async flush(){i(),await s?.flush()},async shutdown(o){return i(),await s?.shutdown({timeoutMs:o?.timeoutMs??r.shutdownTimeoutMs})??{timedOut:!1,pendingEvents:0}}};return s&&ue(a,r.shutdownTimeoutMs),a}function ue(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 de(e){let t=e??w()??R(),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},a={apiUrl:r,apiKey:i,tracking:s},o=F(a),u=b(a);return{...o,kb:u,_config:a}}export{y as WaniWaniError,K as defineConfig,de 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/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 type { TrackingConfig } from \"./tracking/@types.js\";\n\n/**\n * Project-level configuration for WaniWani MCP projects.\n *\n * This is the single source of truth for both CLI tools (`waniwani eval`,\n * `waniwani embed`, etc.) and the runtime SDK client (`waniwani()`).\n *\n * Create a `waniwani.config.ts` at the project root:\n * ```ts\n * import { defineConfig } from \"@waniwani/sdk\";\n *\n * export default defineConfig({\n * apiKey: process.env.WANIWANI_API_KEY,\n * evals: {\n * mcpServerUrl: \"http://localhost:3001\",\n * },\n * });\n * ```\n *\n * Then import it as a side-effect to register the config globally:\n * ```ts\n * import \"./waniwani.config\";\n * import { waniwani } from \"@waniwani/sdk\";\n *\n * const wani = waniwani(); // picks up config from defineConfig\n * ```\n */\nexport interface WaniWaniProjectConfig {\n\t/**\n\t * Your MCP environment API key.\n\t * Defaults to `process.env.WANIWANI_API_KEY` if not provided.\n\t */\n\tapiKey?: 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/** Tracking transport behavior. */\n\ttracking?: TrackingConfig;\n\tevals?: {\n\t\t/** Path to the evals directory (relative to project root).\n\t\t *\n\t\t * @default ./evals */\n\t\tdir?: string;\n\t\t/** MCP server URL to test against. */\n\t\tmcpServerUrl: string;\n\t};\n\tknowledgeBase?: {\n\t\t/** Path to the knowledge base directory (relative to project root).\n\t\t *\n\t\t * @default ./knowledge-base\n\t\t */\n\t\tdir?: string;\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Global singleton — uses globalThis so the config is shared across all\n// SDK entry points (e.g. @waniwani/sdk and @waniwani/sdk/mcp) even when\n// they are bundled as separate chunks with their own module scopes.\n// ---------------------------------------------------------------------------\n\nconst GLOBAL_KEY = \"__waniwani_config__\" as const;\n\n/**\n * Define and register a WaniWani project configuration.\n *\n * Calling this stores the config on `globalThis` so that\n * `waniwani()` and `withWaniwani()` can read from it automatically\n * when no explicit config is passed — even across different SDK\n * entry points (`@waniwani/sdk`, `@waniwani/sdk/mcp`, etc.).\n *\n * The config is also returned for direct use.\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 * Returns `undefined` if `defineConfig` has not been called.\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: \"waniwani/sessionId\", source: \"chatbar\" },\n\t{ key: \"openai/sessionId\", source: \"chatgpt\" },\n\t{ key: \"openai/session\", source: \"chatgpt\" },\n] as const;\n\nexport function extractSource(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\tif (!meta) {\n\t\treturn undefined;\n\t}\n\t// Explicit source set by the caller (e.g. chatbar name)\n\tconst explicit = meta[\"waniwani/source\"];\n\tif (typeof explicit === \"string\" && explicit.length > 0) {\n\t\treturn explicit;\n\t}\n\t// Derive from session ID key\n\tfor (const { key, source } of SOURCE_SESSION_KEYS) {\n\t\tconst value = meta[key];\n\t\tif (typeof value === \"string\" && value.length > 0) {\n\t\t\treturn source;\n\t\t}\n\t}\n\treturn undefined;\n}\n","import {\n\textractCorrelationId,\n\textractExternalUserId,\n\textractRequestId,\n\textractSessionId,\n\textractSource,\n\textractTraceId,\n} from \"../mcp/server/utils.js\";\nimport type { EventType, LegacyTrackEvent, TrackInput } from \"./@types.js\";\nimport type { V2CorrelationIds, V2EventEnvelope } from \"./v2-types.js\";\n\nconst DEFAULT_SOURCE = \"@waniwani/sdk\";\n\nexport interface MapTrackEventOptions {\n\tnow?: () => Date;\n\tgenerateId?: () => string;\n\tsource?: string;\n}\n\nexport function mapTrackEventToV2(\n\tinput: TrackInput,\n\toptions: MapTrackEventOptions = {},\n): V2EventEnvelope {\n\tconst now = options.now ?? (() => new Date());\n\tconst generateId = options.generateId ?? createEventId;\n\tconst eventName = resolveEventName(input);\n\tconst meta = toRecord(input.meta);\n\tconst metadata = toRecord(input.metadata);\n\tconst correlation = resolveCorrelationIds(input, meta);\n\tconst eventId = takeNonEmptyString(input.eventId) ?? generateId();\n\tconst timestamp = normalizeTimestamp(input.timestamp, now);\n\tconst source =\n\t\ttakeNonEmptyString(input.source) ??\n\t\textractSource(meta) ??\n\t\toptions.source ??\n\t\tDEFAULT_SOURCE;\n\tconst rawLegacy = isLegacyTrackEvent(input) ? { ...input } : undefined;\n\n\tconst mappedMetadata: Record<string, unknown> = {\n\t\t...metadata,\n\t};\n\tif (Object.keys(meta).length > 0) {\n\t\tmappedMetadata.meta = meta;\n\t}\n\tif (rawLegacy) {\n\t\tmappedMetadata.rawLegacy = rawLegacy;\n\t}\n\n\treturn {\n\t\tid: eventId,\n\t\ttype: \"mcp.event\",\n\t\tname: eventName,\n\t\tsource,\n\t\ttimestamp,\n\t\tcorrelation,\n\t\tproperties: mapProperties(input, eventName),\n\t\tmetadata: mappedMetadata,\n\t\trawLegacy,\n\t};\n}\n\nexport function createEventId(): string {\n\tif (\n\t\ttypeof crypto !== \"undefined\" &&\n\t\ttypeof crypto.randomUUID === \"function\"\n\t) {\n\t\treturn `evt_${crypto.randomUUID()}`;\n\t}\n\n\treturn `evt_${Math.random().toString(36).slice(2, 10)}_${Date.now().toString(36)}`;\n}\n\nfunction mapProperties(\n\tinput: TrackInput,\n\teventName: EventType,\n): Record<string, unknown> {\n\tif (!isLegacyTrackEvent(input)) {\n\t\treturn toRecord(input.properties);\n\t}\n\n\tconst legacyProperties = mapLegacyProperties(input, eventName);\n\tconst explicitProperties = toRecord(input.properties);\n\treturn {\n\t\t...legacyProperties,\n\t\t...explicitProperties,\n\t};\n}\n\nfunction mapLegacyProperties(\n\tinput: LegacyTrackEvent,\n\teventName: EventType,\n): Record<string, unknown> {\n\tswitch (eventName) {\n\t\tcase \"tool.called\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (takeNonEmptyString(input.toolName)) {\n\t\t\t\tproperties.name = input.toolName;\n\t\t\t}\n\t\t\tif (takeNonEmptyString(input.toolType)) {\n\t\t\t\tproperties.type = input.toolType;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tcase \"quote.succeeded\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (typeof input.quoteAmount === \"number\") {\n\t\t\t\tproperties.amount = input.quoteAmount;\n\t\t\t}\n\t\t\tif (takeNonEmptyString(input.quoteCurrency)) {\n\t\t\t\tproperties.currency = input.quoteCurrency;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tcase \"link.clicked\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (takeNonEmptyString(input.linkUrl)) {\n\t\t\t\tproperties.url = input.linkUrl;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tcase \"purchase.completed\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (typeof input.purchaseAmount === \"number\") {\n\t\t\t\tproperties.amount = input.purchaseAmount;\n\t\t\t}\n\t\t\tif (takeNonEmptyString(input.purchaseCurrency)) {\n\t\t\t\tproperties.currency = input.purchaseCurrency;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tdefault:\n\t\t\treturn {};\n\t}\n}\n\nfunction resolveEventName(input: TrackInput): EventType {\n\tif (isLegacyTrackEvent(input)) {\n\t\treturn input.eventType;\n\t}\n\treturn input.event;\n}\n\nfunction resolveCorrelationIds(\n\tinput: TrackInput,\n\tmeta: Record<string, unknown>,\n): V2CorrelationIds {\n\tconst requestId =\n\t\ttakeNonEmptyString(input.requestId) ?? extractRequestId(meta);\n\n\tconst sessionId =\n\t\ttakeNonEmptyString(input.sessionId) ?? extractSessionId(meta);\n\n\tconst traceId = takeNonEmptyString(input.traceId) ?? extractTraceId(meta);\n\n\tconst externalUserId =\n\t\ttakeNonEmptyString(input.externalUserId) ?? extractExternalUserId(meta);\n\n\tconst correlationId =\n\t\ttakeNonEmptyString(input.correlationId) ??\n\t\textractCorrelationId(meta) ??\n\t\trequestId;\n\n\tconst correlation: V2CorrelationIds = {};\n\tif (sessionId) {\n\t\tcorrelation.sessionId = sessionId;\n\t}\n\tif (traceId) {\n\t\tcorrelation.traceId = traceId;\n\t}\n\tif (requestId) {\n\t\tcorrelation.requestId = requestId;\n\t}\n\tif (correlationId) {\n\t\tcorrelation.correlationId = correlationId;\n\t}\n\tif (externalUserId) {\n\t\tcorrelation.externalUserId = externalUserId;\n\t}\n\treturn correlation;\n}\n\nfunction normalizeTimestamp(\n\tinput: string | Date | undefined,\n\tnow: () => Date,\n): string {\n\tif (input instanceof Date) {\n\t\treturn input.toISOString();\n\t}\n\tif (typeof input === \"string\") {\n\t\tconst date = new Date(input);\n\t\tif (!Number.isNaN(date.getTime())) {\n\t\t\treturn date.toISOString();\n\t\t}\n\t}\n\treturn now().toISOString();\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\treturn {};\n\t}\n\treturn value as Record<string, unknown>;\n}\n\nfunction takeNonEmptyString(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") {\n\t\treturn undefined;\n\t}\n\tif (value.trim().length === 0) {\n\t\treturn undefined;\n\t}\n\treturn value;\n}\n\nfunction isLegacyTrackEvent(input: TrackInput): input is LegacyTrackEvent {\n\treturn \"eventType\" in input;\n}\n","import type {\n\tTrackingShutdownOptions,\n\tTrackingShutdownResult,\n} from \"./@types.js\";\nimport type {\n\tV2BatchRejectedEvent,\n\tV2BatchRequest,\n\tV2BatchResponse,\n\tV2EventEnvelope,\n} from \"./v2-types.js\";\n\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\tTrackInput,\n\tTrackingClient,\n\tTrackingShutdownOptions,\n} from \"./@types.js\";\nimport { mapTrackEventToV2 } from \"./mapper.js\";\nimport { createV2BatchTransport } from \"./transport.js\";\n\n// Re-export types\nexport type {\n\tEventType,\n\tLegacyTrackEvent,\n\tLinkClickedProperties,\n\tPurchaseCompletedProperties,\n\tQuoteSucceededProperties,\n\tToolCalledProperties,\n\tTrackEvent,\n\tTrackInput,\n\tTrackingClient,\n\tTrackingConfig,\n\tTrackingShutdownOptions,\n\tTrackingShutdownResult,\n} from \"./@types.js\";\nexport { createEventId, mapTrackEventToV2 } from \"./mapper.js\";\nexport type {\n\tV2BatchRejectedEvent,\n\tV2BatchRequest,\n\tV2BatchResponse,\n\tV2CorrelationIds,\n\tV2EnvelopeType,\n\tV2EventEnvelope,\n} from \"./v2-types.js\";\n\nexport function createTrackingClient(config: InternalConfig): TrackingClient {\n\tconst { 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\tconst client: TrackingClient = {\n\t\tasync identify(\n\t\t\tuserId: string,\n\t\t\tproperties?: Record<string, unknown>,\n\t\t\tmeta?: Record<string, unknown>,\n\t\t): Promise<{ eventId: string }> {\n\t\t\trequireApiKey();\n\t\t\tconst mappedEvent = mapTrackEventToV2({\n\t\t\t\tevent: \"user.identified\",\n\t\t\t\texternalUserId: userId,\n\t\t\t\tproperties,\n\t\t\t\tmeta,\n\t\t\t});\n\t\t\ttransport?.enqueue(mappedEvent);\n\t\t\treturn { eventId: mappedEvent.id };\n\t\t},\n\t\tasync track(event: TrackInput): Promise<{ eventId: string }> {\n\t\t\trequireApiKey();\n\t\t\tconst mappedEvent = mapTrackEventToV2(event);\n\t\t\ttransport?.enqueue(mappedEvent);\n\t\t\treturn { eventId: mappedEvent.id };\n\t\t},\n\t\tasync flush(): Promise<void> {\n\t\t\trequireApiKey();\n\t\t\tawait transport?.flush();\n\t\t},\n\t\tasync shutdown(options?: TrackingShutdownOptions) {\n\t\t\trequireApiKey();\n\t\t\treturn (\n\t\t\t\t(await transport?.shutdown({\n\t\t\t\t\ttimeoutMs: options?.timeoutMs ?? tracking.shutdownTimeoutMs,\n\t\t\t\t})) ?? { timedOut: false, pendingEvents: 0 }\n\t\t\t);\n\t\t},\n\t};\n\n\tif (transport) {\n\t\tattachShutdownHooks(client, tracking.shutdownTimeoutMs);\n\t}\n\treturn client;\n}\n\nfunction attachShutdownHooks(\n\tclient: TrackingClient,\n\tdefaultTimeoutMs: number,\n): void {\n\tif (\n\t\ttypeof process === \"undefined\" ||\n\t\ttypeof process.once !== \"function\" ||\n\t\ttypeof process.on !== \"function\"\n\t) {\n\t\treturn;\n\t}\n\n\tconst shutdown = () => {\n\t\tvoid client.shutdown({ timeoutMs: defaultTimeoutMs });\n\t};\n\n\tprocess.once(\"beforeExit\", shutdown);\n\tprocess.once(\"SIGINT\", shutdown);\n\tprocess.once(\"SIGTERM\", shutdown);\n}\n","// WaniWani SDK - Main Entry\n\nimport { createKbClient } from \"./kb/client.js\";\nimport type { WaniWaniProjectConfig } 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 from the global\n * config registered by `defineConfig()`, 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 effective = config;\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,ECsDA,IAAMC,EAAa,sBAYZ,SAASC,EACfC,EACwB,CACxB,OAAC,WAAuCF,CAAU,EAAIE,EAC/CA,CACR,CCpEA,IAAMC,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,qBAAsB,OAAQ,SAAU,EAC/C,CAAE,IAAK,mBAAoB,OAAQ,SAAU,EAC7C,CAAE,IAAK,iBAAkB,OAAQ,SAAU,CAC5C,EAEO,SAASC,EACfC,EACqB,CACrB,GAAI,CAACA,EACJ,OAGD,IAAMC,EAAWD,EAAK,iBAAiB,EACvC,GAAI,OAAOC,GAAa,UAAYA,EAAS,OAAS,EACrD,OAAOA,EAGR,OAAW,CAAE,IAAAC,EAAK,OAAAC,CAAO,IAAKL,EAAqB,CAClD,IAAMM,EAAQJ,EAAKE,CAAG,EACtB,GAAI,OAAOE,GAAU,UAAYA,EAAM,OAAS,EAC/C,OAAOD,CAET,CAED,CCjIA,IAAME,EAAiB,gBAQhB,SAASC,EACfC,EACAC,EAAgC,CAAC,EACf,CAClB,IAAMC,EAAMD,EAAQ,MAAQ,IAAM,IAAI,MAChCE,EAAaF,EAAQ,YAAcG,EACnCC,EAAYC,EAAiBN,CAAK,EAClCO,EAAOC,EAASR,EAAM,IAAI,EAC1BS,EAAWD,EAASR,EAAM,QAAQ,EAClCU,EAAcC,EAAsBX,EAAOO,CAAI,EAC/CK,EAAUC,EAAmBb,EAAM,OAAO,GAAKG,EAAW,EAC1DW,EAAYC,EAAmBf,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,EAAcrB,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,EACRrB,EACAK,EAC0B,CAC1B,GAAI,CAACc,EAAmBnB,CAAK,EAC5B,OAAOQ,EAASR,EAAM,UAAU,EAGjC,IAAMsB,EAAmBC,EAAoBvB,EAAOK,CAAS,EACvDmB,EAAqBhB,EAASR,EAAM,UAAU,EACpD,MAAO,CACN,GAAGsB,EACH,GAAGE,CACJ,CACD,CAEA,SAASD,EACRvB,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,EAAiBN,EAA8B,CACvD,OAAImB,EAAmBnB,CAAK,EACpBA,EAAM,UAEPA,EAAM,KACd,CAEA,SAASW,EACRX,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,EACRf,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,CCxMA,IAAMsC,EAAwB,2BAQ9B,IAAMC,EAAW,gBAEXC,EAAsB,IAAI,IAAI,CAAC,IAAK,GAAG,CAAC,EACxCC,EAAmB,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,CACzB,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,EAAoB,IAAImB,EAAS,MAAM,EAC1C,MAAO,CAAE,KAAM,OAAQ,OAAQA,EAAS,MAAO,EAGhD,GAAIlB,EAAiB,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,EAAmCJ,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,EAAyBD,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,EACRD,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,EACdJ,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,CC9ZO,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,OAEGI,EAAyB,CAC9B,MAAM,SACLC,EACAC,EACAC,EAC+B,CAC/BN,EAAc,EACd,IAAMO,EAAcC,EAAkB,CACrC,MAAO,kBACP,eAAgBJ,EAChB,WAAAC,EACA,KAAAC,CACD,CAAC,EACD,OAAAL,GAAW,QAAQM,CAAW,EACvB,CAAE,QAASA,EAAY,EAAG,CAClC,EACA,MAAM,MAAME,EAAiD,CAC5DT,EAAc,EACd,IAAMO,EAAcC,EAAkBC,CAAK,EAC3C,OAAAR,GAAW,QAAQM,CAAW,EACvB,CAAE,QAASA,EAAY,EAAG,CAClC,EACA,MAAM,OAAuB,CAC5BP,EAAc,EACd,MAAMC,GAAW,MAAM,CACxB,EACA,MAAM,SAASS,EAAmC,CACjD,OAAAV,EAAc,EAEZ,MAAMC,GAAW,SAAS,CAC1B,UAAWS,GAAS,WAAaX,EAAS,iBAC3C,CAAC,GAAM,CAAE,SAAU,GAAO,cAAe,CAAE,CAE7C,CACD,EAEA,OAAIE,GACHU,GAAoBR,EAAQJ,EAAS,iBAAiB,EAEhDI,CACR,CAEA,SAASQ,GACRR,EACAS,EACO,CACP,GACC,OAAO,QAAY,KACnB,OAAO,QAAQ,MAAS,YACxB,OAAO,QAAQ,IAAO,WAEtB,OAGD,IAAMC,EAAW,IAAM,CACjBV,EAAO,SAAS,CAAE,UAAWS,CAAiB,CAAC,CACrD,EAEA,QAAQ,KAAK,aAAcC,CAAQ,EACnC,QAAQ,KAAK,SAAUA,CAAQ,EAC/B,QAAQ,KAAK,UAAWA,CAAQ,CACjC,CC/FO,SAASC,GACfC,EACiB,CACjB,IAAMC,EAAYD,EAEZE,EAASD,GAAW,QAAU,0BAC9BE,EAASF,GAAW,QAAU,QAAQ,IAAI,iBAC1CG,EAAiB,CACtB,aACCH,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,EAEMI,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","GLOBAL_KEY","defineConfig","config","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","extractSource","meta","explicit","key","source","value","DEFAULT_SOURCE","mapTrackEventToV2","input","options","now","generateId","createEventId","eventName","resolveEventName","meta","toRecord","metadata","correlation","resolveCorrelationIds","eventId","takeNonEmptyString","timestamp","normalizeTimestamp","source","extractSource","rawLegacy","isLegacyTrackEvent","mappedMetadata","mapProperties","legacyProperties","mapLegacyProperties","explicitProperties","properties","requestId","extractRequestId","sessionId","extractSessionId","traceId","extractTraceId","externalUserId","extractExternalUserId","correlationId","extractCorrelationId","date","value","DEFAULT_ENDPOINT_PATH","SDK_NAME","AUTH_FAILURE_STATUS","RETRYABLE_STATUS","createV2BatchTransport","options","BatchingV2Transport","joinUrl","DEFAULT_ENDPOINT_PATH","delayMs","resolve","event","dropCount","timeoutMs","flushPromise","timeoutSignal","batch","attempt","pendingBatch","result","events","response","error","getErrorMessage","data","parseJsonResponse","partial","rejected","byId","retryable","permanent","rejectedEvent","isRetryableRejectedEvent","rawDelay","status","rejectedCount","buffered","code","body","apiUrl","endpointPath","normalizedBase","normalizedPath","createTrackingClient","config","apiUrl","apiKey","tracking","requireApiKey","transport","createV2BatchTransport","client","userId","properties","meta","mappedEvent","mapTrackEventToV2","event","options","attachShutdownHooks","defaultTimeoutMs","shutdown","waniwani","config","effective","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/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: \"waniwani/sessionId\", source: \"chatbar\" },\n\t{ key: \"openai/sessionId\", source: \"chatgpt\" },\n\t{ key: \"openai/session\", source: \"chatgpt\" },\n] as const;\n\nexport function extractSource(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\tif (!meta) {\n\t\treturn undefined;\n\t}\n\t// Explicit source set by the caller (e.g. chatbar name)\n\tconst explicit = meta[\"waniwani/source\"];\n\tif (typeof explicit === \"string\" && explicit.length > 0) {\n\t\treturn explicit;\n\t}\n\t// Derive from session ID key\n\tfor (const { key, source } of SOURCE_SESSION_KEYS) {\n\t\tconst value = meta[key];\n\t\tif (typeof value === \"string\" && value.length > 0) {\n\t\t\treturn source;\n\t\t}\n\t}\n\treturn undefined;\n}\n","import {\n\textractCorrelationId,\n\textractExternalUserId,\n\textractRequestId,\n\textractSessionId,\n\textractSource,\n\textractTraceId,\n} from \"../mcp/server/utils.js\";\nimport type { EventType, LegacyTrackEvent, TrackInput } from \"./@types.js\";\nimport type { V2CorrelationIds, V2EventEnvelope } from \"./v2-types.js\";\n\nconst DEFAULT_SOURCE = \"@waniwani/sdk\";\n\nexport interface MapTrackEventOptions {\n\tnow?: () => Date;\n\tgenerateId?: () => string;\n\tsource?: string;\n}\n\nexport function mapTrackEventToV2(\n\tinput: TrackInput,\n\toptions: MapTrackEventOptions = {},\n): V2EventEnvelope {\n\tconst now = options.now ?? (() => new Date());\n\tconst generateId = options.generateId ?? createEventId;\n\tconst eventName = resolveEventName(input);\n\tconst meta = toRecord(input.meta);\n\tconst metadata = toRecord(input.metadata);\n\tconst correlation = resolveCorrelationIds(input, meta);\n\tconst eventId = takeNonEmptyString(input.eventId) ?? generateId();\n\tconst timestamp = normalizeTimestamp(input.timestamp, now);\n\tconst source =\n\t\ttakeNonEmptyString(input.source) ??\n\t\textractSource(meta) ??\n\t\toptions.source ??\n\t\tDEFAULT_SOURCE;\n\tconst rawLegacy = isLegacyTrackEvent(input) ? { ...input } : undefined;\n\n\tconst mappedMetadata: Record<string, unknown> = {\n\t\t...metadata,\n\t};\n\tif (Object.keys(meta).length > 0) {\n\t\tmappedMetadata.meta = meta;\n\t}\n\tif (rawLegacy) {\n\t\tmappedMetadata.rawLegacy = rawLegacy;\n\t}\n\n\treturn {\n\t\tid: eventId,\n\t\ttype: \"mcp.event\",\n\t\tname: eventName,\n\t\tsource,\n\t\ttimestamp,\n\t\tcorrelation,\n\t\tproperties: mapProperties(input, eventName),\n\t\tmetadata: mappedMetadata,\n\t\trawLegacy,\n\t};\n}\n\nexport function createEventId(): string {\n\tif (\n\t\ttypeof crypto !== \"undefined\" &&\n\t\ttypeof crypto.randomUUID === \"function\"\n\t) {\n\t\treturn `evt_${crypto.randomUUID()}`;\n\t}\n\n\treturn `evt_${Math.random().toString(36).slice(2, 10)}_${Date.now().toString(36)}`;\n}\n\nfunction mapProperties(\n\tinput: TrackInput,\n\teventName: EventType,\n): Record<string, unknown> {\n\tif (!isLegacyTrackEvent(input)) {\n\t\treturn toRecord(input.properties);\n\t}\n\n\tconst legacyProperties = mapLegacyProperties(input, eventName);\n\tconst explicitProperties = toRecord(input.properties);\n\treturn {\n\t\t...legacyProperties,\n\t\t...explicitProperties,\n\t};\n}\n\nfunction mapLegacyProperties(\n\tinput: LegacyTrackEvent,\n\teventName: EventType,\n): Record<string, unknown> {\n\tswitch (eventName) {\n\t\tcase \"tool.called\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (takeNonEmptyString(input.toolName)) {\n\t\t\t\tproperties.name = input.toolName;\n\t\t\t}\n\t\t\tif (takeNonEmptyString(input.toolType)) {\n\t\t\t\tproperties.type = input.toolType;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tcase \"quote.succeeded\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (typeof input.quoteAmount === \"number\") {\n\t\t\t\tproperties.amount = input.quoteAmount;\n\t\t\t}\n\t\t\tif (takeNonEmptyString(input.quoteCurrency)) {\n\t\t\t\tproperties.currency = input.quoteCurrency;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tcase \"link.clicked\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (takeNonEmptyString(input.linkUrl)) {\n\t\t\t\tproperties.url = input.linkUrl;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tcase \"purchase.completed\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (typeof input.purchaseAmount === \"number\") {\n\t\t\t\tproperties.amount = input.purchaseAmount;\n\t\t\t}\n\t\t\tif (takeNonEmptyString(input.purchaseCurrency)) {\n\t\t\t\tproperties.currency = input.purchaseCurrency;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tdefault:\n\t\t\treturn {};\n\t}\n}\n\nfunction resolveEventName(input: TrackInput): EventType {\n\tif (isLegacyTrackEvent(input)) {\n\t\treturn input.eventType;\n\t}\n\treturn input.event;\n}\n\nfunction resolveCorrelationIds(\n\tinput: TrackInput,\n\tmeta: Record<string, unknown>,\n): V2CorrelationIds {\n\tconst requestId =\n\t\ttakeNonEmptyString(input.requestId) ?? extractRequestId(meta);\n\n\tconst sessionId =\n\t\ttakeNonEmptyString(input.sessionId) ?? extractSessionId(meta);\n\n\tconst traceId = takeNonEmptyString(input.traceId) ?? extractTraceId(meta);\n\n\tconst externalUserId =\n\t\ttakeNonEmptyString(input.externalUserId) ?? extractExternalUserId(meta);\n\n\tconst correlationId =\n\t\ttakeNonEmptyString(input.correlationId) ??\n\t\textractCorrelationId(meta) ??\n\t\trequestId;\n\n\tconst correlation: V2CorrelationIds = {};\n\tif (sessionId) {\n\t\tcorrelation.sessionId = sessionId;\n\t}\n\tif (traceId) {\n\t\tcorrelation.traceId = traceId;\n\t}\n\tif (requestId) {\n\t\tcorrelation.requestId = requestId;\n\t}\n\tif (correlationId) {\n\t\tcorrelation.correlationId = correlationId;\n\t}\n\tif (externalUserId) {\n\t\tcorrelation.externalUserId = externalUserId;\n\t}\n\treturn correlation;\n}\n\nfunction normalizeTimestamp(\n\tinput: string | Date | undefined,\n\tnow: () => Date,\n): string {\n\tif (input instanceof Date) {\n\t\treturn input.toISOString();\n\t}\n\tif (typeof input === \"string\") {\n\t\tconst date = new Date(input);\n\t\tif (!Number.isNaN(date.getTime())) {\n\t\t\treturn date.toISOString();\n\t\t}\n\t}\n\treturn now().toISOString();\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\treturn {};\n\t}\n\treturn value as Record<string, unknown>;\n}\n\nfunction takeNonEmptyString(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") {\n\t\treturn undefined;\n\t}\n\tif (value.trim().length === 0) {\n\t\treturn undefined;\n\t}\n\treturn value;\n}\n\nfunction isLegacyTrackEvent(input: TrackInput): input is LegacyTrackEvent {\n\treturn \"eventType\" in input;\n}\n","import type {\n\tTrackingShutdownOptions,\n\tTrackingShutdownResult,\n} from \"./@types.js\";\nimport type {\n\tV2BatchRejectedEvent,\n\tV2BatchRequest,\n\tV2BatchResponse,\n\tV2EventEnvelope,\n} from \"./v2-types.js\";\n\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\tTrackInput,\n\tTrackingClient,\n\tTrackingShutdownOptions,\n} from \"./@types.js\";\nimport { mapTrackEventToV2 } from \"./mapper.js\";\nimport { createV2BatchTransport } from \"./transport.js\";\n\n// Re-export types\nexport type {\n\tEventType,\n\tLegacyTrackEvent,\n\tLinkClickedProperties,\n\tPurchaseCompletedProperties,\n\tQuoteSucceededProperties,\n\tToolCalledProperties,\n\tTrackEvent,\n\tTrackInput,\n\tTrackingClient,\n\tTrackingConfig,\n\tTrackingShutdownOptions,\n\tTrackingShutdownResult,\n} from \"./@types.js\";\nexport { createEventId, mapTrackEventToV2 } from \"./mapper.js\";\nexport type {\n\tV2BatchRejectedEvent,\n\tV2BatchRequest,\n\tV2BatchResponse,\n\tV2CorrelationIds,\n\tV2EnvelopeType,\n\tV2EventEnvelope,\n} from \"./v2-types.js\";\n\nexport function createTrackingClient(config: InternalConfig): TrackingClient {\n\tconst { 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\tconst client: TrackingClient = {\n\t\tasync identify(\n\t\t\tuserId: string,\n\t\t\tproperties?: Record<string, unknown>,\n\t\t\tmeta?: Record<string, unknown>,\n\t\t): Promise<{ eventId: string }> {\n\t\t\trequireApiKey();\n\t\t\tconst mappedEvent = mapTrackEventToV2({\n\t\t\t\tevent: \"user.identified\",\n\t\t\t\texternalUserId: userId,\n\t\t\t\tproperties,\n\t\t\t\tmeta,\n\t\t\t});\n\t\t\ttransport?.enqueue(mappedEvent);\n\t\t\treturn { eventId: mappedEvent.id };\n\t\t},\n\t\tasync track(event: TrackInput): Promise<{ eventId: string }> {\n\t\t\trequireApiKey();\n\t\t\tconst mappedEvent = mapTrackEventToV2(event);\n\t\t\ttransport?.enqueue(mappedEvent);\n\t\t\treturn { eventId: mappedEvent.id };\n\t\t},\n\t\tasync flush(): Promise<void> {\n\t\t\trequireApiKey();\n\t\t\tawait transport?.flush();\n\t\t},\n\t\tasync shutdown(options?: TrackingShutdownOptions) {\n\t\t\trequireApiKey();\n\t\t\treturn (\n\t\t\t\t(await transport?.shutdown({\n\t\t\t\t\ttimeoutMs: options?.timeoutMs ?? tracking.shutdownTimeoutMs,\n\t\t\t\t})) ?? { timedOut: false, pendingEvents: 0 }\n\t\t\t);\n\t\t},\n\t};\n\n\tif (transport) {\n\t\tattachShutdownHooks(client, tracking.shutdownTimeoutMs);\n\t}\n\treturn client;\n}\n\nfunction attachShutdownHooks(\n\tclient: TrackingClient,\n\tdefaultTimeoutMs: number,\n): void {\n\tif (\n\t\ttypeof process === \"undefined\" ||\n\t\ttypeof process.once !== \"function\" ||\n\t\ttypeof process.on !== \"function\"\n\t) {\n\t\treturn;\n\t}\n\n\tconst shutdown = () => {\n\t\tvoid client.shutdown({ timeoutMs: defaultTimeoutMs });\n\t};\n\n\tprocess.once(\"beforeExit\", shutdown);\n\tprocess.once(\"SIGINT\", shutdown);\n\tprocess.once(\"SIGTERM\", shutdown);\n}\n","// WaniWani SDK - Main Entry\n\nimport { createKbClient } from \"./kb/client.js\";\nimport {\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","extractSource","meta","explicit","key","source","value","DEFAULT_SOURCE","mapTrackEventToV2","input","options","now","generateId","createEventId","eventName","resolveEventName","meta","toRecord","metadata","correlation","resolveCorrelationIds","eventId","takeNonEmptyString","timestamp","normalizeTimestamp","source","extractSource","rawLegacy","isLegacyTrackEvent","mappedMetadata","mapProperties","legacyProperties","mapLegacyProperties","explicitProperties","properties","requestId","extractRequestId","sessionId","extractSessionId","traceId","extractTraceId","externalUserId","extractExternalUserId","correlationId","extractCorrelationId","date","value","DEFAULT_ENDPOINT_PATH","SDK_NAME","AUTH_FAILURE_STATUS","RETRYABLE_STATUS","createV2BatchTransport","options","BatchingV2Transport","joinUrl","DEFAULT_ENDPOINT_PATH","delayMs","resolve","event","dropCount","timeoutMs","flushPromise","timeoutSignal","batch","attempt","pendingBatch","result","events","response","error","getErrorMessage","data","parseJsonResponse","partial","rejected","byId","retryable","permanent","rejectedEvent","isRetryableRejectedEvent","rawDelay","status","rejectedCount","buffered","code","body","apiUrl","endpointPath","normalizedBase","normalizedPath","createTrackingClient","config","apiUrl","apiKey","tracking","requireApiKey","transport","createV2BatchTransport","client","userId","properties","meta","mappedEvent","mapTrackEventToV2","event","options","attachShutdownHooks","defaultTimeoutMs","shutdown","waniwani","config","effective","loadProjectConfig","getGlobalConfig","apiUrl","apiKey","trackingConfig","internalConfig","trackingClient","createTrackingClient","kbClient","createKbClient"]}
|
package/dist/legacy/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { ZodRawShapeCompat } from '@modelcontextprotocol/sdk/server/zod-compat.j
|
|
|
6
6
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
7
7
|
import { ContentBlock, CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
8
8
|
import * as React$1 from 'react';
|
|
9
|
-
import React__default, { ReactNode, SetStateAction } from 'react';
|
|
9
|
+
import React__default, { ReactNode, JSX, SetStateAction } from 'react';
|
|
10
10
|
import * as ai from 'ai';
|
|
11
11
|
import { UIMessage } from 'ai';
|
|
12
12
|
|
|
@@ -528,6 +528,91 @@ type ModelContextUpdate = {
|
|
|
528
528
|
structuredContent?: Record<string, unknown>;
|
|
529
529
|
};
|
|
530
530
|
|
|
531
|
+
interface SendFollowUpOptions {
|
|
532
|
+
modelContext?: ModelContextUpdate | null;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Get a function to send follow-up messages to the AI.
|
|
536
|
+
* Works on both OpenAI widgets and MCP Apps.
|
|
537
|
+
*
|
|
538
|
+
* @deprecated Legacy MCP-widget-in-host stack. Preserved for back-compat; will move to
|
|
539
|
+
* `@waniwani/sdk/legacy/react` in a future minor release.
|
|
540
|
+
* @returns A function that sends a follow-up message
|
|
541
|
+
*/
|
|
542
|
+
declare function useSendFollowUp(): (prompt: string, options?: SendFollowUpOptions) => void;
|
|
543
|
+
|
|
544
|
+
interface UnstableSendFollowUpWithGhostGuardResult {
|
|
545
|
+
/** Wrapped `sendFollowUp`. Same signature as the one you passed in. */
|
|
546
|
+
sendFollowUp: (prompt: string, options?: SendFollowUpOptions) => void;
|
|
547
|
+
/**
|
|
548
|
+
* Wrap your widget's root render with this so the suppression can take
|
|
549
|
+
* effect. On non-ChatGPT hosts it is a transparent pass-through.
|
|
550
|
+
*/
|
|
551
|
+
Guard: (props: {
|
|
552
|
+
children: ReactNode;
|
|
553
|
+
}) => JSX.Element | null;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* ⚠️ EXPERIMENTAL — DO NOT USE UNLESS YOU UNDERSTAND THE TRADE-OFFS ⚠️
|
|
557
|
+
*
|
|
558
|
+
* Wraps an existing `sendFollowUp` with ghost-guard suppression specific
|
|
559
|
+
* to ChatGPT. ChatGPT renders the source widget a SECOND time alongside
|
|
560
|
+
* the new user message a widget emits via `sendFollowUpMessage`. The
|
|
561
|
+
* second iframe is a brand-new React tree with no link to the original;
|
|
562
|
+
* skybridge's `useViewState` persistence does not survive across them.
|
|
563
|
+
* The result is a visible duplicate widget for ~1s before ChatGPT
|
|
564
|
+
* collapses one of them.
|
|
565
|
+
*
|
|
566
|
+
* On ChatGPT the hook adds a per-`viewUUID` `localStorage` write before
|
|
567
|
+
* each `sendFollowUp` call, and the returned `Guard` checks that record
|
|
568
|
+
* on mount — if it sees a record set by a different `mountId` within
|
|
569
|
+
* `ADVANCED_WINDOW_MS`, it collapses the iframe to 0×0.
|
|
570
|
+
*
|
|
571
|
+
* On every other host (WaniWani embed, MCP Apps, etc.) the hook is a
|
|
572
|
+
* pure pass-through: `sendFollowUp` is your function unchanged and
|
|
573
|
+
* `Guard` is a transparent fragment.
|
|
574
|
+
*
|
|
575
|
+
* The hook does NOT require `WidgetProvider`. It reads the widget's
|
|
576
|
+
* `viewUUID` from `WidgetClientContext` when one is available and falls
|
|
577
|
+
* back to `window.openai.toolResponseMetadata.viewUUID` otherwise.
|
|
578
|
+
*
|
|
579
|
+
* Caveats:
|
|
580
|
+
* - Relies on ChatGPT's widget iframes sharing a single `localStorage`
|
|
581
|
+
* scope — verified at the time of writing but not guaranteed.
|
|
582
|
+
* - 10s suppression window per `viewUUID`. Two unrelated `sendFollowUp`
|
|
583
|
+
* calls inside 10s with the same viewUUID could mistakenly suppress one.
|
|
584
|
+
* - The `Guard` MUST wrap the widget's root render. If you forget it,
|
|
585
|
+
* the localStorage write happens but nothing is suppressed.
|
|
586
|
+
*
|
|
587
|
+
* @param sendFollowUp - The underlying `sendFollowUp` to wrap. On ChatGPT the
|
|
588
|
+
* hook adds the per-`viewUUID` localStorage write before delegating; on any
|
|
589
|
+
* other host the hook is a pure pass-through. Typically the value of
|
|
590
|
+
* `useSendFollowUpMessage()` from `skybridge/web` or `useSendFollowUp()`
|
|
591
|
+
* from this module.
|
|
592
|
+
*
|
|
593
|
+
* @example
|
|
594
|
+
* function MyWidget() {
|
|
595
|
+
* const sendFollowUpMessage = useSendFollowUpMessage(); // skybridge/web
|
|
596
|
+
* const { sendFollowUp, Guard } =
|
|
597
|
+
* unstable_useSendFollowUpWithGhostGuard(sendFollowUpMessage);
|
|
598
|
+
*
|
|
599
|
+
* // The Guard MUST wrap the widget's root render, otherwise the ghost
|
|
600
|
+
* // iframe will not be suppressed.
|
|
601
|
+
* return (
|
|
602
|
+
* <Guard>
|
|
603
|
+
* <div className="my-widget">
|
|
604
|
+
* <button onClick={() => sendFollowUp("I uploaded my bill")}>
|
|
605
|
+
* Continue
|
|
606
|
+
* </button>
|
|
607
|
+
* </div>
|
|
608
|
+
* </Guard>
|
|
609
|
+
* );
|
|
610
|
+
* }
|
|
611
|
+
*
|
|
612
|
+
* @experimental Subject to change or removal without notice.
|
|
613
|
+
*/
|
|
614
|
+
declare function unstable_useSendFollowUpWithGhostGuard(sendFollowUp: (prompt: string) => void | Promise<void>): UnstableSendFollowUpWithGhostGuardResult;
|
|
615
|
+
|
|
531
616
|
/**
|
|
532
617
|
* Result from calling a tool
|
|
533
618
|
*/
|
|
@@ -786,19 +871,6 @@ declare function useRequestDisplayMode(): (mode: DisplayMode) => Promise<Display
|
|
|
786
871
|
*/
|
|
787
872
|
declare function useSafeArea(): SafeArea | null;
|
|
788
873
|
|
|
789
|
-
interface SendFollowUpOptions {
|
|
790
|
-
modelContext?: ModelContextUpdate | null;
|
|
791
|
-
}
|
|
792
|
-
/**
|
|
793
|
-
* Get a function to send follow-up messages to the AI.
|
|
794
|
-
* Works on both OpenAI widgets and MCP Apps.
|
|
795
|
-
*
|
|
796
|
-
* @deprecated Legacy MCP-widget-in-host stack. Preserved for back-compat; will move to
|
|
797
|
-
* `@waniwani/sdk/legacy/react` in a future minor release.
|
|
798
|
-
* @returns A function that sends a follow-up message
|
|
799
|
-
*/
|
|
800
|
-
declare function useSendFollowUp(): (prompt: string, options?: SendFollowUpOptions) => void;
|
|
801
|
-
|
|
802
874
|
/**
|
|
803
875
|
* Get the current theme.
|
|
804
876
|
* Works on both OpenAI widgets and MCP Apps.
|
|
@@ -1351,4 +1423,4 @@ interface ChatCardProps extends ChatBaseProps {
|
|
|
1351
1423
|
*/
|
|
1352
1424
|
declare const ChatCard: React$1.ForwardRefExoticComponent<ChatCardProps & React$1.RefAttributes<ChatHandle>>;
|
|
1353
1425
|
|
|
1354
|
-
export { ChatCard, type ChatCardProps, DevModeProvider, type DeviceType, type DisplayMode, type FlowActionResult, type HostContext, InitializeNextJsInIframe, LoadingWidget, type ModelContextContentBlock, type ModelContextUpdate, type NextJsHandlerOptions, type NextJsHandlerResult, type RegisteredResource, type RegisteredTool, type ResourceConfig, type SafeArea, type SafeAreaInsets, type SendFollowUpOptions, type Theme, type ToolCallResult, type ToolConfig, type ToolHandler, type ToolHandlerContext, type ToolResult, type ToolToolCallback, type UnifiedWidgetClient, type UnknownObject, type UserAgent, type WidgetCSP, type WidgetPlatform, WidgetProvider, createResource, createTool, detectPlatform, getMockState, initializeMockOpenAI, isMCPApps, isOpenAI, registerTools, toNextJsHandler, updateMockDisplayMode, updateMockGlobal, updateMockTheme, updateMockToolOutput, useCallTool, useDisplayMode, useFlowAction, useIsChatGptApp, useLocale, useMaxHeight, useOpenExternal, useRequestDisplayMode, useSafeArea, useSendFollowUp, useTheme, useToolOutput, useToolResponseMetadata, useUpdateModelContext, useWidgetClient, useWidgetState };
|
|
1426
|
+
export { ChatCard, type ChatCardProps, DevModeProvider, type DeviceType, type DisplayMode, type FlowActionResult, type HostContext, InitializeNextJsInIframe, LoadingWidget, type ModelContextContentBlock, type ModelContextUpdate, type NextJsHandlerOptions, type NextJsHandlerResult, type RegisteredResource, type RegisteredTool, type ResourceConfig, type SafeArea, type SafeAreaInsets, type SendFollowUpOptions, type Theme, type ToolCallResult, type ToolConfig, type ToolHandler, type ToolHandlerContext, type ToolResult, type ToolToolCallback, type UnifiedWidgetClient, type UnknownObject, type UnstableSendFollowUpWithGhostGuardResult, type UserAgent, type WidgetCSP, type WidgetPlatform, WidgetProvider, createResource, createTool, detectPlatform, getMockState, initializeMockOpenAI, isMCPApps, isOpenAI, registerTools, toNextJsHandler, unstable_useSendFollowUpWithGhostGuard, updateMockDisplayMode, updateMockGlobal, updateMockTheme, updateMockToolOutput, useCallTool, useDisplayMode, useFlowAction, useIsChatGptApp, useLocale, useMaxHeight, useOpenExternal, useRequestDisplayMode, useSafeArea, useSendFollowUp, useTheme, useToolOutput, useToolResponseMetadata, useUpdateModelContext, useWidgetClient, useWidgetState };
|