@waniwani/sdk 0.2.5 → 0.2.6-beta.1

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.
Files changed (33) hide show
  1. package/dist/chat/index.d.ts +8 -0
  2. package/dist/chat/index.js +7 -5
  3. package/dist/chat/index.js.map +1 -1
  4. package/dist/chat/next-js/index.d.ts +11 -0
  5. package/dist/chat/next-js/index.js +9 -1
  6. package/dist/chat/next-js/index.js.map +1 -1
  7. package/dist/chat/server/index.d.ts +15 -0
  8. package/dist/{chunk-HVAT4SNH.js → chunk-5JBHQYU5.js} +2 -2
  9. package/dist/chunk-5JBHQYU5.js.map +1 -0
  10. package/dist/chunk-OMMDVQYW.js +9 -0
  11. package/dist/chunk-OMMDVQYW.js.map +1 -0
  12. package/dist/{code-block-37QAKDTI-M2QCKVTI.js → code-block-37QAKDTI-IIYKFI5S.js} +2 -2
  13. package/dist/mcp/index.d.ts +62 -6
  14. package/dist/mcp/index.js +3 -3
  15. package/dist/mcp/index.js.map +1 -1
  16. package/dist/mcp/react.d.ts +25 -4
  17. package/dist/mcp/react.js +6 -6
  18. package/dist/mcp/react.js.map +1 -1
  19. package/dist/{mcp-apps-client-6WEBHSGH.js → mcp-apps-client-AXOLX2YD.js} +2 -2
  20. package/dist/mcp-apps-client-AXOLX2YD.js.map +1 -0
  21. package/dist/mermaid-4DMBBIKO-PAA57QRQ.js +3 -0
  22. package/dist/openai-client-IAONK3N3.js +5 -0
  23. package/dist/openai-client-IAONK3N3.js.map +1 -0
  24. package/package.json +1 -1
  25. package/dist/chunk-HVAT4SNH.js.map +0 -1
  26. package/dist/chunk-ZUGQBRJF.js +0 -3
  27. package/dist/chunk-ZUGQBRJF.js.map +0 -1
  28. package/dist/mcp-apps-client-6WEBHSGH.js.map +0 -1
  29. package/dist/mermaid-4DMBBIKO-NR6M3PLG.js +0 -3
  30. package/dist/openai-client-HLQSYZJC.js +0 -3
  31. package/dist/openai-client-HLQSYZJC.js.map +0 -1
  32. /package/dist/{code-block-37QAKDTI-M2QCKVTI.js.map → code-block-37QAKDTI-IIYKFI5S.js.map} +0 -0
  33. /package/dist/{mermaid-4DMBBIKO-NR6M3PLG.js.map → mermaid-4DMBBIKO-PAA57QRQ.js.map} +0 -0
@@ -0,0 +1,9 @@
1
+ "use client";
2
+ var l="openai:set_globals",s=class extends CustomEvent{constructor(e){super(l,{detail:e})}};function n(t){if(!t)return!1;let e=Array.isArray(t.content)&&t.content.length>0,o=typeof t.structuredContent=="object"&&t.structuredContent!==null&&Object.keys(t.structuredContent).length>0;return e||o}function p(t,e){return!n(t)&&!n(e)?null:n(t)?n(e)?{...t.content||e.content?{content:[...t.content??[],...e.content??[]]}:{},...t.structuredContent||e.structuredContent?{structuredContent:{...t.structuredContent??{},...e.structuredContent??{}}}:{}}:{...t.content?{content:[...t.content]}:{},...t.structuredContent?{structuredContent:{...t.structuredContent}}:{}}:{...e?.content?{content:[...e.content]}:{},...e?.structuredContent?{structuredContent:{...e.structuredContent}}:{}}}function d(t){if(!n(t))return"";let e=["## Widget Model Context","This hidden context was supplied by an MCP App via `ui/update-model-context`.","Use it for the next assistant turn only. If it includes flow continuation or tool-call instructions, follow them exactly."];if(t.content?.length){let o=t.content.map(r=>r.type==="text"&&typeof r.text=="string"?r.text.trim():JSON.stringify(r,null,2)).filter(Boolean).join(`
3
+
4
+ `);o&&e.push(`Content blocks:
5
+ ${o}`)}return t.structuredContent&&Object.keys(t.structuredContent).length>0&&e.push(`Structured content JSON:
6
+ ${JSON.stringify(t.structuredContent,null,2)}`),e.join(`
7
+
8
+ `)}export{l as a,s as b,n as c,p as d,d as e};
9
+ //# sourceMappingURL=chunk-OMMDVQYW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mcp/react/hooks/@types.ts","../src/shared/model-context.ts"],"sourcesContent":["/**\n * Source: https://github.com/openai/openai-apps-sdk-examples/tree/main/src\n */\n\nexport type OpenAIGlobals<\n\tToolInput = UnknownObject,\n\tToolOutput = UnknownObject,\n\tToolResponseMetadata = UnknownObject,\n\tWidgetState = UnknownObject,\n> = {\n\t// visuals\n\ttheme: Theme;\n\n\tuserAgent: UserAgent;\n\tlocale: string;\n\n\t// layout\n\tmaxHeight: number;\n\tdisplayMode: DisplayMode;\n\tsafeArea: SafeArea;\n\n\t// state\n\ttoolInput: ToolInput;\n\ttoolOutput: ToolOutput | null;\n\ttoolResponseMetadata: ToolResponseMetadata | null;\n\twidgetState: WidgetState | null;\n\tsetWidgetState: (state: WidgetState) => Promise<void>;\n};\n\ntype API = {\n\tcallTool: CallTool;\n\tsendFollowUpMessage: (args: { prompt: string }) => Promise<void>;\n\topenExternal(payload: { href: string }): void;\n\n\t// Layout controls\n\trequestDisplayMode: RequestDisplayMode;\n};\n\nexport type UnknownObject = Record<string, unknown>;\n\nexport type Theme = \"light\" | \"dark\";\n\nexport type SafeAreaInsets = {\n\ttop: number;\n\tbottom: number;\n\tleft: number;\n\tright: number;\n};\n\nexport type SafeArea = {\n\tinsets: SafeAreaInsets;\n};\n\nexport type DeviceType = \"mobile\" | \"tablet\" | \"desktop\" | \"unknown\";\n\nexport type UserAgent = {\n\tdevice: { type: DeviceType };\n\tcapabilities: {\n\t\thover: boolean;\n\t\ttouch: boolean;\n\t};\n};\n\n/** Display mode */\nexport type DisplayMode = \"pip\" | \"inline\" | \"fullscreen\";\nexport type RequestDisplayMode = (args: { mode: DisplayMode }) => Promise<{\n\t/**\n\t * The granted display mode. The host may reject the request.\n\t * For mobile, PiP is always coerced to fullscreen.\n\t */\n\tmode: DisplayMode;\n}>;\n\nexport type CallToolResponse = {\n\tresult: string;\n};\n\n/** Calling APIs */\nexport type CallTool = (\n\tname: string,\n\targs: Record<string, unknown>,\n) => Promise<CallToolResponse>;\n\n/** Extra events */\nexport const SET_GLOBALS_EVENT_TYPE = \"openai:set_globals\";\nexport class SetGlobalsEvent extends CustomEvent<{\n\tglobals: Partial<OpenAIGlobals>;\n}> {\n\tconstructor(detail: { globals: Partial<OpenAIGlobals> }) {\n\t\tsuper(SET_GLOBALS_EVENT_TYPE, { detail });\n\t}\n}\n\n/**\n * Global oai object injected by the web sandbox for communicating with chatgpt host page.\n */\ndeclare global {\n\tinterface Window {\n\t\topenai: API & OpenAIGlobals;\n\t\tinnerBaseUrl: string;\n\t}\n\n\tinterface WindowEventMap {\n\t\t[SET_GLOBALS_EVENT_TYPE]: SetGlobalsEvent;\n\t}\n}\n","import type { ContentBlock } from \"@modelcontextprotocol/sdk/types.js\";\n\nexport type ModelContextContentBlock = ContentBlock;\n\nexport type ModelContextUpdate = {\n\tcontent?: ModelContextContentBlock[];\n\tstructuredContent?: Record<string, unknown>;\n};\n\nexport function hasModelContext(\n\tvalue: ModelContextUpdate | null | undefined,\n): value is ModelContextUpdate {\n\tif (!value) return false;\n\tconst hasContent = Array.isArray(value.content) && value.content.length > 0;\n\tconst hasStructuredContent =\n\t\ttypeof value.structuredContent === \"object\" &&\n\t\tvalue.structuredContent !== null &&\n\t\tObject.keys(value.structuredContent).length > 0;\n\treturn hasContent || hasStructuredContent;\n}\n\nexport function mergeModelContext(\n\tcurrent: ModelContextUpdate | null | undefined,\n\tnext: ModelContextUpdate | null | undefined,\n): ModelContextUpdate | null {\n\tif (!hasModelContext(current) && !hasModelContext(next)) return null;\n\tif (!hasModelContext(current)) {\n\t\treturn {\n\t\t\t...(next?.content ? { content: [...next.content] } : {}),\n\t\t\t...(next?.structuredContent\n\t\t\t\t? { structuredContent: { ...next.structuredContent } }\n\t\t\t\t: {}),\n\t\t};\n\t}\n\tif (!hasModelContext(next)) {\n\t\treturn {\n\t\t\t...(current.content ? { content: [...current.content] } : {}),\n\t\t\t...(current.structuredContent\n\t\t\t\t? { structuredContent: { ...current.structuredContent } }\n\t\t\t\t: {}),\n\t\t};\n\t}\n\n\treturn {\n\t\t...(current.content || next.content\n\t\t\t? { content: [...(current.content ?? []), ...(next.content ?? [])] }\n\t\t\t: {}),\n\t\t...(current.structuredContent || next.structuredContent\n\t\t\t? {\n\t\t\t\t\tstructuredContent: {\n\t\t\t\t\t\t...(current.structuredContent ?? {}),\n\t\t\t\t\t\t...(next.structuredContent ?? {}),\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t: {}),\n\t};\n}\n\nexport function formatModelContextForPrompt(\n\tvalue: ModelContextUpdate | null | undefined,\n): string {\n\tif (!hasModelContext(value)) return \"\";\n\n\tconst sections: string[] = [\n\t\t\"## Widget Model Context\",\n\t\t\"This hidden context was supplied by an MCP App via `ui/update-model-context`.\",\n\t\t\"Use it for the next assistant turn only. If it includes flow continuation or tool-call instructions, follow them exactly.\",\n\t];\n\n\tif (value.content?.length) {\n\t\tconst renderedBlocks = value.content\n\t\t\t.map((block) => {\n\t\t\t\tif (block.type === \"text\" && typeof block.text === \"string\") {\n\t\t\t\t\treturn block.text.trim();\n\t\t\t\t}\n\t\t\t\treturn JSON.stringify(block, null, 2);\n\t\t\t})\n\t\t\t.filter(Boolean)\n\t\t\t.join(\"\\n\\n\");\n\t\tif (renderedBlocks) {\n\t\t\tsections.push(`Content blocks:\\n${renderedBlocks}`);\n\t\t}\n\t}\n\n\tif (\n\t\tvalue.structuredContent &&\n\t\tObject.keys(value.structuredContent).length > 0\n\t) {\n\t\tsections.push(\n\t\t\t`Structured content JSON:\\n${JSON.stringify(value.structuredContent, null, 2)}`,\n\t\t);\n\t}\n\n\treturn sections.join(\"\\n\\n\");\n}\n"],"mappings":";AAoFO,IAAMA,EAAyB,qBACzBC,EAAN,cAA8B,WAElC,CACF,YAAYC,EAA6C,CACxD,MAAMF,EAAwB,CAAE,OAAAE,CAAO,CAAC,CACzC,CACD,EClFO,SAASC,EACfC,EAC8B,CAC9B,GAAI,CAACA,EAAO,MAAO,GACnB,IAAMC,EAAa,MAAM,QAAQD,EAAM,OAAO,GAAKA,EAAM,QAAQ,OAAS,EACpEE,EACL,OAAOF,EAAM,mBAAsB,UACnCA,EAAM,oBAAsB,MAC5B,OAAO,KAAKA,EAAM,iBAAiB,EAAE,OAAS,EAC/C,OAAOC,GAAcC,CACtB,CAEO,SAASC,EACfC,EACAC,EAC4B,CAC5B,MAAI,CAACN,EAAgBK,CAAO,GAAK,CAACL,EAAgBM,CAAI,EAAU,KAC3DN,EAAgBK,CAAO,EAQvBL,EAAgBM,CAAI,EASlB,CACN,GAAID,EAAQ,SAAWC,EAAK,QACzB,CAAE,QAAS,CAAC,GAAID,EAAQ,SAAW,CAAC,EAAI,GAAIC,EAAK,SAAW,CAAC,CAAE,CAAE,EACjE,CAAC,EACJ,GAAID,EAAQ,mBAAqBC,EAAK,kBACnC,CACA,kBAAmB,CAClB,GAAID,EAAQ,mBAAqB,CAAC,EAClC,GAAIC,EAAK,mBAAqB,CAAC,CAChC,CACD,EACC,CAAC,CACL,EApBQ,CACN,GAAID,EAAQ,QAAU,CAAE,QAAS,CAAC,GAAGA,EAAQ,OAAO,CAAE,EAAI,CAAC,EAC3D,GAAIA,EAAQ,kBACT,CAAE,kBAAmB,CAAE,GAAGA,EAAQ,iBAAkB,CAAE,EACtD,CAAC,CACL,EAbO,CACN,GAAIC,GAAM,QAAU,CAAE,QAAS,CAAC,GAAGA,EAAK,OAAO,CAAE,EAAI,CAAC,EACtD,GAAIA,GAAM,kBACP,CAAE,kBAAmB,CAAE,GAAGA,EAAK,iBAAkB,CAAE,EACnD,CAAC,CACL,CAwBF,CAEO,SAASC,EACfN,EACS,CACT,GAAI,CAACD,EAAgBC,CAAK,EAAG,MAAO,GAEpC,IAAMO,EAAqB,CAC1B,0BACA,gFACA,2HACD,EAEA,GAAIP,EAAM,SAAS,OAAQ,CAC1B,IAAMQ,EAAiBR,EAAM,QAC3B,IAAKS,GACDA,EAAM,OAAS,QAAU,OAAOA,EAAM,MAAS,SAC3CA,EAAM,KAAK,KAAK,EAEjB,KAAK,UAAUA,EAAO,KAAM,CAAC,CACpC,EACA,OAAO,OAAO,EACd,KAAK;AAAA;AAAA,CAAM,EACTD,GACHD,EAAS,KAAK;AAAA,EAAoBC,CAAc,EAAE,CAEpD,CAEA,OACCR,EAAM,mBACN,OAAO,KAAKA,EAAM,iBAAiB,EAAE,OAAS,GAE9CO,EAAS,KACR;AAAA,EAA6B,KAAK,UAAUP,EAAM,kBAAmB,KAAM,CAAC,CAAC,EAC9E,EAGMO,EAAS,KAAK;AAAA;AAAA,CAAM,CAC5B","names":["SET_GLOBALS_EVENT_TYPE","SetGlobalsEvent","detail","hasModelContext","value","hasContent","hasStructuredContent","mergeModelContext","current","next","formatModelContextForPrompt","sections","renderedBlocks","block"]}
@@ -1,4 +1,4 @@
1
1
  "use client";
