@waniwani/sdk 0.15.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -62,7 +62,14 @@ interface KbClient {
62
62
  sources(): Promise<KbSource[]>;
63
63
  }
64
64
 
65
- type EventType = "session.started" | "page.viewed" | "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" | "price_shown" | "prices_compared" | "option_selected" | "lead_qualified" | "converted";
65
+ /**
66
+ * Every event name in the typed taxonomy, as a runtime list. Single source of
67
+ * truth for {@link EventType} and for surfaces that need to recognize
68
+ * first-class names at runtime (the widget transport passes these through
69
+ * verbatim instead of namespacing them as custom widget events).
70
+ */
71
+ declare const EVENT_TYPES: readonly ["session.started", "page.viewed", "tool.called", "quote.requested", "quote.succeeded", "quote.failed", "link.clicked", "purchase.completed", "widget_render", "user.identified", "price_shown", "prices_compared", "option_selected", "lead_qualified", "converted"];
72
+ type EventType = (typeof EVENT_TYPES)[number];
66
73
  /**
67
74
  * Properties for `page.viewed` — emitted once when the chat widget initializes
68
75
  * on a host page (the top of the funnel). Attributed to the anonymous
@@ -136,8 +143,6 @@ interface LeadQualifiedProperties {
136
143
  email?: string;
137
144
  /** The lead's name, if known. */
138
145
  name?: string;
139
- /** Acquisition source of the lead (e.g. "newsletter"). */
140
- source?: string;
141
146
  }
142
147
  interface ConvertedProperties {
143
148
  amount: number;
@@ -162,6 +167,13 @@ interface TrackingContext {
162
167
  requestId?: string;
163
168
  correlationId?: string;
164
169
  externalUserId?: string;
170
+ /**
171
+ * Anonymous visitor id (the analytics "device id"). Counts as identity on
172
+ * its own: the ingest API accepts `sessionId`, `externalUserId`, or
173
+ * `visitorId`. The chat widget uses it for pre-session events like
174
+ * `page.viewed`.
175
+ */
176
+ visitorId?: string;
165
177
  /** Optional explicit envelope fields. */
166
178
  eventId?: string;
167
179
  timestamp?: string | Date;
@@ -197,6 +209,8 @@ type TrackEvent = ({
197
209
  properties?: PurchaseCompletedProperties;
198
210
  } & BaseTrackEvent) | ({
199
211
  event: "user.identified";
212
+ } & BaseTrackEvent) | ({
213
+ event: "widget_render";
200
214
  } & BaseTrackEvent) | ({
201
215
  event: "price_shown";
202
216
  properties?: PriceShownProperties;
@@ -238,10 +252,9 @@ interface RevenuePricesComparedInput extends TrackingContext, PricesComparedProp
238
252
  interface RevenueOptionSelectedInput extends TrackingContext, OptionSelectedProperties {
239
253
  }
240
254
  /**
241
- * Input for `track.leadQualified()`. `source` is the lead's acquisition source
242
- * (the event property, e.g. "newsletter") on this helper it shadows the
243
- * envelope `source` from the tracking context. To set a custom envelope source
244
- * on a lead, use the generic `track({ event: "lead_qualified", … })`.
255
+ * Input for `track.leadQualified()` identity only (`externalId`, `email`,
256
+ * `name`) plus the shared tracking context. There is no acquisition-source
257
+ * property; the envelope `source` (the origin channel) is set automatically.
245
258
  */
246
259
  interface RevenueLeadQualifiedInput extends TrackingContext, LeadQualifiedProperties {
247
260
  }
@@ -265,13 +278,6 @@ interface RevenueTrackingApi {
265
278
  leadQualified: (input?: RevenueLeadQualifiedInput) => Promise<{
266
279
  eventId: string;
267
280
  }>;
268
- /**
269
- * @deprecated Renamed in 0.15.0. Use `track.leadQualified()` instead; this
270
- * alias emits a `lead_qualified` event and will be removed in 0.16.0.
271
- */
272
- lead: (input?: RevenueLeadQualifiedInput) => Promise<{
273
- eventId: string;
274
- }>;
275
281
  converted: (input: RevenueConvertedInput) => Promise<{
276
282
  eventId: string;
277
283
  }>;
@@ -0,0 +1,3 @@
1
+ "use client";
2
+ async function l(){let{detectPlatform:o}=await import("./platform-LKQFC3AJ.js");if(o()==="openai"){let{OpenAIWidgetClient:e}=await import("./openai-client-PUBKF5TI.js");return new e}else{let{MCPAppsWidgetClient:e}=await import("./mcp-apps-client-WEXKWHUI.js");return new e}}export{l as a};
3
+ //# sourceMappingURL=chunk-GYLVCP7M.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/legacy/mcp/react/widgets/widget-client.ts"],"sourcesContent":["import type { CallToolResult as McpCallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { ModelContextUpdate } from \"../../../../shared/model-context\";\nimport type {\n\tDisplayMode,\n\tSafeArea,\n\tTheme,\n\tUnknownObject,\n} from \"../hooks/@types\";\n\n/**\n * Result from calling a tool\n */\nexport type ToolCallResult = McpCallToolResult;\n\n/**\n * Tool result notification (what the host pushes to the widget)\n */\nexport type ToolResult = McpCallToolResult;\n\n/**\n * Host context - all values available from the host.\n */\nexport type HostContext = {\n\ttheme: Theme;\n\tlocale: string;\n\tdisplayMode: DisplayMode;\n\tmaxHeight: number | null;\n\tsafeArea: SafeArea | null;\n\ttoolOutput: UnknownObject | null;\n\ttoolResponseMetadata: UnknownObject | null;\n\twidgetState: UnknownObject | null;\n};\n\n/**\n * Store interface for useSyncExternalStore compatibility.\n */\nexport type HostContextStore<K extends keyof HostContext> = {\n\tsubscribe: (onStoreChange: () => void) => () => void;\n\tgetSnapshot: () => HostContext[K];\n};\n\n/**\n * Unified widget client interface that works on both OpenAI and MCP Apps.\n *\n * Platform-specific behavior:\n * - Display mode: OpenAI-only. MCP Apps returns \"inline\" and requestDisplayMode is a no-op.\n * - Follow-up messages: Unified API, different underlying implementations.\n */\nexport interface UnifiedWidgetClient {\n\t/**\n\t * Connect to the host. Must be called before using other methods.\n\t * On OpenAI, this is a no-op (already connected via window.openai).\n\t * On MCP Apps, this establishes the postMessage connection.\n\t */\n\tconnect(): Promise<void>;\n\n\t/**\n\t * Close the connection to the host and clean up resources.\n\t * On OpenAI, this is a no-op.\n\t * On MCP Apps, this closes the transport and removes event listeners.\n\t */\n\tclose(): Promise<void>;\n\n\t/**\n\t * Get the tool output (structured content returned by the tool handler).\n\t * This is the main data source for widget rendering.\n\t */\n\tgetToolOutput<T = Record<string, unknown>>(): T | null;\n\n\t/**\n\t * Register a callback for when tool results are received.\n\t * On OpenAI, this subscribes to toolOutput changes.\n\t * On MCP Apps, this sets app.ontoolresult.\n\t */\n\tonToolResult(callback: (result: ToolResult) => void): () => void;\n\n\t/**\n\t * Call another tool on the server.\n\t */\n\tcallTool(\n\t\tname: string,\n\t\targs: Record<string, unknown>,\n\t): Promise<ToolCallResult>;\n\n\t/**\n\t * Open an external URL.\n\t * On OpenAI: openai.openExternal({ href })\n\t * On MCP Apps: app.sendOpenLink(url)\n\t */\n\topenExternal(url: string): void;\n\n\t/**\n\t * Send a follow-up message to the AI.\n\t * On OpenAI: openai.sendFollowUpMessage({ prompt })\n\t * On MCP Apps: app.sendMessages([{ role: 'user', content: { type: 'text', text: prompt } }])\n\t */\n\tsendFollowUp(prompt: string): void | Promise<void>;\n\n\t/**\n\t * Update hidden model context for the next assistant turn.\n\t * On MCP Apps this uses the standard `ui/update-model-context` request.\n\t * On other hosts this may fall back to best-effort behavior.\n\t */\n\tupdateModelContext(context: ModelContextUpdate): Promise<void> | void;\n\n\t/**\n\t * Get the current theme.\n\t */\n\tgetTheme(): Theme;\n\n\t/**\n\t * Subscribe to theme changes.\n\t */\n\tonThemeChange(callback: (theme: Theme) => void): () => void;\n\n\t/**\n\t * Get the current locale.\n\t */\n\tgetLocale(): string;\n\n\t/**\n\t * Get the current display mode.\n\t * OpenAI-only: returns \"pip\" | \"inline\" | \"fullscreen\"\n\t * MCP Apps: always returns \"inline\"\n\t */\n\tgetDisplayMode(): DisplayMode;\n\n\t/**\n\t * Request a display mode change.\n\t * OpenAI-only: requests the mode from the host.\n\t * MCP Apps: no-op (returns current mode).\n\t */\n\trequestDisplayMode(mode: DisplayMode): Promise<DisplayMode>;\n\n\t/**\n\t * Subscribe to display mode changes.\n\t * OpenAI-only: subscribes to displayMode changes.\n\t * MCP Apps: callback is never called.\n\t */\n\tonDisplayModeChange(callback: (mode: DisplayMode) => void): () => void;\n\n\t/**\n\t * Get the safe area insets.\n\t * OpenAI-only: returns insets from window.openai.safeArea.\n\t * MCP Apps: returns null.\n\t */\n\tgetSafeArea(): SafeArea | null;\n\n\t/**\n\t * Subscribe to safe area changes.\n\t * OpenAI-only: subscribes to safeArea changes.\n\t * MCP Apps: callback is never called.\n\t */\n\tonSafeAreaChange(callback: (safeArea: SafeArea | null) => void): () => void;\n\n\t/**\n\t * Get the max height constraint.\n\t * OpenAI-only: returns maxHeight from window.openai.maxHeight.\n\t * MCP Apps: returns null.\n\t */\n\tgetMaxHeight(): number | null;\n\n\t/**\n\t * Subscribe to max height changes.\n\t * OpenAI-only: subscribes to maxHeight changes.\n\t * MCP Apps: callback is never called.\n\t */\n\tonMaxHeightChange(callback: (maxHeight: number | null) => void): () => void;\n\n\t/**\n\t * Get the tool response metadata.\n\t * OpenAI: returns metadata from window.openai.toolResponseMetadata.\n\t * MCP Apps: returns `_meta` from the latest `ui/notifications/tool-result`, if provided by host.\n\t */\n\tgetToolResponseMetadata(): UnknownObject | null;\n\n\t/**\n\t * Subscribe to tool response metadata changes.\n\t * OpenAI: subscribes to toolResponseMetadata changes.\n\t * MCP Apps: fires when tool result `_meta` changes.\n\t */\n\tonToolResponseMetadataChange(\n\t\tcallback: (metadata: UnknownObject | null) => void,\n\t): () => void;\n\n\t/**\n\t * Get the widget state.\n\t * OpenAI-only: returns state from window.openai.widgetState.\n\t * MCP Apps: returns null.\n\t */\n\tgetWidgetState(): UnknownObject | null;\n\n\t/**\n\t * Subscribe to widget state changes.\n\t * OpenAI-only: subscribes to widgetState changes.\n\t * MCP Apps: callback is never called.\n\t */\n\tonWidgetStateChange(\n\t\tcallback: (state: UnknownObject | null) => void,\n\t): () => void;\n}\n\n/**\n * Creates a unified widget client for the current platform.\n */\nexport async function createWidgetClient(): Promise<UnifiedWidgetClient> {\n\tconst { detectPlatform } = await import(\"./platform\");\n\tconst platform = detectPlatform();\n\n\tif (platform === \"openai\") {\n\t\tconst { OpenAIWidgetClient } = await import(\"./openai-client\");\n\t\treturn new OpenAIWidgetClient();\n\t} else {\n\t\tconst { MCPAppsWidgetClient } = await import(\"./mcp-apps-client\");\n\t\treturn new MCPAppsWidgetClient();\n\t}\n}\n"],"mappings":";AA6MA,eAAsBA,GAAmD,CACxE,GAAM,CAAE,eAAAC,CAAe,EAAI,KAAM,QAAO,wBAAY,EAGpD,GAFiBA,EAAe,IAEf,SAAU,CAC1B,GAAM,CAAE,mBAAAC,CAAmB,EAAI,KAAM,QAAO,6BAAiB,EAC7D,OAAO,IAAIA,CACZ,KAAO,CACN,GAAM,CAAE,oBAAAC,CAAoB,EAAI,KAAM,QAAO,+BAAmB,EAChE,OAAO,IAAIA,CACZ,CACD","names":["createWidgetClient","detectPlatform","OpenAIWidgetClient","MCPAppsWidgetClient"]}
package/dist/index.d.ts CHANGED
@@ -109,7 +109,14 @@ interface WaniWaniProjectConfig {
109
109
  */
110
110
  declare function defineConfig(config: WaniWaniProjectConfig): WaniWaniProjectConfig;
111
111
 
112
- type EventType = "session.started" | "page.viewed" | "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" | "price_shown" | "prices_compared" | "option_selected" | "lead_qualified" | "converted";
112
+ /**
113
+ * Every event name in the typed taxonomy, as a runtime list. Single source of
114
+ * truth for {@link EventType} and for surfaces that need to recognize
115
+ * first-class names at runtime (the widget transport passes these through
116
+ * verbatim instead of namespacing them as custom widget events).
117
+ */
118
+ declare const EVENT_TYPES: readonly ["session.started", "page.viewed", "tool.called", "quote.requested", "quote.succeeded", "quote.failed", "link.clicked", "purchase.completed", "widget_render", "user.identified", "price_shown", "prices_compared", "option_selected", "lead_qualified", "converted"];
119
+ type EventType = (typeof EVENT_TYPES)[number];
113
120
  /**
114
121
  * Properties for `page.viewed` — emitted once when the chat widget initializes
115
122
  * on a host page (the top of the funnel). Attributed to the anonymous
@@ -183,8 +190,6 @@ interface LeadQualifiedProperties {
183
190
  email?: string;
184
191
  /** The lead's name, if known. */
185
192
  name?: string;
186
- /** Acquisition source of the lead (e.g. "newsletter"). */
187
- source?: string;
188
193
  }
189
194
  interface ConvertedProperties {
190
195
  amount: number;
@@ -209,6 +214,13 @@ interface TrackingContext {
209
214
  requestId?: string;
210
215
  correlationId?: string;
211
216
  externalUserId?: string;
217
+ /**
218
+ * Anonymous visitor id (the analytics "device id"). Counts as identity on
219
+ * its own: the ingest API accepts `sessionId`, `externalUserId`, or
220
+ * `visitorId`. The chat widget uses it for pre-session events like
221
+ * `page.viewed`.
222
+ */
223
+ visitorId?: string;
212
224
  /** Optional explicit envelope fields. */
213
225
  eventId?: string;
214
226
  timestamp?: string | Date;
@@ -244,6 +256,8 @@ type TrackEvent = ({
244
256
  properties?: PurchaseCompletedProperties;
245
257
  } & BaseTrackEvent) | ({
246
258
  event: "user.identified";
259
+ } & BaseTrackEvent) | ({
260
+ event: "widget_render";
247
261
  } & BaseTrackEvent) | ({
248
262
  event: "price_shown";
249
263
  properties?: PriceShownProperties;
@@ -285,10 +299,9 @@ interface RevenuePricesComparedInput extends TrackingContext, PricesComparedProp
285
299
  interface RevenueOptionSelectedInput extends TrackingContext, OptionSelectedProperties {
286
300
  }
287
301
  /**
288
- * Input for `track.leadQualified()`. `source` is the lead's acquisition source
289
- * (the event property, e.g. "newsletter") on this helper it shadows the
290
- * envelope `source` from the tracking context. To set a custom envelope source
291
- * on a lead, use the generic `track({ event: "lead_qualified", … })`.
302
+ * Input for `track.leadQualified()` identity only (`externalId`, `email`,
303
+ * `name`) plus the shared tracking context. There is no acquisition-source
304
+ * property; the envelope `source` (the origin channel) is set automatically.
292
305
  */
293
306
  interface RevenueLeadQualifiedInput extends TrackingContext, LeadQualifiedProperties {
294
307
  }
@@ -312,13 +325,6 @@ interface RevenueTrackingApi {
312
325
  leadQualified: (input?: RevenueLeadQualifiedInput) => Promise<{
313
326
  eventId: string;
314
327
  }>;
315
- /**
316
- * @deprecated Renamed in 0.15.0. Use `track.leadQualified()` instead; this
317
- * alias emits a `lead_qualified` event and will be removed in 0.16.0.
318
- */
319
- lead: (input?: RevenueLeadQualifiedInput) => Promise<{
320
- eventId: string;
321
- }>;
322
328
  converted: (input: RevenueConvertedInput) => Promise<{
323
329
  eventId: string;
324
330
  }>;
@@ -387,6 +393,74 @@ interface TrackingClient {
387
393
  shutdown: (options?: TrackingShutdownOptions) => Promise<TrackingShutdownResult>;
388
394
  }
389
395
 
396
+ /**
397
+ * Correlation identity read at emit time, so identifiers that only exist
398
+ * later (a server-assigned chat session id, for example) are picked up on
399
+ * every event without re-creating the client.
400
+ */
401
+ interface FrontendIdentity {
402
+ sessionId?: string;
403
+ visitorId?: string;
404
+ externalUserId?: string;
405
+ traceId?: string;
406
+ }
407
+ interface FrontendClientOptions {
408
+ /**
409
+ * Full URL of the events ingest endpoint
410
+ * (e.g. `https://app.waniwani.ai/api/mcp/events/v2/batch`).
411
+ */
412
+ endpoint: string;
413
+ /**
414
+ * Browser-safe credential: an environment public token (`wwp_...`) or a
415
+ * widget JWT injected by `withWaniwani` into tool response `_meta`. Omit
416
+ * only when the endpoint is your own unauthenticated proxy route.
417
+ */
418
+ token?: string;
419
+ /**
420
+ * Channel attribution stamped on every event (e.g. `"chatgpt"`, `"web"`).
421
+ * The ingest API rejects events it cannot attribute to a channel, so set
422
+ * this (or `channelId`) unless every event carries its own. Accepts a
423
+ * getter for values that resolve after client creation.
424
+ */
425
+ source?: string | (() => string | undefined);
426
+ /**
427
+ * Exact channel attribution, stamped as `properties.channelId` on every
428
+ * event that does not set its own. Accepts a getter for values that
429
+ * resolve after client creation.
430
+ */
431
+ channelId?: string | (() => string | undefined);
432
+ /** Live identity, called on every emit. */
433
+ identity?: () => FrontendIdentity;
434
+ /** Extra fields merged into every event's envelope metadata. */
435
+ metadata?: Record<string, unknown>;
436
+ /** Periodic flush interval for buffered events. */
437
+ flushIntervalMs?: number;
438
+ }
439
+ /**
440
+ * The browser counterpart of the server tracking client: the exact same
441
+ * `track` surface (callable plus the flat revenue helpers) over the same
442
+ * batching transport, with identity stamped automatically.
443
+ */
444
+ interface FrontendTrackingClient {
445
+ track: TrackFn;
446
+ /**
447
+ * Attach a stable external user id. Emits `user.identified` and stamps
448
+ * `externalUserId` on every subsequent event from this client.
449
+ */
450
+ identify(userId: string, properties?: Record<string, unknown>): Promise<{
451
+ eventId: string;
452
+ }>;
453
+ flush(): Promise<void>;
454
+ shutdown(options?: TrackingShutdownOptions): Promise<TrackingShutdownResult>;
455
+ }
456
+ /**
457
+ * Create a tracking client for browser surfaces (MCP-app widgets, the chat
458
+ * embed, or any page holding a `wwp_` public token). Events flush in batches,
459
+ * survive page navigation via keepalive teardown, and never throw into the
460
+ * host page.
461
+ */
462
+ declare function createFrontendClient(options: FrontendClientOptions): FrontendTrackingClient;
463
+
390
464
  interface WaniWaniConfig {
391
465
  /**
392
466
  * Your MCP environment API key
@@ -439,6 +513,8 @@ interface V2CorrelationIds {
439
513
  requestId?: string;
440
514
  correlationId?: string;
441
515
  externalUserId?: string;
516
+ /** Anonymous visitor id; accepted as identity by the ingest API. */
517
+ visitorId?: string;
442
518
  }
443
519
  interface V2EventEnvelope {
444
520
  id: string;
@@ -493,4 +569,4 @@ interface V2BatchResponse {
493
569
  */
494
570
  declare function waniwani(config?: WaniWaniConfig | WaniWaniProjectConfig): WaniWaniClient;
495
571
 
496
- export { type ComparedPriceOption, type ConvertedProperties, type EventType, type KbClient, type KbIngestFile, type KbIngestResult, type KbSearchOptions, type KbSource, type LeadQualifiedProperties, type LegacyTrackEvent, type LinkClickedProperties, type OptionSelectedProperties, type PriceShownProperties, type PricesComparedProperties, type PurchaseCompletedProperties, type QuoteSucceededProperties, type RevenueConvertedInput, type RevenueLeadQualifiedInput, type RevenueOptionSelectedInput, type RevenuePriceShownInput, type RevenuePricesComparedInput, type RevenueTrackingApi, type ToolCalledProperties, type TrackEvent, type TrackFn, type TrackInput, type TrackingConfig, type TrackingShutdownOptions, type TrackingShutdownResult, type V2BatchRejectedEvent, type V2BatchRequest, type V2BatchResponse, type V2CorrelationIds, type V2EnvelopeType, type V2EventEnvelope, type WaniWaniClient, type WaniWaniConfig, WaniWaniError, type WaniWaniProjectConfig, defineConfig, waniwani };
572
+ export { type ComparedPriceOption, type ConvertedProperties, EVENT_TYPES, type EventType, type FrontendClientOptions, type FrontendIdentity, type FrontendTrackingClient, type KbClient, type KbIngestFile, type KbIngestResult, type KbSearchOptions, type KbSource, type LeadQualifiedProperties, type LegacyTrackEvent, type LinkClickedProperties, type OptionSelectedProperties, type PriceShownProperties, type PricesComparedProperties, type PurchaseCompletedProperties, type QuoteSucceededProperties, type RevenueConvertedInput, type RevenueLeadQualifiedInput, type RevenueOptionSelectedInput, type RevenuePriceShownInput, type RevenuePricesComparedInput, type RevenueTrackingApi, type ToolCalledProperties, type TrackEvent, type TrackFn, type TrackInput, type TrackingConfig, type TrackingShutdownOptions, type TrackingShutdownResult, type V2BatchRejectedEvent, type V2BatchRequest, type V2BatchResponse, type V2CorrelationIds, type V2EnvelopeType, type V2EventEnvelope, type WaniWaniClient, type WaniWaniConfig, WaniWaniError, type WaniWaniProjectConfig, createFrontendClient, defineConfig, waniwani };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- var y=class extends Error{constructor(t,r){super(t);this.status=r;this.name="WaniWaniError"}};import{existsSync as N,readFileSync as q}from"fs";import{resolve as z}from"path";var Y="waniwani.json",m;function R(){if(m!==void 0)return m;try{let e=z(process.cwd(),Y);if(!N(e))return m=null,null;let n=q(e,"utf-8");return m=JSON.parse(n),m}catch{return m=null,null}}var b="__waniwani_config__";function $(e){return globalThis[b]=e,e}function x(){return globalThis[b]}import{AsyncLocalStorage as H}from"async_hooks";var C=globalThis,T=C.__waniwaniKbRetrievalStore;T||(T=new H,C.__waniwaniKbRetrievalStore=T);var X=T;function _(e){try{X.getStore()?.searches.push(e)}catch{}}var Q="@waniwani/sdk";function P(e){let{apiUrl:n,apiKey:t}=e;function r(){if(!t)throw new Error("WANIWANI_API_KEY is not set");return t}async function i(s,o,a){let p=r(),g=`${n.replace(/\/$/,"")}${o}`,u={Authorization:`Bearer ${p}`,"X-WaniWani-SDK":Q},c={method:s,headers:u};a!==void 0&&(u["Content-Type"]="application/json",c.body=JSON.stringify(a));let l=await fetch(g,c);if(!l.ok){let V=await l.text().catch(()=>"");throw new y(V||`KB API error: HTTP ${l.status}`,l.status)}return(await l.json()).data}return{async ingest(s){return i("POST","/api/mcp/kb/ingest",{files:s})},async search(s,o){let a=await i("POST","/api/mcp/kb/search",{query:s,...o});return _({query:s,resultCount:a.length,results:a.map(p=>({source:p.source,heading:p.heading,score:p.score}))}),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 G=["waniwani/sessionId","openai/sessionId","openai/session","sessionId","conversationId","mcp-session-id"],J=["waniwani/requestId","openai/requestId","requestId","mcp/requestId"],Z=["waniwani/traceId","openai/traceId","traceId","mcp/traceId","openai/requestId","requestId"],ee=["waniwani/userId","openai/userId","externalUserId","userId","actorId"],te=["correlationId","openai/requestId"];function M(e){return e?v(e,G):void 0}function A(e){return e?v(e,J):void 0}function D(e){return e?v(e,Z):void 0}function W(e){return e?v(e,ee):void 0}function B(e){return e?v(e,te):void 0}var ne=[{key:"openai/sessionId",source:"chatgpt"},{key:"openai/session",source:"chatgpt"}],re=[{needle:"claude",source:"claude"},{needle:"chatgpt",source:"chatgpt"},{needle:"openai",source:"chatgpt"},{needle:"gemini",source:"gemini"}];function F(e,n){if(e){let r=e["waniwani/source"];if(typeof r=="string"&&r.length>0)return r;for(let{key:i,source:s}of ne){let o=e[i];if(typeof o=="string"&&o.length>0)return s}}let t=n?.name;if(typeof t=="string"&&t.length>0){let r=t.toLowerCase();for(let{needle:i,source:s}of re)if(r.includes(i))return s}}var ie="@waniwani/sdk";function w(e,n={}){let t=n.now??(()=>new Date),r=n.generateId??L,i=ae(e),s=I(e.meta),o=I(e.metadata),a=ce(e,s),p=d(e.eventId)??r(),g=ue(e.timestamp,t),u=d(e.source)??F(s)??n.source??ie,c=E(e)?{...e}:void 0,l={...o};return Object.keys(s).length>0&&(l.meta=s),c&&(l.rawLegacy=c),{id:p,type:"mcp.event",name:i,source:u,timestamp:g,correlation:a,properties:se(e,i),metadata:l,rawLegacy:c}}function L(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?`evt_${crypto.randomUUID()}`:`evt_${Math.random().toString(36).slice(2,10)}_${Date.now().toString(36)}`}function se(e,n){if(!E(e))return I(e.properties);let t=oe(e,n),r=I(e.properties);return{...t,...r}}function oe(e,n){switch(n){case"tool.called":{let t={};return d(e.toolName)&&(t.name=e.toolName),d(e.toolType)&&(t.type=e.toolType),t}case"quote.succeeded":{let t={};return typeof e.quoteAmount=="number"&&(t.amount=e.quoteAmount),d(e.quoteCurrency)&&(t.currency=e.quoteCurrency),t}case"link.clicked":{let t={};return d(e.linkUrl)&&(t.url=e.linkUrl),t}case"purchase.completed":{let t={};return typeof e.purchaseAmount=="number"&&(t.amount=e.purchaseAmount),d(e.purchaseCurrency)&&(t.currency=e.purchaseCurrency),t}default:return{}}}function ae(e){let n=E(e)?e.eventType:e.event;return n==="lead"?"lead_qualified":n}function ce(e,n){let t=d(e.requestId)??A(n),r=d(e.sessionId)??M(n),i=d(e.traceId)??D(n),s=d(e.externalUserId)??W(n),o=d(e.correlationId)??B(n)??t,a={};return r&&(a.sessionId=r),i&&(a.traceId=i),t&&(a.requestId=t),o&&(a.correlationId=o),s&&(a.externalUserId=s),a}function ue(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 I(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function d(e){if(typeof e=="string"&&e.trim().length!==0)return e}function E(e){return"eventType"in e}function U(e){let n=t=>{let{externalId:r,email:i,name:s,source:o,...a}=t??{};return e({event:"lead_qualified",properties:{externalId:r,email:i,name:s,source:o},...a})};return{priceShown:({amount:t,currency:r,itemId:i,label:s,...o})=>e({event:"price_shown",properties:{amount:t,currency:r,itemId:i,label:s},...o}),pricesCompared:({options:t,...r})=>e({event:"prices_compared",properties:{options:t},...r}),optionSelected:({id:t,amount:r,currency:i,...s})=>e({event:"option_selected",properties:{id:t,amount:r,currency:i},...s}),leadQualified:n,lead:n,converted:({amount:t,currency:r,occurredAt:i,...s})=>e({event:"converted",properties:{amount:t,currency:r,occurredAt:i},...s})}}var de="/api/mcp/events/v2/batch";var j="@waniwani/sdk",pe=new Set([401,403]),le=new Set([408,425,429,500,502,503,504]);function O(e){return new k(e)}var k=class{endpointUrl;flushIntervalMs;maxBatchSize;maxBufferSize;maxRetries;retryBaseDelayMs;retryMaxDelayMs;shutdownTimeoutMs;sdkVersion;fetchFn;logger;now;sleep;apiKey;buffer=[];flushTimer;flushScheduled=!1;flushScheduledTimer;flushInFlight;inFlightCount=0;isStopped=!1;isShuttingDown=!1;constructor(n){this.endpointUrl=ge(n.apiUrl,n.endpointPath??de),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":j},body:JSON.stringify(this.makeBatchRequest(n))})}catch(s){return{kind:"retryable",reason:me(s)}}if(pe.has(t.status))return{kind:"auth",status:t.status};if(le.has(t.status))return{kind:"retryable",reason:`HTTP ${t.status}`};if(!t.ok)return{kind:"permanent",reason:`HTTP ${t.status}`};let r=await he(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:j,version:this.sdkVersion??"0.0.0"},events:n}}classifyRejectedEvents(n,t){let r=new Map(n.map(o=>[o.id,o])),i=[],s=[];for(let o of t){let a=r.get(o.eventId);if(a){if(fe(o)){i.push(a);continue}s.push(a)}}return{retryable:i,permanent:s}}backoffDelayMs(n){let t=this.retryBaseDelayMs*2**n;return Math.min(t,this.retryMaxDelayMs)}stopTransportForAuthFailure(n,t){this.isStopped=!0;let r=this.buffer.length;this.buffer.splice(0,r),this.logger.error("[Waniwani] Auth failure (HTTP %d). Stopping tracking transport and dropping %d queued event(s)",n,t+r)}};function fe(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 he(e){let n=await e.text();if(n)try{return JSON.parse(n)}catch{return}}function ge(e,n){let t=e.endsWith("/")?e:`${e}/`,r=n.startsWith("/")?n.slice(1):n;return`${t}${r}`}function me(e){return e instanceof Error?e.message:String(e)}function K(e){let{apiUrl:n,apiKey:t,tracking:r}=e;function i(){if(!t)throw new Error("WANIWANI_API_KEY is not set");return t}let s=t?O({apiUrl:n,apiKey:t,endpointPath:r.endpointPath,flushIntervalMs:r.flushIntervalMs,maxBatchSize:r.maxBatchSize,maxBufferSize:r.maxBufferSize,maxRetries:r.maxRetries,retryBaseDelayMs:r.retryBaseDelayMs,retryMaxDelayMs:r.retryMaxDelayMs,shutdownTimeoutMs:r.shutdownTimeoutMs}):void 0;function o(u){i();let c=w(u);return!c.correlation.sessionId&&!c.correlation.externalUserId&&console.warn(`[waniwani] event "${c.name}" has no sessionId or externalUserId; the ingest API requires one and will reject it.`),s?.enqueue(c),{eventId:c.id}}let a=async u=>o(u),p=Object.assign(a,U(a)),g={async identify(u,c,l){i();let S=w({event:"user.identified",externalUserId:u,properties:c,meta:l});return s?.enqueue(S),{eventId:S.id}},track:p,async flush(){i(),await s?.flush()},async shutdown(u){return i(),await s?.shutdown({timeoutMs:u?.timeoutMs??r.shutdownTimeoutMs})??{timedOut:!1,pendingEvents:0}}};return s&&ye(g,r.shutdownTimeoutMs),g}function ye(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 ve(e){let t=e??R()??x(),r=t?.apiUrl??process.env.WANIWANI_API_URL??"https://app.waniwani.ai",i=t?.apiKey??process.env.WANIWANI_API_KEY,s={endpointPath:t?.tracking?.endpointPath??"/api/mcp/events/v2/batch",flushIntervalMs:t?.tracking?.flushIntervalMs??1e3,maxBatchSize:t?.tracking?.maxBatchSize??20,maxBufferSize:t?.tracking?.maxBufferSize??1e3,maxRetries:t?.tracking?.maxRetries??3,retryBaseDelayMs:t?.tracking?.retryBaseDelayMs??200,retryMaxDelayMs:t?.tracking?.retryMaxDelayMs??2e3,shutdownTimeoutMs:t?.tracking?.shutdownTimeoutMs??2e3},o={apiUrl:r,apiKey:i,tracking:s},a=K(o),p=P(o);return{...a,kb:p,_config:o}}export{y as WaniWaniError,$ as defineConfig,ve as waniwani};
1
+ var T=class extends Error{constructor(n,r){super(n);this.status=r;this.name="WaniWaniError"}};import{existsSync as Y,readFileSync as z}from"fs";import{resolve as $}from"path";var H="waniwani.json",m;function P(){if(m!==void 0)return m;try{let e=$(process.cwd(),H);if(!Y(e))return m=null,null;let t=z(e,"utf-8");return m=JSON.parse(t),m}catch{return m=null,null}}var b="__waniwani_config__";function Q(e){return globalThis[b]=e,e}function C(){return globalThis[b]}var _=["session.started","page.viewed","tool.called","quote.requested","quote.succeeded","quote.failed","link.clicked","purchase.completed","widget_render","user.identified","price_shown","prices_compared","option_selected","lead_qualified","converted"];function v(e,t){for(let n of t){let r=e[n];if(typeof r=="string"&&r.length>0)return r}}var X=["waniwani/sessionId","openai/sessionId","openai/session","sessionId","conversationId","mcp-session-id"],G=["waniwani/requestId","openai/requestId","requestId","mcp/requestId"],J=["waniwani/traceId","openai/traceId","traceId","mcp/traceId","openai/requestId","requestId"],Z=["waniwani/userId","openai/userId","externalUserId","userId","actorId"],ee=["waniwani/visitorId","visitorId"],te=["correlationId","openai/requestId"];function M(e){return e?v(e,X):void 0}function A(e){return e?v(e,G):void 0}function B(e){return e?v(e,J):void 0}function F(e){return e?v(e,Z):void 0}function O(e){return e?v(e,ee):void 0}function D(e){return e?v(e,te):void 0}var ne=[{key:"openai/sessionId",source:"chatgpt"},{key:"openai/session",source:"chatgpt"}],re=[{needle:"claude",source:"claude"},{needle:"chatgpt",source:"chatgpt"},{needle:"openai",source:"chatgpt"},{needle:"gemini",source:"gemini"}];function L(e,t){if(e){let r=e["waniwani/source"];if(typeof r=="string"&&r.length>0)return r;for(let{key:i,source:s}of ne){let a=e[i];if(typeof a=="string"&&a.length>0)return s}}let n=t?.name;if(typeof n=="string"&&n.length>0){let r=n.toLowerCase();for(let{needle:i,source:s}of re)if(r.includes(i))return s}}var ie="@waniwani/sdk";function y(e,t={}){let n=t.now??(()=>new Date),r=t.generateId??U,i=ae(e),s=I(e.meta),a=I(e.metadata),o=ce(e,s),c=p(e.eventId)??r(),g=ue(e.timestamp,n),d=p(e.source)??L(s)??t.source??ie,u=x(e)?{...e}:void 0,h={...a};return Object.keys(s).length>0&&(h.meta=s),u&&(h.rawLegacy=u),{id:c,type:"mcp.event",name:i,source:d,timestamp:g,correlation:o,properties:se(e,i),metadata:h,rawLegacy:u}}function U(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?`evt_${crypto.randomUUID()}`:`evt_${Math.random().toString(36).slice(2,10)}_${Date.now().toString(36)}`}function se(e,t){if(!x(e))return I(e.properties);let n=oe(e,t),r=I(e.properties);return{...n,...r}}function oe(e,t){switch(t){case"tool.called":{let n={};return p(e.toolName)&&(n.name=e.toolName),p(e.toolType)&&(n.type=e.toolType),n}case"quote.succeeded":{let n={};return typeof e.quoteAmount=="number"&&(n.amount=e.quoteAmount),p(e.quoteCurrency)&&(n.currency=e.quoteCurrency),n}case"link.clicked":{let n={};return p(e.linkUrl)&&(n.url=e.linkUrl),n}case"purchase.completed":{let n={};return typeof e.purchaseAmount=="number"&&(n.amount=e.purchaseAmount),p(e.purchaseCurrency)&&(n.currency=e.purchaseCurrency),n}default:return{}}}function ae(e){return x(e)?e.eventType:e.event}function ce(e,t){let n=p(e.requestId)??A(t),r=p(e.sessionId)??M(t),i=p(e.traceId)??B(t),s=p(e.externalUserId)??F(t),a=p(e.visitorId)??O(t),o=p(e.correlationId)??D(t)??n,c={};return r&&(c.sessionId=r),i&&(c.traceId=i),n&&(c.requestId=n),o&&(c.correlationId=o),s&&(c.externalUserId=s),a&&(c.visitorId=a),c}function ue(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 I(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function p(e){if(typeof e=="string"&&e.trim().length!==0)return e}function x(e){return"eventType"in e}function k(e){return{priceShown:({amount:t,currency:n,itemId:r,label:i,...s})=>e({event:"price_shown",properties:{amount:t,currency:n,itemId:r,label:i},...s}),pricesCompared:({options:t,...n})=>e({event:"prices_compared",properties:{options:t},...n}),optionSelected:({id:t,amount:n,currency:r,...i})=>e({event:"option_selected",properties:{id:t,amount:n,currency:r},...i}),leadQualified:t=>{let{externalId:n,email:r,name:i,...s}=t??{};return e({event:"lead_qualified",properties:{externalId:n,email:r,name:i},...s})},converted:({amount:t,currency:n,occurredAt:r,...i})=>e({event:"converted",properties:{amount:t,currency:n,occurredAt:r},...i})}}var de="/api/mcp/events/v2/batch";var W="@waniwani/sdk";var pe=new Set([401,403]),le=new Set([408,425,429,500,502,503,504]);function w(e){return new R(e)}var R=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=ge(t.apiUrl,t.endpointPath??de),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)),this.registerBrowserTeardown()}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()})}registerBrowserTeardown(){if(typeof document>"u"||typeof window>"u")return;let t=()=>{document.visibilityState==="hidden"&&this.keepaliveFlush()};document.addEventListener("visibilitychange",t),window.addEventListener("pagehide",()=>this.keepaliveFlush())}keepaliveFlush(){if(this.isStopped||this.buffer.length===0)return;let t=this.buffer.splice(0,this.buffer.length);this.sendKeepaliveChunk(t)}sendKeepaliveChunk(t){let n=JSON.stringify(this.makeBatchRequest(t));if(n.length>6e4&&t.length>1){let r=Math.ceil(t.length/2);this.sendKeepaliveChunk(t.slice(0,r)),this.sendKeepaliveChunk(t.slice(r));return}try{this.fetchFn(this.endpointUrl,{method:"POST",headers:this.requestHeaders(),body:n,keepalive:!0}).catch(()=>{})}catch{}}requestHeaders(){let t={"Content-Type":"application/json","X-WaniWani-SDK":W};return this.apiKey&&(t.Authorization=`Bearer ${this.apiKey}`),t}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:this.requestHeaders(),body:JSON.stringify(this.makeBatchRequest(t))})}catch(s){return{kind:"retryable",reason:me(s)}}if(pe.has(n.status))return{kind:"auth",status:n.status};if(le.has(n.status))return{kind:"retryable",reason:`HTTP ${n.status}`};if(!n.ok)return{kind:"permanent",reason:`HTTP ${n.status}`};let r=await he(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:W,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(fe(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 fe(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 he(e){let t=await e.text();if(t)try{return JSON.parse(t)}catch{return}}function ge(e,t){let n=e.endsWith("/")?e:`${e}/`,r=t.startsWith("/")?t.slice(1):t;return`${n}${r}`}function me(e){return e instanceof Error?e.message:String(e)}function ve(e){let{apiUrl:t,endpointPath:n}=ye(e.endpoint),r=w({apiUrl:t,apiKey:e.token,endpointPath:n,flushIntervalMs:e.flushIntervalMs}),i,s=o=>typeof o=="function"?o():o,a=async o=>{let c=e.identity?.()??{},g=s(e.channelId),d=g===void 0?o.properties:{channelId:g,...o.properties},u=y({sessionId:c.sessionId,visitorId:c.visitorId,externalUserId:c.externalUserId??i,traceId:c.traceId,...o,properties:d,metadata:{...e.metadata,...o.metadata}},{source:s(e.source)});return r.enqueue(u),{eventId:u.id}};return{track:Object.assign(a,k(a)),identify(o,c){return i=o,a({event:"user.identified",externalUserId:o,properties:c})},flush:()=>r.flush(),shutdown:o=>r.shutdown(o)}}function ye(e){let t=new URL(e);return{apiUrl:t.origin,endpointPath:`${t.pathname}${t.search}`}}import{AsyncLocalStorage as Te}from"async_hooks";var V=globalThis,E=V.__waniwaniKbRetrievalStore;E||(E=new Te,V.__waniwaniKbRetrievalStore=E);var Ie=E;function K(e){try{Ie.getStore()?.searches.push(e)}catch{}}var ke="@waniwani/sdk";function j(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 c=r(),g=`${t.replace(/\/$/,"")}${a}`,d={Authorization:`Bearer ${c}`,"X-WaniWani-SDK":ke},u={method:s,headers:d};o!==void 0&&(d["Content-Type"]="application/json",u.body=JSON.stringify(o));let h=await fetch(g,u);if(!h.ok){let N=await h.text().catch(()=>"");throw new T(N||`KB API error: HTTP ${h.status}`,h.status)}return(await h.json()).data}return{async ingest(s){return i("POST","/api/mcp/kb/ingest",{files:s})},async search(s,a){let o=await i("POST","/api/mcp/kb/search",{query:s,...a});return K({query:s,resultCount:o.length,results:o.map(c=>({source:c.source,heading:c.heading,score:c.score}))}),o},async sources(){return i("GET","/api/mcp/kb/sources")}}}function q(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?w({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;function a(d){i();let u=y(d);return!u.correlation.sessionId&&!u.correlation.externalUserId&&!u.correlation.visitorId&&console.warn(`[waniwani] event "${u.name}" has no sessionId, externalUserId, or visitorId; the ingest API requires one and will reject it.`),s?.enqueue(u),{eventId:u.id}}let o=async d=>a(d),c=Object.assign(o,k(o)),g={async identify(d,u,h){i();let S=y({event:"user.identified",externalUserId:d,properties:u,meta:h});return s?.enqueue(S),{eventId:S.id}},track:c,async flush(){i(),await s?.flush()},async shutdown(d){return i(),await s?.shutdown({timeoutMs:d?.timeoutMs??r.shutdownTimeoutMs})??{timedOut:!1,pendingEvents:0}}};return s&&we(g,r.shutdownTimeoutMs),g}function we(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 Ee(e){let n=e??P()??C(),r=n?.apiUrl??process.env.WANIWANI_API_URL??"https://app.waniwani.ai",i=n?.apiKey??process.env.WANIWANI_API_KEY,s={endpointPath:n?.tracking?.endpointPath??"/api/mcp/events/v2/batch",flushIntervalMs:n?.tracking?.flushIntervalMs??1e3,maxBatchSize:n?.tracking?.maxBatchSize??20,maxBufferSize:n?.tracking?.maxBufferSize??1e3,maxRetries:n?.tracking?.maxRetries??3,retryBaseDelayMs:n?.tracking?.retryBaseDelayMs??200,retryMaxDelayMs:n?.tracking?.retryMaxDelayMs??2e3,shutdownTimeoutMs:n?.tracking?.shutdownTimeoutMs??2e3},a={apiUrl:r,apiKey:i,tracking:s},o=q(a),c=j(a);return{...o,kb:c,_config:a}}export{_ as EVENT_TYPES,T as WaniWaniError,ve as createFrontendClient,Q as defineConfig,Ee as waniwani};
2
2
  //# sourceMappingURL=index.js.map