2
- "use client";import{q as g,r as u,s as b,t as h}from"./chunk-HVAT4SNH.js";import"./chunk-V3ZDECK5.js";import{memo as x,useMemo as f,useContext as N,useState as v,useEffect as y}from"react";import{jsx as r,jsxs as k}from"react/jsx-runtime";var w=g("block","before:content-[counter(line)]","before:inline-block","before:[counter-increment:line]","before:w-6","before:mr-4","before:text-[13px]","before:text-right","before:text-muted-foreground/50","before:font-mono","before:select-none"),C=x(({children:a,result:e,language:o,className:s,...m})=>{let i=f(()=>({backgroundColor:e.bg,color:e.fg}),[e.bg,e.fg]);return r("pre",{className:g(s,"p-4 text-sm dark:bg-(--shiki-dark-bg)!"),"data-language":o,"data-streamdown":"code-block-body",style:i,...m,children:r("code",{className:"[counter-increment:line_0] [counter-reset:line]",children:e.tokens.map((c,l)=>r("span",{className:w,children:c.map((t,n)=>r("span",{className:"dark:bg-(--shiki-dark-bg)! dark:text-(--shiki-dark)!",style:{color:t.color,backgroundColor:t.bgColor,...t.htmlStyle},...t.htmlAttrs,children:t.content},n))},l))})})},(a,e)=>a.result===e.result&&a.language===e.language&&a.className===e.className),S=({className:a,language:e,style:o,...s})=>r("div",{className:g("my-4 w-full overflow-hidden rounded-xl border border-border",a),"data-language":e,"data-streamdown":"code-block",style:{contentVisibility:"auto",containIntrinsicSize:"auto 200px",...o},...s}),j=({language:a,children:e})=>k("div",{className:"flex items-center justify-between bg-muted/80 p-3 text-muted-foreground text-xs","data-language":a,"data-streamdown":"code-block-header",children:[r("span",{className:"ml-1 font-mono lowercase",children:a}),r("div",{className:"flex items-center gap-2",children:e})]}),E=({code:a,language:e,className:o,children:s,...m})=>{let{shikiTheme:i}=N(h),c=b(),l=f(()=>({bg:"transparent",fg:"inherit",tokens:a.split(`
2
+ "use client";import{q as g,r as u,s as b,t as h}from"./chunk-5JBHQYU5.js";import"./chunk-V3ZDECK5.js";import{memo as x,useMemo as f,useContext as N,useState as v,useEffect as y}from"react";import{jsx as r,jsxs as k}from"react/jsx-runtime";var w=g("block","before:content-[counter(line)]","before:inline-block","before:[counter-increment:line]","before:w-6","before:mr-4","before:text-[13px]","before:text-right","before:text-muted-foreground/50","before:font-mono","before:select-none"),C=x(({children:a,result:e,language:o,className:s,...m})=>{let i=f(()=>({backgroundColor:e.bg,color:e.fg}),[e.bg,e.fg]);return r("pre",{className:g(s,"p-4 text-sm dark:bg-(--shiki-dark-bg)!"),"data-language":o,"data-streamdown":"code-block-body",style:i,...m,children:r("code",{className:"[counter-increment:line_0] [counter-reset:line]",children:e.tokens.map((c,l)=>r("span",{className:w,children:c.map((t,n)=>r("span",{className:"dark:bg-(--shiki-dark-bg)! dark:text-(--shiki-dark)!",style:{color:t.color,backgroundColor:t.bgColor,...t.htmlStyle},...t.htmlAttrs,children:t.content},n))},l))})})},(a,e)=>a.result===e.result&&a.language===e.language&&a.className===e.className),S=({className:a,language:e,style:o,...s})=>r("div",{className:g("my-4 w-full overflow-hidden rounded-xl border border-border",a),"data-language":e,"data-streamdown":"code-block",style:{contentVisibility:"auto",containIntrinsicSize:"auto 200px",...o},...s}),j=({language:a,children:e})=>k("div",{className:"flex items-center justify-between bg-muted/80 p-3 text-muted-foreground text-xs","data-language":a,"data-streamdown":"code-block-header",children:[r("span",{className:"ml-1 font-mono lowercase",children:a}),r("div",{className:"flex items-center gap-2",children:e})]}),E=({code:a,language:e,className:o,children:s,...m})=>{let{shikiTheme:i}=N(h),c=b(),l=f(()=>({bg:"transparent",fg:"inherit",tokens:a.split(`
3
3
  `).map(d=>[{content:d,color:"inherit",bgColor:"transparent",htmlStyle:{},offset:0}])}),[a]),[t,n]=v(l);return y(()=>{if(!c){n(l);return}let d=c.highlight({code:a,language:e,themes:i},p=>{n(p)});if(d){n(d);return}n(l)},[a,e,i,c,l]),r(u.Provider,{value:{code:a},children:k(S,{language:e,children:[r(j,{language:e,children:s}),r(C,{className:o,language:e,result:t,...m})]})})};export{E as CodeBlock};
4
- //# sourceMappingURL=code-block-37QAKDTI-M2QCKVTI.js.map
4
+ //# sourceMappingURL=code-block-37QAKDTI-IIYKFI5S.js.map
@@ -1,4 +1,4 @@
1
- import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
1
+ import { ContentBlock, CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
2
  import { z } from 'zod';
3
3
  import { McpServer, ToolCallback } from '@modelcontextprotocol/sdk/server/mcp.js';
4
4
  export { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
@@ -25,6 +25,12 @@ declare function isOpenAI(): boolean;
25
25
  */
26
26
  declare function isMCPApps(): boolean;
27
27
 
28
+ type ModelContextContentBlock = ContentBlock;
29
+ type ModelContextUpdate = {
30
+ content?: ModelContextContentBlock[];
31
+ structuredContent?: Record<string, unknown>;
32
+ };
33
+
28
34
  /**
29
35
  * Source: https://github.com/openai/openai-apps-sdk-examples/tree/main/src
30
36
  */
@@ -177,7 +183,13 @@ interface UnifiedWidgetClient {
177
183
  * On OpenAI: openai.sendFollowUpMessage({ prompt })
178
184
  * On MCP Apps: app.sendMessages([{ role: 'user', content: { type: 'text', text: prompt } }])
179
185
  */
180
- sendFollowUp(prompt: string): void;
186
+ sendFollowUp(prompt: string): void | Promise<void>;
187
+ /**
188
+ * Update hidden model context for the next assistant turn.
189
+ * On MCP Apps this uses the standard `ui/update-model-context` request.
190
+ * On other hosts this may fall back to best-effort behavior.
191
+ */
192
+ updateModelContext(context: ModelContextUpdate): Promise<void> | void;
181
193
  /**
182
194
  * Get the current theme.
183
195
  */
@@ -324,6 +336,13 @@ type ToolConfig<TInput extends ZodRawShapeCompat> = {
324
336
  invoking?: string;
325
337
  /** Optional loaded message (defaults to "Loaded"). Only relevant when resource is present. */
326
338
  invoked?: string;
339
+ /**
340
+ * When a widget calls this tool via `tools/call`, should the host auto-inject
341
+ * the tool's text result into the next follow-up message if the widget does
342
+ * not send one itself. Defaults to true. Set false for helper tools whose
343
+ * result is consumed programmatically by the widget.
344
+ */
345
+ autoInjectResultText?: boolean;
327
346
  /** Annotations describe the tool's potential impact. */
328
347
  annotations?: {
329
348
  readOnlyHint?: boolean;
@@ -335,7 +354,7 @@ type ToolConfig<TInput extends ZodRawShapeCompat> = {
335
354
  type ToolHandler<TInput extends ZodRawShapeCompat> = (input: ShapeOutput<TInput>, context: ToolHandlerContext) => Promise<{
336
355
  /** Text content to return */
337
356
  text: string;
338
- /** Structured data to pass to the widget. Only meaningful when resource is present. */
357
+ /** Structured data returned as MCP `structuredContent`. */
339
358
  data?: Record<string, unknown>;
340
359
  }>;
341
360
  type ToolToolCallback<TInput extends ZodRawShapeCompat> = ToolCallback<TInput>;
@@ -381,6 +400,12 @@ type WidgetSignal = {
381
400
  data: Record<string, unknown>;
382
401
  /** Description of what the widget does (for the AI's context) */
383
402
  description?: string;
403
+ /**
404
+ * Whether the user is expected to interact with the widget before the flow continues.
405
+ * Defaults to true. Set to false for informational widgets that should render and then
406
+ * immediately advance to the next flow step.
407
+ */
408
+ interactive?: boolean;
384
409
  /**
385
410
  * State key this widget fills — enables auto-skip when the field is already in state.
386
411
  * Pass this so the engine can skip the widget step when the answer is already known.
@@ -427,6 +452,7 @@ declare function interrupt(config: {
427
452
  declare function showWidget(tool: RegisteredTool, config: {
428
453
  data: Record<string, unknown>;
429
454
  description?: string;
455
+ interactive?: boolean;
430
456
  field?: string;
431
457
  }): WidgetSignal;
432
458
  type MaybePromise<T> = T | Promise<T>;
@@ -518,6 +544,21 @@ type RegisteredFlow = {
518
544
  description: string;
519
545
  register: (server: McpServer) => Promise<void>;
520
546
  };
547
+ type FlowTokenContent = {
548
+ step?: string;
549
+ state: Record<string, unknown>;
550
+ field?: string;
551
+ widgetId?: string;
552
+ /** Cached interrupt questions — avoids re-executing the handler on partial answers */
553
+ questions?: Array<{
554
+ question: string;
555
+ field: string;
556
+ suggestions?: string[];
557
+ context?: string;
558
+ }>;
559
+ /** Cached overall interrupt context */
560
+ interruptContext?: string;
561
+ };
521
562
 
522
563
  /**
523
564
  * A LangGraph-inspired state graph builder for MCP tools.
@@ -606,6 +647,21 @@ declare function createFlow<const TSchema extends Record<string, z.ZodType>>(con
606
647
  state: TSchema;
607
648
  }): StateGraph<InferFlowState<TSchema>>;
608
649
 
650
+ /**
651
+ * Opaque compressed token for flow state round-tripping.
652
+ *
653
+ * Flow state is compressed (zlib deflate) then base64-encoded. This produces
654
+ * a token that:
655
+ * 1. Looks like random noise — models can't mentally decode it
656
+ * 2. Is smaller than raw base64 (~30% compression on typical state)
657
+ * 3. The model treats it as an opaque string and passes it back unchanged
658
+ *
659
+ * Only encoded/decoded on the server (Node.js). Browser code should never
660
+ * need to decode flow tokens.
661
+ */
662
+
663
+ declare function encodeFlowToken(data: FlowTokenContent): string;
664
+
609
665
  /**
610
666
  * Creates a reusable UI resource (HTML template) that can be attached
611
667
  * to tools or flow nodes.
@@ -628,8 +684,8 @@ declare function createResource(config: ResourceConfig): RegisteredResource;
628
684
  /**
629
685
  * Creates an MCP tool with minimal boilerplate.
630
686
  *
631
- * When `config.resource` is provided, the tool returns `structuredContent` + widget metadata.
632
- * Without a resource, the tool returns plain text content.
687
+ * When `handler()` returns `data`, the tool includes it as MCP `structuredContent`.
688
+ * When `config.resource` is provided, the tool also returns widget metadata.
633
689
  *
634
690
  * @example
635
691
  * ```ts
@@ -966,4 +1022,4 @@ type WithWaniwaniOptions = {
966
1022
  */
967
1023
  declare function withWaniwani(server: McpServer, options?: WithWaniwaniOptions): McpServer;
968
1024
 
969
- export { type ConditionFn, END, type FlowConfig, type HostContext, type InferFlowState, type InterruptSignal, type NodeConfig, type NodeHandler, type RegisteredFlow, type RegisteredResource, type RegisteredTool, type ResourceConfig, START, StateGraph, type ToolCallResult, type ToolConfig, type ToolHandler, type ToolHandlerContext, type ToolResult, type ToolToolCallback, type TrackingRouteOptions, type UnifiedWidgetClient, type WidgetCSP, type WidgetPlatform, type WidgetSignal, type WithWaniwaniOptions, createFlow, createResource, createTool, createTrackingRoute, detectPlatform, interrupt, isMCPApps, isOpenAI, registerTools, showWidget, withWaniwani };
1025
+ export { type ConditionFn, END, type FlowConfig, type HostContext, type InferFlowState, type InterruptSignal, type NodeConfig, type NodeHandler, type RegisteredFlow, type RegisteredResource, type RegisteredTool, type ResourceConfig, START, StateGraph, type ToolCallResult, type ToolConfig, type ToolHandler, type ToolHandlerContext, type ToolResult, type ToolToolCallback, type TrackingRouteOptions, type UnifiedWidgetClient, type WidgetCSP, type WidgetPlatform, type WidgetSignal, type WithWaniwaniOptions, createFlow, createResource, createTool, createTrackingRoute, detectPlatform, encodeFlowToken, interrupt, isMCPApps, isOpenAI, registerTools, showWidget, withWaniwani };
package/dist/mcp/index.js CHANGED
@@ -1,4 +1,4 @@
1
- function D(){return typeof window<"u"&&"openai"in window?"openai":"mcp-apps"}function ge(){return D()==="openai"}function he(){return D()==="mcp-apps"}var k="__start__",S="__end__",U=Symbol.for("waniwani.flow.interrupt"),H=Symbol.for("waniwani.flow.widget");function z(e){if("questions"in e)return{__type:U,questions:e.questions,context:e.context};let{question:t,field:n,context:r,suggestions:o}=e;return{__type:U,questions:[{question:t,field:n,context:r,suggestions:o}]}}function K(e,t){return{__type:H,tool:e,...t}}function Y(e){return typeof e=="object"&&e!==null&&"__type"in e&&e.__type===U}function J(e){return typeof e=="object"&&e!==null&&"__type"in e&&e.__type===H}import{z as R}from"zod";import{deflateSync as me,inflateSync as we}from"zlib";function X(e){let t=JSON.stringify(e);return me(t).toString("base64")}function Z(e){try{let t=Buffer.from(e,"base64"),n=we(t).toString("utf-8");return JSON.parse(n)}catch{return null}}function ye(e){let t=e.description??"",n=e._zod?.def;if(n?.type==="enum"&&n.entries){let r=Object.keys(n.entries).map(o=>`"${o}"`).join(" | ");return t?`${r} \u2014 ${t}`:r}return t}function Te(e){let t=["","## FLOW EXECUTION PROTOCOL","","This tool implements a multi-step conversational flow. Follow this protocol exactly:","",'1. Call with `action: "start"` to begin. If the user\'s message already'," contains answers to likely questions, extract them into `stateUpdates`"," as `{ field: value }` pairs. The engine will auto-skip steps whose"," fields are already filled."," Only extract values the user explicitly stated \u2014 do NOT guess or invent values."];if(e.state){let n=Object.entries(e.state).map(([r,o])=>{let s=ye(o);return s?`\`${r}\` (${s})`:`\`${r}\``}).join(", ");t.push(` Known fields: ${n}.`)}return t.push("2. The response JSON `status` field tells you what to do next:",' - `"interrupt"`: Pause and ask the user. Two forms:'," a. Single question: `{ question, field, context? }` \u2014 ask `question`, store answer in `field`."," b. Multi-question: `{ questions: [{question, field}, ...], context? }` \u2014 ask ALL questions"," in one conversational message, collect all answers."," `context` (if present) is hidden AI instructions \u2014 use to shape your response, do NOT show verbatim."," Then call again with:",' `action: "continue"`, `flowToken` = the `flowToken` from the response (pass back exactly as received),'," `stateUpdates` = answers keyed by their `field` names, plus any other fields the user mentioned.",' - `"widget"`: The flow wants to show a UI widget. Call the tool named in the `tool`'," field, passing the `data` object as the tool's input."," Present the widget result to the user. When the user makes a choice or interacts"," with the widget, call THIS flow tool again with:",' `action: "continue"`, `flowToken` = the `flowToken` from the response,'," `stateUpdates` = `{ [field]: <user's selection> }` plus any other fields the user mentioned.",' - `"complete"`: The flow is done. Present the result to the user.',' - `"error"`: Something went wrong. Show the `error` message.',"","3. ALWAYS pass back the `flowToken` string exactly as received \u2014 it is an opaque token, do not modify it.","4. Do NOT invent state values. Only use `stateUpdates` for information the user explicitly provided.","5. Include only the fields the user actually answered in `stateUpdates` \u2014 do NOT guess missing ones."," If the user did not answer all pending questions, the engine will re-prompt for the remaining ones."," If the user mentioned values for other known fields, include those too \u2014"," they will be applied immediately and those steps will be auto-skipped."),t.join(`
2
- `)}function ke(e){if(e.flowToken){let t=Z(e.flowToken);if(t)return t}return{step:void 0,state:{}}}async function I(e,t){return e.type==="direct"?e.to:e.condition(t)}function O(e){return e!=null&&e!==""}function G(e,t,n,r){if(e.every(c=>O(r[c.field])))return null;let o=e.filter(c=>!O(r[c.field])),s=o.length===1,i=o[0];return{content:s&&i?{status:"interrupt",question:i.question,field:i.field,...i.suggestions?{suggestions:i.suggestions}:{},...i.context||t?{context:i.context??t}:{}}:{status:"interrupt",questions:o,...t?{context:t}:{}},flowTokenContent:{step:n,state:r,...s&&i?{field:i.field}:{},questions:e,...t?{interruptContext:t}:{}}}}async function B(e,t,n,r,o,s){let i=e,a={...t},c=50,f=0;for(;f++<c;){if(i===S)return{content:{status:"complete"},flowTokenContent:{state:a}};let g=n.get(i);if(!g)return{content:{status:"error",error:`Unknown node: "${i}"`}};try{let d=await g(a,s);if(Y(d)){let l=G(d.questions,d.context,i,a);if(l)return l;let p=o.get(i);if(!p)return{content:{status:"error",error:`No outgoing edge from node "${i}"`}};i=await I(p,a);continue}if(J(d)){let l=d.field??r.get(i)?.field;if(l&&O(a[l])){let p=o.get(i);if(!p)return{content:{status:"error",error:`No outgoing edge from node "${i}"`}};i=await I(p,a);continue}return{content:{status:"widget",tool:d.tool.id,data:d.data,description:d.description},flowTokenContent:{step:i,state:a,field:l,widgetId:d.tool.id},structuredContent:d.data}}a={...a,...d};let u=o.get(i);if(!u)return{content:{status:"error",error:`No outgoing edge from node "${i}"`}};i=await I(u,a)}catch(d){return{content:{status:"error",error:d instanceof Error?d.message:String(d)},flowTokenContent:{step:i,state:a}}}}return{content:{status:"error",error:"Flow exceeded maximum iterations (possible infinite loop)"}}}var Se={action:R.enum(["start","continue"]).describe('"start" to begin the flow, "continue" to resume after a pause (interrupt or widget)'),stateUpdates:R.record(R.string(),R.unknown()).optional().describe("State field values to set before processing the next node. Use this to pass the user's answer (keyed by the field name from the response) and any other values the user mentioned."),flowToken:R.string().optional().describe("Opaque flow token from the previous response. Pass back exactly as received.")};function Q(e){let{config:t,nodes:n,nodeConfigs:r,edges:o}=e,s=Te(t),i=`${t.description}
3
- ${s}`;async function a(c,f){let g=ke(c),d=g.state;if(c.action==="start"){let u=o.get(k);if(!u)return{content:{status:"error",error:"No start edge"}};let l={...d,...c.stateUpdates??{}},p=await I(u,l);return B(p,l,n,r,o,f)}if(c.action==="continue"){let u=g.step;if(!u)return{content:{status:"error",error:'Missing or invalid "flowToken" for continue action. Pass back the flowToken from the previous response exactly as received.'}};let l={...d,...c.stateUpdates??{}};if(g.questions){let p=G(g.questions,g.interruptContext,u,l);if(p)return p}if(g.questions||g.widgetId){let p=o.get(u);if(!p)return{content:{status:"error",error:`No edge from step "${u}"`}};let v=await I(p,l);return B(v,l,n,r,o,f)}return B(u,l,n,r,o,f)}return{content:{status:"error",error:`Unknown action: "${c.action}"`}}}return{id:t.id,title:t.title,description:i,async register(c){c.registerTool(t.id,{title:t.title,description:i,inputSchema:Se,annotations:t.annotations},(async(f,g)=>{let u=g._meta??{},l=await a(f,u),p=l.flowTokenContent?X(l.flowTokenContent):void 0,v={...l.content,...p?{flowToken:p}:{}};return{content:[{type:"text",text:JSON.stringify(v,null,2)}],...l.structuredContent&&{structuredContent:l.structuredContent},_meta:u}}))}}}var E=class{nodes=new Map;nodeConfigs=new Map;edges=new Map;config;constructor(t){this.config=t}addNode(t,n,r){if(t===k||t===S)throw new Error(`"${t}" is a reserved name and cannot be used as a node name`);if(this.nodes.has(t))throw new Error(`Node "${t}" already exists`);let o,s={};if(typeof n=="function")o=n;else if(r)o=r,s=n;else throw new Error(`Node "${t}" requires a handler function.`);return this.nodes.set(t,o),this.nodeConfigs.set(t,s),this}addEdge(t,n){if(this.edges.has(t))throw new Error(`Node "${t}" already has an outgoing edge. Use addConditionalEdge for branching.`);return this.edges.set(t,{type:"direct",to:n}),this}addConditionalEdge(t,n){if(this.edges.has(t))throw new Error(`Node "${t}" already has an outgoing edge.`);return this.edges.set(t,{type:"conditional",condition:n}),this}compile(){return this.validate(),Q({config:this.config,nodes:new Map(this.nodes),nodeConfigs:new Map(this.nodeConfigs),edges:new Map(this.edges)})}validate(){if(!this.edges.has(k))throw new Error('Flow must have an entry point. Add an edge from START: .addEdge(START, "first_node")');let t=this.edges.get(k);if(t?.type==="direct"&&t.to!==S&&!this.nodes.has(t.to))throw new Error(`START edge references non-existent node: "${t.to}"`);for(let[n,r]of this.edges){if(n!==k&&!this.nodes.has(n))throw new Error(`Edge from non-existent node: "${n}"`);if(r.type==="direct"&&r.to!==S&&!this.nodes.has(r.to))throw new Error(`Edge from "${n}" references non-existent node: "${r.to}"`)}for(let[n]of this.nodes)if(!this.edges.has(n))throw new Error(`Node "${n}" has no outgoing edge. Add one with .addEdge("${n}", ...) or .addConditionalEdge("${n}", ...)`)}};function ee(e){return new E(e)}var b="text/html+skybridge",_="text/html;profile=mcp-app",te=async(e,t)=>{let n=e.endsWith("/")?e.slice(0,-1):e;return await(await fetch(`${n}${t}`)).text()};function ne(e){return{"openai/widgetDescription":e.description,"openai/widgetPrefersBorder":e.prefersBorder,"openai/widgetDomain":e.widgetDomain,...e.widgetCSP&&{"openai/widgetCSP":e.widgetCSP}}}function re(e){let t=e.widgetCSP?{connectDomains:e.widgetCSP.connect_domains,resourceDomains:e.widgetCSP.resource_domains,frameDomains:e.widgetCSP.frame_domains,redirectDomains:e.widgetCSP.redirect_domains}:void 0;return{ui:{...t&&{csp:t},...e.widgetDomain&&{domain:e.widgetDomain},...e.prefersBorder!==void 0&&{prefersBorder:e.prefersBorder}}}}function q(e){return{...e.openaiTemplateUri&&{"openai/outputTemplate":e.openaiTemplateUri},"openai/toolInvocation/invoking":e.invoking,"openai/toolInvocation/invoked":e.invoked,"openai/widgetAccessible":!0,"openai/resultCanProduceWidget":!0,...e.mcpTemplateUri&&{ui:{resourceUri:e.mcpTemplateUri,...e.autoHeight&&{autoHeight:!0}}}}}function oe(e){let{id:t,title:n,description:r,baseUrl:o,htmlPath:s,widgetDomain:i,prefersBorder:a=!0,autoHeight:c=!0}=e,f=e.widgetCSP??{connect_domains:[o],resource_domains:[o]};if(process.env.NODE_ENV==="development")try{let{hostname:w}=new URL(o);(w==="localhost"||w==="127.0.0.1")&&(f={...f,connect_domains:[...f.connect_domains||[],`ws://${w}:*`,`wss://${w}:*`],resource_domains:[...f.resource_domains||[],`http://${w}:*`]})}catch{}let g=`ui://widgets/apps-sdk/${t}.html`,d=`ui://widgets/ext-apps/${t}.html`,u=null,l=()=>(u||(u=te(o,s)),u),p=r;async function v(w){let x=await l();w.registerResource(`${t}-openai-widget`,g,{title:n,description:p,mimeType:b,_meta:{"openai/widgetDescription":p,"openai/widgetPrefersBorder":a}},async T=>({contents:[{uri:T.href,mimeType:b,text:x,_meta:ne({description:p,prefersBorder:a,widgetDomain:i,widgetCSP:f})}]})),w.registerResource(`${t}-mcp-widget`,d,{title:n,description:p,mimeType:_,_meta:{ui:{prefersBorder:a}}},async T=>({contents:[{uri:T.href,mimeType:_,text:x,_meta:re({description:p,prefersBorder:a,widgetCSP:f})}]}))}return{id:t,title:n,description:r,openaiUri:g,mcpUri:d,autoHeight:c,register:v}}function ve(e,t){let{resource:n,description:r,inputSchema:o,annotations:s}=e,i=e.id??n?.id,a=e.title??n?.title;if(!i)throw new Error("createTool: `id` is required when no resource is provided");if(!a)throw new Error("createTool: `title` is required when no resource is provided");let c=n?q({openaiTemplateUri:n.openaiUri,mcpTemplateUri:n.mcpUri,invoking:e.invoking??"Loading...",invoked:e.invoked??"Loaded",autoHeight:n.autoHeight}):void 0;return{id:i,title:a,description:r,async register(f){f.registerTool(i,{title:a,description:r,inputSchema:o,annotations:s,...c&&{_meta:c}},(async(g,d)=>{let l=d._meta??{},p=await t(g,{extra:{_meta:l}});return n&&p.data?{content:[{type:"text",text:p.text}],structuredContent:p.data,_meta:{...c,...l}}:{content:[{type:"text",text:p.text}]}}))}}}async function Ee(e,t){await Promise.all(t.map(n=>n.register(e)))}var M=class extends Error{constructor(n,r){super(n);this.status=r;this.name="WaniWaniError"}};var xe="@waniwani/sdk";function ie(e){let{baseUrl:t,apiKey:n}=e;function r(){if(!n)throw new Error("WANIWANI_API_KEY is not set");return n}async function o(s,i,a){let c=r(),f=`${t.replace(/\/$/,"")}${i}`,g={Authorization:`Bearer ${c}`,"X-WaniWani-SDK":xe},d={method:s,headers:g};a!==void 0&&(g["Content-Type"]="application/json",d.body=JSON.stringify(a));let u=await fetch(f,d);if(!u.ok){let p=await u.text().catch(()=>"");throw new M(p||`KB API error: HTTP ${u.status}`,u.status)}return(await u.json()).data}return{async ingest(s){return o("POST","/api/mcp/kb/ingest",{files:s})},async search(s,i){return o("POST","/api/mcp/kb/search",{query:s,...i})},async sources(){return o("GET","/api/mcp/kb/sources")}}}var Re="@waniwani/sdk";function j(e,t={}){let n=t.now??(()=>new Date),r=t.generateId??se,o=be(e),s=P(e.meta),i=P(e.metadata),a=_e(e,s),c=y(e.eventId)??r(),f=Me(e.timestamp,n),g=y(e.source)??t.source??Re,d=$(e)?{...e}:void 0,u={...i};return Object.keys(s).length>0&&(u.meta=s),d&&(u.rawLegacy=d),{id:c,type:"mcp.event",name:o,source:g,timestamp:f,correlation:a,properties:Ie(e,o),metadata:u,rawLegacy:d}}function se(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?`evt_${crypto.randomUUID()}`:`evt_${Math.random().toString(36).slice(2,10)}_${Date.now().toString(36)}`}function Ie(e,t){if(!$(e))return P(e.properties);let n=Ce(e,t),r=P(e.properties);return{...n,...r}}function Ce(e,t){switch(t){case"tool.called":{let n={};return y(e.toolName)&&(n.name=e.toolName),y(e.toolType)&&(n.type=e.toolType),n}case"quote.succeeded":{let n={};return typeof e.quoteAmount=="number"&&(n.amount=e.quoteAmount),y(e.quoteCurrency)&&(n.currency=e.quoteCurrency),n}case"link.clicked":{let n={};return y(e.linkUrl)&&(n.url=e.linkUrl),n}case"purchase.completed":{let n={};return typeof e.purchaseAmount=="number"&&(n.amount=e.purchaseAmount),y(e.purchaseCurrency)&&(n.currency=e.purchaseCurrency),n}default:return{}}}function be(e){return $(e)?e.eventType:e.event}function _e(e,t){let n=y(e.requestId)??C(t,["openai/requestId","requestId","mcp/requestId"]),r=y(e.sessionId)??C(t,["openai/sessionId","sessionId","conversationId","anthropic/sessionId"]),o=y(e.traceId)??C(t,["openai/traceId","traceId","mcp/traceId","openai/requestId","requestId"]),s=y(e.externalUserId)??C(t,["openai/userId","externalUserId","userId","actorId"]),i=y(e.correlationId)??C(t,["correlationId","openai/requestId"])??n,a={};return r&&(a.sessionId=r),o&&(a.traceId=o),n&&(a.requestId=n),i&&(a.correlationId=i),s&&(a.externalUserId=s),a}function Me(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 C(e,t){for(let n of t){let r=e[n];if(typeof r=="string"&&r.trim().length>0)return r}}function P(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function y(e){if(typeof e=="string"&&e.trim().length!==0)return e}function $(e){return"eventType"in e}var Pe="/api/mcp/events/v2/batch";var ae="@waniwani/sdk",We=new Set([401,403]),Fe=new Set([408,425,429,500,502,503,504]);function ce(e){return new L(e)}var L=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=De(t.baseUrl,t.endpointPath??Pe),this.flushIntervalMs=t.flushIntervalMs??1e3,this.maxBatchSize=t.maxBatchSize??20,this.maxBufferSize=t.maxBufferSize??1e3,this.maxRetries=t.maxRetries??3,this.retryBaseDelayMs=t.retryBaseDelayMs??200,this.retryMaxDelayMs=t.retryMaxDelayMs??2e3,this.shutdownTimeoutMs=t.shutdownTimeoutMs??2e3,this.fetchFn=t.fetchFn??fetch,this.logger=t.logger??console,this.now=t.now??(()=>new Date),this.sleep=t.sleep??(n=>new Promise(r=>setTimeout(r,n))),this.apiKey=t.apiKey,this.sdkVersion=t.sdkVersion,this.flushIntervalMs>0&&(this.flushTimer=setInterval(()=>{this.flush()},this.flushIntervalMs))}enqueue(t){if(this.isStopped||this.isShuttingDown){this.logger.warn("[WaniWani] Tracking transport is stopped, dropping event %s",t.id);return}if(this.buffer.length>=this.maxBufferSize){let n=this.buffer.length-this.maxBufferSize+1;this.buffer.splice(0,n),this.logger.warn("[WaniWani] Tracking buffer overflow, dropped %d oldest event(s)",n)}if(this.buffer.push(t),this.buffer.length>=this.maxBatchSize){this.flush();return}this.scheduleMicroFlush()}pendingEvents(){return this.buffer.length+this.inFlightCount}async flush(){return this.flushInFlight?this.flushInFlight:(this.flushInFlight=this.flushLoop().finally(()=>{this.flushInFlight=void 0}),this.flushInFlight)}async shutdown(t){this.isShuttingDown=!0,this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=void 0),this.flushScheduledTimer&&(clearTimeout(this.flushScheduledTimer),this.flushScheduledTimer=void 0,this.flushScheduled=!1);let n=t?.timeoutMs??this.shutdownTimeoutMs,r=this.flush();if(!Number.isFinite(n)||n<=0)return await r,this.isStopped=!0,{timedOut:!1,pendingEvents:this.pendingEvents()};let o=Symbol("shutdown-timeout");return await Promise.race([r.then(()=>"flushed"),this.sleep(n).then(()=>o)])===o?(this.isStopped=!0,{timedOut:!0,pendingEvents:this.pendingEvents()}):(this.isStopped=!0,{timedOut:!1,pendingEvents:this.pendingEvents()})}scheduleMicroFlush(){this.flushScheduled||(this.flushScheduled=!0,this.flushScheduledTimer=setTimeout(()=>{this.flushScheduledTimer=void 0,this.flushScheduled=!1,this.flush()},0))}async flushLoop(){for(;this.buffer.length>0&&!this.isStopped;){let t=this.buffer.splice(0,this.maxBatchSize);await this.sendBatchWithRetry(t)}}async sendBatchWithRetry(t){let n=0,r=t;for(;r.length>0&&!this.isStopped;){this.inFlightCount=r.length;let o=await this.sendBatchOnce(r);switch(this.inFlightCount=0,o.kind){case"success":return;case"auth":this.stopTransportForAuthFailure(o.status,r.length);return;case"permanent":this.logger.error("[WaniWani] Dropping %d event(s) after permanent failure: %s",r.length,o.reason);return;case"retryable":if(n>=this.maxRetries){this.logger.error("[WaniWani] Dropping %d event(s) after retry exhaustion: %s",r.length,o.reason);return}await this.sleep(this.backoffDelayMs(n)),n+=1;continue;case"partial":if(o.permanent.length>0&&this.logger.error("[WaniWani] Dropping %d event(s) rejected as permanent",o.permanent.length),o.retryable.length===0)return;if(n>=this.maxRetries){this.logger.error("[WaniWani] Dropping %d retryable event(s) after retry exhaustion",o.retryable.length);return}r=o.retryable,await this.sleep(this.backoffDelayMs(n)),n+=1;continue}}}async sendBatchOnce(t){let n;try{n=await this.fetchFn(this.endpointUrl,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,"X-WaniWani-SDK":ae},body:JSON.stringify(this.makeBatchRequest(t))})}catch(s){return{kind:"retryable",reason:Ue(s)}}if(We.has(n.status))return{kind:"auth",status:n.status};if(Fe.has(n.status))return{kind:"retryable",reason:`HTTP ${n.status}`};if(!n.ok)return{kind:"permanent",reason:`HTTP ${n.status}`};let r=await Ne(n);if(!r?.rejected||r.rejected.length===0)return{kind:"success"};let o=this.classifyRejectedEvents(t,r.rejected);return o.retryable.length===0&&o.permanent.length===0?{kind:"success"}:{kind:"partial",retryable:o.retryable,permanent:o.permanent}}makeBatchRequest(t){return{sentAt:this.now().toISOString(),source:{sdk:ae,version:this.sdkVersion??"0.0.0"},events:t}}classifyRejectedEvents(t,n){let r=new Map(t.map(i=>[i.id,i])),o=[],s=[];for(let i of n){let a=r.get(i.eventId);if(a){if(Ae(i)){o.push(a);continue}s.push(a)}}return{retryable:o,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 Ae(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 Ne(e){let t=await e.text();if(t)try{return JSON.parse(t)}catch{return}}function De(e,t){let n=e.endsWith("/")?e:`${e}/`,r=t.startsWith("/")?t.slice(1):t;return`${n}${r}`}function Ue(e){return e instanceof Error?e.message:String(e)}function de(e){let{baseUrl:t,apiKey:n,tracking:r}=e;function o(){if(!n)throw new Error("WANIWANI_API_KEY is not set");return n}let s=n?ce({baseUrl:t,apiKey:n,endpointPath:r.endpointPath,flushIntervalMs:r.flushIntervalMs,maxBatchSize:r.maxBatchSize,maxBufferSize:r.maxBufferSize,maxRetries:r.maxRetries,retryBaseDelayMs:r.retryBaseDelayMs,retryMaxDelayMs:r.retryMaxDelayMs,shutdownTimeoutMs:r.shutdownTimeoutMs}):void 0,i={async track(a){o();let c=j(a);return s?.enqueue(c),{eventId:c.id}},async flush(){o(),await s?.flush()},async shutdown(a){return o(),await s?.shutdown({timeoutMs:a?.timeoutMs??r.shutdownTimeoutMs})??{timedOut:!1,pendingEvents:0}}};return s&&Be(i,r.shutdownTimeoutMs),i}function Be(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 W(e){let t=e?.baseUrl??"https://app.waniwani.ai",n=e?.apiKey??process.env.WANIWANI_API_KEY,r={endpointPath:e?.tracking?.endpointPath??"/api/mcp/events/v2/batch",flushIntervalMs:e?.tracking?.flushIntervalMs??1e3,maxBatchSize:e?.tracking?.maxBatchSize??20,maxBufferSize:e?.tracking?.maxBufferSize??1e3,maxRetries:e?.tracking?.maxRetries??3,retryBaseDelayMs:e?.tracking?.retryBaseDelayMs??200,retryMaxDelayMs:e?.tracking?.retryMaxDelayMs??2e3,shutdownTimeoutMs:e?.tracking?.shutdownTimeoutMs??2e3},o={baseUrl:t,apiKey:n,tracking:r},s=de(o),i=ie(o);return{...s,kb:i,_config:o}}function Oe(e){let t=e.event_type??"widget_click",r=t.startsWith("widget_")?t:`widget_${t}`,o={...e.metadata??{}};return e.event_name&&(o.event_name=e.event_name),{event:r,properties:o,sessionId:e.session_id,traceId:e.trace_id,externalUserId:e.user_id,eventId:e.event_id,timestamp:e.timestamp,source:e.source??"widget"}}function qe(e){let t={apiKey:e?.apiKey,baseUrl:e?.baseUrl},n;function r(){return n||(n=W(t)),n}return async function(s){let i;try{i=await s.json()}catch{return new Response(JSON.stringify({error:"Invalid JSON"}),{status:400,headers:{"Content-Type":"application/json"}})}if(!Array.isArray(i.events)||i.events.length===0)return new Response(JSON.stringify({error:"Missing or empty events array"}),{status:400,headers:{"Content-Type":"application/json"}});try{let a=r(),c=[];for(let f of i.events){let g=Oe(f),d=await a.track(g);c.push(d.eventId)}return await a.flush(),new Response(JSON.stringify({ok:!0,accepted:c.length}),{status:200,headers:{"Content-Type":"application/json"}})}catch(a){let c=a instanceof Error?a.message:"Unknown error";return new Response(JSON.stringify({error:c}),{status:500,headers:{"Content-Type":"application/json"}})}}}var F=class{cached=null;pending=null;config;constructor(t){this.config=t}async getToken(t,n){return this.cached&&Date.now()<this.cached.expiresAt-12e4?this.cached.token:this.pending?this.pending:(this.pending=this.mint(t,n).finally(()=>{this.pending=null}),this.pending)}async mint(t,n){let r=je(this.config.baseUrl,"/api/mcp/widget-tokens"),o={};t&&(o.sessionId=t),n&&(o.traceId=n);try{let s=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify(o)});if(!s.ok)return null;let i=await s.json(),a=i.data&&typeof i.data=="object"?i.data:i,c=new Date(a.expiresAt).getTime();return!a.token||Number.isNaN(c)?null:(this.cached={token:a.token,expiresAt:c},a.token)}catch{return null}}};function je(e,t){return`${e.endsWith("/")?e.slice(0,-1):e}${t}`}var ue="https://app.waniwani.ai";function $e(e,t={}){let n=e;if(n.__waniwaniWrapped)return n;n.__waniwaniWrapped=!0;let r=t.client??W(t.config),o=t.injectWidgetToken!==!1,s=null;function i(){if(s)return s;let c=r._config.apiKey;return c?(s=new F({baseUrl:r._config.baseUrl??ue,apiKey:c}),s):null}let a=e.registerTool.bind(e);return n.registerTool=((...c)=>{let[f,g,d]=c,u=typeof f=="string"&&f.trim().length>0?f:"unknown";if(typeof d!="function")return a(...c);let l=d;return a(f,g,async(v,w)=>{let x=performance.now();try{let T=await l(v,w),N=Math.round(performance.now()-x);return await le(r,pe(u,w,t,{durationMs:N,status:"ok"}),t.onError),t.flushAfterToolCall&&await fe(r,t.onError),o&&await Le(T,i(),r._config.baseUrl??ue,t.onError),T}catch(T){let N=Math.round(performance.now()-x);throw await le(r,pe(u,w,t,{durationMs:N,status:"error",errorMessage:T instanceof Error?T.message:String(T)}),t.onError),t.flushAfterToolCall&&await fe(r,t.onError),T}})}),n}async function Le(e,t,n,r){if(!A(e))return;A(e._meta)||(e._meta={});let o=e._meta,s={endpoint:`${n.replace(/\/$/,"")}/api/mcp/events/v2/batch`};if(t)try{let i=await t.getToken();i&&(s.token=i)}catch(i){r?.(V(i))}o.waniwani=s}function pe(e,t,n,r){let o=Ve(e,n.toolType),s=He(t);return{event:"tool.called",properties:{name:e,type:o,...r??{}},meta:s,metadata:{source:"withWaniwani",...n.metadata??{}}}}function Ve(e,t){return typeof t=="function"?t(e)??"other":t??"other"}function He(e){if(!A(e))return;let t=e._meta;if(A(t))return t}function A(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}async function le(e,t,n){try{await e.track(t)}catch(r){n?.(V(r))}}async function fe(e,t){try{await e.flush()}catch(n){t?.(V(n))}}function V(e){return e instanceof Error?e:new Error(String(e))}export{S as END,k as START,E as StateGraph,ee as createFlow,oe as createResource,ve as createTool,qe as createTrackingRoute,D as detectPlatform,z as interrupt,he as isMCPApps,ge as isOpenAI,Ee as registerTools,K as showWidget,$e as withWaniwani};
1
+ function U(){return typeof window<"u"&&"openai"in window?"openai":"mcp-apps"}function ge(){return U()==="openai"}function he(){return U()==="mcp-apps"}var S="__start__",v="__end__",B=Symbol.for("waniwani.flow.interrupt"),z=Symbol.for("waniwani.flow.widget");function K(e){if("questions"in e)return{__type:B,questions:e.questions,context:e.context};let{question:t,field:n,context:r,suggestions:o}=e;return{__type:B,questions:[{question:t,field:n,context:r,suggestions:o}]}}function Y(e,t){return{__type:z,tool:e,...t}}function J(e){return typeof e=="object"&&e!==null&&"__type"in e&&e.__type===B}function X(e){return typeof e=="object"&&e!==null&&"__type"in e&&e.__type===z}import{z as R}from"zod";import{deflateSync as me,inflateSync as we}from"zlib";function b(e){let t=JSON.stringify(e);return me(t).toString("base64")}function Z(e){try{let t=Buffer.from(e,"base64"),n=we(t).toString("utf-8");return JSON.parse(n)}catch{return null}}function ye(e){let t=e.description??"",n=e._zod?.def;if(n?.type==="enum"&&n.entries){let r=Object.keys(n.entries).map(o=>`"${o}"`).join(" | ");return t?`${r} \u2014 ${t}`:r}return t}function Te(e){let t=["","## FLOW EXECUTION PROTOCOL","","This tool implements a multi-step conversational flow. Follow this protocol exactly:","",'1. Call with `action: "start"` to begin. If the user\'s message already'," contains answers to likely questions, extract them into `stateUpdates`"," as `{ field: value }` pairs. The engine will auto-skip steps whose"," fields are already filled."," Only extract values the user explicitly stated \u2014 do NOT guess or invent values."];if(e.state){let n=Object.entries(e.state).map(([r,o])=>{let s=ye(o);return s?`\`${r}\` (${s})`:`\`${r}\``}).join(", ");t.push(` Known fields: ${n}.`)}return t.push("2. The response JSON `status` field tells you what to do next:",' - `"interrupt"`: Pause and ask the user. Two forms:'," a. Single question: `{ question, field, context? }` \u2014 ask `question`, store answer in `field`."," b. Multi-question: `{ questions: [{question, field}, ...], context? }` \u2014 ask ALL questions"," in one conversational message, collect all answers."," `context` (if present) is hidden AI instructions \u2014 use to shape your response, do NOT show verbatim."," Then call again with:",' `action: "continue"`, `flowToken` = the `flowToken` from the response (pass back exactly as received),'," `stateUpdates` = answers keyed by their `field` names, plus any other fields the user mentioned.",' - `"widget"`: The flow wants to show a UI widget. Call the tool named in the `tool`'," field, passing the `data` object as the tool's input."," If the response includes `interactive: false`, the widget is display-only:"," call the display tool, show the widget, then immediately call THIS flow tool again with",' `action: "continue"` and the same `flowToken`. In that case, do NOT wait for the user'," to click or use the widget, and do NOT ask them to interact with it unless the"," description explicitly says otherwise."," Otherwise, present the widget result to the user. When the user makes a choice or interacts"," with the widget, call THIS flow tool again with:",' `action: "continue"`, `flowToken` = the `flowToken` from the response,'," `stateUpdates` = `{ [field]: <user's selection> }` plus any other fields the user mentioned.",' - `"complete"`: The flow is done. Present the result to the user.',' - `"error"`: Something went wrong. Show the `error` message.',"","3. ALWAYS pass back the `flowToken` string exactly as received \u2014 it is an opaque token, do not modify it.","4. Do NOT invent state values. Only use `stateUpdates` for information the user explicitly provided.","5. Include only the fields the user actually answered in `stateUpdates` \u2014 do NOT guess missing ones."," If the user did not answer all pending questions, the engine will re-prompt for the remaining ones."," If the user mentioned values for other known fields, include those too \u2014"," they will be applied immediately and those steps will be auto-skipped."),t.join(`
2
+ `)}function ke(e){if(e.flowToken){let t=Z(e.flowToken);if(t)return t}return{step:void 0,state:{}}}async function I(e,t){return e.type==="direct"?e.to:e.condition(t)}function q(e){return e!=null&&e!==""}function G(e,t,n,r){if(e.every(c=>q(r[c.field])))return null;let o=e.filter(c=>!q(r[c.field])),s=o.length===1,i=o[0];return{content:s&&i?{status:"interrupt",question:i.question,field:i.field,...i.suggestions?{suggestions:i.suggestions}:{},...i.context||t?{context:i.context??t}:{}}:{status:"interrupt",questions:o,...t?{context:t}:{}},flowTokenContent:{step:n,state:r,...s&&i?{field:i.field}:{},questions:e,...t?{interruptContext:t}:{}}}}async function O(e,t,n,r,o,s){let i=e,a={...t},c=50,l=0;for(;l++<c;){if(i===v)return{content:{status:"complete"},flowTokenContent:{state:a}};let f=n.get(i);if(!f)return{content:{status:"error",error:`Unknown node: "${i}"`}};try{let d=await f(a,s);if(J(d)){let g=G(d.questions,d.context,i,a);if(g)return g;let p=o.get(i);if(!p)return{content:{status:"error",error:`No outgoing edge from node "${i}"`}};i=await I(p,a);continue}if(X(d)){let g=d.field??r.get(i)?.field;if(g&&q(a[g])){let p=o.get(i);if(!p)return{content:{status:"error",error:`No outgoing edge from node "${i}"`}};i=await I(p,a);continue}return{content:{status:"widget",tool:d.tool.id,data:d.data,description:d.description,...d.interactive===!1?{interactive:!1}:{}},flowTokenContent:{step:i,state:a,field:g,widgetId:d.tool.id},structuredContent:d.data}}a={...a,...d};let u=o.get(i);if(!u)return{content:{status:"error",error:`No outgoing edge from node "${i}"`}};i=await I(u,a)}catch(d){return{content:{status:"error",error:d instanceof Error?d.message:String(d)},flowTokenContent:{step:i,state:a}}}}return{content:{status:"error",error:"Flow exceeded maximum iterations (possible infinite loop)"}}}var Se={action:R.enum(["start","continue"]).describe('"start" to begin the flow, "continue" to resume after a pause (interrupt or widget)'),stateUpdates:R.record(R.string(),R.unknown()).optional().describe("State field values to set before processing the next node. Use this to pass the user's answer (keyed by the field name from the response) and any other values the user mentioned."),flowToken:R.string().optional().describe("Opaque flow token from the previous response. Pass back exactly as received.")};function Q(e){let{config:t,nodes:n,nodeConfigs:r,edges:o}=e,s=Te(t),i=`${t.description}
3
+ ${s}`;async function a(c,l){let f=ke(c),d=f.state;if(c.action==="start"){let u=o.get(S);if(!u)return{content:{status:"error",error:"No start edge"}};let g={...d,...c.stateUpdates??{}},p=await I(u,g);return O(p,g,n,r,o,l)}if(c.action==="continue"){let u=f.step;if(!u)return{content:{status:"error",error:'Missing or invalid "flowToken" for continue action. Pass back the flowToken from the previous response exactly as received.'}};let g={...d,...c.stateUpdates??{}};if(f.questions){let p=G(f.questions,f.interruptContext,u,g);if(p)return p}if(f.questions||f.widgetId){let p=o.get(u);if(!p)return{content:{status:"error",error:`No edge from step "${u}"`}};let w=await I(p,g);return O(w,g,n,r,o,l)}return O(u,g,n,r,o,l)}return{content:{status:"error",error:`Unknown action: "${c.action}"`}}}return{id:t.id,title:t.title,description:i,async register(c){c.registerTool(t.id,{title:t.title,description:i,inputSchema:Se,annotations:t.annotations},(async(l,f)=>{let u=f._meta??{},g=await a(l,u),p=g.flowTokenContent?b(g.flowTokenContent):void 0,w={...g.content,...p?{flowToken:p}:{}};return{content:[{type:"text",text:JSON.stringify(w,null,2)}],...g.structuredContent&&{structuredContent:g.structuredContent},_meta:u}}))}}}var E=class{nodes=new Map;nodeConfigs=new Map;edges=new Map;config;constructor(t){this.config=t}addNode(t,n,r){if(t===S||t===v)throw new Error(`"${t}" is a reserved name and cannot be used as a node name`);if(this.nodes.has(t))throw new Error(`Node "${t}" already exists`);let o,s={};if(typeof n=="function")o=n;else if(r)o=r,s=n;else throw new Error(`Node "${t}" requires a handler function.`);return this.nodes.set(t,o),this.nodeConfigs.set(t,s),this}addEdge(t,n){if(this.edges.has(t))throw new Error(`Node "${t}" already has an outgoing edge. Use addConditionalEdge for branching.`);return this.edges.set(t,{type:"direct",to:n}),this}addConditionalEdge(t,n){if(this.edges.has(t))throw new Error(`Node "${t}" already has an outgoing edge.`);return this.edges.set(t,{type:"conditional",condition:n}),this}compile(){return this.validate(),Q({config:this.config,nodes:new Map(this.nodes),nodeConfigs:new Map(this.nodeConfigs),edges:new Map(this.edges)})}validate(){if(!this.edges.has(S))throw new Error('Flow must have an entry point. Add an edge from START: .addEdge(START, "first_node")');let t=this.edges.get(S);if(t?.type==="direct"&&t.to!==v&&!this.nodes.has(t.to))throw new Error(`START edge references non-existent node: "${t.to}"`);for(let[n,r]of this.edges){if(n!==S&&!this.nodes.has(n))throw new Error(`Edge from non-existent node: "${n}"`);if(r.type==="direct"&&r.to!==v&&!this.nodes.has(r.to))throw new Error(`Edge from "${n}" references non-existent node: "${r.to}"`)}for(let[n]of this.nodes)if(!this.edges.has(n))throw new Error(`Node "${n}" has no outgoing edge. Add one with .addEdge("${n}", ...) or .addConditionalEdge("${n}", ...)`)}};function ee(e){return new E(e)}var _="text/html+skybridge",M="text/html;profile=mcp-app",te=async(e,t)=>{let n=e.endsWith("/")?e.slice(0,-1):e;return await(await fetch(`${n}${t}`)).text()};function ne(e){return{"openai/widgetDescription":e.description,"openai/widgetPrefersBorder":e.prefersBorder,"openai/widgetDomain":e.widgetDomain,...e.widgetCSP&&{"openai/widgetCSP":e.widgetCSP}}}function re(e){let t=e.widgetCSP?{connectDomains:e.widgetCSP.connect_domains,resourceDomains:e.widgetCSP.resource_domains,frameDomains:e.widgetCSP.frame_domains,redirectDomains:e.widgetCSP.redirect_domains}:void 0;return{ui:{...t&&{csp:t},...e.widgetDomain&&{domain:e.widgetDomain},...e.prefersBorder!==void 0&&{prefersBorder:e.prefersBorder}}}}function j(e){return{...e.openaiTemplateUri&&{"openai/outputTemplate":e.openaiTemplateUri},"openai/toolInvocation/invoking":e.invoking,"openai/toolInvocation/invoked":e.invoked,"openai/widgetAccessible":!0,"openai/resultCanProduceWidget":!0,...e.mcpTemplateUri&&{ui:{resourceUri:e.mcpTemplateUri,...e.autoHeight&&{autoHeight:!0}}}}}function oe(e){let{id:t,title:n,description:r,baseUrl:o,htmlPath:s,widgetDomain:i,prefersBorder:a=!0,autoHeight:c=!0}=e,l=e.widgetCSP??{connect_domains:[o],resource_domains:[o]};if(process.env.NODE_ENV==="development")try{let{hostname:y}=new URL(o);(y==="localhost"||y==="127.0.0.1")&&(l={...l,connect_domains:[...l.connect_domains||[],`ws://${y}:*`,`wss://${y}:*`],resource_domains:[...l.resource_domains||[],`http://${y}:*`]})}catch{}let f=`ui://widgets/apps-sdk/${t}.html`,d=`ui://widgets/ext-apps/${t}.html`,u=null,g=()=>(u||(u=te(o,s)),u),p=r;async function w(y){let x=await g();y.registerResource(`${t}-openai-widget`,f,{title:n,description:p,mimeType:_,_meta:{"openai/widgetDescription":p,"openai/widgetPrefersBorder":a}},async k=>({contents:[{uri:k.href,mimeType:_,text:x,_meta:ne({description:p,prefersBorder:a,widgetDomain:i,widgetCSP:l})}]})),y.registerResource(`${t}-mcp-widget`,d,{title:n,description:p,mimeType:M,_meta:{ui:{prefersBorder:a}}},async k=>({contents:[{uri:k.href,mimeType:M,text:x,_meta:re({description:p,prefersBorder:a,widgetCSP:l})}]}))}return{id:t,title:n,description:r,openaiUri:f,mcpUri:d,autoHeight:c,register:w}}function ve(e,t){let{resource:n,description:r,inputSchema:o,annotations:s,autoInjectResultText:i=!0}=e,a=e.id??n?.id,c=e.title??n?.title;if(!a)throw new Error("createTool: `id` is required when no resource is provided");if(!c)throw new Error("createTool: `title` is required when no resource is provided");let l=n?j({openaiTemplateUri:n.openaiUri,mcpTemplateUri:n.mcpUri,invoking:e.invoking??"Loading...",invoked:e.invoked??"Loaded",autoHeight:n.autoHeight}):void 0;return{id:a,title:c,description:r,async register(f){f.registerTool(a,{title:c,description:r,inputSchema:o,annotations:s,...l&&{_meta:l}},(async(d,u)=>{let p=u._meta??{},w=await t(d,{extra:{_meta:p}});return n&&w.data?{content:[{type:"text",text:w.text}],structuredContent:w.data,_meta:{...l,...p,...i===!1?{"waniwani/autoInjectResultText":!1}:{}}}:{content:[{type:"text",text:w.text}],...w.data?{structuredContent:w.data}:{},...i===!1?{_meta:{"waniwani/autoInjectResultText":!1}}:{}}}))}}}async function Ee(e,t){await Promise.all(t.map(n=>n.register(e)))}var P=class extends Error{constructor(n,r){super(n);this.status=r;this.name="WaniWaniError"}};var xe="@waniwani/sdk";function ie(e){let{baseUrl:t,apiKey:n}=e;function r(){if(!n)throw new Error("WANIWANI_API_KEY is not set");return n}async function o(s,i,a){let c=r(),l=`${t.replace(/\/$/,"")}${i}`,f={Authorization:`Bearer ${c}`,"X-WaniWani-SDK":xe},d={method:s,headers:f};a!==void 0&&(f["Content-Type"]="application/json",d.body=JSON.stringify(a));let u=await fetch(l,d);if(!u.ok){let p=await u.text().catch(()=>"");throw new P(p||`KB API error: HTTP ${u.status}`,u.status)}return(await u.json()).data}return{async ingest(s){return o("POST","/api/mcp/kb/ingest",{files:s})},async search(s,i){return o("POST","/api/mcp/kb/search",{query:s,...i})},async sources(){return o("GET","/api/mcp/kb/sources")}}}var Re="@waniwani/sdk";function $(e,t={}){let n=t.now??(()=>new Date),r=t.generateId??se,o=be(e),s=W(e.meta),i=W(e.metadata),a=_e(e,s),c=T(e.eventId)??r(),l=Me(e.timestamp,n),f=T(e.source)??t.source??Re,d=L(e)?{...e}:void 0,u={...i};return Object.keys(s).length>0&&(u.meta=s),d&&(u.rawLegacy=d),{id:c,type:"mcp.event",name:o,source:f,timestamp:l,correlation:a,properties:Ie(e,o),metadata:u,rawLegacy:d}}function se(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?`evt_${crypto.randomUUID()}`:`evt_${Math.random().toString(36).slice(2,10)}_${Date.now().toString(36)}`}function Ie(e,t){if(!L(e))return W(e.properties);let n=Ce(e,t),r=W(e.properties);return{...n,...r}}function Ce(e,t){switch(t){case"tool.called":{let n={};return T(e.toolName)&&(n.name=e.toolName),T(e.toolType)&&(n.type=e.toolType),n}case"quote.succeeded":{let n={};return typeof e.quoteAmount=="number"&&(n.amount=e.quoteAmount),T(e.quoteCurrency)&&(n.currency=e.quoteCurrency),n}case"link.clicked":{let n={};return T(e.linkUrl)&&(n.url=e.linkUrl),n}case"purchase.completed":{let n={};return typeof e.purchaseAmount=="number"&&(n.amount=e.purchaseAmount),T(e.purchaseCurrency)&&(n.currency=e.purchaseCurrency),n}default:return{}}}function be(e){return L(e)?e.eventType:e.event}function _e(e,t){let n=T(e.requestId)??C(t,["openai/requestId","requestId","mcp/requestId"]),r=T(e.sessionId)??C(t,["openai/sessionId","sessionId","conversationId","anthropic/sessionId"]),o=T(e.traceId)??C(t,["openai/traceId","traceId","mcp/traceId","openai/requestId","requestId"]),s=T(e.externalUserId)??C(t,["openai/userId","externalUserId","userId","actorId"]),i=T(e.correlationId)??C(t,["correlationId","openai/requestId"])??n,a={};return r&&(a.sessionId=r),o&&(a.traceId=o),n&&(a.requestId=n),i&&(a.correlationId=i),s&&(a.externalUserId=s),a}function Me(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 C(e,t){for(let n of t){let r=e[n];if(typeof r=="string"&&r.trim().length>0)return r}}function W(e){return!e||typeof e!="object"||Array.isArray(e)?{}:e}function T(e){if(typeof e=="string"&&e.trim().length!==0)return e}function L(e){return"eventType"in e}var Pe="/api/mcp/events/v2/batch";var ae="@waniwani/sdk",We=new Set([401,403]),Fe=new Set([408,425,429,500,502,503,504]);function ce(e){return new V(e)}var V=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=De(t.baseUrl,t.endpointPath??Pe),this.flushIntervalMs=t.flushIntervalMs??1e3,this.maxBatchSize=t.maxBatchSize??20,this.maxBufferSize=t.maxBufferSize??1e3,this.maxRetries=t.maxRetries??3,this.retryBaseDelayMs=t.retryBaseDelayMs??200,this.retryMaxDelayMs=t.retryMaxDelayMs??2e3,this.shutdownTimeoutMs=t.shutdownTimeoutMs??2e3,this.fetchFn=t.fetchFn??fetch,this.logger=t.logger??console,this.now=t.now??(()=>new Date),this.sleep=t.sleep??(n=>new Promise(r=>setTimeout(r,n))),this.apiKey=t.apiKey,this.sdkVersion=t.sdkVersion,this.flushIntervalMs>0&&(this.flushTimer=setInterval(()=>{this.flush()},this.flushIntervalMs))}enqueue(t){if(this.isStopped||this.isShuttingDown){this.logger.warn("[WaniWani] Tracking transport is stopped, dropping event %s",t.id);return}if(this.buffer.length>=this.maxBufferSize){let n=this.buffer.length-this.maxBufferSize+1;this.buffer.splice(0,n),this.logger.warn("[WaniWani] Tracking buffer overflow, dropped %d oldest event(s)",n)}if(this.buffer.push(t),this.buffer.length>=this.maxBatchSize){this.flush();return}this.scheduleMicroFlush()}pendingEvents(){return this.buffer.length+this.inFlightCount}async flush(){return this.flushInFlight?this.flushInFlight:(this.flushInFlight=this.flushLoop().finally(()=>{this.flushInFlight=void 0}),this.flushInFlight)}async shutdown(t){this.isShuttingDown=!0,this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=void 0),this.flushScheduledTimer&&(clearTimeout(this.flushScheduledTimer),this.flushScheduledTimer=void 0,this.flushScheduled=!1);let n=t?.timeoutMs??this.shutdownTimeoutMs,r=this.flush();if(!Number.isFinite(n)||n<=0)return await r,this.isStopped=!0,{timedOut:!1,pendingEvents:this.pendingEvents()};let o=Symbol("shutdown-timeout");return await Promise.race([r.then(()=>"flushed"),this.sleep(n).then(()=>o)])===o?(this.isStopped=!0,{timedOut:!0,pendingEvents:this.pendingEvents()}):(this.isStopped=!0,{timedOut:!1,pendingEvents:this.pendingEvents()})}scheduleMicroFlush(){this.flushScheduled||(this.flushScheduled=!0,this.flushScheduledTimer=setTimeout(()=>{this.flushScheduledTimer=void 0,this.flushScheduled=!1,this.flush()},0))}async flushLoop(){for(;this.buffer.length>0&&!this.isStopped;){let t=this.buffer.splice(0,this.maxBatchSize);await this.sendBatchWithRetry(t)}}async sendBatchWithRetry(t){let n=0,r=t;for(;r.length>0&&!this.isStopped;){this.inFlightCount=r.length;let o=await this.sendBatchOnce(r);switch(this.inFlightCount=0,o.kind){case"success":return;case"auth":this.stopTransportForAuthFailure(o.status,r.length);return;case"permanent":this.logger.error("[WaniWani] Dropping %d event(s) after permanent failure: %s",r.length,o.reason);return;case"retryable":if(n>=this.maxRetries){this.logger.error("[WaniWani] Dropping %d event(s) after retry exhaustion: %s",r.length,o.reason);return}await this.sleep(this.backoffDelayMs(n)),n+=1;continue;case"partial":if(o.permanent.length>0&&this.logger.error("[WaniWani] Dropping %d event(s) rejected as permanent",o.permanent.length),o.retryable.length===0)return;if(n>=this.maxRetries){this.logger.error("[WaniWani] Dropping %d retryable event(s) after retry exhaustion",o.retryable.length);return}r=o.retryable,await this.sleep(this.backoffDelayMs(n)),n+=1;continue}}}async sendBatchOnce(t){let n;try{n=await this.fetchFn(this.endpointUrl,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,"X-WaniWani-SDK":ae},body:JSON.stringify(this.makeBatchRequest(t))})}catch(s){return{kind:"retryable",reason:Ue(s)}}if(We.has(n.status))return{kind:"auth",status:n.status};if(Fe.has(n.status))return{kind:"retryable",reason:`HTTP ${n.status}`};if(!n.ok)return{kind:"permanent",reason:`HTTP ${n.status}`};let r=await Ne(n);if(!r?.rejected||r.rejected.length===0)return{kind:"success"};let o=this.classifyRejectedEvents(t,r.rejected);return o.retryable.length===0&&o.permanent.length===0?{kind:"success"}:{kind:"partial",retryable:o.retryable,permanent:o.permanent}}makeBatchRequest(t){return{sentAt:this.now().toISOString(),source:{sdk:ae,version:this.sdkVersion??"0.0.0"},events:t}}classifyRejectedEvents(t,n){let r=new Map(t.map(i=>[i.id,i])),o=[],s=[];for(let i of n){let a=r.get(i.eventId);if(a){if(Ae(i)){o.push(a);continue}s.push(a)}}return{retryable:o,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 Ae(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 Ne(e){let t=await e.text();if(t)try{return JSON.parse(t)}catch{return}}function De(e,t){let n=e.endsWith("/")?e:`${e}/`,r=t.startsWith("/")?t.slice(1):t;return`${n}${r}`}function Ue(e){return e instanceof Error?e.message:String(e)}function de(e){let{baseUrl:t,apiKey:n,tracking:r}=e;function o(){if(!n)throw new Error("WANIWANI_API_KEY is not set");return n}let s=n?ce({baseUrl:t,apiKey:n,endpointPath:r.endpointPath,flushIntervalMs:r.flushIntervalMs,maxBatchSize:r.maxBatchSize,maxBufferSize:r.maxBufferSize,maxRetries:r.maxRetries,retryBaseDelayMs:r.retryBaseDelayMs,retryMaxDelayMs:r.retryMaxDelayMs,shutdownTimeoutMs:r.shutdownTimeoutMs}):void 0,i={async track(a){o();let c=$(a);return s?.enqueue(c),{eventId:c.id}},async flush(){o(),await s?.flush()},async shutdown(a){return o(),await s?.shutdown({timeoutMs:a?.timeoutMs??r.shutdownTimeoutMs})??{timedOut:!1,pendingEvents:0}}};return s&&Be(i,r.shutdownTimeoutMs),i}function Be(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 F(e){let t=e?.baseUrl??"https://app.waniwani.ai",n=e?.apiKey??process.env.WANIWANI_API_KEY,r={endpointPath:e?.tracking?.endpointPath??"/api/mcp/events/v2/batch",flushIntervalMs:e?.tracking?.flushIntervalMs??1e3,maxBatchSize:e?.tracking?.maxBatchSize??20,maxBufferSize:e?.tracking?.maxBufferSize??1e3,maxRetries:e?.tracking?.maxRetries??3,retryBaseDelayMs:e?.tracking?.retryBaseDelayMs??200,retryMaxDelayMs:e?.tracking?.retryMaxDelayMs??2e3,shutdownTimeoutMs:e?.tracking?.shutdownTimeoutMs??2e3},o={baseUrl:t,apiKey:n,tracking:r},s=de(o),i=ie(o);return{...s,kb:i,_config:o}}function Oe(e){let t=e.event_type??"widget_click",r=t.startsWith("widget_")?t:`widget_${t}`,o={...e.metadata??{}};return e.event_name&&(o.event_name=e.event_name),{event:r,properties:o,sessionId:e.session_id,traceId:e.trace_id,externalUserId:e.user_id,eventId:e.event_id,timestamp:e.timestamp,source:e.source??"widget"}}function qe(e){let t={apiKey:e?.apiKey,baseUrl:e?.baseUrl},n;function r(){return n||(n=F(t)),n}return async function(s){let i;try{i=await s.json()}catch{return new Response(JSON.stringify({error:"Invalid JSON"}),{status:400,headers:{"Content-Type":"application/json"}})}if(!Array.isArray(i.events)||i.events.length===0)return new Response(JSON.stringify({error:"Missing or empty events array"}),{status:400,headers:{"Content-Type":"application/json"}});try{let a=r(),c=[];for(let l of i.events){let f=Oe(l),d=await a.track(f);c.push(d.eventId)}return await a.flush(),new Response(JSON.stringify({ok:!0,accepted:c.length}),{status:200,headers:{"Content-Type":"application/json"}})}catch(a){let c=a instanceof Error?a.message:"Unknown error";return new Response(JSON.stringify({error:c}),{status:500,headers:{"Content-Type":"application/json"}})}}}var A=class{cached=null;pending=null;config;constructor(t){this.config=t}async getToken(t,n){return this.cached&&Date.now()<this.cached.expiresAt-12e4?this.cached.token:this.pending?this.pending:(this.pending=this.mint(t,n).finally(()=>{this.pending=null}),this.pending)}async mint(t,n){let r=je(this.config.baseUrl,"/api/mcp/widget-tokens"),o={};t&&(o.sessionId=t),n&&(o.traceId=n);try{let s=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify(o)});if(!s.ok)return null;let i=await s.json(),a=i.data&&typeof i.data=="object"?i.data:i,c=new Date(a.expiresAt).getTime();return!a.token||Number.isNaN(c)?null:(this.cached={token:a.token,expiresAt:c},a.token)}catch{return null}}};function je(e,t){return`${e.endsWith("/")?e.slice(0,-1):e}${t}`}var ue="https://app.waniwani.ai";function $e(e,t={}){let n=e;if(n.__waniwaniWrapped)return n;n.__waniwaniWrapped=!0;let r=t.client??F(t.config),o=t.injectWidgetToken!==!1,s=null;function i(){if(s)return s;let c=r._config.apiKey;return c?(s=new A({baseUrl:r._config.baseUrl??ue,apiKey:c}),s):null}let a=e.registerTool.bind(e);return n.registerTool=((...c)=>{let[l,f,d]=c,u=typeof l=="string"&&l.trim().length>0?l:"unknown";if(typeof d!="function")return a(...c);let g=d;return a(l,f,async(w,y)=>{let x=performance.now();try{let k=await g(w,y),D=Math.round(performance.now()-x);return await pe(r,le(u,y,t,{durationMs:D,status:"ok"}),t.onError),t.flushAfterToolCall&&await fe(r,t.onError),o&&await Le(k,i(),r._config.baseUrl??ue,t.onError),k}catch(k){let D=Math.round(performance.now()-x);throw await pe(r,le(u,y,t,{durationMs:D,status:"error",errorMessage:k instanceof Error?k.message:String(k)}),t.onError),t.flushAfterToolCall&&await fe(r,t.onError),k}})}),n}async function Le(e,t,n,r){if(!N(e))return;N(e._meta)||(e._meta={});let o=e._meta,s={endpoint:`${n.replace(/\/$/,"")}/api/mcp/events/v2/batch`};if(t)try{let i=await t.getToken();i&&(s.token=i)}catch(i){r?.(H(i))}o.waniwani=s}function le(e,t,n,r){let o=Ve(e,n.toolType),s=He(t);return{event:"tool.called",properties:{name:e,type:o,...r??{}},meta:s,metadata:{source:"withWaniwani",...n.metadata??{}}}}function Ve(e,t){return typeof t=="function"?t(e)??"other":t??"other"}function He(e){if(!N(e))return;let t=e._meta;if(N(t))return t}function N(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}async function pe(e,t,n){try{await e.track(t)}catch(r){n?.(H(r))}}async function fe(e,t){try{await e.flush()}catch(n){t?.(H(n))}}function H(e){return e instanceof Error?e:new Error(String(e))}export{v as END,S as START,E as StateGraph,ee as createFlow,oe as createResource,ve as createTool,qe as createTrackingRoute,U as detectPlatform,b as encodeFlowToken,K as interrupt,he as isMCPApps,ge as isOpenAI,Ee as registerTools,Y as showWidget,$e as withWaniwani};
4
4
  //# sourceMappingURL=index.js.map