@waniwani/sdk 0.20.4 → 0.21.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 (92) hide show
  1. package/dist/{angular-html-EOIKHM3D.js → angular-html-PU3MXNBE.js} +2 -2
  2. package/dist/{angular-ts-DFS2FCNO.js → angular-ts-WMZKXXGP.js} +2 -2
  3. package/dist/{apl-FBR445XW.js → apl-YU3KF3Y6.js} +2 -2
  4. package/dist/{blade-OHP5YUT7.js → blade-YKL22AM4.js} +2 -2
  5. package/dist/chat/embed.js +62 -62
  6. package/dist/chat/embed.js.map +1 -1
  7. package/dist/chat/index.js +7 -7
  8. package/dist/chat/index.js.map +1 -1
  9. package/dist/{chunk-AI3BZXLG.js → chunk-C5BU72RY.js} +2 -2
  10. package/dist/{chunk-HMEGK7JX.js → chunk-IS746YRY.js} +2 -2
  11. package/dist/{chunk-HMEGK7JX.js.map → chunk-IS746YRY.js.map} +1 -1
  12. package/dist/{chunk-VNL3EXC2.js → chunk-ITMYIBN5.js} +2 -2
  13. package/dist/{chunk-F4UUXA3U.js → chunk-QIQG7C7A.js} +57 -57
  14. package/dist/chunk-QIQG7C7A.js.map +1 -0
  15. package/dist/{chunk-ED2SBPIQ.js → chunk-USNZZOYV.js} +2 -2
  16. package/dist/{chunk-M4CKEROZ.js → chunk-UVLNWQRP.js} +2 -2
  17. package/dist/{cobol-LJNKRWP5.js → cobol-YD3RINFS.js} +2 -2
  18. package/dist/{code-block-37QAKDTI-HV355RRX.js → code-block-37QAKDTI-FHGJZOOY.js} +2 -2
  19. package/dist/{crystal-ZDP3FIUI.js → crystal-AUUIRTAZ.js} +2 -2
  20. package/dist/{edge-CCLQ2CKA.js → edge-44UF6IIM.js} +2 -2
  21. package/dist/{elixir-F6DQGRKA.js → elixir-ZJ4WTHOY.js} +2 -2
  22. package/dist/{erb-KUYJX32Q.js → erb-ZF7YKJP7.js} +2 -2
  23. package/dist/{glimmer-js-RSUW6JYN.js → glimmer-js-TKADNZF5.js} +2 -2
  24. package/dist/{glimmer-js-RSUW6JYN.js.map → glimmer-js-TKADNZF5.js.map} +1 -1
  25. package/dist/{glimmer-ts-EZGWLX5T.js → glimmer-ts-WFYWDU2O.js} +2 -2
  26. package/dist/{glimmer-ts-EZGWLX5T.js.map → glimmer-ts-WFYWDU2O.js.map} +1 -1
  27. package/dist/{hack-3TGPQNR2.js → hack-HCRIHDCF.js} +2 -2
  28. package/dist/{handlebars-73IS6B2J.js → handlebars-7EKNR6OR.js} +2 -2
  29. package/dist/{handlebars-73IS6B2J.js.map → handlebars-7EKNR6OR.js.map} +1 -1
  30. package/dist/html-G4Q6FSTL.js +3 -0
  31. package/dist/{html-derivative-CMQ7BFDQ.js → html-derivative-2AGKXLI2.js} +2 -2
  32. package/dist/index.d.ts +5 -3
  33. package/dist/{jinja-WPD26JJ6.js → jinja-K2EDFFOP.js} +2 -2
  34. package/dist/kb/index.d.ts +5 -3
  35. package/dist/{liquid-7CFNUD3V.js → liquid-YEOV5OY7.js} +2 -2
  36. package/dist/mcp/index.d.ts +6 -3
  37. package/dist/mcp/index.js +6 -5
  38. package/dist/mcp/index.js.map +1 -1
  39. package/dist/{mdc-XWCHWP6B.js → mdc-I5IJFJ2O.js} +2 -2
  40. package/dist/mermaid-4DMBBIKO-YNUUOSE3.js +3 -0
  41. package/dist/{nim-U57UBFBS.js → nim-JXWLGPJS.js} +2 -2
  42. package/dist/{perl-IZHDGBO6.js → perl-HORNVRGS.js} +2 -2
  43. package/dist/php-JGPDZKQ3.js +3 -0
  44. package/dist/{pug-P7LS6DER.js → pug-3YFWZUJ6.js} +2 -2
  45. package/dist/{razor-4QOCK4UD.js → razor-7T4OK2LD.js} +2 -2
  46. package/dist/{rst-6KQSGP2V.js → rst-SR6APMGO.js} +2 -2
  47. package/dist/{ruby-OWE2HOOR.js → ruby-IYOQSPDU.js} +2 -2
  48. package/dist/{soy-VW3CKXWF.js → soy-WHPD3NJL.js} +2 -2
  49. package/dist/{templ-3XM7DLQ3.js → templ-VI6LYHRW.js} +2 -2
  50. package/dist/{templ-3XM7DLQ3.js.map → templ-VI6LYHRW.js.map} +1 -1
  51. package/dist/{ts-tags-XNK5N5RZ.js → ts-tags-MU4UAPH3.js} +2 -2
  52. package/dist/twig-OY2C64LG.js +3 -0
  53. package/dist/{twig-OEIJ72KK.js.map → twig-OY2C64LG.js.map} +1 -1
  54. package/dist/vue-M6XWA362.js +3 -0
  55. package/dist/{vue-FIH2DYBE.js.map → vue-M6XWA362.js.map} +1 -1
  56. package/package.json +3 -2
  57. package/dist/chunk-F4UUXA3U.js.map +0 -1
  58. package/dist/html-654LNGWF.js +0 -3
  59. package/dist/mermaid-4DMBBIKO-IGH2CKFF.js +0 -3
  60. package/dist/php-MNP62HWP.js +0 -3
  61. package/dist/twig-OEIJ72KK.js +0 -3
  62. package/dist/vue-FIH2DYBE.js +0 -3
  63. /package/dist/{angular-html-EOIKHM3D.js.map → angular-html-PU3MXNBE.js.map} +0 -0
  64. /package/dist/{angular-ts-DFS2FCNO.js.map → angular-ts-WMZKXXGP.js.map} +0 -0
  65. /package/dist/{apl-FBR445XW.js.map → apl-YU3KF3Y6.js.map} +0 -0
  66. /package/dist/{blade-OHP5YUT7.js.map → blade-YKL22AM4.js.map} +0 -0
  67. /package/dist/{chunk-AI3BZXLG.js.map → chunk-C5BU72RY.js.map} +0 -0
  68. /package/dist/{chunk-VNL3EXC2.js.map → chunk-ITMYIBN5.js.map} +0 -0
  69. /package/dist/{chunk-ED2SBPIQ.js.map → chunk-USNZZOYV.js.map} +0 -0
  70. /package/dist/{chunk-M4CKEROZ.js.map → chunk-UVLNWQRP.js.map} +0 -0
  71. /package/dist/{cobol-LJNKRWP5.js.map → cobol-YD3RINFS.js.map} +0 -0
  72. /package/dist/{code-block-37QAKDTI-HV355RRX.js.map → code-block-37QAKDTI-FHGJZOOY.js.map} +0 -0
  73. /package/dist/{crystal-ZDP3FIUI.js.map → crystal-AUUIRTAZ.js.map} +0 -0
  74. /package/dist/{edge-CCLQ2CKA.js.map → edge-44UF6IIM.js.map} +0 -0
  75. /package/dist/{elixir-F6DQGRKA.js.map → elixir-ZJ4WTHOY.js.map} +0 -0
  76. /package/dist/{erb-KUYJX32Q.js.map → erb-ZF7YKJP7.js.map} +0 -0
  77. /package/dist/{hack-3TGPQNR2.js.map → hack-HCRIHDCF.js.map} +0 -0
  78. /package/dist/{html-654LNGWF.js.map → html-G4Q6FSTL.js.map} +0 -0
  79. /package/dist/{html-derivative-CMQ7BFDQ.js.map → html-derivative-2AGKXLI2.js.map} +0 -0
  80. /package/dist/{jinja-WPD26JJ6.js.map → jinja-K2EDFFOP.js.map} +0 -0
  81. /package/dist/{liquid-7CFNUD3V.js.map → liquid-YEOV5OY7.js.map} +0 -0
  82. /package/dist/{mdc-XWCHWP6B.js.map → mdc-I5IJFJ2O.js.map} +0 -0
  83. /package/dist/{mermaid-4DMBBIKO-IGH2CKFF.js.map → mermaid-4DMBBIKO-YNUUOSE3.js.map} +0 -0
  84. /package/dist/{nim-U57UBFBS.js.map → nim-JXWLGPJS.js.map} +0 -0
  85. /package/dist/{perl-IZHDGBO6.js.map → perl-HORNVRGS.js.map} +0 -0
  86. /package/dist/{php-MNP62HWP.js.map → php-JGPDZKQ3.js.map} +0 -0
  87. /package/dist/{pug-P7LS6DER.js.map → pug-3YFWZUJ6.js.map} +0 -0
  88. /package/dist/{razor-4QOCK4UD.js.map → razor-7T4OK2LD.js.map} +0 -0
  89. /package/dist/{rst-6KQSGP2V.js.map → rst-SR6APMGO.js.map} +0 -0
  90. /package/dist/{ruby-OWE2HOOR.js.map → ruby-IYOQSPDU.js.map} +0 -0
  91. /package/dist/{soy-VW3CKXWF.js.map → soy-WHPD3NJL.js.map} +0 -0
  92. /package/dist/{ts-tags-XNK5N5RZ.js.map → ts-tags-MU4UAPH3.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/shared/view-uri.ts","../../src/webmcp/bridge.ts","../../src/webmcp/widget-step.ts","../../src/mcp/server/flows/@types.ts","../../src/mcp/server/flows/compile.ts","../../src/error.ts","../../src/documents/client.ts","../../src/tracking/revenue.ts","../../src/types.ts","../../src/documents/messages.ts","../../src/shared/meta-keys.ts","../../src/mcp/server/utils.ts","../../src/mcp/server/scoped-client.ts","../../src/mcp/server/session-errors/classify.ts","../../src/mcp/server/session-errors/report.ts","../../src/mcp/server/flows/nested.ts","../../src/mcp/server/flows/field-schema.ts","../../src/mcp/server/flows/execute.ts","../../src/mcp/server/kv/crypto.ts","../../src/utils/logger.ts","../../src/mcp/server/kv/kv-store.ts","../../src/mcp/server/kv/memory-kv-store.ts","../../src/mcp/server/flows/flow-store.ts","../../src/mcp/server/flows/graph-extract.ts","../../src/mcp/server/flows/intro.ts","../../src/mcp/server/flows/internal-state.ts","../../src/mcp/server/flows/output-schema.ts","../../src/mcp/server/flows/protocol.ts","../../src/mcp/server/flows/redacted.ts","../../src/mcp/server/flows/state-graph.ts","../../src/mcp/server/flows/create-flow.ts","../../src/mcp/server/flows/test-utils.ts","../../src/kb/retrieval-context.ts","../../src/kb/client.ts","../../src/project-config.ts","../../src/tracking/mapper.ts","../../src/tracking/transport.ts","../../src/tracking/index.ts","../../src/waniwani.ts","../../src/mcp/server/tracking-route.ts","../../src/mcp/server/widget-token.ts","../../src/mcp/server/with-waniwani/funnel-sync.ts","../../src/mcp/server/with-waniwani/helpers.ts","../../src/mcp/server/with-waniwani/transport-session.ts","../../src/mcp/server/with-waniwani/index.ts"],"sourcesContent":["/**\n * Reading a widget's view binding off MCP `_meta`.\n *\n * Kept in `shared/` because three surfaces ask the same question and none of\n * them should own the answer: the chat resolves a tool part's view before\n * rendering it, the WebMCP endpoint resolves a display tool's view before\n * handing it to the page, and both have to agree or a widget renders in one\n * place and not the other.\n *\n * The three spellings below are not alternatives to choose between. They are\n * what three host generations actually emit, and a server built on any of them\n * is a server we have to render.\n */\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn undefined;\n\t}\n\treturn value as Record<string, unknown>;\n}\n\nfunction uiObject(\n\tmeta: Record<string, unknown> | undefined,\n): Record<string, unknown> | undefined {\n\treturn asRecord(meta?.ui);\n}\n\n/** Anything carrying MCP `_meta`: a tool definition, a tool result, a resource. */\nexport type MetaCarrier = {\n\t_meta?: Record<string, unknown>;\n};\n\n/**\n * The view a `_meta` binds to, checking all three shapes in priority order:\n *\n * 1. `_meta.ui.resourceUri` — MCP Apps, nested (skybridge, ext-apps)\n * 2. `_meta[\"ui/resourceUri\"]` — MCP Apps, flat (earlier drafts, still in the wild)\n * 3. `_meta[\"openai/outputTemplate\"]` — OpenAI Apps SDK\n *\n * Order is priority, not preference: a server emitting more than one emits them\n * as aliases of the same view, and the nested form is the one the current spec\n * defines.\n */\nexport function resourceUriFromMeta(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\tif (!meta) {\n\t\treturn undefined;\n\t}\n\tconst nested = uiObject(meta)?.resourceUri;\n\tif (typeof nested === \"string\" && nested.length > 0) {\n\t\treturn nested;\n\t}\n\tconst flat = meta[\"ui/resourceUri\"];\n\tif (typeof flat === \"string\" && flat.length > 0) {\n\t\treturn flat;\n\t}\n\tconst openai = meta[\"openai/outputTemplate\"];\n\tif (typeof openai === \"string\" && openai.length > 0) {\n\t\treturn openai;\n\t}\n\treturn undefined;\n}\n\n/** Whether the host should size the frame from the view's content. */\nexport function autoHeightFromMeta(\n\tmeta: Record<string, unknown> | undefined,\n): boolean {\n\treturn uiObject(meta)?.autoHeight === true;\n}\n\n/** The `_meta` on a tool result, if it has one. */\nexport function metaOf(value: unknown): Record<string, unknown> | undefined {\n\treturn asRecord(asRecord(value)?._meta);\n}\n\n/**\n * The view a tool renders, read from its definition.\n *\n * Definition rather than result on purpose. A spec-compliant server binds the\n * view on the tool, so a result carries the binding only by accident, and\n * reading the result first means a tool whose view is declared correctly looks\n * like a tool with no view at all.\n */\nexport function viewUriFor(tool: MetaCarrier | undefined): string | undefined {\n\treturn resourceUriFromMeta(tool?._meta);\n}\n\n/**\n * Whether a tool exists to render something rather than to be reasoned about.\n *\n * The distinction matters wherever a tool list crosses a boundary. A display\n * tool advertised to a browsing agent is a tool it will try to call to make a\n * widget appear, on a surface with no widget host in it, which burns a turn and\n * strands the conversation on a render nobody performs.\n */\nexport function isDisplayTool(tool: MetaCarrier | undefined): boolean {\n\treturn viewUriFor(tool) !== undefined;\n}\n","/**\n * The WebMCP bridge.\n *\n * A browsing agent standing on a page never speaks to the MCP server. It speaks\n * to the page, through `document.modelContext`, and this is what puts the\n * server's tools there. Every `execute()` the agent calls lands on the endpoint\n * below, which stamps attribution server-side and answers.\n *\n * Nothing here discovers its own configuration. The endpoint, both ids, and the\n * widget callback are arguments, because there are two callers with two\n * different ways of knowing them: the chat embed reads them from its remote\n * config, and the standalone loader derives them from its own script tag.\n * Sniffing `document.currentScript` inside a bundle that may be loaded twenty\n * modules deep finds the wrong script or none.\n */\n\nimport type {\n\tWebMcpCallResponse,\n\tWebMcpContentBlock,\n\tWebMcpListResponse,\n\tWebMcpPageContext,\n\tWebMcpRequest,\n\tWebMcpTool,\n\tWebMcpWidgetPayload,\n} from \"./@types\";\n\n/**\n * The slice of the WebMCP browser API this uses.\n *\n * Declared structurally rather than pulled from a types package: the API is\n * still moving (the getter migrated from `Navigator` to `Document` in\n * webmcp#173, which is why both are probed below), and a dependency that\n * disagrees with the browser is worse than four lines here.\n */\ntype ModelContext = {\n\tregisterTool: (\n\t\tdescriptor: {\n\t\t\tname: string;\n\t\t\tdescription: string;\n\t\t\tinputSchema?: Record<string, unknown>;\n\t\t\tannotations?: Record<string, unknown>;\n\t\t\texecute: (\n\t\t\t\targs: Record<string, unknown>,\n\t\t\t\toptions?: { signal?: AbortSignal },\n\t\t\t) => Promise<{ content: WebMcpContentBlock[] }>;\n\t\t},\n\t\toptions?: { signal?: AbortSignal },\n\t) => Promise<unknown> | unknown;\n};\n\nexport type WebMcpBridgeOptions = {\n\t/** Absolute URL of the server's page-facing tools endpoint. */\n\tendpoint: string;\n\t/**\n\t * Extra request headers, merged over `content-type`.\n\t *\n\t * The hosted API authenticates with `Authorization: Bearer <public token>`\n\t * here, the same as every other call the embed makes. A self-hosted server\n\t * reached directly needs none.\n\t */\n\theaders?: Record<string, string>;\n\t/**\n\t * Tab-scoped id, and the key the flow engine stores its state under.\n\t *\n\t * Must be stable for the tab and must not outlive it. A value that changes\n\t * between calls silently restarts every flow; a value that persists across\n\t * days resumes a flow abandoned last week mid-question.\n\t */\n\tsessionId: string;\n\t/**\n\t * The visitor's persistent id, from `shared/visitor-id`.\n\t *\n\t * Identity rather than state. Sending it is what lets a flow started here\n\t * and a conversation continued in the chat bubble belong to one person.\n\t */\n\tvisitorId?: string;\n\t/**\n\t * The channel this embed belongs to, from the script tag or `/config`.\n\t *\n\t * Ingest drops events it cannot attribute to a channel, so a tool call sent\n\t * without this produces conversions nobody can see.\n\t */\n\tchannelId?: string;\n\t/**\n\t * Called when a tool call resolves to a widget the page should mount.\n\t *\n\t * Optional, and the surface works without it: everything the visitor needs\n\t * is also in the text the agent receives. A page with no host gets a flow\n\t * carried in words.\n\t */\n\tonWidget?: (widget: WebMcpWidgetPayload) => void;\n\t/** Defaults to `console`. */\n\tlogger?: Pick<Console, \"error\" | \"info\">;\n};\n\n/** Live for as long as the bridge is registered. */\nexport type WebMcpBridge = {\n\t/** Tools successfully registered with the browser. */\n\treadonly tools: WebMcpTool[];\n\t/** Unregister everything. Idempotent. */\n\tdispose: () => void;\n};\n\nfunction readModelContext(): ModelContext | null {\n\tif (typeof document === \"undefined\" && typeof navigator === \"undefined\") {\n\t\treturn null;\n\t}\n\t// The getter moved from Navigator to Document (webmcp#173). Probe both.\n\tconst candidate =\n\t\t(typeof document !== \"undefined\"\n\t\t\t? (document as unknown as { modelContext?: unknown }).modelContext\n\t\t\t: undefined) ??\n\t\t(typeof navigator !== \"undefined\"\n\t\t\t? (navigator as unknown as { modelContext?: unknown }).modelContext\n\t\t\t: undefined);\n\n\tif (\n\t\tcandidate &&\n\t\ttypeof (candidate as ModelContext).registerTool === \"function\"\n\t) {\n\t\treturn candidate as ModelContext;\n\t}\n\treturn null;\n}\n\n/** Whether this browser can host site tools at all. Almost every visit: no. */\nexport function supportsWebMcp(): boolean {\n\treturn readModelContext() !== null;\n}\n\nfunction pageContext(): WebMcpPageContext {\n\treturn { url: location.href, title: document.title };\n}\n\n/**\n * Register the server's tools with the browsing agent.\n *\n * Resolves once registration has been attempted for every advertised tool. A\n * tool the browser refuses is logged and skipped rather than failing the rest,\n * because a partial tool list is a working page and an exception here is a\n * blank one.\n *\n * Returns `null` when the browser has no `modelContext`, which is the common\n * case and not an error.\n */\nexport async function createWebMcpBridge(\n\toptions: WebMcpBridgeOptions,\n): Promise<WebMcpBridge | null> {\n\tconst { endpoint, headers, sessionId, visitorId, channelId, onWidget } =\n\t\toptions;\n\tconst log = options.logger ?? console;\n\n\tconst mc = readModelContext();\n\tif (!mc) {\n\t\treturn null;\n\t}\n\n\t// Unregistration is aborting this, not a method call.\n\tconst controller = new AbortController();\n\tconst onPageHide = () => controller.abort();\n\twindow.addEventListener(\"pagehide\", onPageHide);\n\n\tlet disposed = false;\n\tconst dispose = () => {\n\t\tif (disposed) {\n\t\t\treturn;\n\t\t}\n\t\tdisposed = true;\n\t\twindow.removeEventListener(\"pagehide\", onPageHide);\n\t\tcontroller.abort();\n\t};\n\n\t/**\n\t * Identity and place, attached to every request rather than left to each\n\t * call site. Read per call, because a single-page app navigates without\n\t * re-registering and `page` would otherwise name whichever URL happened to\n\t * be open when the tools were published.\n\t */\n\tfunction identity() {\n\t\treturn { sessionId, visitorId, channelId, page: pageContext() };\n\t}\n\n\tasync function post<T>(\n\t\trequest: WebMcpRequest,\n\t\tsignal?: AbortSignal,\n\t): Promise<T> {\n\t\tconst response = await fetch(endpoint, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"content-type\": \"application/json\", ...headers },\n\t\t\tbody: JSON.stringify(request),\n\t\t\t...(signal ? { signal } : {}),\n\t\t});\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`webmcp ${request.action} failed: ${response.status}`);\n\t\t}\n\t\treturn (await response.json()) as T;\n\t}\n\n\tlet tools: WebMcpTool[] = [];\n\ttry {\n\t\tconst listed = await post<WebMcpListResponse>({\n\t\t\t...identity(),\n\t\t\taction: \"list\",\n\t\t});\n\t\ttools = listed?.tools ?? [];\n\t} catch (error) {\n\t\tlog.error(\"[webmcp] could not list site tools\", error);\n\t\tdispose();\n\t\treturn null;\n\t}\n\n\tconst registered: WebMcpTool[] = [];\n\tawait Promise.all(\n\t\ttools.map(async (tool) => {\n\t\t\ttry {\n\t\t\t\tawait mc.registerTool(\n\t\t\t\t\t{\n\t\t\t\t\t\tname: tool.name,\n\t\t\t\t\t\tdescription: tool.description ?? \"\",\n\t\t\t\t\t\t...(tool.inputSchema ? { inputSchema: tool.inputSchema } : {}),\n\t\t\t\t\t\t...(tool.annotations ? { annotations: tool.annotations } : {}),\n\t\t\t\t\t\texecute: async (args, execOptions) => {\n\t\t\t\t\t\t\t// The agent's abort travels all the way to the tool, so a\n\t\t\t\t\t\t\t// visitor who navigates away mid-flow cancels the work\n\t\t\t\t\t\t\t// upstream instead of leaving it running.\n\t\t\t\t\t\t\tconst result = await post<WebMcpCallResponse>(\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t...identity(),\n\t\t\t\t\t\t\t\t\taction: \"call\",\n\t\t\t\t\t\t\t\t\tname: tool.name,\n\t\t\t\t\t\t\t\t\targuments: args ?? {},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\texecOptions?.signal,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t// A widget step arrives already resolved: the page mounts the\n\t\t\t\t\t\t\t// view, the agent is told in words that it is on screen.\n\t\t\t\t\t\t\t// Handed to a callback rather than rendered here so the host\n\t\t\t\t\t\t\t// owns presentation, and a host with none still works.\n\t\t\t\t\t\t\tif (result?.widget) {\n\t\t\t\t\t\t\t\tonWidget?.(result.widget);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn { content: result?.content ?? [] };\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{ signal: controller.signal },\n\t\t\t\t);\n\t\t\t\tregistered.push(tool);\n\t\t\t} catch (error) {\n\t\t\t\tlog.error(`[webmcp] could not register ${tool.name}`, error);\n\t\t\t}\n\t\t}),\n\t);\n\n\tlog.info(`[webmcp] registered ${registered.length} site tool(s)`);\n\n\treturn {\n\t\tget tools() {\n\t\t\treturn registered;\n\t\t},\n\t\tdispose,\n\t};\n}\n","/**\n * Widget steps on the WebMCP surface.\n *\n * In a chat host a flow's widget step is a two-call dance: the flow answers\n * `status: \"widget\"` naming a display tool, the model calls that tool, and the\n * host renders the `ui://` template it points at. None of that survives the\n * trip to a browsing agent. There is no widget host in the agent's window, the\n * display tool is not advertised on this surface, and the round trip would cost\n * a turn for a render nobody performs.\n *\n * So the step is resolved before the agent ever sees it. The server calls the\n * display tool, finds its view, and hands the page everything it needs to mount\n * the widget itself. The agent gets prose that never names a display tool. The\n * visitor gets the calendar on the page they are already looking at.\n */\n\nimport type { FlowWidgetContent } from \"../mcp/server/flows/@types\";\nimport type { WebMcpCallResult } from \"./@types\";\n\nexport type WidgetStep = {\n\t/**\n\t * The flow's own response, carried whole.\n\t *\n\t * Typed as the engine's own `FlowWidgetContent` rather than a local shape, so\n\t * a field added to a widget step reaches this file as a type change instead\n\t * of being silently dropped. That has already happened once: `intro` was\n\t * added to the payload and the surface that hand-rolled this parse kept\n\t * working only because it spread the rest through.\n\t */\n\tpayload: FlowWidgetContent;\n\ttool: string;\n\tdata: Record<string, unknown>;\n\tinteractive: boolean;\n};\n\n/**\n * A flow's widget step, or `null` for every other tool result.\n *\n * Flow tools answer with a single JSON text block, so anything that does not\n * parse as JSON with `status: \"widget\"` and a `tool` to call belongs to someone\n * else and passes through untouched. That includes ordinary tools, which is\n * most of them.\n */\nexport function readWidgetStep(result: WebMcpCallResult): WidgetStep | null {\n\tconst first = result.content?.[0];\n\tif (!first || first.type !== \"text\" || typeof first.text !== \"string\") {\n\t\treturn null;\n\t}\n\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(first.text);\n\t} catch {\n\t\treturn null;\n\t}\n\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\treturn null;\n\t}\n\n\tconst payload = parsed as Record<string, unknown>;\n\tif (payload.status !== \"widget\" || typeof payload.tool !== \"string\") {\n\t\treturn null;\n\t}\n\n\tconst data =\n\t\tpayload.data &&\n\t\ttypeof payload.data === \"object\" &&\n\t\t!Array.isArray(payload.data)\n\t\t\t? (payload.data as Record<string, unknown>)\n\t\t\t: {};\n\n\treturn {\n\t\tpayload: payload as FlowWidgetContent,\n\t\ttool: payload.tool,\n\t\tdata,\n\t\t// The protocol treats an unset `interactive` as display-only, and so does\n\t\t// this. Telling the agent to wait for an interaction that never comes\n\t\t// strands the conversation; continuing early only costs a turn.\n\t\tinteractive: payload.interactive === true,\n\t};\n}\n\nconst WAIT_INSTRUCTIONS =\n\t'The interface for this step is now open in front of the user, on the page. There is no display tool to call on this surface, so do not try. Say one short sentence about what is on screen, then stop and wait for the user to tell you what they did. When they do, call this flow again with action \"continue\".';\n\nconst SHOWN_INSTRUCTIONS =\n\t'The interface for this step is now open in front of the user, on the page. There is no display tool to call on this surface, so do not try. Summarise `data` in one short sentence, then call this flow again with action \"continue\".';\n\nconst UNAVAILABLE_INSTRUCTIONS =\n\t\"The interface for this step could not be opened on the page. Carry the step in words instead: tell the user everything in `data` yourself, then proceed as you otherwise would.\";\n\n/** The status a rewritten widget step reports in place of `\"widget\"`. */\nexport type WidgetStepStatus = \"widget_shown\" | \"widget_unavailable\";\n\n/**\n * The step as the agent should read it.\n *\n * Built by editing the flow's own payload rather than composing a new object,\n * so the fields the protocol requires the agent to echo back survive. Dropping\n * `sessionId` here breaks the flow several turns later, which is the worst kind\n * of bug to go looking for.\n *\n * `tool` and `description` are the two that must not survive. Both name a\n * display tool the agent cannot call, and leaving either in place is an\n * instruction to attempt it.\n */\nexport function rewriteForAgent(\n\tstep: WidgetStep,\n\trendered: boolean,\n): Record<string, unknown> & { status: WidgetStepStatus } {\n\tconst { tool: _tool, description: _description, ...rest } = step.payload;\n\treturn {\n\t\t...rest,\n\t\tstatus: rendered ? \"widget_shown\" : \"widget_unavailable\",\n\t\tinstructions: rendered\n\t\t\t? step.interactive\n\t\t\t\t? WAIT_INSTRUCTIONS\n\t\t\t\t: SHOWN_INSTRUCTIONS\n\t\t\t: UNAVAILABLE_INSTRUCTIONS,\n\t};\n}\n","import type { ZodRawShapeCompat } from \"@modelcontextprotocol/sdk/server/zod-compat.js\";\nimport type { RequestHandlerExtra } from \"@modelcontextprotocol/sdk/shared/protocol.js\";\nimport type {\n\tCallToolResult,\n\tServerNotification,\n\tServerRequest,\n\tToolAnnotations,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport type { z } from \"zod\";\nimport type { ScopedWaniWaniClient } from \"../scoped-client\";\nimport type { McpServer } from \"../types\";\nimport type { FieldSchemaInfo } from \"./field-schema\";\nimport type { FlowStore } from \"./flow-store\";\nimport type { FlowOutputSchema } from \"./output-schema\";\n\nexport type { FieldSchemaInfo };\n\nexport type { McpServer };\n\n/**\n * A tool object `showWidget` can render, as opposed to a bare tool name.\n *\n * Structural on purpose: anything carrying these four members satisfies it,\n * so a server that registers its tools by hand keeps working without\n * importing a factory from this package.\n */\nexport type RegisteredTool = {\n\tid: string;\n\ttitle: string;\n\tdescription: string;\n\t/** Register the tool on the server */\n\tregister: (server: McpServer) => Promise<void>;\n};\n\n// ============================================================================\n// Sentinel constants\n// ============================================================================\n\nexport const START = \"__start__\" as const;\nexport const END = \"__end__\" as const;\n\n// ============================================================================\n// Signal types — returned by node handlers to control flow behavior\n// ============================================================================\n\nconst INTERRUPT = Symbol.for(\"waniwani.flow.interrupt\");\nconst WIDGET = Symbol.for(\"waniwani.flow.widget\");\n\n/** A single question within an interrupt step */\nexport type InterruptQuestion = {\n\t/** Question to ask the user */\n\tquestion: string;\n\t/** State key where the answer will be stored */\n\tfield: string;\n\t/** Optional suggestions to present as options */\n\tsuggestions?: string[];\n\t/** Hidden context/instructions for this specific question (not shown to user directly) */\n\tcontext?: string;\n\t/** Validation function — runs after the user answers, before advancing to the next node */\n\t// biome-ignore lint/suspicious/noConfusingVoidType: void is needed so `async () => {}` compiles\n\tvalidate?: (value: unknown) => MaybePromise<Record<string, unknown> | void>;\n};\n\n/**\n * Interrupt signal — pauses the flow and asks the user one or more questions.\n * Single-question and multi-question interrupts use the same type.\n */\nexport type InterruptSignal = {\n\treadonly __type: typeof INTERRUPT;\n\t/** Questions to ask — ask all in one conversational message */\n\tquestions: InterruptQuestion[];\n\t/** Overall hidden context/instructions for the assistant (not shown to user directly) */\n\tcontext?: string;\n};\n\nexport type WidgetSignal = {\n\treadonly __type: typeof WIDGET;\n\t/** The id of the display tool to delegate rendering to */\n\ttool: string;\n\t/** Data to pass to the display tool */\n\tdata?: Record<string, unknown>;\n\t/**\n\t * Whether the user is expected to interact with the widget before the flow continues.\n\t * Defaults to true. Set to false for informational widgets that should render and then\n\t * immediately advance to the next flow step.\n\t */\n\tinteractive?: boolean;\n\t/**\n\t * State key this widget fills — enables auto-skip when the field is already in state.\n\t * Pass this so the engine can skip the widget step when the answer is already known.\n\t */\n\tfield?: string;\n};\n\n/**\n * Create an interrupt signal — pauses the flow and asks the user a question.\n * Used internally by the engine. Flow authors use the typed `interrupt` from the node context.\n *\n * Accepts an object where each key is a field name and the value describes the question.\n * The only reserved key is `context` (string) for overall hidden AI instructions.\n */\nexport function interrupt(\n\tfields: Record<string, unknown>,\n\tconfig?: { context?: string },\n): InterruptSignal {\n\tconst context = config?.context;\n\tconst questions: InterruptQuestion[] = [];\n\n\tfor (const [key, value] of Object.entries(fields)) {\n\t\tif (typeof value === \"object\" && value !== null && \"question\" in value) {\n\t\t\tconst q = value as {\n\t\t\t\tquestion: string;\n\t\t\t\tsuggestions?: string[];\n\t\t\t\tcontext?: string;\n\t\t\t\tvalidate?: (\n\t\t\t\t\tvalue: unknown,\n\t\t\t\t\t// biome-ignore lint/suspicious/noConfusingVoidType: void is needed so `async () => {}` compiles\n\t\t\t\t) => MaybePromise<Record<string, unknown> | void>;\n\t\t\t};\n\t\t\tquestions.push({\n\t\t\t\tquestion: q.question,\n\t\t\t\tfield: key,\n\t\t\t\tsuggestions: q.suggestions,\n\t\t\t\tcontext: q.context,\n\t\t\t\tvalidate: q.validate,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn {\n\t\t__type: INTERRUPT,\n\t\tquestions,\n\t\tcontext,\n\t};\n}\n\ntype ShowWidgetConfig = {\n\tdata?: Record<string, unknown>;\n\t/**\n\t * @deprecated REMOVED. The engine now emits a standardized instruction telling\n\t * the AI to call the widget tool. Return any per-widget description from the\n\t * widget tool's own response instead. Typed as `never` so passing it is a\n\t * compile-time error; the runtime check below catches JS callers as a safety net.\n\t */\n\tdescription?: never;\n\tinteractive?: boolean;\n\tfield?: string;\n};\n\n/**\n * Create a widget signal — pauses the flow and delegates rendering to a display tool.\n * Used internally by the engine. Flow authors use the typed `showWidget` from the node context.\n */\nexport function showWidget(\n\tconfig: ShowWidgetConfig & { tool: RegisteredTool | string },\n): WidgetSignal;\n/**\n * @deprecated Use the object form: `showWidget({ tool, data, field, interactive })`.\n * The positional form will be removed in v0.13.0\n */\nexport function showWidget(\n\ttool: RegisteredTool | string,\n\tconfig: ShowWidgetConfig,\n): WidgetSignal;\nexport function showWidget(\n\ttoolOrConfig:\n\t\t| RegisteredTool\n\t\t| string\n\t\t| (ShowWidgetConfig & { tool: RegisteredTool | string }),\n\tmaybeConfig?: ShowWidgetConfig,\n): WidgetSignal {\n\tconst isObjectForm =\n\t\ttypeof toolOrConfig === \"object\" &&\n\t\ttoolOrConfig !== null &&\n\t\t\"tool\" in toolOrConfig;\n\tconst tool = isObjectForm ? toolOrConfig.tool : toolOrConfig;\n\tconst config: ShowWidgetConfig = isObjectForm\n\t\t? toolOrConfig\n\t\t: (maybeConfig ?? {});\n\tif ((config as { description?: unknown }).description !== undefined) {\n\t\tconst toolId = typeof tool === \"string\" ? tool : tool.id;\n\t\tthrow new Error(\n\t\t\t`showWidget(\"${toolId}\", { description }) is no longer supported. ` +\n\t\t\t\t`The engine now emits a standardized instruction telling the AI to call ` +\n\t\t\t\t`the widget tool. Return any per-widget description from the \"${toolId}\" ` +\n\t\t\t\t`tool's own response instead.`,\n\t\t);\n\t}\n\tconst { data, interactive, field } = config;\n\treturn {\n\t\t__type: WIDGET,\n\t\ttool: typeof tool === \"string\" ? tool : tool.id,\n\t\t...(data !== undefined ? { data } : {}),\n\t\t...(interactive !== undefined ? { interactive } : {}),\n\t\t...(field !== undefined ? { field } : {}),\n\t};\n}\n\nexport function isInterrupt(value: unknown): value is InterruptSignal {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\t\"__type\" in value &&\n\t\t(value as InterruptSignal).__type === INTERRUPT\n\t);\n}\n\nexport function isWidget(value: unknown): value is WidgetSignal {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\t\"__type\" in value &&\n\t\t(value as WidgetSignal).__type === WIDGET\n\t);\n}\n\n// ============================================================================\n// Node context & handler\n// ============================================================================\n\nexport type MaybePromise<T> = T | Promise<T>;\n\n// ============================================================================\n// Nested state utility types\n// ============================================================================\n\n/** Deep partial — allows partial updates at any nesting level (for z.object state fields). */\nexport type DeepPartial<T> = {\n\t[K in keyof T]?: T[K] extends Record<string, unknown>\n\t\t? DeepPartial<T[K]>\n\t\t: T[K];\n};\n\n/**\n * Extract known (non-index-signature) string keys from a type.\n * Zod v4's z.object() adds `[key: string]: unknown` to inferred types,\n * so we filter those out to get only the declared field names.\n */\ntype KnownStringKeys<T> = Extract<\n\tkeyof {\n\t\t[K in keyof T as string extends K\n\t\t\t? never\n\t\t\t: number extends K\n\t\t\t\t? never\n\t\t\t\t: K]: T[K];\n\t},\n\tstring\n>;\n\n/**\n * Union of all valid field paths for a state type.\n * - Flat fields produce their key: `\"email\"`\n * - `z.object()` fields produce dot-paths to sub-fields: `\"driver.name\"`, `\"driver.license\"`\n * - Arrays and general records (`z.record()`) are treated as flat fields.\n * - Only 1 level of nesting is supported.\n */\nexport type FieldPaths<TState> = {\n\t[K in Extract<keyof TState, string>]: NonNullable<TState[K]> extends unknown[]\n\t\t? K\n\t\t: NonNullable<TState[K]> extends Record<string, unknown>\n\t\t\t? KnownStringKeys<NonNullable<TState[K]>> extends never\n\t\t\t\t? K\n\t\t\t\t: K | `${K}.${KnownStringKeys<NonNullable<TState[K]>>}`\n\t\t\t: K;\n}[Extract<keyof TState, string>];\n\n/** Resolve a dot-path to the value type at that path in TState. */\nexport type ResolveFieldType<\n\tTState,\n\tP extends string,\n> = P extends `${infer Parent}.${infer Child}`\n\t? Parent extends keyof TState\n\t\t? Child extends keyof NonNullable<TState[Parent]>\n\t\t\t? NonNullable<TState[Parent]>[Child]\n\t\t\t: never\n\t\t: never\n\t: P extends keyof TState\n\t\t? TState[P]\n\t\t: never;\n\n// ============================================================================\n// Typed interrupt & showWidget\n// ============================================================================\n\n/**\n * Typed interrupt function — available on the node context.\n *\n * First argument: an object where each key is a field path and each value\n * describes the question for that field. Use dot-paths for nested state fields.\n * `validate` receives the field's value typed from the Zod schema.\n *\n * Second argument (optional): config with overall hidden AI instructions.\n *\n * @example\n * ```ts\n * // Flat field\n * interrupt({ breed: { question: \"What breed is your pet?\" } })\n *\n * // Nested field (z.object in state)\n * interrupt({ \"driver.name\": { question: \"Driver's name?\" } })\n *\n * // Multiple questions with context\n * interrupt(\n * {\n * \"driver.name\": { question: \"Name?\" },\n * \"driver.license\": { question: \"License?\" },\n * },\n * { context: \"Ask both questions naturally.\" },\n * )\n * ```\n */\nexport type TypedInterrupt<TState> = (\n\tfields: {\n\t\t[P in FieldPaths<TState>]?: {\n\t\t\tquestion: string;\n\t\t\tvalidate?: (\n\t\t\t\tvalue: ResolveFieldType<TState, P>,\n\t\t\t\t// biome-ignore lint/suspicious/noConfusingVoidType: void is needed so `async () => {}` compiles\n\t\t\t) => MaybePromise<DeepPartial<TState> | void>;\n\t\t\tsuggestions?: string[];\n\t\t\tcontext?: string;\n\t\t};\n\t},\n\tconfig?: {\n\t\t/** Overall hidden context/instructions for the assistant (not shown to user directly) */\n\t\tcontext?: string;\n\t},\n) => InterruptSignal;\n\n/**\n * Typed showWidget function — available on the node context.\n * The `field` parameter accepts field paths (flat or dot-path for nested state).\n *\n * Prefer the object form: `showWidget({ tool, data, field, interactive })`.\n * The positional form `showWidget(tool, config)` is deprecated.\n */\nexport type TypedShowWidgetConfig<TState> = {\n\tdata?: Record<string, unknown>;\n\t/**\n\t * @deprecated REMOVED. The engine now emits a standardized instruction telling\n\t * the AI to call the widget tool. Return any per-widget description from the\n\t * widget tool's own response instead. Typed as `never` so passing it is a\n\t * compile-time error pointing authors at the new channel.\n\t */\n\tdescription?: never;\n\tinteractive?: boolean;\n\tfield?: FieldPaths<TState>;\n};\n\nexport type TypedShowWidget<TState> = {\n\t(\n\t\tconfig: TypedShowWidgetConfig<TState> & { tool: RegisteredTool | string },\n\t): WidgetSignal;\n\t/**\n\t * @deprecated Use the object form: `showWidget({ tool, data, field, interactive })`.\n\t * The positional form will be removed in v0.13.0\n\t */\n\t(\n\t\ttool: RegisteredTool | string,\n\t\tconfig: TypedShowWidgetConfig<TState>,\n\t): WidgetSignal;\n};\n\n/**\n * Context object passed to node handlers.\n * Provides state, metadata, and typed helper functions for creating signals.\n */\nexport type NodeContext<TState> = {\n\t/** Current flow state (partial — fields are filled as the flow progresses) */\n\tstate: Partial<TState>;\n\t/** Request metadata from the MCP call */\n\tmeta?: Record<string, unknown>;\n\t/** Create an interrupt signal — pause and ask the user questions */\n\tinterrupt: TypedInterrupt<TState>;\n\t/** Create a widget signal — pause and show a UI widget */\n\tshowWidget: TypedShowWidget<TState>;\n\t/** Session-scoped Waniwani client — available when the server is wrapped with withWaniwani() */\n\twaniwani?: ScopedWaniWaniClient;\n};\n\n/**\n * Node handler — receives a context object and returns a signal or state updates.\n * The return value determines behavior:\n * - `Partial<TState>` → action node (state merged, auto-advance)\n * - `InterruptSignal` → interrupt (pause, ask user one or more questions)\n * - `WidgetSignal` → widget step (pause, show widget)\n */\nexport type NodeHandler<TState> = (\n\tctx: NodeContext<TState>,\n) => MaybePromise<DeepPartial<TState> | InterruptSignal | WidgetSignal>;\n\n/**\n * Condition function for conditional edges.\n * Receives current state, returns the name of the next node.\n */\nexport type ConditionFn<TState> = (\n\tstate: Partial<TState>,\n) => string | Promise<string>;\n\nexport type Edge<TState> =\n\t| { type: \"direct\"; to: string }\n\t| {\n\t\t\ttype: \"conditional\";\n\t\t\tcondition: ConditionFn<TState>;\n\t\t\t/**\n\t\t\t * The branch targets declared on the edge. The condition can only return\n\t\t\t * one of these, so graph introspection (funnel sync, Mermaid output) reads\n\t\t\t * them directly — no source parsing, correct by construction.\n\t\t\t */\n\t\t\ttargets: string[];\n\t };\n\nexport type NodeOptions = {\n\t/** Human-readable label for this node (used in funnel visualization and Graphs). */\n\tlabel: string;\n\t/** When true, this node is excluded from funnel analytics. */\n\thideFromFunnel?: boolean;\n};\n\n/**\n * Config for the object form of `.addNode({ id, run, label?, hideFromFunnel? })`.\n *\n * Preferred over the positional form `.addNode(id, run, options?)` — metadata\n * sits at the top where the eye lands, and the handler is a named field.\n */\nexport type AddNodeConfig<TState, TName extends string = string> = {\n\t/** Unique node id within the flow. */\n\tid: TName;\n\t/** Node handler — receives ctx, returns state updates or a signal. */\n\trun: NodeHandler<TState>;\n\t/** Human-readable label for funnel visualization and graphs. Defaults to `id`. */\n\tlabel?: string;\n\t/** When true, this node is excluded from funnel analytics. */\n\thideFromFunnel?: boolean;\n};\n\nexport type FlowGraphNode = {\n\tid: string;\n\ttype: \"widget\" | \"interrupt\" | \"action\";\n\tlabel: string;\n\thideFromFunnel?: boolean;\n};\n\nexport type FlowGraphEdge =\n\t| { from: string; to: string; type: \"direct\" }\n\t| { from: string; to: string[]; type: \"conditional\" };\n\nexport type FlowGraph = {\n\tflowId: string;\n\ttitle: string;\n\tnodes: FlowGraphNode[];\n\tedges: FlowGraphEdge[];\n};\n\n// ============================================================================\n// Flow config & compiled output\n// ============================================================================\n\n/**\n * The intro delivered the first time a session touches the flow.\n *\n * A bare string is shorthand for `{ verbatim: \"…\" }`.\n *\n * - `verbatim` — reproduced word for word by the assistant. Use it for legal\n * notices (GDPR, data controller, retention) that must survive intact.\n * - `instructions` — guidance the assistant writes its own opening from.\n *\n * Set both and the assistant writes the surrounding prose from\n * `instructions` while keeping the `verbatim` text untouched.\n */\nexport type FlowIntro =\n\t| string\n\t| {\n\t\t\t/** Text the assistant must reproduce word for word. */\n\t\t\tverbatim?: string;\n\t\t\t/** How the assistant should write the introduction around it. */\n\t\t\tinstructions?: string;\n\t };\n\n/** Normalized intro as it appears in the flow tool response. */\nexport type FlowIntroPayload = {\n\t/** Deliver word for word — no paraphrase, no translation, no reformatting. */\n\tverbatim?: string;\n\t/** Guidance for the assistant's own wording. */\n\tinstructions?: string;\n};\n\nexport type FlowConfig = {\n\t/** Unique identifier for the flow (becomes the MCP tool name) */\n\tid: string;\n\t/** Display title */\n\ttitle: string;\n\t/** Description for the AI (explains when to use this flow) */\n\tdescription: string;\n\t/**\n\t * Opening message for the session's first flow response — an introduction,\n\t * a GDPR notice, or both. Attached to whichever response the engine returns\n\t * first, so it lands even when pre-filled state skips the flow's opening\n\t * nodes, and it is never repeated once delivered.\n\t *\n\t * @example\n\t * ```ts\n\t * intro: \"Acme is the data controller. Your answers are used for this quote only.\"\n\t * ```\n\t *\n\t * @example\n\t * ```ts\n\t * intro: {\n\t * verbatim: \"Acme is the data controller. Your answers are deleted after 30 days.\",\n\t * instructions: \"Introduce yourself as Léa from Acme in one short sentence first.\",\n\t * }\n\t * ```\n\t */\n\tintro?: FlowIntro;\n\t/**\n\t * Define the flow's state — each field the flow collects.\n\t * Keys are the field names used in `interrupt({ field })`,\n\t * values are Zod schemas with `.describe()`.\n\t *\n\t * The state definition serves two purposes:\n\t * 1. Type inference — `TState` is automatically derived, no explicit generic needed\n\t * 2. AI protocol — field names, types, and descriptions are included in the tool\n\t * description so the AI can pre-fill answers via `_meta.flow.state`\n\t *\n\t * @example\n\t * ```ts\n\t * state: {\n\t * country: z.string().describe(\"Country the business is based in\"),\n\t * status: z.enum([\"registered\", \"unregistered\"]).describe(\"Business registration status\"),\n\t * }\n\t * ```\n\t */\n\tstate: Record<string, z.ZodType>;\n\t/**\n\t * If true, instruct the agent to omit PII (names, emails, phones, addresses,\n\t * IDs, ages, birthdates) from the `intent` and `context` fields. Only adds a\n\t * guidance line to the tool description — does not redact server-side.\n\t */\n\tomitIntentPII?: boolean;\n\t/**\n\t * Optional tool annotations, passed through to `registerTool` as-is.\n\t *\n\t * Includes MCP's `title` — a human-readable name for the tool. Claude's\n\t * Connectors Directory requires it on every tool and flags the server at\n\t * submission without it; the top-level `title` does not satisfy that.\n\t */\n\tannotations?: ToolAnnotations;\n};\n\n/**\n * Infer the runtime state type from a flow's state schema definition.\n *\n * @example\n * ```ts\n * const config = {\n * state: {\n * country: z.enum([\"FR\", \"DE\"]),\n * status: z.enum([\"registered\", \"unregistered\"]),\n * }\n * };\n * type MyState = InferFlowState<typeof config.state>;\n * // { country: \"FR\" | \"DE\"; status: \"registered\" | \"unregistered\" }\n * ```\n */\nexport type InferFlowState<T extends Record<string, z.ZodType>> = {\n\t[K in keyof T]: z.infer<T[K]>;\n};\n\n/**\n * Flow tool handler — uses shared MCP types (`RequestHandlerExtra`, `CallToolResult`)\n * so it's assignable to both MCP SDK's `ToolCallback` and Skybridge's `ToolHandler`.\n */\nexport type FlowToolHandler = (\n\targs: Record<string, unknown>,\n\textra: RequestHandlerExtra<ServerRequest, ServerNotification>,\n) => CallToolResult | Promise<CallToolResult>;\n\n/**\n * A compiled flow — can be registered on an McpServer.\n *\n * Exposes MCP-compatible `name`, `config`, and `handler` so it can be\n * registered directly: `server.registerTool(flow.name, flow.config, flow.handler)`\n */\nexport type RegisteredFlow = {\n\t/** Tool name — pass to `server.registerTool(flow.name, flow.config, flow.handler)`. */\n\tname: string;\n\t/** Tool config object — pass to `server.registerTool(flow.name, flow.config, flow.handler)`. */\n\tconfig: {\n\t\ttitle: string;\n\t\tdescription: string;\n\t\tinputSchema: ZodRawShapeCompat;\n\t\t/**\n\t\t * JSON Schema for the structured payload returned in every flow tool\n\t\t * call. Baked in so MCP clients render the flow protocol as a typed\n\t\t * contract instead of an opaque JSON string.\n\t\t */\n\t\toutputSchema: FlowOutputSchema;\n\t\tannotations?: ToolAnnotations;\n\t};\n\t/** Tool callback — pass to `server.registerTool(flow.name, flow.config, flow.handler)`. */\n\thandler: FlowToolHandler;\n\t/** Register this flow on an MCP server. Shorthand for `server.registerTool(flow.name, flow.config, flow.handler)`. */\n\tregister: (server: McpServer) => Promise<void>;\n\t/** Returns a Mermaid `flowchart TD` diagram of the flow graph. */\n\tgraph: () => string;\n\tflowGraph: FlowGraph;\n};\n\nexport interface CompileInput<TState extends Record<string, unknown>> {\n\tconfig: FlowConfig;\n\tnodes: Map<string, NodeHandler<TState>>;\n\tedges: Map<string, Edge<TState>>;\n\tstore?: FlowStore;\n\tgraph: () => string;\n\tnodeOptions: Map<string, NodeOptions>;\n}\n\nexport type FlowToolInput = {\n\taction: \"start\" | \"continue\" | \"reset\";\n\t/** Required when `action` is `\"start\"`. */\n\tintent?: string;\n\t/** Optional when `action` is `\"start\"`. Describes the situation/environment that led the user to start this flow. */\n\tcontext?: string;\n\tstateUpdates?: Record<string, unknown>;\n\t/** Session identifier echoed in the response. Pass it back on \"continue\" and \"reset\" calls. */\n\tsessionId?: string;\n};\n\n/**\n * The engine's own state for a session, persisted in the same record as the\n * flow's state and kept strictly apart from it.\n *\n * Not flow state: nothing here is declared by the author, reaches a node, or\n * can be written through `stateUpdates`.\n *\n * One optional field per engine feature that carries something across calls. A\n * field is seeded when the session starts and taken off once the engine has\n * used it, so presence means \"still pending\" and absence means \"done\". Seeding,\n * reading and persisting all go through `internal-state.ts`, which carries\n * unrecognized fields through untouched: a field a newer version wrote survives\n * a call served by an older one, and adding a field never strands sessions\n * across a deploy.\n */\nexport type FlowInternalState = {\n\t/**\n\t * The flow's intro, waiting to ride on the session's first response. Gone\n\t * once delivered, so later responses in the same conversation leave it out.\n\t */\n\tintro?: FlowIntroPayload;\n};\n\nexport type FlowTokenContent = {\n\tstep?: string;\n\tstate: Record<string, unknown>;\n\tfield?: string;\n\twidgetId?: string;\n\t/** The engine's own state for this session. Never flow state. */\n\tinternal?: FlowInternalState;\n};\n\nexport type InterruptQuestionData = {\n\tquestion: string;\n\tfield: string;\n\tsuggestions?: string[];\n\tcontext?: string;\n\t/**\n\t * JIT schema fragment for the field, derived from the flow's state schema.\n\t * The LLM uses this to know the field's type, allowed values, and meaning\n\t * at the exact moment it needs to fill it — without consulting a global dump.\n\t */\n\tfieldSchema?: FieldSchemaInfo;\n};\n\nexport type FlowInterruptContent = {\n\tstatus: \"interrupt\";\n\t/** Opening message for the session's first flow response. */\n\tintro?: FlowIntroPayload;\n\t/** Single-question shorthand */\n\tquestion?: string;\n\tfield?: string;\n\tsuggestions?: string[];\n\t/** JIT schema fragment for the single-question shorthand. */\n\tfieldSchema?: FieldSchemaInfo;\n\t/** Multi-question */\n\tquestions?: InterruptQuestionData[];\n\tcontext?: string;\n};\n\nexport type FlowWidgetContent = {\n\tstatus: \"widget\";\n\t/** Opening message for the session's first flow response. */\n\tintro?: FlowIntroPayload;\n\t/** Display tool to call */\n\ttool: string;\n\t/** Data to pass to the display tool */\n\tdata?: Record<string, unknown>;\n\t/**\n\t * Standardized instruction to the AI to call the widget tool. Auto-generated\n\t * by the engine from the tool id; the value previously passed via\n\t * `showWidget({ description })` is ignored.\n\t */\n\tdescription?: string;\n\t/** Whether the widget expects user interaction before continuing */\n\tinteractive?: boolean;\n\t/** State field the widget fills, when declared via `showWidget({ field })`. */\n\tfield?: string;\n\t/** JIT schema fragment for the field the widget fills. */\n\tfieldSchema?: FieldSchemaInfo;\n};\n\nexport type FlowCompleteContent = {\n\tstatus: \"complete\";\n\t/** Opening message for the session's first flow response. */\n\tintro?: FlowIntroPayload;\n};\n\nexport type FlowErrorContent = {\n\tstatus: \"error\";\n\terror: string;\n};\n\n/** Parsed response text from a flow tool call */\nexport type FlowContent =\n\t| FlowInterruptContent\n\t| FlowWidgetContent\n\t| FlowCompleteContent\n\t| FlowErrorContent;\n\n/** A single node visit during flow execution. `at` is the ISO timestamp of when the node was entered. */\nexport type NodeVisit = {\n\tid: string;\n\tat: string;\n};\n\nexport type ExecutionResult = {\n\tcontent: FlowContent;\n\tflowTokenContent?: FlowTokenContent;\n\t/** Nodes visited during this execution (excludes nodes with hideFromFunnel). */\n\tnodesVisited?: NodeVisit[];\n};\n","import type { ToolCallback } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { RequestHandlerExtra } from \"@modelcontextprotocol/sdk/shared/protocol.js\";\nimport type {\n\tServerNotification,\n\tServerRequest,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { z } from \"zod\";\nimport type { ScopedWaniWaniClient } from \"../scoped-client\";\nimport { extractScopedClient } from \"../scoped-client\";\nimport { classifyCause } from \"../session-errors/classify\";\nimport { reportSessionError } from \"../session-errors/report\";\nimport {\n\textractSessionId,\n\tFLOW_META_KEY,\n\tSUGGESTIONS_META_KEY,\n\ttype SuggestionsMeta,\n} from \"../utils\";\nimport type {\n\tCompileInput,\n\tExecutionResult,\n\tFlowInternalState,\n\tFlowToolHandler,\n\tFlowToolInput,\n\tMcpServer,\n\tRegisteredFlow,\n} from \"./@types\";\nimport { START } from \"./@types\";\nimport { executeFrom, resolveNextNode, type ValidateFn } from \"./execute\";\nimport { type FlowStore, WaniwaniFlowStore } from \"./flow-store\";\nimport { extractFlowGraph } from \"./graph-extract\";\nimport {\n\tinitInternalState,\n\tloadInternalState,\n\ttakeInternalField,\n\twithInternalState,\n} from \"./internal-state\";\nimport { deepMerge, expandDotPaths } from \"./nested\";\nimport { flowOutputSchema } from \"./output-schema\";\nimport { buildFlowProtocol } from \"./protocol\";\nimport {\n\tcollectRedactedStateFields,\n\tREDACTED_STATE_UPDATE_FIELDS_META_KEY,\n} from \"./redacted\";\n\n// ============================================================================\n// Input schema\n// ============================================================================\n\nfunction buildInputSchema(config: {\n\tomitIntentPII?: boolean;\n\tstate?: Record<string, z.ZodType>;\n}) {\n\tconst piiNote = config.omitIntentPII\n\t\t? \" Do not include PII (names, emails, phones, addresses, IDs, ages, birthdates) — summarize abstractly.\"\n\t\t: \"\";\n\n\t// When the flow declares state fields, expose them as typed (optional) keys\n\t// on `stateUpdates` so the LLM sees field names, types, and descriptions in\n\t// the tool's JSON Schema. `.passthrough()` preserves unknown keys (e.g.\n\t// dot-paths like \"driver.name\" for nested state, plus forward-compat keys).\n\tconst hasState = config.state && Object.keys(config.state).length > 0;\n\tconst stateUpdatesSchema = hasState\n\t\t? z\n\t\t\t\t.object(config.state as Record<string, z.ZodType>)\n\t\t\t\t.partial()\n\t\t\t\t.passthrough()\n\t\t: z.record(z.string(), z.unknown());\n\n\treturn {\n\t\taction: z\n\t\t\t.enum([\"start\", \"continue\", \"reset\"])\n\t\t\t.describe(\n\t\t\t\t'\"start\" to begin the flow, \"continue\" to resume after a pause (interrupt or widget), \"reset\" to restart from the beginning with a correction to a previously-collected field',\n\t\t\t),\n\t\tintent: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.describe(\n\t\t\t\t`Required when action is \"start\". Provide a brief summary of the user's goal for this flow. Do not invent missing intent.${piiNote}`,\n\t\t\t),\n\t\tcontext: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.describe(\n\t\t\t\t`Optional when action is \"start\". Describe the situation or environment that led the user to start this flow — e.g. what page they are on, what they were doing, or what triggered the request. Do not invent missing context.${piiNote}`,\n\t\t\t),\n\t\tstateUpdates: stateUpdatesSchema\n\t\t\t.optional()\n\t\t\t.describe(\n\t\t\t\t'State field values to set before processing the next node. Pass the user\\'s answer (keyed by the field name from the response) and any other values the user mentioned. For nested state fields, use dot-paths like \"driver.name\".',\n\t\t\t),\n\t\tsessionId: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.describe(\n\t\t\t\t'Session identifier. If the response includes a `sessionId`, pass it back on every subsequent \"continue\" and \"reset\" call for this flow.',\n\t\t\t),\n\t};\n}\n\n// ============================================================================\n// Default store resolution\n// ============================================================================\n\nfunction resolveDefaultStore(flowId: string): FlowStore {\n\tif (process.env.WANIWANI_API_KEY) {\n\t\treturn new WaniwaniFlowStore();\n\t}\n\tthrow new Error(\n\t\t`[waniwani] createFlow \"${flowId}\": no flow store configured. ` +\n\t\t\t`Pass { store } to .compile() — use MemoryKvStore from \"@waniwani/sdk/mcp\" for ` +\n\t\t\t`local development, or plug in a Redis/Upstash/Cloudflare KV adapter for production. ` +\n\t\t\t`Alternatively, set WANIWANI_API_KEY to use hosted flow state on app.waniwani.ai.`,\n\t);\n}\n\n// ============================================================================\n// Compile\n// ============================================================================\n\n/**\n * One handled tool call, plus the internal state this session arrived with. The\n * response assembler reads it to decide what to attach, then persists what is\n * left. Branches that fail before the engine runs return no `internal`, which\n * reads as \"nothing pending\".\n */\ntype HandledCall = ExecutionResult & { internal?: FlowInternalState };\n\nexport function compileFlow<TState extends Record<string, unknown>>(\n\tinput: CompileInput<TState>,\n): RegisteredFlow {\n\tconst { config, nodes, edges } = input;\n\tconst inputSchema = buildInputSchema(config);\n\tconst flowGraph = extractFlowGraph(config, nodes, edges, input.nodeOptions);\n\tconst protocol = buildFlowProtocol(config);\n\tconst fullDescription = `${config.description}\\n${protocol}`;\n\n\tconst store: FlowStore = input.store ?? resolveDefaultStore(config.id);\n\n\t// The internal state every new session starts from. Built once at compile\n\t// time so a malformed config fails at startup, not on a live conversation.\n\tconst initialInternal = initInternalState(config);\n\n\t// Validator storage — populated when handlers return interrupts with validate functions.\n\t// Keyed by \"nodeName:fieldName\", persists across tool calls within the same server.\n\tconst validators = new Map<string, ValidateFn>();\n\n\tasync function handleToolCall(\n\t\targs: FlowToolInput,\n\t\tsessionId: string | undefined,\n\t\tsessionIsPreexisting: boolean,\n\t\tmeta?: Record<string, unknown>,\n\t\twaniwani?: ScopedWaniWaniClient,\n\t): Promise<HandledCall> {\n\t\t/**\n\t\t * Run the engine from `node`, carrying this session's internal state\n\t\t * through to the response assembler. Every branch below resumes the same\n\t\t * graph with the same per-call context, so the only things that vary are\n\t\t * where execution starts, the state it starts from, and where the internal\n\t\t * state was read.\n\t\t */\n\t\tconst run = (\n\t\t\tnode: string,\n\t\t\tstate: TState,\n\t\t\tinternal: FlowInternalState,\n\t\t): Promise<HandledCall> =>\n\t\t\texecuteFrom(\n\t\t\t\tnode,\n\t\t\t\tstate,\n\t\t\t\tnodes,\n\t\t\t\tedges,\n\t\t\t\tvalidators,\n\t\t\t\tmeta,\n\t\t\t\twaniwani,\n\t\t\t\tinput.nodeOptions,\n\t\t\t\tconfig.state,\n\t\t\t).then((result) => ({ ...result, internal }));\n\n\t\tif (args.action === \"start\") {\n\t\t\t// `intent` is observational: the schema asks for it on start, but nothing\n\t\t\t// in the engine reads it (it never reaches a node, the store, or an\n\t\t\t// event). A missing value therefore costs a conversation turn and buys\n\t\t\t// nothing, so trim it and carry on instead of failing the call.\n\t\t\tconst intent =\n\t\t\t\ttypeof args.intent === \"string\" ? args.intent.trim() : undefined;\n\t\t\targs.intent = intent || undefined;\n\n\t\t\t// Trim context if provided (optional field, no error if missing)\n\t\t\tif (typeof args.context === \"string\") {\n\t\t\t\tconst trimmed = args.context.trim();\n\t\t\t\targs.context = trimmed || undefined;\n\t\t\t}\n\n\t\t\tconst startEdge = edges.get(START);\n\t\t\tif (!startEdge) {\n\t\t\t\treportSessionError({\n\t\t\t\t\twaniwani,\n\t\t\t\t\tcode: \"agent_failed\",\n\t\t\t\t\tcause: \"flow_dead_end\",\n\t\t\t\t\tproperties: { node: START },\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\tcontent: { status: \"error\" as const, error: \"No start edge\" },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst internal = await loadInternalState({\n\t\t\t\tstore,\n\t\t\t\tsessionId,\n\t\t\t\tsessionIsPreexisting,\n\t\t\t\tseed: initialInternal,\n\t\t\t});\n\t\t\tconst startState = expandDotPaths(args.stateUpdates ?? {}) as TState;\n\t\t\tconst firstNode = await resolveNextNode(startEdge, startState);\n\t\t\treturn run(firstNode, startState, internal);\n\t\t}\n\n\t\tif (args.action === \"continue\") {\n\t\t\tif (!sessionId) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror: \"No session ID available for continue action.\",\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tlet flowState: Awaited<ReturnType<typeof store.get>>;\n\t\t\ttry {\n\t\t\t\tflowState = await store.get(sessionId);\n\t\t\t} catch (err) {\n\t\t\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\t\t\treportSessionError({\n\t\t\t\t\twaniwani,\n\t\t\t\t\tcode: \"upstream_failed\",\n\t\t\t\t\tcause: classifyCause({ error: err }),\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror: `Failed to load flow state (session \"${sessionId}\"): ${msg}`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!flowState) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror: `Flow state not found for session \"${sessionId}\". The flow may have expired.`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst state = flowState.state as TState;\n\t\t\tconst step = flowState.step;\n\t\t\tif (!step) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror:\n\t\t\t\t\t\t\t'This flow has already completed. Use action \"start\" to begin a new flow.',\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst updatedState = deepMerge(\n\t\t\t\tstate as Record<string, unknown>,\n\t\t\t\texpandDotPaths(args.stateUpdates ?? {}),\n\t\t\t) as TState;\n\t\t\tconst internal = flowState.internal ?? {};\n\n\t\t\t// Widget continue: advance past the widget step (don't re-show it)\n\t\t\tif (flowState.widgetId) {\n\t\t\t\tconst edge = edges.get(step);\n\t\t\t\tif (!edge) {\n\t\t\t\t\treportSessionError({\n\t\t\t\t\t\twaniwani,\n\t\t\t\t\t\tcode: \"agent_failed\",\n\t\t\t\t\t\tcause: \"flow_dead_end\",\n\t\t\t\t\t\tproperties: { node: step },\n\t\t\t\t\t});\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\t\terror: `No edge from step \"${step}\"`,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst nextNode = await resolveNextNode(edge, updatedState);\n\t\t\t\treturn run(nextNode, updatedState, internal);\n\t\t\t}\n\n\t\t\t// Interrupt continue: re-execute from current step.\n\t\t\t// The handler re-runs, filters answered questions, and runs\n\t\t\t// validators if all questions are filled.\n\t\t\treturn run(step, updatedState, internal);\n\t\t}\n\n\t\tif (args.action === \"reset\") {\n\t\t\tif (!sessionId) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror: \"No session ID available for reset action.\",\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tlet flowState: Awaited<ReturnType<typeof store.get>>;\n\t\t\ttry {\n\t\t\t\tflowState = await store.get(sessionId);\n\t\t\t} catch (err) {\n\t\t\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\t\t\treportSessionError({\n\t\t\t\t\twaniwani,\n\t\t\t\t\tcode: \"upstream_failed\",\n\t\t\t\t\tcause: classifyCause({ error: err }),\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror: `Failed to load flow state (session \"${sessionId}\"): ${msg}`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!flowState) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror: `Flow state not found for session \"${sessionId}\". The flow may have completed or expired. Use action \"start\" to begin a new flow.`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!flowState.step) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror:\n\t\t\t\t\t\t\t'This flow has already completed. Use action \"start\" to begin a new flow.',\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!args.stateUpdates || Object.keys(args.stateUpdates).length === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror:\n\t\t\t\t\t\t\t'Missing \"stateUpdates\" for action \"reset\". Include the corrected field(s).',\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst startEdge = edges.get(START);\n\t\t\tif (!startEdge) {\n\t\t\t\treportSessionError({\n\t\t\t\t\twaniwani,\n\t\t\t\t\tcode: \"agent_failed\",\n\t\t\t\t\tcause: \"flow_dead_end\",\n\t\t\t\t\tproperties: { node: START },\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\tcontent: { status: \"error\" as const, error: \"No start edge\" },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst existingState = flowState.state as TState;\n\t\t\tconst mergedState = deepMerge(\n\t\t\t\texistingState as Record<string, unknown>,\n\t\t\t\texpandDotPaths(args.stateUpdates),\n\t\t\t) as TState;\n\n\t\t\tconst internal = flowState.internal ?? {};\n\t\t\tconst firstNode = await resolveNextNode(startEdge, mergedState);\n\t\t\treturn run(firstNode, mergedState, internal);\n\t\t}\n\n\t\treturn {\n\t\t\tcontent: {\n\t\t\t\tstatus: \"error\" as const,\n\t\t\t\terror: `Unknown action: \"${args.action}\"`,\n\t\t\t},\n\t\t};\n\t}\n\n\tconst redactedStateFields = collectRedactedStateFields(\n\t\tconfig.state as Record<string, z.ZodType> | undefined,\n\t);\n\tconst toolConfig = {\n\t\ttitle: config.title,\n\t\tdescription: fullDescription,\n\t\tinputSchema,\n\t\toutputSchema: flowOutputSchema,\n\t\tannotations: config.annotations,\n\t\t...(redactedStateFields.length > 0 && {\n\t\t\t_meta: {\n\t\t\t\t[REDACTED_STATE_UPDATE_FIELDS_META_KEY]: redactedStateFields,\n\t\t\t},\n\t\t}),\n\t};\n\n\tconst toolHandler = (async (args: FlowToolInput, extra: unknown) => {\n\t\tconst requestExtra = extra as RequestHandlerExtra<\n\t\t\tServerRequest,\n\t\t\tServerNotification\n\t\t>;\n\t\tconst _meta: Record<string, unknown> = requestExtra._meta ?? {};\n\t\tconst metaSessionId = extractSessionId(_meta);\n\t\tlet sessionId = metaSessionId ?? args.sessionId;\n\t\t// A session id the caller supplied may already carry flow history; one we\n\t\t// generate below cannot.\n\t\tconst sessionIsPreexisting = Boolean(sessionId);\n\n\t\t// Auto-generate session ID for clients that don't provide one (e.g. Claude Code)\n\t\tif (!sessionId && args.action === \"start\") {\n\t\t\tsessionId = crypto.randomUUID();\n\t\t}\n\n\t\t// Bridge the resolved sessionId into _meta when the transport didn't\n\t\t// supply one (auto-generated on start, or echoed back via args.sessionId\n\t\t// on continue/reset). Without this, downstream tracking, scoped-client,\n\t\t// and source detection see an empty _meta on every turn after start.\n\t\tif (sessionId && !metaSessionId) {\n\t\t\t_meta[\"waniwani/sessionId\"] = sessionId;\n\t\t}\n\n\t\tconst waniwani = extractScopedClient(requestExtra);\n\n\t\tconst result = await handleToolCall(\n\t\t\targs,\n\t\t\tsessionId,\n\t\t\tsessionIsPreexisting,\n\t\t\t_meta,\n\t\t\twaniwani,\n\t\t);\n\n\t\t// Echo sessionId in response when not sourced from _meta (client must pass it back)\n\t\tconst contentObj =\n\t\t\t!metaSessionId && sessionId\n\t\t\t\t? { ...result.content, sessionId }\n\t\t\t\t: result.content;\n\n\t\t// The intro goes on whichever response the engine returns first for a\n\t\t// session, so it lands even when pre-filled state skips the flow's opening\n\t\t// nodes, and taking it off the internal state is what keeps it to once per\n\t\t// conversation. Error responses keep it pending: the assistant may never\n\t\t// surface one to the user, so delivering it there would spend it on a\n\t\t// message nobody reads.\n\t\tconst { value: intro, internal } =\n\t\t\tcontentObj.status === \"error\"\n\t\t\t\t? { value: undefined, internal: result.internal ?? {} }\n\t\t\t\t: takeInternalField(result.internal, \"intro\");\n\n\t\t// `intro` first so it reads as the opening instruction rather than a\n\t\t// trailing detail after the question and its schema.\n\t\tconst payload = intro ? { intro, ...contentObj } : contentObj;\n\n\t\t// Authoritative for the turn: an empty array clears pills an earlier flow\n\t\t// call in the same turn set. Only the single-open-question shorthand\n\t\t// carries a top-level `suggestions`. Set before both return paths below\n\t\t// so every flow tool result — including the state-persistence failure —\n\t\t// carries the key.\n\t\t_meta[SUGGESTIONS_META_KEY] = {\n\t\t\tsuggestions:\n\t\t\t\tcontentObj.status === \"interrupt\" ? (contentObj.suggestions ?? []) : [],\n\t\t} satisfies SuggestionsMeta;\n\n\t\t// Persist flow state under session ID. On completion we store the final\n\t\t// `{ state }` (no `step`) so customers can read the final state until\n\t\t// the KV TTL expires; a stale `continue` falls into the \"already\n\t\t// completed\" branch at the loader since `step` is undefined.\n\t\t// TODO: expose a `deleteOnComplete` compile option for customers who\n\t\t// want the prior behavior (drop the session as soon as END is reached).\n\t\tif (sessionId && result.flowTokenContent) {\n\t\t\tconst tokenContent = withInternalState(result.flowTokenContent, internal);\n\t\t\ttry {\n\t\t\t\tawait store.set(sessionId, tokenContent);\n\t\t\t} catch (err) {\n\t\t\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\t\t\treportSessionError({\n\t\t\t\t\twaniwani,\n\t\t\t\t\tcode: \"upstream_failed\",\n\t\t\t\t\tcause: classifyCause({ error: err }),\n\t\t\t\t\tproperties: { node: result.flowTokenContent.step },\n\t\t\t\t});\n\t\t\t\tconst errorContent = [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: JSON.stringify(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\t\t\terror: `Flow state failed to persist (session \"${sessionId}\"): ${msg}`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t),\n\t\t\t\t\t},\n\t\t\t\t];\n\t\t\t\treturn {\n\t\t\t\t\tcontent: errorContent,\n\t\t\t\t\t_meta,\n\t\t\t\t\tisError: true,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst content = [\n\t\t\t{\n\t\t\t\ttype: \"text\" as const,\n\t\t\t\ttext: JSON.stringify(payload, null, 2),\n\t\t\t},\n\t\t];\n\n\t\t// Attach flow execution path to _meta so it's captured in the tool.called event\n\t\tif (result.nodesVisited?.length) {\n\t\t\t_meta[FLOW_META_KEY] = {\n\t\t\t\tflowId: config.id,\n\t\t\t\tnodesVisited: result.nodesVisited,\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tcontent,\n\t\t\tstructuredContent: payload as Record<string, unknown>,\n\t\t\t_meta,\n\t\t\t...(result.content.status === \"error\" ? { isError: true } : {}),\n\t\t};\n\t}) satisfies ToolCallback<typeof inputSchema>;\n\n\treturn {\n\t\t// MCP-compatible — server.registerTool(flow.name, flow.config, flow.handler)\n\t\tname: config.id,\n\t\tconfig: toolConfig,\n\t\thandler: toolHandler as unknown as FlowToolHandler,\n\n\t\tasync register(server: McpServer): Promise<void> {\n\t\t\tconst configWithGraph = {\n\t\t\t\t...toolConfig,\n\t\t\t\t_meta: { ...toolConfig._meta, _flowGraph: flowGraph },\n\t\t\t};\n\t\t\tserver.registerTool(\n\t\t\t\tconfig.id,\n\t\t\t\tconfigWithGraph as typeof toolConfig,\n\t\t\t\ttoolHandler,\n\t\t\t);\n\t\t},\n\t\tgraph: input.graph,\n\t\tflowGraph,\n\t};\n}\n","// Waniwani SDK - Errors\n\nexport class WaniWaniError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic status: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"WaniWaniError\";\n\t}\n}\n","import { WaniWaniError } from \"../error.js\";\nimport type { InternalConfig } from \"../types.js\";\nimport type {\n\tDocumentExtractInput,\n\tDocumentExtractResult,\n\tDocumentExtractStoredInput,\n\tDocumentSchema,\n\tDocumentsClient,\n} from \"./types.js\";\n\nconst SDK_NAME = \"@waniwani/sdk\";\nconst EXTRACT_PATH = \"/api/mcp/modules/documents/extract\";\n\n/** Method syntax makes the parameter bivariant, so a structural schema is accepted without an assertion. */\ninterface ZodModule {\n\ttoJSONSchema(schema: DocumentSchema<unknown>): Record<string, unknown>;\n}\n\ninterface ExtractWireResponse {\n\tfields: unknown;\n\tpageCount: number;\n\tpageConfidence: number | null;\n\tdocumentId: string;\n}\n\n/**\n * The platform puts the machine-readable code on `message` and the human reason\n * on `detail`, so a refusal reads as \"UNSUPPORTED_DOCUMENT_TYPE: photo.heic is\n * not one of ...\". The raw body is the fallback.\n */\nfunction refusalMessage(body: string, status: number): string {\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(body);\n\t\tif (parsed !== null && typeof parsed === \"object\") {\n\t\t\tconst code =\n\t\t\t\t\"message\" in parsed && typeof parsed.message === \"string\"\n\t\t\t\t\t? parsed.message\n\t\t\t\t\t: \"\";\n\t\t\tconst detail =\n\t\t\t\t\"detail\" in parsed && typeof parsed.detail === \"string\"\n\t\t\t\t\t? parsed.detail\n\t\t\t\t\t: \"\";\n\t\t\tif (code && detail) {\n\t\t\t\treturn `${code}: ${detail}`;\n\t\t\t}\n\t\t\tif (code) {\n\t\t\t\treturn code;\n\t\t\t}\n\t\t\tif (detail) {\n\t\t\t\treturn detail;\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// A non-JSON body is surfaced as-is below.\n\t}\n\treturn body || `Documents API error: HTTP ${status}`;\n}\n\nfunction isStoredInput<T>(\n\tinput: DocumentExtractInput<T>,\n): input is DocumentExtractStoredInput<T> {\n\treturn \"documentId\" in input && typeof input.documentId === \"string\";\n}\n\n/** The platform's body is a union, so each branch's fields must travel alone. */\nfunction sourceFields<T>(\n\tinput: DocumentExtractInput<T>,\n): Record<string, string> {\n\tif (isStoredInput(input)) {\n\t\treturn { documentId: input.documentId };\n\t}\n\treturn { url: input.url, filename: input.filename };\n}\n\nexport function createDocumentsClient(\n\tconfig: Pick<InternalConfig, \"apiUrl\" | \"apiKey\">,\n): DocumentsClient {\n\tconst { apiUrl, apiKey } = config;\n\n\treturn {\n\t\tasync extract<T>(\n\t\t\tinput: DocumentExtractInput<T>,\n\t\t): Promise<DocumentExtractResult<T>> {\n\t\t\tif (!apiKey) {\n\t\t\t\tthrow new Error(\"WANIWANI_API_KEY is not set\");\n\t\t\t}\n\n\t\t\t// zod is an optional peer dependency and this module is reachable from the\n\t\t\t// package root, so a top-level import would break every consumer that only\n\t\t\t// uses tracking.\n\t\t\tconst { toJSONSchema }: ZodModule = await import(\"zod\");\n\n\t\t\tconst response = await fetch(\n\t\t\t\t`${apiUrl.replace(/\\/$/, \"\")}${EXTRACT_PATH}`,\n\t\t\t\t{\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t\"X-WaniWani-SDK\": SDK_NAME,\n\t\t\t\t\t},\n\t\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\t\t...sourceFields(input),\n\t\t\t\t\t\tschema: toJSONSchema(input.schema),\n\t\t\t\t\t\tpages: input.pages,\n\t\t\t\t\t\tsessionId: input.sessionId,\n\t\t\t\t\t\tcorrelationId: input.correlationId,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tif (!response.ok) {\n\t\t\t\tconst text = await response.text().catch(() => \"\");\n\t\t\t\tthrow new WaniWaniError(\n\t\t\t\t\trefusalMessage(text, response.status),\n\t\t\t\t\tresponse.status,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst json = (await response.json()) as { data: ExtractWireResponse };\n\t\t\tconst { fields, pageCount, pageConfidence, documentId } = json.data;\n\n\t\t\treturn {\n\t\t\t\tfields: input.schema.parse(fields),\n\t\t\t\tpageCount,\n\t\t\t\tpageConfidence: pageConfidence ?? null,\n\t\t\t\tdocumentId,\n\t\t\t};\n\t\t},\n\t};\n}\n","import type { RevenueTrackingApi, TrackInput } from \"./@types.js\";\n\n/** Enqueue one event and resolve to its id. */\ntype Emit = (event: TrackInput) => Promise<{ eventId: string }>;\n\n/**\n * Build the flat `track.*` revenue helpers over an `emit` function. Shared\n * by the top-level client and the request-scoped client (which injects meta\n * into `emit`) so both expose the same revenue API (WAN-386).\n *\n * Each helper maps its flat input to a typed first-class revenue event; the\n * remaining tracking context (sessionId / externalUserId / meta) is forwarded\n * as-is. Identity (one of those, or meta-derived) is required by the ingest\n * API — see the warning emitted from the client's `emit`.\n */\nexport function createRevenueApi(emit: Emit): RevenueTrackingApi {\n\treturn {\n\t\tpriceShown: ({ amount, currency, itemId, label, ...context }) =>\n\t\t\temit({\n\t\t\t\tevent: \"price_shown\",\n\t\t\t\tproperties: { amount, currency, itemId, label },\n\t\t\t\t...context,\n\t\t\t}),\n\t\tpricesCompared: ({ options, ...context }) =>\n\t\t\temit({ event: \"prices_compared\", properties: { options }, ...context }),\n\t\toptionSelected: ({ id, amount, currency, ...context }) =>\n\t\t\temit({\n\t\t\t\tevent: \"option_selected\",\n\t\t\t\tproperties: { id, amount, currency },\n\t\t\t\t...context,\n\t\t\t}),\n\t\tleadQualified: (input) => {\n\t\t\tconst { externalId, email, name, ...context } = input ?? {};\n\t\t\treturn emit({\n\t\t\t\tevent: \"lead_qualified\",\n\t\t\t\tproperties: { externalId, email, name },\n\t\t\t\t...context,\n\t\t\t});\n\t\t},\n\t\tconverted: ({ amount, currency, occurredAt, ...context }) =>\n\t\t\temit({\n\t\t\t\tevent: \"converted\",\n\t\t\t\tproperties: { amount, currency, occurredAt },\n\t\t\t\t...context,\n\t\t\t}),\n\t};\n}\n","// Waniwani SDK - Core Types\n\nimport type { DocumentsClient } from \"./documents/types.js\";\nimport type { KbClient } from \"./kb/types.js\";\nimport type { TrackingClient, TrackingConfig } from \"./tracking/@types.js\";\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nexport interface WaniWaniConfig {\n\t/**\n\t * Your MCP environment API key\n\t *\n\t * Defaults to process.env.WANIWANI_API_KEY if not provided\n\t *\n\t * To create one, visit:\n\t * https://app.waniwani.com/mcp/environments\n\t */\n\tapiKey?: string;\n\t/**\n\t * The base URL of the Waniwani API\n\t *\n\t * Defaults to process.env.WANIWANI_API_URL if not provided, then to\n\t * https://app.waniwani.ai. Set WANIWANI_API_URL (e.g. to\n\t * https://eu.app.waniwani.ai) to keep .env as the single source of truth\n\t * and avoid pointing at the wrong region.\n\t */\n\tapiUrl?: string;\n\t/**\n\t * Tracking transport behavior.\n\t */\n\ttracking?: TrackingConfig;\n}\n\n// ============================================================================\n// Client (composed from modules)\n// ============================================================================\n\n/**\n * Waniwani SDK Client\n *\n * Extends with each module:\n * - TrackingClient: track(), flush(), shutdown()\n *\n * Pass this client to framework adapters:\n * - `toNextJsHandler(wani, { ... })` for Next.js route handlers\n */\nexport interface WaniWaniClient extends TrackingClient {\n\t/** @internal Resolved config — used by framework adapters */\n\treadonly _config: InternalConfig;\n\t/** Knowledge base client for ingestion, search, and source listing */\n\treadonly kb: KbClient;\n\t/** Documents client for reading a file into typed fields */\n\treadonly documents: DocumentsClient;\n}\n\n// ============================================================================\n// Internal\n// ============================================================================\n\nexport const DEFAULT_API_URL = \"https://app.waniwani.ai\";\n\nexport interface InternalConfig {\n\tapiUrl: string;\n\tapiKey: string | undefined;\n\ttracking: Required<TrackingConfig>;\n}\n","import type { AttachedDocument } from \"./types.js\";\n\nconst DOCUMENT_ID_KEYS = [\"documentId\", \"document_id\"] as const;\nconst FILENAME_KEYS = [\"filename\", \"file_name\", \"fileName\", \"name\"] as const;\nconst MEDIA_TYPE_KEYS = [\n\t\"mediaType\",\n\t\"media_type\",\n\t\"mimeType\",\n\t\"mime_type\",\n\t\"contentType\",\n\t\"content_type\",\n] as const;\n\nconst UNNAMED_FILENAME = \"document\";\nconst UNKNOWN_MEDIA_TYPE = \"application/octet-stream\";\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction pickFirst(\n\tsource: Record<string, unknown>,\n\tkeys: readonly string[],\n): string | undefined {\n\tfor (const key of keys) {\n\t\tconst value = source[key];\n\t\tif (typeof value === \"string\" && value.length > 0) {\n\t\t\treturn value;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/** Only the id is load-bearing: it is what `extract()` needs, so an entry missing the descriptive two is still usable. */\nfunction toAttachedDocument(entry: unknown): AttachedDocument | undefined {\n\tif (!isRecord(entry)) {\n\t\treturn undefined;\n\t}\n\n\tconst documentId = pickFirst(entry, DOCUMENT_ID_KEYS);\n\tif (!documentId) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\tdocumentId,\n\t\tfilename: pickFirst(entry, FILENAME_KEYS) ?? UNNAMED_FILENAME,\n\t\tmediaType: pickFirst(entry, MEDIA_TYPE_KEYS) ?? UNKNOWN_MEDIA_TYPE,\n\t};\n}\n\nfunction collect(candidate: unknown): AttachedDocument[] {\n\tconst documents: AttachedDocument[] = [];\n\tconst seen = new Set<string>();\n\n\tfor (const entry of Array.isArray(candidate) ? candidate : [candidate]) {\n\t\tconst document = toAttachedDocument(entry);\n\t\tif (document && !seen.has(document.documentId)) {\n\t\t\tseen.add(document.documentId);\n\t\t\tdocuments.push(document);\n\t\t}\n\t}\n\n\treturn documents;\n}\n\nfunction fromLatestUserTurn(messages: unknown): AttachedDocument[] {\n\tif (!Array.isArray(messages)) {\n\t\treturn [];\n\t}\n\n\tconst latest = [...messages]\n\t\t.reverse()\n\t\t.find((message) => isRecord(message) && message.role === \"user\");\n\tif (!isRecord(latest) || !Array.isArray(latest.parts)) {\n\t\treturn [];\n\t}\n\n\treturn collect(\n\t\tlatest.parts.flatMap((part: unknown) =>\n\t\t\tisRecord(part) && isRecord(part.data) ? [part, part.data] : [part],\n\t\t),\n\t);\n}\n\n/**\n * The documents a visitor attached on the latest user turn, for a chat route you\n * run yourself. Pass the parsed request body: the widget sends them in a\n * top-level `documents` field, outside `messages`, so the bytes stay out of the\n * transcript that is reposted on every later turn.\n *\n * ```ts\n * const body = await request.json();\n * for (const { documentId } of readAttachedDocuments(body)) {\n * const { fields } = await wani.documents.extract({ documentId, schema });\n * }\n * ```\n *\n * Also accepts the `documents` array alone, or a `messages` array whose latest\n * user message carries the ids in its parts. Unparseable input yields an empty\n * array; repeated ids collapse to one.\n */\nexport function readAttachedDocuments(input: unknown): AttachedDocument[] {\n\tconst body = isRecord(input) ? input : undefined;\n\n\tconst declared = collect(body ? body.documents : input);\n\tif (declared.length > 0) {\n\t\treturn declared;\n\t}\n\n\treturn fromLatestUserTurn(body ? body.messages : input);\n}\n","/**\n * `_meta` keys shared between the MCP server surface and the browser chat\n * bundle. Kept in `shared/` so both import the same string literal instead of\n * duplicating it, without the chat bundle pulling in server code.\n */\n\n/**\n * Suggested answers for the flow's current step. Every flow tool result\n * carries this key — a non-empty array only when the step has exactly one\n * open question (the only case where a single pill is an unambiguous\n * answer), an empty array otherwise. The chat widget renders a non-empty\n * array as pills and treats the key's presence, empty or not, as\n * authoritative for the turn; other MCP hosts ignore the key.\n */\nexport const SUGGESTIONS_META_KEY = \"waniwani/suggestions\" as const;\n\n/** Shape stored under {@link SUGGESTIONS_META_KEY}. */\nexport type SuggestionsMeta = {\n\tsuggestions: string[];\n};\n","import { readAttachedDocuments } from \"../../documents/messages.js\";\nimport type { AttachedDocument } from \"../../documents/types.js\";\n\n// ============================================================================\n// Meta key extraction helpers\n// ============================================================================\n\n/** Pick the first non-empty string value from `meta` matching the given keys. */\nfunction pickFirst(\n\tmeta: Record<string, unknown>,\n\tkeys: readonly string[],\n): string | undefined {\n\tfor (const key of keys) {\n\t\tconst value = meta[key];\n\t\tif (typeof value === \"string\" && value.length > 0) {\n\t\t\treturn value;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n// --- Key lists (ordered by priority) ---\n\nconst SESSION_ID_KEYS = [\n\t\"waniwani/sessionId\",\n\t\"openai/sessionId\",\n\t\"openai/session\",\n\t\"sessionId\",\n\t\"conversationId\",\n\t\"mcp-session-id\",\n] as const;\n\nconst REQUEST_ID_KEYS = [\n\t\"waniwani/requestId\",\n\t\"openai/requestId\",\n\t\"requestId\",\n\t\"mcp/requestId\",\n] as const;\n\nconst TRACE_ID_KEYS = [\n\t\"waniwani/traceId\",\n\t\"openai/traceId\",\n\t\"traceId\",\n\t\"mcp/traceId\",\n\t\"openai/requestId\",\n\t\"requestId\",\n] as const;\n\nconst EXTERNAL_USER_ID_KEYS = [\n\t\"waniwani/userId\",\n\t\"openai/userId\",\n\t\"externalUserId\",\n\t\"userId\",\n\t\"actorId\",\n] as const;\n\nconst VISITOR_ID_KEYS = [\"waniwani/visitorId\", \"visitorId\"] as const;\n\nconst CORRELATION_ID_KEYS = [\"correlationId\", \"openai/requestId\"] as const;\n\nconst TURN_COUNT_KEYS = [\"waniwani/turnCount\"] as const;\n\nconst FILE_PARAMS_KEYS = [\"waniwani/fileParams\", \"openai/fileParams\"] as const;\n\nconst FILE_URL_KEYS = [\n\t\"url\",\n\t\"uri\",\n\t\"download_url\",\n\t\"downloadUrl\",\n\t\"file_url\",\n\t\"fileUrl\",\n\t\"signed_url\",\n\t\"signedUrl\",\n] as const;\n\nconst FILE_NAME_KEYS = [\"filename\", \"file_name\", \"fileName\", \"name\"] as const;\n\n/** Meta key for flow execution path (nodesVisited, flowId). */\nexport const FLOW_META_KEY = \"waniwani/flow\" as const;\n\nexport type { SuggestionsMeta } from \"../../shared/meta-keys\";\nexport { SUGGESTIONS_META_KEY } from \"../../shared/meta-keys\";\n\n/**\n * Meta key under which `withWaniwani` injects the widget tracking config\n * (`{ endpoint, token, sessionId, source, geoLocation }`) into tool response\n * `_meta`. The frontend client reads it to track without any manual wiring.\n * Older SDKs wrote the same object under the bare `waniwani` key.\n */\nexport const WIDGET_CONFIG_META_KEY = \"waniwani/widget\" as const;\n\n// --- Extractors ---\n\nexport function extractSessionId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, SESSION_ID_KEYS) : undefined;\n}\n\nexport function extractRequestId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, REQUEST_ID_KEYS) : undefined;\n}\n\nexport function extractTraceId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, TRACE_ID_KEYS) : undefined;\n}\n\nexport function extractExternalUserId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, EXTERNAL_USER_ID_KEYS) : undefined;\n}\n\nexport function extractVisitorId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, VISITOR_ID_KEYS) : undefined;\n}\n\nexport function extractCorrelationId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, CORRELATION_ID_KEYS) : undefined;\n}\n\n/**\n * Number of user messages in the current chat session, as counted by the\n * Waniwani app before dispatching the MCP request. Useful for MCPs that\n * gate behavior on conversation length (e.g. compulsory email verification\n * after N turns) without each MCP having to track turn state itself.\n *\n * Forwarded as `waniwani/turnCount` (non-negative integer).\n */\nexport function extractTurnCount(\n\tmeta: Record<string, unknown> | undefined,\n): number | undefined {\n\tif (!meta) {\n\t\treturn undefined;\n\t}\n\tfor (const key of TURN_COUNT_KEYS) {\n\t\tconst value = meta[key];\n\t\tif (typeof value === \"number\" && Number.isFinite(value) && value >= 0) {\n\t\t\treturn value;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/** A file the host bound to this tool call, resolved to something fetchable. */\nexport interface AttachedFile {\n\turl: string;\n\tfilename: string;\n}\n\nexport type { AttachedDocument };\n\nfunction isRecordValue(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction fetchableUrl(value: unknown): string | undefined {\n\tif (typeof value !== \"string\" || value.length === 0) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\tconst url = new URL(value);\n\t\treturn url.protocol === \"https:\" || url.protocol === \"http:\"\n\t\t\t? value\n\t\t\t: undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction filenameFromUrl(url: string): string {\n\ttry {\n\t\tconst segments = new URL(url).pathname.split(\"/\").filter(Boolean);\n\t\tconst last = segments[segments.length - 1];\n\t\treturn last ? decodeURIComponent(last) : \"document\";\n\t} catch {\n\t\treturn \"document\";\n\t}\n}\n\nfunction toAttachedFile(entry: unknown): AttachedFile | undefined {\n\tif (!isRecordValue(entry)) {\n\t\treturn undefined;\n\t}\n\n\tconst url = fetchableUrl(pickFirst(entry, FILE_URL_KEYS));\n\tif (!url) {\n\t\treturn undefined;\n\t}\n\n\tconst filename = pickFirst(entry, FILE_NAME_KEYS);\n\treturn { url, filename: filename ?? filenameFromUrl(url) };\n}\n\n/**\n * Files the host attached to this tool call, from the first of\n * `waniwani/fileParams` or `openai/fileParams` (ChatGPT) that carries any. The\n * container may be a list or an object keyed by parameter name, and entries name\n * their URL differently per host, so every plausible spelling is read and\n * anything without a fetchable URL is skipped.\n */\nexport function extractAttachedFiles(\n\tmeta: Record<string, unknown> | undefined,\n): AttachedFile[] {\n\tif (!meta) {\n\t\treturn [];\n\t}\n\n\tfor (const key of FILE_PARAMS_KEYS) {\n\t\tconst files = filesUnder(meta[key]);\n\t\tif (files.length > 0) {\n\t\t\treturn files;\n\t\t}\n\t}\n\n\treturn [];\n}\n\nfunction filesUnder(candidate: unknown): AttachedFile[] {\n\tconst files: AttachedFile[] = [];\n\tconst seen = new Set<string>();\n\n\tconst collect = (value: unknown): void => {\n\t\tif (Array.isArray(value)) {\n\t\t\tfor (const entry of value) {\n\t\t\t\tcollect(entry);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tconst file = toAttachedFile(value);\n\t\tif (file) {\n\t\t\tif (!seen.has(file.url)) {\n\t\t\t\tseen.add(file.url);\n\t\t\t\tfiles.push(file);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (isRecordValue(value)) {\n\t\t\tfor (const entry of Object.values(value)) {\n\t\t\t\tcollect(entry);\n\t\t\t}\n\t\t}\n\t};\n\n\tcollect(candidate);\n\treturn files;\n}\n\nconst DOCUMENTS_META_KEY = \"waniwani/documents\";\n\n/**\n * Documents the visitor attached to this chat turn, stamped by the Waniwani chat\n * route under `waniwani/documents`. The bytes stay on the platform, so each entry\n * is a handle: spread one straight into `documents.extract({ documentId, schema })`.\n * Empty on hosts that upload nothing, which is every host but the web widget.\n */\nexport function extractAttachedDocuments(\n\tmeta: Record<string, unknown> | undefined,\n): AttachedDocument[] {\n\tif (!meta) {\n\t\treturn [];\n\t}\n\tconst raw = meta[DOCUMENTS_META_KEY];\n\treturn readAttachedDocuments(Array.isArray(raw) ? raw : [raw]);\n}\n\nconst SOURCE_SESSION_KEYS = [\n\t{ key: \"openai/sessionId\", source: \"chatgpt\" },\n\t{ key: \"openai/session\", source: \"chatgpt\" },\n] as const;\n\n/**\n * Client identifiers advertised via MCP `initialize` → `clientInfo.name` that\n * the SDK maps to a known source. Match is case-insensitive substring on the\n * advertised name, so this catches \"Claude\", \"Claude Code\", \"claude-ai\", etc.\n * without having to enumerate every surface.\n *\n * ChatGPT is primarily identified by its `openai/*` session key (see\n * SOURCE_SESSION_KEYS), but a session without that key still resolves here from\n * the advertised client name so assistant traffic is attributed consistently.\n * First match wins, so order the needles most-specific first.\n */\nconst CLIENT_INFO_NAME_SOURCES: ReadonlyArray<{\n\tneedle: string;\n\tsource: string;\n}> = [\n\t{ needle: \"claude\", source: \"claude\" },\n\t{ needle: \"chatgpt\", source: \"chatgpt\" },\n\t{ needle: \"openai\", source: \"chatgpt\" },\n\t{ needle: \"gemini\", source: \"gemini\" },\n];\n\nexport type ExtractSourceClientInfo = {\n\tname?: string;\n\tversion?: string;\n};\n\nexport function extractSource(\n\tmeta: Record<string, unknown> | undefined,\n\tclientInfo?: ExtractSourceClientInfo,\n): string | undefined {\n\tif (meta) {\n\t\tconst explicit = meta[\"waniwani/source\"];\n\t\tif (typeof explicit === \"string\" && explicit.length > 0) {\n\t\t\treturn explicit;\n\t\t}\n\t\tfor (const { key, source } of SOURCE_SESSION_KEYS) {\n\t\t\tconst value = meta[key];\n\t\t\tif (typeof value === \"string\" && value.length > 0) {\n\t\t\t\treturn source;\n\t\t\t}\n\t\t}\n\t}\n\t// Fall back to MCP `initialize` clientInfo.name when no _meta key matches.\n\t// Claude surfaces (Code, Desktop, claude.ai connector, Anthropic API MCP\n\t// connector) don't expose a namespaced session id in _meta but do advertise\n\t// themselves in the handshake. Per-MCP-session clientInfo is captured by the\n\t// MCP transport and exposed via server.getClientVersion().\n\tconst name = clientInfo?.name;\n\tif (typeof name === \"string\" && name.length > 0) {\n\t\tconst lower = name.toLowerCase();\n\t\tfor (const { needle, source } of CLIENT_INFO_NAME_SOURCES) {\n\t\t\tif (lower.includes(needle)) {\n\t\t\t\treturn source;\n\t\t\t}\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * HTTP request headers that identify a known caller when neither `_meta` nor\n * `clientInfo` carries a source. This is the most robust Claude signal: every\n * Claude surface sends `User-Agent: Claude-User` and `x-anthropic-client` on\n * MCP HTTP requests, whereas the MCP transport carries no session id and the\n * `initialize` `clientInfo` may not be surfaced on stateless deployments.\n */\nfunction headerValue(\n\theaders: Record<string, unknown>,\n\tkey: string,\n): string | undefined {\n\t// HTTP header names are case-insensitive; the transport may preserve the\n\t// original casing, so match the key case-insensitively rather than assuming\n\t// it was lowercased.\n\tconst lowerKey = key.toLowerCase();\n\tlet raw = headers[key] ?? headers[lowerKey];\n\tif (raw === undefined) {\n\t\tfor (const headerKey of Object.keys(headers)) {\n\t\t\tif (headerKey.toLowerCase() === lowerKey) {\n\t\t\t\traw = headers[headerKey];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\tconst value = Array.isArray(raw) ? raw[0] : raw;\n\treturn typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nexport function extractSourceFromHeaders(\n\theaders: Record<string, unknown> | undefined,\n): string | undefined {\n\tif (!headers) {\n\t\treturn undefined;\n\t}\n\tconst userAgent = headerValue(headers, \"user-agent\");\n\tconst anthropicClient = headerValue(headers, \"x-anthropic-client\");\n\tif (\n\t\t(userAgent && /claude/i.test(userAgent)) ||\n\t\t(anthropicClient && /claude|anthropic/i.test(anthropicClient))\n\t) {\n\t\treturn \"claude\";\n\t}\n\t// ChatGPT / OpenAI surfaces without an `openai/*` session key still send an\n\t// OpenAI user-agent (e.g. `ChatGPT-User`), so attribute them here too.\n\tif (userAgent && /chatgpt|openai/i.test(userAgent)) {\n\t\treturn \"chatgpt\";\n\t}\n\treturn undefined;\n}\n","import { createDocumentsClient } from \"../../documents/client.js\";\nimport type {\n\tDocumentExtractInput,\n\tDocumentExtractResult,\n\tDocumentsClient,\n} from \"../../documents/types.js\";\nimport type { KbClient } from \"../../kb/types.js\";\nimport type {\n\tCallableTrack,\n\tTrackFn,\n\tTrackInput,\n\tTrackingClient,\n} from \"../../tracking/@types.js\";\nimport { createRevenueApi } from \"../../tracking/revenue.js\";\nimport { DEFAULT_API_URL } from \"../../types.js\";\nimport {\n\ttype AttachedDocument,\n\ttype AttachedFile,\n\textractAttachedDocuments,\n\textractAttachedFiles,\n\textractCorrelationId,\n\textractExternalUserId,\n\textractSessionId,\n\textractVisitorId,\n} from \"./utils.js\";\n\n/**\n * Well-known key used to attach the scoped client to the MCP `extra` object.\n * Read by `createTool` and flow compilation to surface it in handler contexts.\n */\nexport const SCOPED_CLIENT_KEY = \"waniwani/client\";\n\n/**\n * A request-scoped Waniwani client with meta pre-attached.\n *\n * Available as `context.waniwani` inside `createTool` handlers and flow nodes\n * when the server is wrapped with `withWaniwani()`.\n */\nexport interface ScopedWaniWaniClient {\n\t/**\n\t * The session id this request's events correlate to, resolved from the\n\t * request meta. Read it to link the session to your own records (store it\n\t * on a lead, then send an off-platform `converted` with the same\n\t * `sessionId` later). `undefined` when the host provided no session id.\n\t */\n\treadonly sessionId?: string;\n\t/**\n\t * The anonymous visitor id, resolved from the request meta. Present on the\n\t * web chat surface (a persisted per-device id threaded through as\n\t * `waniwani/visitorId`); `undefined` on hosts that do not supply one, such\n\t * as Claude. Ingest accepts it as an identity in its own right, so use it\n\t * to correlate events before a session or user id exists.\n\t */\n\treadonly visitorId?: string;\n\t/**\n\t * The external user id, resolved from the request meta (e.g. `openai/userId`\n\t * for ChatGPT's anonymous users, or a host-provided `externalUserId`). Read\n\t * it alongside `sessionId`/`visitorId` — whichever the current host provides.\n\t * `undefined` when the host supplied no user id.\n\t */\n\treadonly externalUserId?: string;\n\t/**\n\t * Track an event — request meta is automatically merged. Also exposes the\n\t * revenue helpers flat (`track.priceShown()`, `track.converted()`, …), which\n\t * inherit the same scoped meta (so identity is carried from the request).\n\t */\n\ttrack: TrackFn;\n\t/** Identify a user — request meta is automatically merged. */\n\tidentify(\n\t\tuserId: string,\n\t\tproperties?: Record<string, unknown>,\n\t): Promise<{ eventId: string }>;\n\t/** Knowledge base client (no meta needed). */\n\treadonly kb: KbClient;\n\t/**\n\t * Files the host attached to this tool call, each with a fetchable `url` and\n\t * a `filename` — spread one straight into `documents.extract()`. Empty on\n\t * hosts that attach nothing.\n\t */\n\treadonly attachedFiles: AttachedFile[];\n\t/**\n\t * Documents the visitor uploaded through the chat widget on this turn. The\n\t * platform holds the bytes, so each entry is a handle: spread one straight\n\t * into `documents.extract({ documentId, schema })`. Empty on hosts that\n\t * upload nothing.\n\t */\n\treadonly attachedDocuments: AttachedDocument[];\n\t/** Documents client; `sessionId` and `correlationId` are carried from the request. */\n\treadonly documents: DocumentsClient;\n\t/** @internal Resolved API config from withWaniwani(). */\n\treadonly _config?: { apiUrl?: string; apiKey?: string };\n}\n\n/**\n * Extract the scoped client from the MCP `extra` object.\n * Returns undefined if `withWaniwani()` is not wrapping the server.\n */\nexport function extractScopedClient(\n\textra: unknown,\n): ScopedWaniWaniClient | undefined {\n\tif (typeof extra === \"object\" && extra !== null) {\n\t\treturn (extra as Record<string, unknown>)[SCOPED_CLIENT_KEY] as\n\t\t\t| ScopedWaniWaniClient\n\t\t\t| undefined;\n\t}\n\treturn undefined;\n}\n\nexport function createScopedClient(\n\tbase: { track: CallableTrack; identify: TrackingClient[\"identify\"] } & {\n\t\treadonly kb: KbClient;\n\t},\n\tmeta: Record<string, unknown>,\n\tconfig?: { apiUrl?: string; apiKey?: string },\n): ScopedWaniWaniClient {\n\tconst trackOnce = (event: TrackInput): Promise<{ eventId: string }> =>\n\t\tbase.track({ ...event, meta: { ...meta, ...event.meta } });\n\n\tconst sessionId = extractSessionId(meta);\n\tconst correlationId = extractCorrelationId(meta);\n\tconst documents = createDocumentsClient({\n\t\tapiUrl: config?.apiUrl ?? process.env.WANIWANI_API_URL ?? DEFAULT_API_URL,\n\t\tapiKey: config?.apiKey,\n\t});\n\n\treturn {\n\t\tsessionId,\n\t\tvisitorId: extractVisitorId(meta),\n\t\texternalUserId: extractExternalUserId(meta),\n\t\ttrack: Object.assign(trackOnce, createRevenueApi(trackOnce)),\n\t\tidentify(userId, properties) {\n\t\t\treturn base.identify(userId, properties, meta);\n\t\t},\n\t\tkb: base.kb,\n\t\tattachedFiles: extractAttachedFiles(meta),\n\t\tattachedDocuments: extractAttachedDocuments(meta),\n\t\tdocuments: {\n\t\t\textract<T>(\n\t\t\t\tinput: DocumentExtractInput<T>,\n\t\t\t): Promise<DocumentExtractResult<T>> {\n\t\t\t\treturn documents.extract({\n\t\t\t\t\t...input,\n\t\t\t\t\tsessionId: input.sessionId ?? sessionId,\n\t\t\t\t\tcorrelationId: input.correlationId ?? correlationId,\n\t\t\t\t});\n\t\t\t},\n\t\t},\n\t\t_config: config,\n\t};\n}\n","import { z } from \"zod\";\nimport type { ErrorCauseType } from \"../../../tracking/@types.js\";\n\nconst statusCarrierSchema = z.object({\n\tstatus: z.number().optional(),\n\tstatusCode: z.number().optional(),\n});\n\nfunction readStatus(error: unknown): number | undefined {\n\tconst parsed = statusCarrierSchema.safeParse(error);\n\tif (!parsed.success) {\n\t\treturn undefined;\n\t}\n\treturn parsed.data.status ?? parsed.data.statusCode;\n}\n\nconst NETWORK_ERROR_MESSAGES = [\n\t\"fetch failed\",\n\t\"failed to fetch\",\n\t\"networkerror when attempting to fetch\",\n];\n\nexport function classifyCause({ error }: { error: unknown }): ErrorCauseType {\n\t// zod is an optional peer dep; a nested copy fails instanceof across realms.\n\tif (error instanceof Error && error.name === \"ZodError\") {\n\t\treturn \"invalid_output\";\n\t}\n\n\tif (\n\t\terror instanceof Error &&\n\t\t(error.name === \"AbortError\" || error.name === \"TimeoutError\")\n\t) {\n\t\treturn \"timeout\";\n\t}\n\n\tconst status = readStatus(error);\n\tif (status === 429) {\n\t\treturn \"rate_limited\";\n\t}\n\tif (status !== undefined && status >= 500) {\n\t\treturn \"upstream_5xx\";\n\t}\n\tif (status !== undefined && status >= 400) {\n\t\treturn \"upstream_4xx\";\n\t}\n\n\tif (\n\t\terror instanceof TypeError &&\n\t\tNETWORK_ERROR_MESSAGES.some((message) =>\n\t\t\terror.message.toLowerCase().includes(message),\n\t\t)\n\t) {\n\t\treturn \"network\";\n\t}\n\n\treturn \"unknown\";\n}\n","import type {\n\tErrorCauseType,\n\tSessionErrorCodeType,\n\tSessionErrorProperties,\n} from \"../../../tracking/@types.js\";\nimport type { ScopedWaniWaniClient } from \"../scoped-client.js\";\n\n/** Fire-and-forget: never throws, so a failing caller keeps its own error. */\nexport function reportSessionError({\n\twaniwani,\n\tcode,\n\tcause,\n\tproperties,\n}: {\n\twaniwani?: ScopedWaniWaniClient;\n\tcode: SessionErrorCodeType;\n\tcause: ErrorCauseType;\n\tproperties?: Omit<SessionErrorProperties, \"code\" | \"cause\">;\n}): void {\n\t// Hosts patch console; a logger that throws must not cost us the event either.\n\ttry {\n\t\tconsole.error(\n\t\t\t`[waniwani][session-error] ${code}:${properties?.tool ?? \"-\"}:${cause}`,\n\t\t\t{ node: properties?.node },\n\t\t);\n\t} catch {}\n\n\tif (!waniwani) {\n\t\treturn;\n\t}\n\n\t// track() is a public interface: it can throw synchronously or return a non-promise.\n\ttry {\n\t\tvoid Promise.resolve(\n\t\t\twaniwani.track({\n\t\t\t\tevent: \"session.error\",\n\t\t\t\tproperties: { code, cause, ...properties },\n\t\t\t}),\n\t\t).catch(() => {});\n\t} catch {}\n}\n","import type { z } from \"zod\";\n\n// ============================================================================\n// Zod object schema detection\n// ============================================================================\n\n/** Check whether a Zod schema is a `z.object(...)`. */\nexport function isObjectSchema(schema: z.ZodType): boolean {\n\tconst def = (schema as unknown as { _zod?: { def?: { type?: string } } })._zod\n\t\t?.def;\n\treturn def?.type === \"object\";\n}\n\n/** Extract the shape of a `z.object(...)` schema, or null if not an object. */\nexport function getObjectShape(\n\tschema: z.ZodType,\n): Record<string, z.ZodType> | null {\n\tconst def = (\n\t\tschema as unknown as {\n\t\t\t_zod?: { def?: { type?: string; shape?: Record<string, z.ZodType> } };\n\t\t}\n\t)._zod?.def;\n\tif (def?.type === \"object\" && def.shape) {\n\t\treturn def.shape;\n\t}\n\treturn null;\n}\n\n// ============================================================================\n// Dot-path value access\n// ============================================================================\n\n/** Resolve a dot-path like `\"driver.name\"` to its value in a nested object. */\nexport function getNestedValue(\n\tobj: Record<string, unknown>,\n\tpath: string,\n): unknown {\n\tconst parts = path.split(\".\");\n\tlet current: unknown = obj;\n\tfor (const part of parts) {\n\t\tif (current == null || typeof current !== \"object\") {\n\t\t\treturn undefined;\n\t\t}\n\t\tcurrent = (current as Record<string, unknown>)[part];\n\t}\n\treturn current;\n}\n\n/** Set a value at a dot-path, creating intermediate objects as needed. */\nexport function setNestedValue(\n\tobj: Record<string, unknown>,\n\tpath: string,\n\tvalue: unknown,\n): void {\n\tconst parts = path.split(\".\");\n\tconst lastKey = parts.pop();\n\tif (!lastKey) {\n\t\treturn;\n\t}\n\tlet current = obj;\n\tfor (const part of parts) {\n\t\tif (\n\t\t\tcurrent[part] == null ||\n\t\t\ttypeof current[part] !== \"object\" ||\n\t\t\tArray.isArray(current[part])\n\t\t) {\n\t\t\tcurrent[part] = {};\n\t\t}\n\t\tcurrent = current[part] as Record<string, unknown>;\n\t}\n\tcurrent[lastKey] = value;\n}\n\n/** Delete a value at a dot-path. Only removes the leaf key. */\nexport function deleteNestedValue(\n\tobj: Record<string, unknown>,\n\tpath: string,\n): void {\n\tconst parts = path.split(\".\");\n\tconst lastKey = parts.pop();\n\tif (!lastKey) {\n\t\treturn;\n\t}\n\tlet current: unknown = obj;\n\tfor (const part of parts) {\n\t\tif (current == null || typeof current !== \"object\") {\n\t\t\treturn;\n\t\t}\n\t\tcurrent = (current as Record<string, unknown>)[part];\n\t}\n\tif (current != null && typeof current === \"object\") {\n\t\tdelete (current as Record<string, unknown>)[lastKey];\n\t}\n}\n\n// ============================================================================\n// State merging\n// ============================================================================\n\n/**\n * Expand dot-path keys into nested objects.\n * `{ \"driver.name\": \"John\", \"email\": \"a@b.com\" }` → `{ driver: { name: \"John\" }, email: \"a@b.com\" }`\n *\n * Mixed input where a dot-key and its nested sibling share a parent\n * (e.g. `{ \"driver.name\": \"John\", driver: { license: \"ABC\" } }`) is deep-merged\n * regardless of key order, so neither side clobbers the other.\n */\nexport function expandDotPaths(\n\tflat: Record<string, unknown>,\n): Record<string, unknown> {\n\tlet result: Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(flat)) {\n\t\tif (key.includes(\".\")) {\n\t\t\tsetNestedValue(result, key, value);\n\t\t} else if (\n\t\t\tvalue !== null &&\n\t\t\ttypeof value === \"object\" &&\n\t\t\t!Array.isArray(value)\n\t\t) {\n\t\t\tresult = deepMerge(result, { [key]: value as Record<string, unknown> });\n\t\t} else {\n\t\t\tresult[key] = value;\n\t\t}\n\t}\n\treturn result;\n}\n\n/**\n * Deep-merge source into target. Preserves existing nested keys.\n * Only merges plain objects — arrays and primitives are overwritten.\n */\nexport function deepMerge(\n\ttarget: Record<string, unknown>,\n\tsource: Record<string, unknown>,\n): Record<string, unknown> {\n\tconst result = { ...target };\n\tfor (const [key, value] of Object.entries(source)) {\n\t\tif (\n\t\t\tvalue !== null &&\n\t\t\ttypeof value === \"object\" &&\n\t\t\t!Array.isArray(value) &&\n\t\t\tresult[key] !== null &&\n\t\t\ttypeof result[key] === \"object\" &&\n\t\t\t!Array.isArray(result[key])\n\t\t) {\n\t\t\tresult[key] = deepMerge(\n\t\t\t\tresult[key] as Record<string, unknown>,\n\t\t\t\tvalue as Record<string, unknown>,\n\t\t\t);\n\t\t} else {\n\t\t\tresult[key] = value;\n\t\t}\n\t}\n\treturn result;\n}\n","import type { z } from \"zod\";\nimport { getObjectShape } from \"./nested\";\n\n/**\n * Compact, JSON-serializable schema fragment for a single state field.\n * Emitted inside interrupt/widget responses so the LLM gets focused, just-in-time\n * guidance for the field it is being asked to write — without needing to consult\n * a global schema dump.\n */\nexport type FieldSchemaInfo = {\n\ttype:\n\t\t| \"enum\"\n\t\t| \"string\"\n\t\t| \"number\"\n\t\t| \"boolean\"\n\t\t| \"object\"\n\t\t| \"array\"\n\t\t| \"unknown\";\n\t/** Allowed values for enum fields. */\n\tvalues?: string[];\n\t/** Field description from `.describe()` (or the parent group description if applicable). */\n\tdescription?: string;\n\t/** True when the underlying schema is optional/nullable/has a default. */\n\toptional?: boolean;\n};\n\ntype ZodDef = {\n\ttype?: string;\n\tentries?: Record<string, string>;\n\tinnerType?: z.ZodType;\n};\n\ntype ZodInternals = {\n\t_zod?: {\n\t\tdef?: ZodDef;\n\t};\n};\n\nfunction readDef(schema: z.ZodType): ZodDef | undefined {\n\treturn (schema as unknown as ZodInternals)._zod?.def;\n}\n\n/**\n * Unwrap optional/nullable/default wrappers to reach the underlying schema.\n * Returns whether at least one wrapper marked the field as optional from the\n * caller's perspective (optional/nullable/default all mean the LLM may omit it).\n */\nfunction unwrapOptional(schema: z.ZodType): {\n\tinner: z.ZodType;\n\toptional: boolean;\n} {\n\tlet current = schema;\n\tlet optional = false;\n\tfor (let i = 0; i < 8; i++) {\n\t\tconst def = readDef(current);\n\t\tif (!def?.innerType) {\n\t\t\tbreak;\n\t\t}\n\t\tif (\n\t\t\tdef.type === \"optional\" ||\n\t\t\tdef.type === \"nullable\" ||\n\t\t\tdef.type === \"default\"\n\t\t) {\n\t\t\toptional = true;\n\t\t\tcurrent = def.innerType;\n\t\t\tcontinue;\n\t\t}\n\t\tbreak;\n\t}\n\treturn { inner: current, optional };\n}\n\nexport function introspectField(schema: z.ZodType): FieldSchemaInfo {\n\tconst { inner, optional } = unwrapOptional(schema);\n\tconst description = inner.description ?? schema.description ?? undefined;\n\tconst def = readDef(inner);\n\tconst t = def?.type;\n\n\tconst base: FieldSchemaInfo = {\n\t\ttype: \"unknown\",\n\t\t...(description ? { description } : {}),\n\t\t...(optional ? { optional: true } : {}),\n\t};\n\n\tif (t === \"enum\" && def?.entries) {\n\t\treturn { ...base, type: \"enum\", values: Object.keys(def.entries) };\n\t}\n\tif (\n\t\tt === \"string\" ||\n\t\tt === \"number\" ||\n\t\tt === \"boolean\" ||\n\t\tt === \"object\" ||\n\t\tt === \"array\"\n\t) {\n\t\treturn { ...base, type: t };\n\t}\n\treturn base;\n}\n\n/**\n * Resolve a field path (flat key or `parent.child` dot-path) to its schema info,\n * using the flow's declared state schema. Returns `undefined` when the path is\n * not declared — the engine then simply omits `fieldSchema` from the response.\n */\nexport function resolveFieldSchema(\n\tstate: Record<string, z.ZodType> | undefined,\n\tfieldPath: string,\n): FieldSchemaInfo | undefined {\n\tif (!state || !fieldPath) {\n\t\treturn undefined;\n\t}\n\tif (fieldPath.includes(\".\")) {\n\t\tconst dot = fieldPath.indexOf(\".\");\n\t\tconst parent = fieldPath.slice(0, dot);\n\t\tconst child = fieldPath.slice(dot + 1);\n\t\tconst parentSchema = state[parent];\n\t\tif (!parentSchema) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst shape = getObjectShape(parentSchema);\n\t\tconst childSchema = shape?.[child];\n\t\tif (!childSchema) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn introspectField(childSchema);\n\t}\n\tconst schema = state[fieldPath];\n\tif (!schema) {\n\t\treturn undefined;\n\t}\n\treturn introspectField(schema);\n}\n","import type { z } from \"zod\";\nimport type { ScopedWaniWaniClient } from \"../scoped-client\";\nimport { classifyCause } from \"../session-errors/classify\";\nimport { reportSessionError } from \"../session-errors/report\";\nimport type {\n\tEdge,\n\tExecutionResult,\n\tInterruptQuestionData,\n\tMaybePromise,\n\tNodeHandler,\n\tNodeOptions,\n\tNodeVisit,\n} from \"./@types\";\nimport { END, interrupt, isInterrupt, isWidget, showWidget } from \"./@types\";\nimport { resolveFieldSchema } from \"./field-schema\";\nimport { deepMerge, deleteNestedValue, getNestedValue } from \"./nested\";\n\nexport type StateSchema = Record<string, z.ZodType>;\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Check whether a state value counts as \"filled\" (not empty/missing). */\nexport function isFilled(v: unknown): boolean {\n\treturn v !== undefined && v !== null && v !== \"\";\n}\n\nexport type ValidateFn = (\n\tvalue: unknown,\n\t// biome-ignore lint/suspicious/noConfusingVoidType: void needed for async () => {} validators\n) => MaybePromise<Record<string, unknown> | void>;\n\n// ============================================================================\n// Edge resolution\n// ============================================================================\n\nexport async function resolveNextNode<TState extends Record<string, unknown>>(\n\tedge: Edge<TState>,\n\tstate: Partial<TState>,\n): Promise<string> {\n\tif (edge.type === \"direct\") {\n\t\treturn edge.to;\n\t}\n\treturn edge.condition(state);\n}\n\n// ============================================================================\n// Interrupt result builder\n// ============================================================================\n\n/**\n * Build an interrupt ExecutionResult from a list of questions and current state.\n * Filters out already-answered questions and caches the full question list in\n * flowMeta so partial-answer continues can filter without re-executing the handler.\n *\n * Returns `null` when all questions are already filled (caller should advance).\n */\nexport function buildInterruptResult<TState extends Record<string, unknown>>(\n\tquestions: InterruptQuestionData[],\n\tcontext: string | undefined,\n\tcurrentNode: string,\n\tstate: TState,\n\tstateSchema?: StateSchema,\n): ExecutionResult | null {\n\t// All filled — caller should advance to the next node\n\tif (\n\t\tquestions.every((q) =>\n\t\t\tisFilled(getNestedValue(state as Record<string, unknown>, q.field)),\n\t\t)\n\t) {\n\t\treturn null;\n\t}\n\n\t// Filter out questions whose fields are already answered, and attach JIT\n\t// schema fragments so the LLM sees the field's type/values/description at\n\t// the moment it needs to fill it.\n\tconst unanswered = questions\n\t\t.filter(\n\t\t\t(q) =>\n\t\t\t\t!isFilled(getNestedValue(state as Record<string, unknown>, q.field)),\n\t\t)\n\t\t.map((q) => {\n\t\t\tconst fieldSchema = resolveFieldSchema(stateSchema, q.field);\n\t\t\treturn fieldSchema ? { ...q, fieldSchema } : q;\n\t\t});\n\n\t// Single-question shorthand: unwrap for cleaner AI payload\n\tconst isSingle = unanswered.length === 1;\n\tconst q0 = unanswered[0];\n\tconst payload =\n\t\tisSingle && q0\n\t\t\t? {\n\t\t\t\t\tstatus: \"interrupt\" as const,\n\t\t\t\t\tquestion: q0.question,\n\t\t\t\t\tfield: q0.field,\n\t\t\t\t\t...(q0.suggestions ? { suggestions: q0.suggestions } : {}),\n\t\t\t\t\t...(q0.fieldSchema ? { fieldSchema: q0.fieldSchema } : {}),\n\t\t\t\t\t...(q0.context || context ? { context: q0.context ?? context } : {}),\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tstatus: \"interrupt\" as const,\n\t\t\t\t\tquestions: unanswered,\n\t\t\t\t\t...(context ? { context } : {}),\n\t\t\t\t};\n\n\treturn {\n\t\tcontent: payload,\n\t\tflowTokenContent: {\n\t\t\tstep: currentNode,\n\t\t\tstate,\n\t\t\t...(isSingle && q0 ? { field: q0.field } : {}),\n\t\t},\n\t};\n}\n\n// ============================================================================\n// Execution engine\n// ============================================================================\n\nexport async function executeFrom<TState extends Record<string, unknown>>(\n\tstartNodeName: string,\n\tstartState: TState,\n\tnodes: Map<string, NodeHandler<TState>>,\n\tedges: Map<string, Edge<TState>>,\n\tvalidators: Map<string, ValidateFn>,\n\tmeta?: Record<string, unknown>,\n\twaniwani?: ScopedWaniWaniClient,\n\tnodeOptions?: Map<string, NodeOptions>,\n\tstateSchema?: StateSchema,\n): Promise<ExecutionResult> {\n\tlet currentNode = startNodeName;\n\tlet state = { ...startState };\n\tconst nodesVisited: NodeVisit[] = [];\n\n\t// Safety limit to prevent infinite loops\n\tconst MAX_ITERATIONS = 50;\n\tlet iterations = 0;\n\n\twhile (iterations++ < MAX_ITERATIONS) {\n\t\t// Reached END\n\t\tif (currentNode === END) {\n\t\t\treturn {\n\t\t\t\tcontent: { status: \"complete\" },\n\t\t\t\tflowTokenContent: { state },\n\t\t\t\tnodesVisited,\n\t\t\t};\n\t\t}\n\n\t\tconst handler = nodes.get(currentNode);\n\t\tif (!handler) {\n\t\t\treportSessionError({\n\t\t\t\twaniwani,\n\t\t\t\tcode: \"agent_failed\",\n\t\t\t\tcause: \"flow_unknown_node\",\n\t\t\t\tproperties: { node: currentNode },\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tcontent: {\n\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\terror: `Unknown node: \"${currentNode}\"`,\n\t\t\t\t},\n\t\t\t\tnodesVisited,\n\t\t\t};\n\t\t}\n\n\t\tif (!nodeOptions?.get(currentNode)?.hideFromFunnel) {\n\t\t\tnodesVisited.push({ id: currentNode, at: new Date().toISOString() });\n\t\t}\n\n\t\ttry {\n\t\t\t// Build context object for the handler\n\t\t\tconst ctx = {\n\t\t\t\tstate,\n\t\t\t\tmeta,\n\t\t\t\tinterrupt: interrupt as NodeHandler<TState> extends never\n\t\t\t\t\t? never\n\t\t\t\t\t: typeof interrupt,\n\t\t\t\tshowWidget: showWidget as NodeHandler<TState> extends never\n\t\t\t\t\t? never\n\t\t\t\t\t: typeof showWidget,\n\t\t\t\twaniwani,\n\t\t\t};\n\t\t\tconst result = await handler(ctx as Parameters<typeof handler>[0]);\n\n\t\t\t// Interrupt signal — pause and ask the user one or more questions\n\t\t\tif (isInterrupt(result)) {\n\t\t\t\t// Extract and store any validate functions from the interrupt questions\n\t\t\t\tfor (const q of result.questions) {\n\t\t\t\t\tif (q.validate) {\n\t\t\t\t\t\tvalidators.set(`${currentNode}:${q.field}`, q.validate);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst interruptResult = buildInterruptResult(\n\t\t\t\t\tresult.questions,\n\t\t\t\t\tresult.context,\n\t\t\t\t\tcurrentNode,\n\t\t\t\t\tstate,\n\t\t\t\t\tstateSchema,\n\t\t\t\t);\n\n\t\t\t\tif (interruptResult) {\n\t\t\t\t\treturn { ...interruptResult, nodesVisited };\n\t\t\t\t}\n\n\t\t\t\t// All questions filled — run validators before advancing\n\t\t\t\tfor (const q of result.questions) {\n\t\t\t\t\tconst fn = validators.get(`${currentNode}:${q.field}`);\n\t\t\t\t\tif (fn) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst value = getNestedValue(\n\t\t\t\t\t\t\t\tstate as Record<string, unknown>,\n\t\t\t\t\t\t\t\tq.field,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tconst vResult = await fn(value);\n\t\t\t\t\t\t\tif (vResult && typeof vResult === \"object\") {\n\t\t\t\t\t\t\t\tstate = deepMerge(\n\t\t\t\t\t\t\t\t\tstate as Record<string, unknown>,\n\t\t\t\t\t\t\t\t\tvResult as Record<string, unknown>,\n\t\t\t\t\t\t\t\t) as TState;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tdeleteNestedValue(state as Record<string, unknown>, q.field);\n\t\t\t\t\t\t\tconst questionsWithError = result.questions.map((qq) =>\n\t\t\t\t\t\t\t\tqq.field === q.field\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\t...qq,\n\t\t\t\t\t\t\t\t\t\t\tcontext: qq.context\n\t\t\t\t\t\t\t\t\t\t\t\t? `ERROR: ${msg}\\n\\n${qq.context}`\n\t\t\t\t\t\t\t\t\t\t\t\t: `ERROR: ${msg}`,\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: qq,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tconst errResult = buildInterruptResult(\n\t\t\t\t\t\t\t\tquestionsWithError,\n\t\t\t\t\t\t\t\tresult.context,\n\t\t\t\t\t\t\t\tcurrentNode,\n\t\t\t\t\t\t\t\tstate,\n\t\t\t\t\t\t\t\tstateSchema,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (errResult) {\n\t\t\t\t\t\t\t\treturn { ...errResult, nodesVisited };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// All questions filled and validated — advance to next node\n\t\t\t\tconst edge = edges.get(currentNode);\n\t\t\t\tif (!edge) {\n\t\t\t\t\treportSessionError({\n\t\t\t\t\t\twaniwani,\n\t\t\t\t\t\tcode: \"agent_failed\",\n\t\t\t\t\t\tcause: \"flow_dead_end\",\n\t\t\t\t\t\tproperties: { node: currentNode },\n\t\t\t\t\t});\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\t\terror: `No outgoing edge from node \"${currentNode}\"`,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tnodesVisited,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tcurrentNode = await resolveNextNode(edge, state);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Widget signal — delegate to display tool\n\t\t\tif (isWidget(result)) {\n\t\t\t\tconst widgetField = result.field;\n\t\t\t\tif (widgetField) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tisFilled(\n\t\t\t\t\t\t\tgetNestedValue(state as Record<string, unknown>, widgetField),\n\t\t\t\t\t\t)\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst edge = edges.get(currentNode);\n\t\t\t\t\t\tif (!edge) {\n\t\t\t\t\t\t\treportSessionError({\n\t\t\t\t\t\t\t\twaniwani,\n\t\t\t\t\t\t\t\tcode: \"agent_failed\",\n\t\t\t\t\t\t\t\tcause: \"flow_dead_end\",\n\t\t\t\t\t\t\t\tproperties: { node: currentNode },\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\t\t\t\terror: `No outgoing edge from node \"${currentNode}\"`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tnodesVisited,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentNode = await resolveNextNode(edge, state);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst widgetFieldSchema = widgetField\n\t\t\t\t\t? resolveFieldSchema(stateSchema, widgetField)\n\t\t\t\t\t: undefined;\n\t\t\t\tconst interactive = result.interactive !== false;\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"widget\",\n\t\t\t\t\t\ttool: result.tool,\n\t\t\t\t\t\t...(result.data !== undefined ? { data: result.data } : {}),\n\t\t\t\t\t\t// Non-interactive widgets are the most-skipped step: the model\n\t\t\t\t\t\t// sees \"display then continue\" and jumps straight to continue,\n\t\t\t\t\t\t// dropping the visible render. Spell out the order and the\n\t\t\t\t\t\t// failure mode so the display call cannot be shortcut.\n\t\t\t\t\t\tdescription: interactive\n\t\t\t\t\t\t\t? `IMPORTANT: You MUST now call the ${result.tool} tool to display the widget. Do NOT skip this step`\n\t\t\t\t\t\t\t: `IMPORTANT — two ordered steps. STEP 1: call the ${result.tool} tool RIGHT NOW to display this widget to the user. This renders UI the user must see — do NOT skip it and do NOT jump straight to action:\"continue\". STEP 2: ONLY AFTER ${result.tool} has been called, call this flow again with action:\"continue\".`,\n\t\t\t\t\t\tinteractive,\n\t\t\t\t\t\t...(widgetField ? { field: widgetField } : {}),\n\t\t\t\t\t\t...(widgetFieldSchema ? { fieldSchema: widgetFieldSchema } : {}),\n\t\t\t\t\t},\n\t\t\t\t\tflowTokenContent: {\n\t\t\t\t\t\tstep: currentNode,\n\t\t\t\t\t\tstate,\n\t\t\t\t\t\tfield: widgetField,\n\t\t\t\t\t\twidgetId: result.tool,\n\t\t\t\t\t},\n\t\t\t\t\tnodesVisited,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Action node — deep-merge state (preserves nested object siblings)\n\t\t\tstate = deepMerge(\n\t\t\t\tstate as Record<string, unknown>,\n\t\t\t\tresult as Record<string, unknown>,\n\t\t\t) as TState;\n\n\t\t\tconst edge = edges.get(currentNode);\n\t\t\tif (!edge) {\n\t\t\t\treportSessionError({\n\t\t\t\t\twaniwani,\n\t\t\t\t\tcode: \"agent_failed\",\n\t\t\t\t\tcause: \"flow_dead_end\",\n\t\t\t\t\tproperties: { node: currentNode },\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\terror: `No outgoing edge from node \"${currentNode}\"`,\n\t\t\t\t\t},\n\t\t\t\t\tnodesVisited,\n\t\t\t\t};\n\t\t\t}\n\t\t\tcurrentNode = await resolveNextNode(edge, state);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\treportSessionError({\n\t\t\t\twaniwani,\n\t\t\t\tcode: \"agent_failed\",\n\t\t\t\tcause: classifyCause({ error }),\n\t\t\t\tproperties: { node: currentNode },\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tcontent: { status: \"error\", error: message },\n\t\t\t\tflowTokenContent: { step: currentNode, state },\n\t\t\t\tnodesVisited,\n\t\t\t};\n\t\t}\n\t}\n\n\treportSessionError({\n\t\twaniwani,\n\t\tcode: \"agent_failed\",\n\t\tcause: \"flow_loop\",\n\t\tproperties: { node: currentNode },\n\t});\n\treturn {\n\t\tcontent: {\n\t\t\tstatus: \"error\",\n\t\t\terror: \"Flow exceeded maximum iterations (possible infinite loop)\",\n\t\t},\n\t\tnodesVisited,\n\t};\n}\n","/**\n * AES-256-GCM encryption for KV store values.\n *\n * When `WANIWANI_ENCRYPTION_KEY` is set, values are encrypted before\n * being sent to the Waniwani API and decrypted on read. The server\n * never sees plaintext flow state.\n *\n * Key format: base64-encoded 32-byte (256-bit) key.\n * Generate with: `openssl rand -base64 32`\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Encrypted value envelope stored as JSONB in the KV store. */\nexport type EncryptedEnvelope = {\n\t/** Version marker — allows future algorithm changes. */\n\t__ww_enc: 1;\n\t/** Base64-encoded ciphertext (includes GCM auth tag). */\n\tct: string;\n\t/** Base64-encoded 12-byte IV. */\n\tiv: string;\n};\n\n// ============================================================================\n// Type guard\n// ============================================================================\n\nexport function isEncryptedEnvelope(\n\tvalue: unknown,\n): value is EncryptedEnvelope {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\t(value as EncryptedEnvelope).__ww_enc === 1 &&\n\t\ttypeof (value as EncryptedEnvelope).ct === \"string\" &&\n\t\ttypeof (value as EncryptedEnvelope).iv === \"string\"\n\t);\n}\n\n// ============================================================================\n// Key cache\n// ============================================================================\n\nconst keyCache = new Map<string, CryptoKey>();\n\nasync function importKey(keyBase64: string): Promise<CryptoKey> {\n\tconst cached = keyCache.get(keyBase64);\n\tif (cached) {\n\t\treturn cached;\n\t}\n\n\tconst raw = Buffer.from(keyBase64, \"base64\");\n\tif (raw.length !== 32) {\n\t\tthrow new Error(\n\t\t\t\"[Waniwani KV] WANIWANI_ENCRYPTION_KEY must be a base64-encoded 32-byte (256-bit) key.\",\n\t\t);\n\t}\n\n\tconst cryptoKey = await globalThis.crypto.subtle.importKey(\n\t\t\"raw\",\n\t\traw,\n\t\t{ name: \"AES-GCM\" },\n\t\tfalse,\n\t\t[\"encrypt\", \"decrypt\"],\n\t);\n\n\tkeyCache.set(keyBase64, cryptoKey);\n\treturn cryptoKey;\n}\n\n// ============================================================================\n// Encrypt / Decrypt\n// ============================================================================\n\nexport async function encryptValue(\n\tvalue: Record<string, unknown>,\n\tkeyBase64: string,\n): Promise<EncryptedEnvelope> {\n\tconst key = await importKey(keyBase64);\n\tconst iv = globalThis.crypto.getRandomValues(new Uint8Array(12));\n\tconst plaintext = new TextEncoder().encode(JSON.stringify(value));\n\n\tconst ciphertext = await globalThis.crypto.subtle.encrypt(\n\t\t{ name: \"AES-GCM\", iv },\n\t\tkey,\n\t\tplaintext,\n\t);\n\n\treturn {\n\t\t__ww_enc: 1,\n\t\tct: Buffer.from(ciphertext).toString(\"base64\"),\n\t\tiv: Buffer.from(iv).toString(\"base64\"),\n\t};\n}\n\nexport async function decryptValue<T = Record<string, unknown>>(\n\tenvelope: EncryptedEnvelope,\n\tkeyBase64: string,\n): Promise<T> {\n\tconst key = await importKey(keyBase64);\n\tconst ct = Buffer.from(envelope.ct, \"base64\");\n\tconst iv = Buffer.from(envelope.iv, \"base64\");\n\n\tlet plaintext: ArrayBuffer;\n\ttry {\n\t\tplaintext = await globalThis.crypto.subtle.decrypt(\n\t\t\t{ name: \"AES-GCM\", iv },\n\t\t\tkey,\n\t\t\tct,\n\t\t);\n\t} catch {\n\t\tthrow new Error(\n\t\t\t\"[Waniwani KV] Decryption failed. The encryption key may be incorrect or the data may be corrupted.\",\n\t\t);\n\t}\n\n\treturn JSON.parse(new TextDecoder().decode(plaintext)) as T;\n}\n","type LogLevel = \"debug\" | \"warn\" | \"error\" | \"none\";\n\nconst LEVELS: Record<LogLevel, number> = {\n\tdebug: 0,\n\twarn: 1,\n\terror: 2,\n\tnone: 3,\n};\n\nfunction getGlobalLevel(): LogLevel {\n\tconst explicit = process.env.WANIWANI_LOG_LEVEL as LogLevel | undefined;\n\tif (explicit && explicit in LEVELS) {\n\t\treturn explicit;\n\t}\n\tif (process.env.WANIWANI_DEBUG) {\n\t\treturn \"debug\";\n\t}\n\treturn \"none\";\n}\n\n/**\n * Creates a namespaced logger that writes to console when the log level permits.\n *\n * When `enabled` is omitted the logger checks `WANIWANI_LOG_LEVEL` (or falls\n * back to `WANIWANI_DEBUG` for backward compat). Pass an explicit boolean to\n * override the env-var check (e.g. from a user-facing `debug` option).\n *\n * @example\n * const log = createLogger(\"chat\"); // env-var driven\n * const log = createLogger(\"chat\", debug); // explicit override\n */\nexport function createLogger(\n\tnamespace: string,\n\tenabled?: boolean,\n): (...args: unknown[]) => void {\n\tconst active = enabled ?? getGlobalLevel() === \"debug\";\n\treturn active\n\t\t? (...args: unknown[]) => console.log(`[waniwani:${namespace}]`, ...args)\n\t\t: () => {};\n}\n","/**\n * Generic key-value store backed by the Waniwani API.\n *\n * Values are stored as JSON objects (`Record<string, unknown>`) in the\n * `/api/mcp/redis/*` endpoints. Tenant isolation is handled by the API key.\n *\n * Config is read from env vars:\n * - `WANIWANI_API_KEY` (required)\n * - `WANIWANI_API_URL` (optional, defaults to https://app.waniwani.ai)\n * - `WANIWANI_ENCRYPTION_KEY` (optional, base64-encoded 32-byte key for AES-256-GCM encryption)\n */\n\nimport { createLogger } from \"../../../utils/logger.js\";\nimport { decryptValue, encryptValue, isEncryptedEnvelope } from \"./crypto\";\n\n// ============================================================================\n// Interface\n// ============================================================================\n\nexport interface KvStoreSetOptions {\n\t/**\n\t * Time-to-live in seconds, mirroring Redis `SET ... EX`. When omitted the\n\t * server applies its default retention window. The value is kept until it\n\t * expires, however far in the future.\n\t */\n\tttlSeconds?: number;\n}\n\nexport interface KvStore<T = Record<string, unknown>> {\n\tget(key: string): Promise<T | null>;\n\tset(key: string, value: T, options?: KvStoreSetOptions): Promise<void>;\n\tdelete(key: string): Promise<void>;\n}\n\n// ============================================================================\n// Waniwani API implementation\n// ============================================================================\n\nconst SDK_NAME = \"@waniwani/sdk\";\nconst DEFAULT_BASE_URL = \"https://app.waniwani.ai\";\n\nconst kvDebug = createLogger(\"kv\");\n\nexport class WaniwaniKvStore<T = Record<string, unknown>>\n\timplements KvStore<T>\n{\n\tprivate get baseUrl(): string {\n\t\treturn (process.env.WANIWANI_API_URL ?? DEFAULT_BASE_URL).replace(\n\t\t\t/\\/$/,\n\t\t\t\"\",\n\t\t);\n\t}\n\n\tprivate get apiKey(): string | undefined {\n\t\treturn process.env.WANIWANI_API_KEY;\n\t}\n\n\tprivate get encryptionKey(): string | undefined {\n\t\treturn process.env.WANIWANI_ENCRYPTION_KEY;\n\t}\n\n\tasync get(key: string): Promise<T | null> {\n\t\tif (!this.apiKey) {\n\t\t\tthrow new Error(\n\t\t\t\t\"[Waniwani KV] No API key configured. Set WANIWANI_API_KEY env var.\",\n\t\t\t);\n\t\t}\n\t\tconst data = await this.request<Record<string, unknown> | null>(\n\t\t\t\"/api/mcp/redis/get\",\n\t\t\t{ key },\n\t\t);\n\t\tif (data == null) {\n\t\t\treturn null;\n\t\t}\n\t\tif (isEncryptedEnvelope(data)) {\n\t\t\tif (!this.encryptionKey) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"[Waniwani KV] Encrypted data found but WANIWANI_ENCRYPTION_KEY is not set.\",\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn decryptValue<T>(data, this.encryptionKey);\n\t\t}\n\t\treturn data as T;\n\t}\n\n\tasync set(key: string, value: T, options?: KvStoreSetOptions): Promise<void> {\n\t\tif (!this.apiKey) {\n\t\t\tthrow new Error(\n\t\t\t\t\"[Waniwani KV] No API key configured. Set WANIWANI_API_KEY env var.\",\n\t\t\t);\n\t\t}\n\t\tconst encKey = this.encryptionKey;\n\t\tkvDebug(\n\t\t\t`set \"${key}\" — encryption ${encKey ? \"enabled\" : \"disabled (no WANIWANI_ENCRYPTION_KEY)\"}`,\n\t\t);\n\t\tconst payload = encKey\n\t\t\t? await encryptValue(value as Record<string, unknown>, encKey)\n\t\t\t: value;\n\t\tawait this.request(\"/api/mcp/redis/set\", {\n\t\t\tkey,\n\t\t\tvalue: payload,\n\t\t\tttlSeconds: options?.ttlSeconds,\n\t\t});\n\t}\n\n\tasync delete(key: string): Promise<void> {\n\t\tif (!this.apiKey) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait this.request(\"/api/mcp/redis/delete\", { key });\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[Waniwani KV] delete failed for key:\", key, error);\n\t\t}\n\t}\n\n\tprivate async request<R>(path: string, body: unknown): Promise<R> {\n\t\tconst url = `${this.baseUrl}${path}`;\n\t\tconst response = await fetch(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${this.apiKey}`,\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\"X-WaniWani-SDK\": SDK_NAME,\n\t\t\t},\n\t\t\tbody: JSON.stringify(body),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst text = await response.text().catch(() => \"\");\n\t\t\tthrow new Error(text || `KV store API error: HTTP ${response.status}`);\n\t\t}\n\n\t\tconst json = (await response.json()) as { data: R };\n\t\treturn json.data;\n\t}\n}\n","/**\n * In-memory KvStore implementation.\n *\n * State lives in a `Map` for the lifetime of the process — nothing is\n * persisted, nothing is shared across instances. Use for local development\n * and tests. For production, plug in a Redis/Upstash/CF-KV adapter or set\n * `WANIWANI_API_KEY` to use the hosted store.\n */\n\nimport type { KvStore, KvStoreSetOptions } from \"./kv-store\";\n\nexport class MemoryKvStore<T = Record<string, unknown>> implements KvStore<T> {\n\tprivate readonly map = new Map<string, T>();\n\n\tasync get(key: string): Promise<T | null> {\n\t\treturn this.map.get(key) ?? null;\n\t}\n\n\t// In-memory storage has no expiry; `options` (e.g. `ttlSeconds`) is accepted\n\t// for parity with WaniwaniKvStore but ignored.\n\tasync set(\n\t\tkey: string,\n\t\tvalue: T,\n\t\t_options?: KvStoreSetOptions,\n\t): Promise<void> {\n\t\tthis.map.set(key, value);\n\t}\n\n\tasync delete(key: string): Promise<void> {\n\t\tthis.map.delete(key);\n\t}\n}\n","/**\n * Server-side flow state store.\n *\n * Flow state is stored via the Waniwani API, keyed by session ID.\n * Config comes from env vars (WANIWANI_API_KEY, WANIWANI_API_URL).\n */\n\nimport { type KvStoreSetOptions, WaniwaniKvStore } from \"../kv\";\nimport type { FlowTokenContent } from \"./@types\";\n\n// ============================================================================\n// Interface\n// ============================================================================\n\nexport interface FlowStore {\n\tget(key: string): Promise<FlowTokenContent | null>;\n\tset(\n\t\tkey: string,\n\t\tvalue: FlowTokenContent,\n\t\toptions?: KvStoreSetOptions,\n\t): Promise<void>;\n\tdelete(key: string): Promise<void>;\n}\n\n// ============================================================================\n// Waniwani API implementation\n// ============================================================================\n\nexport class WaniwaniFlowStore implements FlowStore {\n\tprivate readonly store = new WaniwaniKvStore<FlowTokenContent>();\n\n\tget(key: string): Promise<FlowTokenContent | null> {\n\t\treturn this.store.get(key);\n\t}\n\n\tset(\n\t\tkey: string,\n\t\tvalue: FlowTokenContent,\n\t\toptions?: KvStoreSetOptions,\n\t): Promise<void> {\n\t\treturn this.store.set(key, value, options);\n\t}\n\n\tdelete(key: string): Promise<void> {\n\t\treturn this.store.delete(key);\n\t}\n}\n","import type {\n\tEdge,\n\tFlowConfig,\n\tFlowGraph,\n\tFlowGraphEdge,\n\tFlowGraphNode,\n\tNodeHandler,\n\tNodeOptions,\n} from \"./@types\";\n\nfunction classifyNode<TState extends Record<string, unknown>>(\n\thandler: NodeHandler<TState>,\n): \"widget\" | \"interrupt\" | \"action\" {\n\tconst src = handler.toString();\n\tif (src.includes(\"showWidget\")) {\n\t\treturn \"widget\";\n\t}\n\tif (src.includes(\"interrupt\")) {\n\t\treturn \"interrupt\";\n\t}\n\treturn \"action\";\n}\n\nexport function extractFlowGraph<TState extends Record<string, unknown>>(\n\tconfig: FlowConfig,\n\tnodes: Map<string, NodeHandler<TState>>,\n\tedges: Map<string, Edge<TState>>,\n\tnodeOptions: Map<string, NodeOptions>,\n): FlowGraph {\n\tconst graphNodes: FlowGraphNode[] = [];\n\tfor (const [id, handler] of nodes) {\n\t\tconst opts = nodeOptions.get(id);\n\t\tgraphNodes.push({\n\t\t\tid,\n\t\t\ttype: classifyNode(handler),\n\t\t\tlabel: opts?.label ?? id,\n\t\t\t...(opts?.hideFromFunnel ? { hideFromFunnel: true } : {}),\n\t\t});\n\t}\n\n\tconst graphEdges: FlowGraphEdge[] = [];\n\tfor (const [from, edge] of edges) {\n\t\tif (edge.type === \"direct\") {\n\t\t\tgraphEdges.push({ from, to: edge.to, type: \"direct\" });\n\t\t} else {\n\t\t\tgraphEdges.push({ from, to: edge.targets, type: \"conditional\" });\n\t\t}\n\t}\n\n\treturn {\n\t\tflowId: config.id,\n\t\ttitle: config.title,\n\t\tnodes: graphNodes,\n\t\tedges: graphEdges,\n\t};\n}\n","/**\n * Flow intro — the first thing the assistant says when a session first\n * touches the flow.\n *\n * A flow can be entered at any node: when the user's opening message already\n * answers the first questions, the engine skips those steps and the\n * conversation starts mid-flow. So the intro is not tied to a node. It is\n * seeded into the session's internal state at `start` and rides along with\n * whatever response the engine returns first, which delivers the introduction\n * and any legal notice exactly once, wherever the flow happens to land,\n * without costing an extra conversation turn.\n *\n * This file only normalizes what the author declared. Where it lives and when\n * it goes out is `internal-state.ts` and the response assembler in\n * `compile.ts`.\n */\n\nimport type { FlowIntro, FlowIntroPayload } from \"./@types\";\n\n/**\n * Normalize an author-supplied `intro` into the payload shape sent on the wire.\n * Called once at compile time so a malformed intro fails at startup rather\n * than on a live conversation.\n *\n * Returns `undefined` when the flow declares no intro.\n */\nexport function normalizeIntro(\n\tintro: FlowIntro | undefined,\n\tflowId: string,\n): FlowIntroPayload | undefined {\n\tif (intro === undefined) {\n\t\treturn undefined;\n\t}\n\n\tif (typeof intro === \"string\") {\n\t\tconst verbatim = intro.trim();\n\t\tif (!verbatim) {\n\t\t\tthrow new Error(\n\t\t\t\t`[waniwani] createFlow \"${flowId}\": \\`intro\\` is an empty string. ` +\n\t\t\t\t\t`Pass the text to deliver word for word, or drop the option.`,\n\t\t\t);\n\t\t}\n\t\treturn { verbatim };\n\t}\n\n\tif (typeof intro !== \"object\" || intro === null) {\n\t\tthrow new Error(\n\t\t\t`[waniwani] createFlow \"${flowId}\": \\`intro\\` must be a string (delivered verbatim) ` +\n\t\t\t\t`or an object with \\`verbatim\\` and/or \\`instructions\\`.`,\n\t\t);\n\t}\n\n\tconst verbatim =\n\t\ttypeof intro.verbatim === \"string\" ? intro.verbatim.trim() : undefined;\n\tconst instructions =\n\t\ttypeof intro.instructions === \"string\"\n\t\t\t? intro.instructions.trim()\n\t\t\t: undefined;\n\n\tif (!verbatim && !instructions) {\n\t\tthrow new Error(\n\t\t\t`[waniwani] createFlow \"${flowId}\": \\`intro\\` needs \\`verbatim\\` (text delivered ` +\n\t\t\t\t`word for word), \\`instructions\\` (guidance the assistant writes from), or both.`,\n\t\t);\n\t}\n\n\treturn {\n\t\t...(verbatim ? { verbatim } : {}),\n\t\t...(instructions ? { instructions } : {}),\n\t};\n}\n","/**\n * The session's internal state: what the engine carries for a session, as\n * opposed to the state the flow collects.\n *\n * A field is seeded when the session starts, read where the engine needs it,\n * and taken off once used. Everything that seeds, loads, takes from, or\n * persists internal state lives here, so the rest of the engine just reads\n * `internal.<field>`.\n */\n\nimport type { FlowConfig, FlowInternalState, FlowTokenContent } from \"./@types\";\nimport type { FlowStore } from \"./flow-store\";\nimport { normalizeIntro } from \"./intro\";\n\n/**\n * How each internal field starts life. One entry per field: it reads the flow\n * config and returns the value a fresh session begins with, or `undefined`\n * when the flow does not use that feature.\n *\n * Adding an engine feature that remembers something at init means adding a\n * field to `FlowInternalState` and its seed here. Nothing else changes.\n */\nconst SEEDS: {\n\t[K in keyof FlowInternalState]-?: (\n\t\tconfig: FlowConfig,\n\t) => FlowInternalState[K];\n} = {\n\tintro: (config) => normalizeIntro(config.intro, config.id),\n};\n\n/**\n * Build the internal state a fresh session begins with.\n *\n * Called once at compile time, so a malformed config fails at startup rather\n * than on a live conversation, and every new session copies the same seed.\n */\nexport function initInternalState(config: FlowConfig): FlowInternalState {\n\tconst seeded = Object.entries(SEEDS)\n\t\t.map(([field, seed]) => [field, seed(config)] as const)\n\t\t.filter(([, value]) => value !== undefined);\n\treturn Object.fromEntries(seeded) as FlowInternalState;\n}\n\n/**\n * Resolve the internal state a `start` call should run with: what the session\n * has left if it is already underway, the seed if it is not.\n *\n * The store read is skipped when the seed is empty (a flow using none of these\n * features has nothing to look up) and when the session id was minted by this\n * very call (there is no record behind it yet). A failed read falls back to the\n * seed: repeating at worst an introduction beats refusing the call.\n */\nexport async function loadInternalState(args: {\n\tstore: FlowStore;\n\tsessionId: string | undefined;\n\t/** False when the engine minted the session id in this call. */\n\tsessionIsPreexisting: boolean;\n\t/** What a session that has not started yet begins with. */\n\tseed: FlowInternalState;\n}): Promise<FlowInternalState> {\n\tconst { store, sessionId, sessionIsPreexisting, seed } = args;\n\n\tif (Object.keys(seed).length === 0) {\n\t\treturn {};\n\t}\n\tif (!sessionId || !sessionIsPreexisting) {\n\t\treturn seed;\n\t}\n\ttry {\n\t\tconst record = await store.get(sessionId);\n\t\treturn record ? (record.internal ?? {}) : seed;\n\t} catch {\n\t\treturn seed;\n\t}\n}\n\n/**\n * Take one field off the session's internal state: the value to use on this\n * response, plus the state to persist without it. Absence then means the field\n * has been used, so a later call in the same conversation leaves it out.\n *\n * Fields this version does not recognize are carried through untouched, so a\n * record written by a newer engine survives a call served by an older one.\n */\nexport function takeInternalField<K extends keyof FlowInternalState>(\n\tinternal: FlowInternalState | undefined,\n\tfield: K,\n): { value: FlowInternalState[K]; internal: FlowInternalState } {\n\tconst { [field]: value, ...rest } = internal ?? {};\n\treturn { value, internal: rest };\n}\n\n/**\n * Combine the record the engine wants to persist with the session's internal\n * state. An empty bag is left off entirely, so a flow using none of this\n * writes the same record it always did.\n */\nexport function withInternalState(\n\ttokenContent: FlowTokenContent,\n\tinternal: FlowInternalState,\n): FlowTokenContent {\n\tif (Object.keys(internal).length === 0) {\n\t\treturn tokenContent;\n\t}\n\treturn { ...tokenContent, internal };\n}\n","import { z } from \"zod\";\n\n// ============================================================================\n// Output schema — the JSON shape returned in `structuredContent` for every\n// flow tool call. Mirrors `FlowContent` plus the optional `sessionId` echo.\n//\n// Baked into the compiled tool config so MCP clients (Claude, ChatGPT, etc.)\n// can present a typed, validated contract instead of an opaque string payload.\n// ============================================================================\n\nconst flowFieldSchema = z\n\t.object({\n\t\ttype: z.enum([\n\t\t\t\"enum\",\n\t\t\t\"string\",\n\t\t\t\"number\",\n\t\t\t\"boolean\",\n\t\t\t\"object\",\n\t\t\t\"array\",\n\t\t\t\"unknown\",\n\t\t]),\n\t\tvalues: z.array(z.string()).optional(),\n\t\tdescription: z.string().optional(),\n\t\toptional: z.boolean().optional(),\n\t})\n\t.describe(\n\t\t\"JIT schema fragment for a state field — type, allowed values, and description.\",\n\t);\n\nconst flowIntroSchema = z\n\t.object({\n\t\tverbatim: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.describe(\n\t\t\t\t\"Text to reproduce word for word, before anything else in the reply.\",\n\t\t\t),\n\t\tinstructions: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.describe(\"How to write the introduction in your own words.\"),\n\t})\n\t.describe(\n\t\t\"Opening message for the session's first flow response. Delivered once per conversation.\",\n\t);\n\nconst flowQuestionSchema = z\n\t.object({\n\t\tquestion: z.string(),\n\t\tfield: z.string(),\n\t\tsuggestions: z.array(z.string()).optional(),\n\t\tcontext: z.string().optional(),\n\t\tfieldSchema: flowFieldSchema.optional(),\n\t})\n\t.describe(\"One question within a multi-question interrupt.\");\n\nexport const flowOutputSchema = {\n\tintro: flowIntroSchema\n\t\t.optional()\n\t\t.describe(\n\t\t\t\"Present only on the session's first flow response. Deliver it at the very start of your next message, then never again.\",\n\t\t),\n\tstatus: z\n\t\t.enum([\"interrupt\", \"widget\", \"complete\", \"error\"])\n\t\t.describe(\n\t\t\t\"Current flow status and the next action the assistant should take.\",\n\t\t),\n\tquestion: z\n\t\t.string()\n\t\t.optional()\n\t\t.describe(\"Single question to ask the user when status is interrupt.\"),\n\tfield: z\n\t\t.string()\n\t\t.optional()\n\t\t.describe(\n\t\t\t\"State field to fill with the user's answer on the next continue call.\",\n\t\t),\n\tfieldSchema: flowFieldSchema\n\t\t.optional()\n\t\t.describe(\"JIT schema fragment for the single-question shorthand.\"),\n\tsuggestions: z\n\t\t.array(z.string())\n\t\t.optional()\n\t\t.describe(\"Suggested answers for the single-question shorthand.\"),\n\tquestions: z\n\t\t.array(flowQuestionSchema)\n\t\t.optional()\n\t\t.describe(\"Multiple questions to ask the user when status is interrupt.\"),\n\tcontext: z\n\t\t.string()\n\t\t.optional()\n\t\t.describe(\"Private instruction context for the assistant.\"),\n\ttool: z\n\t\t.string()\n\t\t.optional()\n\t\t.describe(\"Widget tool to call when status is widget.\"),\n\tdata: z\n\t\t.record(z.string(), z.unknown())\n\t\t.optional()\n\t\t.describe(\"Input data to pass to the widget tool.\"),\n\tdescription: z\n\t\t.string()\n\t\t.optional()\n\t\t.describe(\"Instruction for rendering the requested widget.\"),\n\tinteractive: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe(\n\t\t\t\"Whether the widget requires user interaction before continuing.\",\n\t\t),\n\tsessionId: z\n\t\t.string()\n\t\t.optional()\n\t\t.describe(\"Session identifier to pass on future continue and reset calls.\"),\n\terror: z.string().optional().describe(\"Error message when status is error.\"),\n};\n\nexport type FlowOutputSchema = typeof flowOutputSchema;\n","import type { FlowConfig } from \"./@types\";\n\nexport function buildFlowProtocol(config: FlowConfig): string {\n\tconst lines = [\n\t\t\"\",\n\t\t\"## FLOW EXECUTION PROTOCOL\",\n\t\t\"\",\n\t\t\"This tool implements a multi-step conversational flow. Follow this protocol exactly:\",\n\t\t\"\",\n\t\t'1. Call with `action: \"start\"` to begin and include `intent`.',\n\t\t\" `intent` must be a brief summary of the user's goal for this flow.\",\n\t\t\" Do NOT invent missing intent.\",\n\t\t\" Optionally include `context` — the situation or environment that led the user to start\",\n\t\t\" this flow (e.g. what page they are on, what they were doing, or what triggered the request).\",\n\t\t\" Only provide `context` when there is genuinely relevant situational information. Do NOT invent missing context.\",\n\t\t\" If the user's message already contains answers to likely questions,\",\n\t\t\" extract them into `stateUpdates` as `{ field: value }` pairs (see the `stateUpdates` schema\",\n\t\t\" for the list of writable fields). The engine will auto-skip steps whose fields are already filled.\",\n\t\t\" Only extract values the user explicitly stated — do NOT guess or invent values.\",\n\t];\n\n\tif (config.omitIntentPII) {\n\t\tlines.push(\n\t\t\t\" Do NOT include PII in `intent` or `context` — no names, emails, phones, addresses, IDs, ages, or birthdates.\",\n\t\t\t' Summarize the goal abstractly (e.g. \"user wants a quote\", not \"Jane Doe wants a quote\").',\n\t\t);\n\t}\n\n\tlines.push(\n\t\t\" For grouped fields (z.object state), use dot-notation keys in `stateUpdates`:\",\n\t\t' e.g. `{ \"driver.name\": \"John\", \"driver.license\": \"ABC123\" }`.',\n\t\t\"2. The response JSON `status` field tells you what to do next:\",\n\t\t' - `\"interrupt\"`: Pause and ask the user. Two forms:',\n\t\t\" a. Single question: `{ question, field, fieldSchema?, context? }` — ask `question`, store answer in `field`.\",\n\t\t\" b. Multi-question: `{ questions: [{question, field, fieldSchema?}, ...], context? }` — ask ALL questions\",\n\t\t\" in one conversational message, collect all answers.\",\n\t\t\" `fieldSchema` (when present) describes the expected value: `{ type, values?, description?, optional? }`.\",\n\t\t' Use it to validate before sending — match enum `values` exactly, coerce strings to numbers where `type: \"number\"`.',\n\t\t\" `context` (if present) is hidden AI instructions — use to shape your response, do NOT show verbatim.\",\n\t\t\" Then call again with:\",\n\t\t' `action: \"continue\"`,',\n\t\t\" `stateUpdates` = answers keyed by their `field` names, plus any other fields the user mentioned.\",\n\t\t' - `\"widget\"`: The flow wants to show a UI widget. Call the tool named in the `tool`',\n\t\t\" field, passing the `data` object as the tool's input.\",\n\t\t\" Check the `interactive` field in the response:\",\n\t\t\" • `interactive: true` — The widget requires user interaction. After calling the display tool,\",\n\t\t\" STOP and WAIT for the user to interact with the widget. Do NOT call this flow tool again\",\n\t\t\" until the user has responded. When they do, call with:\",\n\t\t' `action: \"continue\"`,',\n\t\t\" `stateUpdates` = `{ [field]: <user's selection> }` plus any other fields the user mentioned.\",\n\t\t\" • `interactive: false` — The widget is display-only. You MUST STILL call the display tool\",\n\t\t\" FIRST to render it for the user — this is a required, user-visible step. Do NOT skip it\",\n\t\t' and do NOT jump straight to `action: \"continue\"`. ONLY AFTER you have called the display',\n\t\t' tool, call THIS flow tool again with `action: \"continue\"`. Do NOT wait for user interaction.',\n\t\t' - `\"complete\"`: The flow is done. Present the result to the user.',\n\t\t' - `\"error\"`: Something went wrong. Show the `error` message.',\n\t\t\"\",\n\t\t\"3. Do NOT invent state values. Only use `stateUpdates` for information the user explicitly provided.\",\n\t\t\"4. Include only the fields the user actually answered in `stateUpdates` — do NOT guess missing ones.\",\n\t\t\" If the user did not answer all pending questions, the engine will re-prompt for the remaining ones.\",\n\t\t\" If the user mentioned values for other known fields, include those too —\",\n\t\t\" they will be applied immediately and those steps will be auto-skipped.\",\n\t\t\"5. CORRECTION: If the user wants to CHANGE a previously-answered field\",\n\t\t' (e.g. \"actually my email is X\" or \"go back and change my country\"),',\n\t\t' call with `action: \"reset\"` and `stateUpdates` containing the corrected field(s).',\n\t\t\" The engine will restart the flow from the beginning with all existing answers preserved\",\n\t\t\" plus your corrections. Steps with filled answers will be auto-skipped.\",\n\t\t\" The flow may take a different path if the corrected value affects routing.\",\n\t\t' Do NOT use \"reset\" for the CURRENT question — use \"continue\" for that.',\n\t\t\"6. If the response includes a `sessionId`, you MUST pass it back as `sessionId`\",\n\t\t' in every subsequent \"continue\" and \"reset\" call for this flow.',\n\t);\n\n\tif (config.intro) {\n\t\tlines.push(\n\t\t\t\"7. OPENING MESSAGE: if the response contains an `intro` object, deliver it at the very\",\n\t\t\t\" start of your next message to the user — before the question, before calling a widget\",\n\t\t\t\" tool, before presenting the result.\",\n\t\t\t\" - `intro.verbatim` (when present) MUST be reproduced word for word, exactly as written.\",\n\t\t\t\" Do NOT paraphrase, translate, shorten, expand, reformat, or fold it into another sentence.\",\n\t\t\t\" - `intro.instructions` (when present) tells you how to write the introduction in your own words.\",\n\t\t\t\" - When both are present, follow `instructions` for the prose around the notice and leave the\",\n\t\t\t\" `verbatim` text untouched.\",\n\t\t\t\" `intro` appears at most once per conversation. Later responses omit it, and you must not\",\n\t\t\t\" repeat it or reuse it from earlier in the conversation.\",\n\t\t);\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n","import type { z } from \"zod\";\n\ntype UnknownRecord = Record<string, unknown>;\n\nfunction isRecord(value: unknown): value is UnknownRecord {\n\treturn Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/**\n * Mark a Zod schema as PII — its value will be replaced with `\"REDACTED\"` in\n * any `tool.called` event payload sent to the Waniwani API. The handler still\n * receives the original value; only the tracked copy is scrubbed.\n *\n * Apply at the end of the schema chain so the marker is attached to the final\n * schema:\n *\n * ```ts\n * ages: redacted(z.string().describe(\"Comma-separated ages\")),\n * zipcode: redacted(z.string().describe(\"Spanish postal code\")),\n * ```\n *\n * Uses Zod v4's `.meta()` registry, so the marker is preserved across schema\n * clones (`.optional()`, `.default()`, etc.).\n */\nexport function redacted<T extends z.ZodType>(schema: T): T {\n\tconst existing = readMeta(schema);\n\tconst waniwani = isRecord(existing.waniwani) ? existing.waniwani : {};\n\treturn (schema as unknown as { meta: (m: UnknownRecord) => T }).meta({\n\t\t...existing,\n\t\twaniwani: { ...waniwani, redacted: true },\n\t});\n}\n\nexport function isFieldRedacted(schema: z.ZodType): boolean {\n\tconst meta = readMeta(schema);\n\tconst waniwani = meta.waniwani;\n\treturn isRecord(waniwani) && waniwani.redacted === true;\n}\n\nfunction readMeta(schema: z.ZodType): UnknownRecord {\n\tconst metaFn = (schema as unknown as { meta?: () => unknown }).meta;\n\tif (typeof metaFn !== \"function\") {\n\t\treturn {};\n\t}\n\tconst value = metaFn.call(schema);\n\treturn isRecord(value) ? value : {};\n}\n\n/**\n * Walk a flow state schema and return the field names marked via `redacted()`.\n */\nexport function collectRedactedStateFields(\n\tstate: Record<string, z.ZodType> | undefined,\n): string[] {\n\tif (!state) {\n\t\treturn [];\n\t}\n\tconst out: string[] = [];\n\tfor (const [name, schema] of Object.entries(state)) {\n\t\tif (isFieldRedacted(schema)) {\n\t\t\tout.push(name);\n\t\t}\n\t}\n\treturn out;\n}\n\nexport const REDACTED_STATE_UPDATE_FIELDS_META_KEY =\n\t\"waniwani/redactedStateUpdateFields\";\n","import type {\n\tAddNodeConfig,\n\tConditionFn,\n\tEdge,\n\tFlowConfig,\n\tNodeHandler,\n\tNodeOptions,\n\tRegisteredFlow,\n} from \"./@types\";\nimport { END, START } from \"./@types\";\nimport { compileFlow } from \"./compile\";\nimport type { FlowStore } from \"./flow-store\";\n\nfunction buildMermaidGraph(\n\tnodes: Map<string, unknown>,\n\tedges: Map<string, Edge<unknown>>,\n): string {\n\tconst lines: string[] = [\"flowchart TD\"];\n\tlines.push(` ${START}((Start))`);\n\tfor (const [name] of nodes) {\n\t\tlines.push(` ${name}[${name}]`);\n\t}\n\tlines.push(` ${END}((End))`);\n\tfor (const [from, edge] of edges) {\n\t\tif (edge.type === \"direct\") {\n\t\t\tlines.push(` ${from} --> ${edge.to}`);\n\t\t} else {\n\t\t\tfor (const target of edge.targets) {\n\t\t\t\tlines.push(` ${from} -.-> ${target}`);\n\t\t\t}\n\t\t}\n\t}\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * A LangGraph-inspired state graph builder for MCP tools.\n *\n * @example\n * ```ts\n * const flow = new StateGraph<MyState>({\n * id: \"onboarding\",\n * title: \"User Onboarding\",\n * description: \"Guides users through onboarding\",\n * })\n * .addNode({\n * id: \"ask_name\",\n * run: ({ interrupt }) => interrupt({ question: \"What's your name?\", field: \"name\" }),\n * })\n * .addNode({\n * id: \"greet\",\n * run: ({ state }) => ({ greeting: `Hello ${state.name}!` }),\n * })\n * .addEdge(START, \"ask_name\")\n * .addEdge(\"ask_name\", \"greet\")\n * .addEdge(\"greet\", END)\n * .compile();\n * ```\n */\nexport class StateGraph<\n\tTState extends Record<string, unknown>,\n\tTNodes extends string = never,\n> {\n\tprivate nodes = new Map<string, NodeHandler<TState>>();\n\tprivate edges = new Map<string, Edge<TState>>();\n\tprivate nodeOptions = new Map<string, NodeOptions>();\n\tprivate config: FlowConfig;\n\n\tconstructor(config: FlowConfig) {\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * Add a node with a handler.\n\t *\n\t * The handler receives a context object with `state`, `meta`, `interrupt`, and `showWidget`.\n\t *\n\t * @example\n\t * ```ts\n\t * .addNode({\n\t * id: \"ask_name\",\n\t * label: \"Ask for name\",\n\t * run: ({ interrupt }) => interrupt({ question: \"What's your name?\", field: \"name\" }),\n\t * })\n\t * ```\n\t */\n\taddNode<TName extends string>(\n\t\tconfig: AddNodeConfig<TState, TName>,\n\t): StateGraph<TState, TNodes | TName>;\n\t/**\n\t * @deprecated Use the object form: `.addNode({ id, run, label? })`.\n\t * The positional form will be removed in v0.13.0\n\t */\n\taddNode<TName extends string>(\n\t\tname: TName,\n\t\thandler: NodeHandler<TState>,\n\t\toptions?: NodeOptions,\n\t): StateGraph<TState, TNodes | TName>;\n\taddNode<TName extends string>(\n\t\tconfigOrName: TName | AddNodeConfig<TState, TName>,\n\t\thandler?: NodeHandler<TState>,\n\t\toptions?: NodeOptions,\n\t): StateGraph<TState, TNodes | TName> {\n\t\tconst id =\n\t\t\ttypeof configOrName === \"string\" ? configOrName : configOrName.id;\n\t\tconst run =\n\t\t\ttypeof configOrName === \"string\"\n\t\t\t\t? (handler as NodeHandler<TState>)\n\t\t\t\t: configOrName.run;\n\t\tconst label =\n\t\t\ttypeof configOrName === \"string\" ? options?.label : configOrName.label;\n\t\tconst hideFromFunnel =\n\t\t\ttypeof configOrName === \"string\"\n\t\t\t\t? options?.hideFromFunnel\n\t\t\t\t: configOrName.hideFromFunnel;\n\n\t\tif (id === START || id === END) {\n\t\t\tthrow new Error(\n\t\t\t\t`\"${id}\" is a reserved name and cannot be used as a node name`,\n\t\t\t);\n\t\t}\n\t\tif (this.nodes.has(id)) {\n\t\t\tthrow new Error(`Node \"${id}\" already exists`);\n\t\t}\n\n\t\tthis.nodes.set(id, run);\n\t\tif (label !== undefined || hideFromFunnel !== undefined) {\n\t\t\tthis.nodeOptions.set(id, { label: label ?? id, hideFromFunnel });\n\t\t}\n\t\treturn this as unknown as StateGraph<TState, TNodes | TName>;\n\t}\n\n\t/**\n\t * Add a direct edge between two nodes.\n\t *\n\t * Use `START` as `from` to set the entry point.\n\t * Use `END` as `to` to mark a terminal node.\n\t */\n\taddEdge(from: typeof START | TNodes, to: TNodes | typeof END): this {\n\t\tif (this.edges.has(from)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Node \"${from}\" already has an outgoing edge. Use addConditionalEdge for branching.`,\n\t\t\t);\n\t\t}\n\t\tthis.edges.set(from, { type: \"direct\", to });\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add a conditional edge from a node.\n\t *\n\t * Declare every node the branch can lead to in `to`, then `condition`\n\t * receives the current state and returns which of those nodes to go to next.\n\t * The condition's return type is constrained to `to`, so it can never route\n\t * somewhere undeclared — and graph introspection (funnel sync, Mermaid\n\t * output, the platform's flow graph) reads `to` directly, correct by\n\t * construction.\n\t *\n\t * @example\n\t * ```ts\n\t * .addConditionalEdge(\n\t * \"analyze_email\",\n\t * [\"done\", \"ask_company\"],\n\t * (state) => (state.isCompanyEmail ? \"done\" : \"ask_company\"),\n\t * )\n\t * ```\n\t */\n\taddConditionalEdge<const TTo extends TNodes | typeof END>(\n\t\tfrom: TNodes,\n\t\tto: readonly TTo[],\n\t\tcondition: (state: Partial<TState>) => NoInfer<TTo> | Promise<NoInfer<TTo>>,\n\t): this {\n\t\tif (this.edges.has(from)) {\n\t\t\tthrow new Error(`Node \"${from}\" already has an outgoing edge.`);\n\t\t}\n\t\tif (to.length === 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`Conditional edge from \"${from}\" must declare at least one target node in \\`to\\`.`,\n\t\t\t);\n\t\t}\n\t\tthis.edges.set(from, {\n\t\t\ttype: \"conditional\",\n\t\t\tcondition: condition as ConditionFn<TState>,\n\t\t\ttargets: [...to],\n\t\t});\n\t\treturn this;\n\t}\n\n\t/**\n\t * Generate a Mermaid `flowchart TD` diagram of the graph.\n\t *\n\t * Direct edges use solid arrows. Conditional edges render one dashed arrow\n\t * per declared target.\n\t *\n\t * @example\n\t * ```ts\n\t * console.log(graph.graph());\n\t * // flowchart TD\n\t * // __start__((Start))\n\t * // ask_name[ask_name]\n\t * // greet[greet]\n\t * // __end__((End))\n\t * // __start__ --> ask_name\n\t * // ask_name --> greet\n\t * // greet --> __end__\n\t * ```\n\t */\n\tgraph(): string {\n\t\treturn buildMermaidGraph(this.nodes, this.edges);\n\t}\n\n\t/**\n\t * Compile the graph into a RegisteredFlow that can be registered on an McpServer.\n\t *\n\t * Validates the graph structure and returns a registration-compatible object.\n\t */\n\tcompile(options?: { store?: FlowStore }): RegisteredFlow {\n\t\tthis.validate();\n\n\t\tconst nodesCopy = new Map(this.nodes);\n\t\tconst edgesCopy = new Map(this.edges);\n\n\t\treturn compileFlow<TState>({\n\t\t\tconfig: this.config,\n\t\t\tnodes: nodesCopy,\n\t\t\tedges: edgesCopy,\n\t\t\tstore: options?.store,\n\t\t\tgraph: () => buildMermaidGraph(nodesCopy, edgesCopy),\n\t\t\tnodeOptions: new Map(this.nodeOptions),\n\t\t});\n\t}\n\n\tprivate validate(): void {\n\t\t// Must have a START edge\n\t\tif (!this.edges.has(START)) {\n\t\t\tthrow new Error(\n\t\t\t\t'Flow must have an entry point. Add an edge from START: .addEdge(START, \"first_node\")',\n\t\t\t);\n\t\t}\n\n\t\t// START edge target must exist\n\t\tconst startEdge = this.edges.get(START);\n\t\tif (\n\t\t\tstartEdge?.type === \"direct\" &&\n\t\t\tstartEdge.to !== END &&\n\t\t\t!this.nodes.has(startEdge.to)\n\t\t) {\n\t\t\tthrow new Error(\n\t\t\t\t`START edge references non-existent node: \"${startEdge.to}\"`,\n\t\t\t);\n\t\t}\n\n\t\t// All static edge targets must reference existing nodes (or END)\n\t\tfor (const [from, edge] of this.edges) {\n\t\t\tif (from !== START && !this.nodes.has(from)) {\n\t\t\t\tthrow new Error(`Edge from non-existent node: \"${from}\"`);\n\t\t\t}\n\t\t\tif (\n\t\t\t\tedge.type === \"direct\" &&\n\t\t\t\tedge.to !== END &&\n\t\t\t\t!this.nodes.has(edge.to)\n\t\t\t) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Edge from \"${from}\" references non-existent node: \"${edge.to}\"`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (edge.type === \"conditional\" && edge.targets) {\n\t\t\t\tfor (const target of edge.targets) {\n\t\t\t\t\tif (target !== END && !this.nodes.has(target)) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`Conditional edge from \"${from}\" declares non-existent target: \"${target}\"`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Every node must have an outgoing edge\n\t\tfor (const [name] of this.nodes) {\n\t\t\tif (!this.edges.has(name)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Node \"${name}\" has no outgoing edge. Add one with .addEdge(\"${name}\", ...) or .addConditionalEdge(\"${name}\", ...)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n}\n","import type { z } from \"zod\";\nimport type { FlowConfig, InferFlowState } from \"./@types\";\nimport { StateGraph } from \"./state-graph\";\n\n/**\n * Create a new flow graph — convenience factory for `new StateGraph()`.\n *\n * The state type is automatically inferred from the `state` definition —\n * no explicit generic parameter needed.\n *\n * @example\n * ```ts\n * import { createFlow, interrupt, START, END } from \"@waniwani/sdk/mcp\";\n * import { z } from \"zod\";\n *\n * const flow = createFlow({\n * id: \"onboarding\",\n * title: \"User Onboarding\",\n * description: \"Guides users through onboarding. Use when a user wants to get started.\",\n * state: {\n * name: z.string().describe(\"The user's name\"),\n * email: z.string().describe(\"The user's email address\"),\n * },\n * })\n * .addNode({\n * id: \"ask_name\",\n * run: () => interrupt({ question: \"What's your name?\", field: \"name\" }),\n * })\n * .addNode({\n * id: \"ask_email\",\n * run: () => interrupt({ question: \"What's your email?\", field: \"email\" }),\n * })\n * .addEdge(START, \"ask_name\")\n * .addEdge(\"ask_name\", \"ask_email\")\n * .addEdge(\"ask_email\", END)\n * .compile();\n * ```\n */\nexport function createFlow<const TSchema extends Record<string, z.ZodType>>(\n\tconfig: Omit<FlowConfig, \"state\"> & { state: TSchema },\n): StateGraph<InferFlowState<TSchema>> {\n\treturn new StateGraph<InferFlowState<TSchema>>(config);\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type {\n\tFlowCompleteContent,\n\tFlowContent,\n\tFlowErrorContent,\n\tFlowInterruptContent,\n\tFlowTokenContent,\n\tFlowWidgetContent,\n\tRegisteredFlow,\n} from \"./@types\";\nimport type { FlowStore } from \"./flow-store\";\n\n// ============================================================================\n// Test harness for compiled flows\n// ============================================================================\n\ntype WithDecodedState = { decodedState: FlowTokenContent | null };\n\nexport type FlowTestResult =\n\t| (FlowInterruptContent & WithDecodedState)\n\t| (FlowWidgetContent & WithDecodedState)\n\t| (FlowCompleteContent & WithDecodedState)\n\t| (FlowErrorContent & WithDecodedState);\n\ntype Handler = (input: unknown, extra: unknown) => Promise<unknown>;\ntype RegisterToolArgs = [string, Record<string, unknown>, Handler];\n\nfunction parsePayload(result: Record<string, unknown>): FlowContent {\n\tconst content = result.content as Array<{ type: string; text?: string }>;\n\treturn JSON.parse(content[0]?.text ?? \"\") as FlowContent;\n}\n\nexport async function createFlowTestHarness(\n\tflow: RegisteredFlow,\n\toptions?: { stateStore?: FlowStore },\n) {\n\tconst store = options?.stateStore;\n\tconst registered: RegisterToolArgs[] = [];\n\tconst sessionId = `test-session-${Math.random().toString(36).slice(2, 10)}`;\n\n\tconst server = {\n\t\tregisterTool: (...args: unknown[]) => {\n\t\t\tregistered.push(args as RegisterToolArgs);\n\t\t},\n\t} as unknown as McpServer;\n\n\tawait flow.register(server);\n\n\tconst handler = registered[0]?.[2];\n\tif (!handler) {\n\t\tthrow new Error(`Flow \"${flow.name}\" did not register a handler`);\n\t}\n\n\tconst extra = { _meta: { sessionId } };\n\n\tasync function toResult(parsed: FlowContent): Promise<FlowTestResult> {\n\t\treturn {\n\t\t\t...parsed,\n\t\t\tdecodedState: store ? await store.get(sessionId) : null,\n\t\t} satisfies FlowTestResult;\n\t}\n\n\treturn {\n\t\tasync start(\n\t\t\tintent: string,\n\t\t\tstateUpdates?: Record<string, unknown>,\n\t\t\tcontext?: string,\n\t\t): Promise<FlowTestResult> {\n\t\t\tconst result = (await handler(\n\t\t\t\t{\n\t\t\t\t\taction: \"start\",\n\t\t\t\t\tintent,\n\t\t\t\t\t...(context ? { context } : {}),\n\t\t\t\t\t...(stateUpdates ? { stateUpdates } : {}),\n\t\t\t\t},\n\t\t\t\textra,\n\t\t\t)) as Record<string, unknown>;\n\t\t\treturn toResult(parsePayload(result));\n\t\t},\n\n\t\tasync continueWith(\n\t\t\tstateUpdates?: Record<string, unknown>,\n\t\t): Promise<FlowTestResult> {\n\t\t\tconst result = (await handler(\n\t\t\t\t{\n\t\t\t\t\taction: \"continue\",\n\t\t\t\t\t...(stateUpdates ? { stateUpdates } : {}),\n\t\t\t\t},\n\t\t\t\textra,\n\t\t\t)) as Record<string, unknown>;\n\t\t\treturn toResult(parsePayload(result));\n\t\t},\n\n\t\tasync resetWith(\n\t\t\tstateUpdates: Record<string, unknown>,\n\t\t): Promise<FlowTestResult> {\n\t\t\tconst result = (await handler(\n\t\t\t\t{\n\t\t\t\t\taction: \"reset\",\n\t\t\t\t\tstateUpdates,\n\t\t\t\t},\n\t\t\t\textra,\n\t\t\t)) as Record<string, unknown>;\n\t\t\treturn toResult(parsePayload(result));\n\t\t},\n\n\t\tasync lastState(): Promise<FlowTokenContent | null> {\n\t\t\treturn store ? store.get(sessionId) : null;\n\t\t},\n\t};\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { KbSearchTrace } from \"./types.js\";\n\n// Per-request bucket the tool wrapper opens; kb.search records into it.\nexport interface RetrievalCollector {\n\tsearches: KbSearchTrace[];\n}\n\n// Global slot, not a module-level instance: core (kb.search) and mcp\n// (withWaniwani) ship as separate bundles, so only globalThis keeps a single\n// shared AsyncLocalStorage per process. Two SDK copies in one process share\n// this slot, so RetrievalCollector's shape must stay backward-compatible.\nconst globalWithStore = globalThis as typeof globalThis & {\n\t__waniwaniKbRetrievalStore?: AsyncLocalStorage<RetrievalCollector>;\n};\n\nlet store = globalWithStore.__waniwaniKbRetrievalStore;\nif (!store) {\n\tstore = new AsyncLocalStorage<RetrievalCollector>();\n\tglobalWithStore.__waniwaniKbRetrievalStore = store;\n}\nexport const retrievalCollectorStore = store;\n\n// Best-effort: recording runs inside kb.search(), so it must never throw back\n// into a user's search (e.g. a corrupted collector from a mixed SDK version).\nexport function recordKbSearch(trace: KbSearchTrace): void {\n\ttry {\n\t\tretrievalCollectorStore.getStore()?.searches.push(trace);\n\t} catch {}\n}\n","// KB Client — thin HTTP wrapper for knowledge base API\n\nimport { WaniWaniError } from \"../error.js\";\nimport type { InternalConfig } from \"../types.js\";\nimport { recordKbSearch } from \"./retrieval-context.js\";\nimport type {\n\tKbClient,\n\tKbIngestFile,\n\tKbIngestResult,\n\tKbSearchOptions,\n\tKbSource,\n\tSearchResult,\n} from \"./types.js\";\n\nconst SDK_NAME = \"@waniwani/sdk\";\n\nexport function createKbClient(config: InternalConfig): KbClient {\n\tconst { apiUrl, apiKey } = config;\n\n\tfunction requireApiKey(): string {\n\t\tif (!apiKey) {\n\t\t\tthrow new Error(\"WANIWANI_API_KEY is not set\");\n\t\t}\n\t\treturn apiKey;\n\t}\n\n\tasync function request<T>(\n\t\tmethod: \"GET\" | \"POST\",\n\t\tpath: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tconst key = requireApiKey();\n\t\tconst url = `${apiUrl.replace(/\\/$/, \"\")}${path}`;\n\n\t\tconst headers: Record<string, string> = {\n\t\t\tAuthorization: `Bearer ${key}`,\n\t\t\t\"X-WaniWani-SDK\": SDK_NAME,\n\t\t};\n\n\t\tconst init: RequestInit = { method, headers };\n\n\t\tif (body !== undefined) {\n\t\t\theaders[\"Content-Type\"] = \"application/json\";\n\t\t\tinit.body = JSON.stringify(body);\n\t\t}\n\n\t\tconst response = await fetch(url, init);\n\n\t\tif (!response.ok) {\n\t\t\tconst text = await response.text().catch(() => \"\");\n\t\t\tthrow new WaniWaniError(\n\t\t\t\ttext || `KB API error: HTTP ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t);\n\t\t}\n\n\t\tconst json = (await response.json()) as { data: T };\n\t\treturn json.data;\n\t}\n\n\treturn {\n\t\tasync ingest(files: KbIngestFile[]): Promise<KbIngestResult> {\n\t\t\treturn request<KbIngestResult>(\"POST\", \"/api/mcp/kb/ingest\", {\n\t\t\t\tfiles,\n\t\t\t});\n\t\t},\n\n\t\tasync search(\n\t\t\tquery: string,\n\t\t\toptions?: KbSearchOptions,\n\t\t): Promise<SearchResult[]> {\n\t\t\tconst results = await request<SearchResult[]>(\n\t\t\t\t\"POST\",\n\t\t\t\t\"/api/mcp/kb/search\",\n\t\t\t\t{\n\t\t\t\t\tquery,\n\t\t\t\t\t...options,\n\t\t\t\t},\n\t\t\t);\n\t\t\trecordKbSearch({\n\t\t\t\tquery,\n\t\t\t\tresultCount: results.length,\n\t\t\t\tresults: results.map((r) => ({\n\t\t\t\t\tsource: r.source,\n\t\t\t\t\theading: r.heading,\n\t\t\t\t\tscore: r.score,\n\t\t\t\t})),\n\t\t\t});\n\t\t\treturn results;\n\t\t},\n\n\t\tasync sources(): Promise<KbSource[]> {\n\t\t\treturn request<KbSource[]>(\"GET\", \"/api/mcp/kb/sources\");\n\t\t},\n\t};\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\n/**\n * Project-level configuration for Waniwani MCP projects.\n *\n * Mirrors the JSON Schema hosted at https://docs.waniwani.ai/waniwani.json.\n * The canonical config file is `waniwani.json` at the project root:\n *\n * ```json\n * {\n * \"$schema\": \"https://docs.waniwani.ai/waniwani.json\",\n * \"orgId\": \"...\",\n * \"projectId\": \"...\"\n * }\n * ```\n *\n * `waniwani()` and the CLI both read this file automatically — no\n * explicit import is required.\n */\nexport interface WaniWaniProjectConfig {\n\t/** URL of the JSON Schema for editor autocomplete. Ignored at runtime. */\n\t$schema?: string;\n\t/** Waniwani organization ID this project belongs to. */\n\torgId?: string;\n\t/** Waniwani MCP project ID. */\n\tprojectId?: string;\n\t/**\n\t * The base URL of the Waniwani API.\n\t * Falls back to the `WANIWANI_API_URL` env var when omitted here, then to\n\t * `https://app.waniwani.ai`.\n\t */\n\tapiUrl?: string;\n\t/**\n\t * Local port the MCP listens on during `waniwani dev`. Overridden by\n\t * `--port`. Defaults to 3000.\n\t */\n\tdevPort?: number;\n}\n\n// ---------------------------------------------------------------------------\n// waniwani.json loader\n// ---------------------------------------------------------------------------\n\nconst CONFIG_FILENAME = \"waniwani.json\";\n\nlet _cached: WaniWaniProjectConfig | null | undefined;\n\n/**\n * Load `waniwani.json` from the current working directory.\n *\n * Returns `null` if the file doesn't exist or the runtime doesn't support\n * synchronous filesystem reads (e.g. edge / worker runtimes). Cached after\n * the first call.\n *\n * @internal\n */\nexport function loadProjectConfig(): WaniWaniProjectConfig | null {\n\tif (_cached !== undefined) {\n\t\treturn _cached;\n\t}\n\n\ttry {\n\t\tconst filePath = resolve(process.cwd(), CONFIG_FILENAME);\n\t\tif (!existsSync(filePath)) {\n\t\t\t_cached = null;\n\t\t\treturn null;\n\t\t}\n\t\tconst raw = readFileSync(filePath, \"utf-8\");\n\t\t_cached = JSON.parse(raw) as WaniWaniProjectConfig;\n\t\treturn _cached;\n\t} catch {\n\t\t_cached = null;\n\t\treturn null;\n\t}\n}\n\n/**\n * Reset the cached config. Test-only.\n *\n * @internal\n */\nexport function resetProjectConfigCache(): void {\n\t_cached = undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Legacy: defineConfig + globalThis registration\n// ---------------------------------------------------------------------------\n\nconst GLOBAL_KEY = \"__waniwani_config__\" as const;\n\n/**\n * Register a Waniwani project configuration on `globalThis`.\n *\n * @deprecated Create a `waniwani.json` at the project root instead. The SDK\n * and CLI both auto-load that file — no `defineConfig` call required.\n * See https://docs.waniwani.ai/waniwani.json for the schema.\n */\nexport function defineConfig(\n\tconfig: WaniWaniProjectConfig,\n): WaniWaniProjectConfig {\n\t(globalThis as Record<string, unknown>)[GLOBAL_KEY] = config;\n\treturn config;\n}\n\n/**\n * Retrieve the globally registered config (set by `defineConfig`).\n *\n * @deprecated Use `loadProjectConfig()` instead. `defineConfig` is going away.\n * @internal\n */\nexport function getGlobalConfig(): WaniWaniProjectConfig | undefined {\n\treturn (globalThis as Record<string, unknown>)[GLOBAL_KEY] as\n\t\t| WaniWaniProjectConfig\n\t\t| undefined;\n}\n","import {\n\textractCorrelationId,\n\textractExternalUserId,\n\textractRequestId,\n\textractSessionId,\n\textractSource,\n\textractTraceId,\n\textractVisitorId,\n} from \"../mcp/server/utils.js\";\nimport type { EventType, LegacyTrackEvent, TrackInput } from \"./@types.js\";\nimport type { V2CorrelationIds, V2EventEnvelope } from \"./v2-types.js\";\n\nconst DEFAULT_SOURCE = \"@waniwani/sdk\";\n\nexport interface MapTrackEventOptions {\n\tnow?: () => Date;\n\tgenerateId?: () => string;\n\tsource?: string;\n}\n\nexport function mapTrackEventToV2(\n\tinput: TrackInput,\n\toptions: MapTrackEventOptions = {},\n): V2EventEnvelope {\n\tconst now = options.now ?? (() => new Date());\n\tconst generateId = options.generateId ?? createEventId;\n\tconst eventName = resolveEventName(input);\n\tconst meta = toRecord(input.meta);\n\tconst metadata = toRecord(input.metadata);\n\tconst correlation = resolveCorrelationIds(input, meta);\n\tconst eventId = takeNonEmptyString(input.eventId) ?? generateId();\n\tconst timestamp = normalizeTimestamp(input.timestamp, now);\n\tconst source =\n\t\ttakeNonEmptyString(input.source) ??\n\t\textractSource(meta) ??\n\t\toptions.source ??\n\t\tDEFAULT_SOURCE;\n\tconst rawLegacy = isLegacyTrackEvent(input) ? { ...input } : undefined;\n\n\tconst mappedMetadata: Record<string, unknown> = {\n\t\t...metadata,\n\t};\n\tif (Object.keys(meta).length > 0) {\n\t\tmappedMetadata.meta = meta;\n\t}\n\tif (rawLegacy) {\n\t\tmappedMetadata.rawLegacy = rawLegacy;\n\t}\n\n\treturn {\n\t\tid: eventId,\n\t\ttype: \"mcp.event\",\n\t\tname: eventName,\n\t\tsource,\n\t\ttimestamp,\n\t\tcorrelation,\n\t\tproperties: mapProperties(input, eventName),\n\t\tmetadata: mappedMetadata,\n\t\trawLegacy,\n\t};\n}\n\nexport function createEventId(): string {\n\tif (\n\t\ttypeof crypto !== \"undefined\" &&\n\t\ttypeof crypto.randomUUID === \"function\"\n\t) {\n\t\treturn `evt_${crypto.randomUUID()}`;\n\t}\n\n\treturn `evt_${Math.random().toString(36).slice(2, 10)}_${Date.now().toString(36)}`;\n}\n\nfunction mapProperties(\n\tinput: TrackInput,\n\teventName: EventType,\n): Record<string, unknown> {\n\tif (!isLegacyTrackEvent(input)) {\n\t\treturn toRecord(input.properties);\n\t}\n\n\tconst legacyProperties = mapLegacyProperties(input, eventName);\n\tconst explicitProperties = toRecord(input.properties);\n\treturn {\n\t\t...legacyProperties,\n\t\t...explicitProperties,\n\t};\n}\n\nfunction mapLegacyProperties(\n\tinput: LegacyTrackEvent,\n\teventName: EventType,\n): Record<string, unknown> {\n\tswitch (eventName) {\n\t\tcase \"tool.called\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (takeNonEmptyString(input.toolName)) {\n\t\t\t\tproperties.name = input.toolName;\n\t\t\t}\n\t\t\tif (takeNonEmptyString(input.toolType)) {\n\t\t\t\tproperties.type = input.toolType;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tcase \"link.clicked\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (takeNonEmptyString(input.linkUrl)) {\n\t\t\t\tproperties.url = input.linkUrl;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tdefault:\n\t\t\treturn {};\n\t}\n}\n\nfunction resolveEventName(input: TrackInput): EventType {\n\treturn isLegacyTrackEvent(input) ? input.eventType : input.event;\n}\n\nfunction resolveCorrelationIds(\n\tinput: TrackInput,\n\tmeta: Record<string, unknown>,\n): V2CorrelationIds {\n\tconst requestId =\n\t\ttakeNonEmptyString(input.requestId) ?? extractRequestId(meta);\n\n\tconst sessionId =\n\t\ttakeNonEmptyString(input.sessionId) ?? extractSessionId(meta);\n\n\tconst traceId = takeNonEmptyString(input.traceId) ?? extractTraceId(meta);\n\n\tconst externalUserId =\n\t\ttakeNonEmptyString(input.externalUserId) ?? extractExternalUserId(meta);\n\n\tconst visitorId =\n\t\ttakeNonEmptyString(input.visitorId) ?? extractVisitorId(meta);\n\n\tconst correlationId =\n\t\ttakeNonEmptyString(input.correlationId) ??\n\t\textractCorrelationId(meta) ??\n\t\trequestId;\n\n\tconst correlation: V2CorrelationIds = {};\n\tif (sessionId) {\n\t\tcorrelation.sessionId = sessionId;\n\t}\n\tif (traceId) {\n\t\tcorrelation.traceId = traceId;\n\t}\n\tif (requestId) {\n\t\tcorrelation.requestId = requestId;\n\t}\n\tif (correlationId) {\n\t\tcorrelation.correlationId = correlationId;\n\t}\n\tif (externalUserId) {\n\t\tcorrelation.externalUserId = externalUserId;\n\t}\n\tif (visitorId) {\n\t\tcorrelation.visitorId = visitorId;\n\t}\n\treturn correlation;\n}\n\nfunction normalizeTimestamp(\n\tinput: string | Date | undefined,\n\tnow: () => Date,\n): string {\n\tif (input instanceof Date) {\n\t\treturn input.toISOString();\n\t}\n\tif (typeof input === \"string\") {\n\t\tconst date = new Date(input);\n\t\tif (!Number.isNaN(date.getTime())) {\n\t\t\treturn date.toISOString();\n\t\t}\n\t}\n\treturn now().toISOString();\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\treturn {};\n\t}\n\treturn value as Record<string, unknown>;\n}\n\nfunction takeNonEmptyString(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") {\n\t\treturn undefined;\n\t}\n\tif (value.trim().length === 0) {\n\t\treturn undefined;\n\t}\n\treturn value;\n}\n\nfunction isLegacyTrackEvent(input: TrackInput): input is LegacyTrackEvent {\n\treturn \"eventType\" in input;\n}\n","import type {\n\tTrackingShutdownOptions,\n\tTrackingShutdownResult,\n} from \"./@types.js\";\nimport type {\n\tV2BatchRejectedEvent,\n\tV2BatchRequest,\n\tV2BatchResponse,\n\tV2EventEnvelope,\n} from \"./v2-types.js\";\n\ntype FetchFn = (\n\tinput: URL | RequestInfo,\n\tinit?: RequestInit,\n) => Promise<Response>;\n\nconst DEFAULT_ENDPOINT_PATH = \"/api/mcp/events/v2/batch\";\nconst DEFAULT_FLUSH_INTERVAL_MS = 1_000;\nconst DEFAULT_MAX_BATCH_SIZE = 20;\nconst DEFAULT_MAX_BUFFER_SIZE = 1_000;\nconst DEFAULT_MAX_RETRIES = 3;\nconst DEFAULT_RETRY_BASE_DELAY_MS = 200;\nconst DEFAULT_RETRY_MAX_DELAY_MS = 2_000;\nconst DEFAULT_SHUTDOWN_TIMEOUT_MS = 2_000;\nconst SDK_NAME = \"@waniwani/sdk\";\n// Keepalive requests are capped by browsers (64KiB in-flight quota); stay under it.\nconst TEARDOWN_MAX_BODY_BYTES = 60_000;\n\nconst AUTH_FAILURE_STATUS = new Set([401, 403]);\nconst RETRYABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504]);\n\n// `fetch` brand-checks its receiver, so it cannot be stored on an instance field.\n// Resolve it off `globalThis` per call so a host swapping `fetch` later is honoured.\nconst defaultFetch: FetchFn = (input, init) => globalThis.fetch(input, init);\n\ninterface Logger {\n\twarn: (message: string, ...args: unknown[]) => void;\n\terror: (message: string, ...args: unknown[]) => void;\n}\n\nexport interface V2TransportOptions {\n\tapiUrl: string;\n\t/**\n\t * Bearer credential for the ingest endpoint: secret key (`wwk_`), public\n\t * token (`wwp_`), or widget JWT. Omit only for unauthenticated proxies.\n\t */\n\tapiKey?: string;\n\tendpointPath?: string;\n\tflushIntervalMs?: number;\n\tmaxBatchSize?: number;\n\tmaxBufferSize?: number;\n\tmaxRetries?: number;\n\tretryBaseDelayMs?: number;\n\tretryMaxDelayMs?: number;\n\tshutdownTimeoutMs?: number;\n\tsdkVersion?: string;\n\tfetchFn?: FetchFn;\n\tlogger?: Logger;\n\tnow?: () => Date;\n\tsleep?: (delayMs: number) => Promise<void>;\n}\n\nexport interface V2BatchTransport {\n\tenqueue: (event: V2EventEnvelope) => void;\n\tflush: () => Promise<void>;\n\tshutdown: (\n\t\toptions?: TrackingShutdownOptions,\n\t) => Promise<TrackingShutdownResult>;\n\tpendingEvents: () => number;\n}\n\ntype SendBatchResult =\n\t| { kind: \"success\" }\n\t| { kind: \"retryable\"; reason: string }\n\t| { kind: \"permanent\"; reason: string }\n\t| { kind: \"auth\"; status: number }\n\t| {\n\t\t\tkind: \"partial\";\n\t\t\tretryable: V2EventEnvelope[];\n\t\t\tpermanent: V2EventEnvelope[];\n\t };\n\nexport function createV2BatchTransport(\n\toptions: V2TransportOptions,\n): V2BatchTransport {\n\treturn new BatchingV2Transport(options);\n}\n\nclass BatchingV2Transport implements V2BatchTransport {\n\tprivate readonly endpointUrl: string;\n\tprivate readonly flushIntervalMs: number;\n\tprivate readonly maxBatchSize: number;\n\tprivate readonly maxBufferSize: number;\n\tprivate readonly maxRetries: number;\n\tprivate readonly retryBaseDelayMs: number;\n\tprivate readonly retryMaxDelayMs: number;\n\tprivate readonly shutdownTimeoutMs: number;\n\tprivate readonly sdkVersion?: string;\n\tprivate readonly fetchFn: FetchFn;\n\tprivate readonly logger: Logger;\n\tprivate readonly now: () => Date;\n\tprivate readonly sleep: (delayMs: number) => Promise<void>;\n\tprivate readonly apiKey?: string;\n\n\tprivate readonly buffer: V2EventEnvelope[] = [];\n\tprivate flushTimer: ReturnType<typeof setInterval> | undefined;\n\tprivate flushScheduled = false;\n\tprivate flushScheduledTimer: ReturnType<typeof setTimeout> | undefined;\n\tprivate flushInFlight: Promise<void> | undefined;\n\tprivate inFlightCount = 0;\n\tprivate isStopped = false;\n\tprivate isShuttingDown = false;\n\n\tconstructor(options: V2TransportOptions) {\n\t\tthis.endpointUrl = joinUrl(\n\t\t\toptions.apiUrl,\n\t\t\toptions.endpointPath ?? DEFAULT_ENDPOINT_PATH,\n\t\t);\n\t\tthis.flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;\n\t\tthis.maxBatchSize = options.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;\n\t\tthis.maxBufferSize = options.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE;\n\t\tthis.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;\n\t\tthis.retryBaseDelayMs =\n\t\t\toptions.retryBaseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS;\n\t\tthis.retryMaxDelayMs =\n\t\t\toptions.retryMaxDelayMs ?? DEFAULT_RETRY_MAX_DELAY_MS;\n\t\tthis.shutdownTimeoutMs =\n\t\t\toptions.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;\n\t\tthis.fetchFn = options.fetchFn ?? defaultFetch;\n\t\tthis.logger = options.logger ?? console;\n\t\tthis.now = options.now ?? (() => new Date());\n\t\tthis.sleep =\n\t\t\toptions.sleep ??\n\t\t\t((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)));\n\t\tthis.apiKey = options.apiKey;\n\t\tthis.sdkVersion = options.sdkVersion;\n\n\t\tif (this.flushIntervalMs > 0) {\n\t\t\tthis.flushTimer = setInterval(() => {\n\t\t\t\tvoid this.flush();\n\t\t\t}, this.flushIntervalMs);\n\t\t}\n\n\t\tthis.registerBrowserTeardown();\n\t}\n\n\tenqueue(event: V2EventEnvelope): void {\n\t\tif (this.isStopped || this.isShuttingDown) {\n\t\t\tthis.logger.warn(\n\t\t\t\t\"[Waniwani] Tracking transport is stopped, dropping event %s\",\n\t\t\t\tevent.id,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.buffer.length >= this.maxBufferSize) {\n\t\t\tconst dropCount = this.buffer.length - this.maxBufferSize + 1;\n\t\t\tthis.buffer.splice(0, dropCount);\n\t\t\tthis.logger.warn(\n\t\t\t\t\"[Waniwani] Tracking buffer overflow, dropped %d oldest event(s)\",\n\t\t\t\tdropCount,\n\t\t\t);\n\t\t}\n\n\t\tthis.buffer.push(event);\n\n\t\tif (this.buffer.length >= this.maxBatchSize) {\n\t\t\tvoid this.flush();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.scheduleMicroFlush();\n\t}\n\n\tpendingEvents(): number {\n\t\treturn this.buffer.length + this.inFlightCount;\n\t}\n\n\tasync flush(): Promise<void> {\n\t\tif (this.flushInFlight) {\n\t\t\treturn this.flushInFlight;\n\t\t}\n\t\tthis.flushInFlight = this.flushLoop().finally(() => {\n\t\t\tthis.flushInFlight = undefined;\n\t\t});\n\t\treturn this.flushInFlight;\n\t}\n\n\tasync shutdown(\n\t\toptions?: TrackingShutdownOptions,\n\t): Promise<TrackingShutdownResult> {\n\t\tthis.isShuttingDown = true;\n\t\tif (this.flushTimer) {\n\t\t\tclearInterval(this.flushTimer);\n\t\t\tthis.flushTimer = undefined;\n\t\t}\n\t\tif (this.flushScheduledTimer) {\n\t\t\tclearTimeout(this.flushScheduledTimer);\n\t\t\tthis.flushScheduledTimer = undefined;\n\t\t\tthis.flushScheduled = false;\n\t\t}\n\n\t\tconst timeoutMs = options?.timeoutMs ?? this.shutdownTimeoutMs;\n\t\tconst flushPromise = this.flush();\n\n\t\tif (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n\t\t\tawait flushPromise;\n\t\t\tthis.isStopped = true;\n\t\t\treturn { timedOut: false, pendingEvents: this.pendingEvents() };\n\t\t}\n\n\t\tconst timeoutSignal = Symbol(\"shutdown-timeout\");\n\t\tconst result = await Promise.race([\n\t\t\tflushPromise.then(() => \"flushed\" as const),\n\t\t\tthis.sleep(timeoutMs).then(() => timeoutSignal),\n\t\t]);\n\n\t\tif (result === timeoutSignal) {\n\t\t\tthis.isStopped = true;\n\t\t\treturn { timedOut: true, pendingEvents: this.pendingEvents() };\n\t\t}\n\n\t\tthis.isStopped = true;\n\t\treturn { timedOut: false, pendingEvents: this.pendingEvents() };\n\t}\n\n\t/**\n\t * In browsers, drain the buffer with keepalive fetches when the page is\n\t * hidden or unloading. The regular async flush loop does not survive\n\t * navigation; `keepalive` requests do. No-op outside a DOM environment.\n\t */\n\tprivate registerBrowserTeardown(): void {\n\t\tif (typeof document === \"undefined\" || typeof window === \"undefined\") {\n\t\t\treturn;\n\t\t}\n\n\t\tconst teardownFlush = () => {\n\t\t\tif (document.visibilityState === \"hidden\") {\n\t\t\t\tthis.keepaliveFlush();\n\t\t\t}\n\t\t};\n\t\tdocument.addEventListener(\"visibilitychange\", teardownFlush);\n\t\twindow.addEventListener(\"pagehide\", () => this.keepaliveFlush());\n\t}\n\n\t/** Fire-and-forget flush of everything buffered, chunked under the keepalive body cap. */\n\tprivate keepaliveFlush(): void {\n\t\tif (this.isStopped || this.buffer.length === 0) {\n\t\t\treturn;\n\t\t}\n\t\tconst events = this.buffer.splice(0, this.buffer.length);\n\t\tthis.sendKeepaliveChunk(events);\n\t}\n\n\tprivate sendKeepaliveChunk(events: V2EventEnvelope[]): void {\n\t\tconst body = JSON.stringify(this.makeBatchRequest(events));\n\t\tif (body.length > TEARDOWN_MAX_BODY_BYTES && events.length > 1) {\n\t\t\tconst mid = Math.ceil(events.length / 2);\n\t\t\tthis.sendKeepaliveChunk(events.slice(0, mid));\n\t\t\tthis.sendKeepaliveChunk(events.slice(mid));\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tvoid this.fetchFn(this.endpointUrl, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: this.requestHeaders(),\n\t\t\t\tbody,\n\t\t\t\tkeepalive: true,\n\t\t\t}).catch(() => {});\n\t\t} catch {\n\t\t\t// Teardown is best-effort; never throw during page unload.\n\t\t}\n\t}\n\n\tprivate requestHeaders(): Record<string, string> {\n\t\tconst headers: Record<string, string> = {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\"X-WaniWani-SDK\": SDK_NAME,\n\t\t};\n\t\tif (this.apiKey) {\n\t\t\theaders.Authorization = `Bearer ${this.apiKey}`;\n\t\t}\n\t\treturn headers;\n\t}\n\n\tprivate scheduleMicroFlush(): void {\n\t\tif (this.flushScheduled) {\n\t\t\treturn;\n\t\t}\n\t\tthis.flushScheduled = true;\n\t\tthis.flushScheduledTimer = setTimeout(() => {\n\t\t\tthis.flushScheduledTimer = undefined;\n\t\t\tthis.flushScheduled = false;\n\t\t\tvoid this.flush();\n\t\t}, 0);\n\t}\n\n\tprivate async flushLoop(): Promise<void> {\n\t\twhile (this.buffer.length > 0 && !this.isStopped) {\n\t\t\tconst batch = this.buffer.splice(0, this.maxBatchSize);\n\t\t\tawait this.sendBatchWithRetry(batch);\n\t\t}\n\t}\n\n\tprivate async sendBatchWithRetry(batch: V2EventEnvelope[]): Promise<void> {\n\t\tlet attempt = 0;\n\t\tlet pendingBatch = batch;\n\n\t\twhile (pendingBatch.length > 0 && !this.isStopped) {\n\t\t\tthis.inFlightCount = pendingBatch.length;\n\t\t\tconst result = await this.sendBatchOnce(pendingBatch);\n\t\t\tthis.inFlightCount = 0;\n\n\t\t\tswitch (result.kind) {\n\t\t\t\tcase \"success\":\n\t\t\t\t\treturn;\n\t\t\t\tcase \"auth\":\n\t\t\t\t\tthis.stopTransportForAuthFailure(result.status, pendingBatch.length);\n\t\t\t\t\treturn;\n\t\t\t\tcase \"permanent\":\n\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\"[Waniwani] Dropping %d event(s) after permanent failure: %s\",\n\t\t\t\t\t\tpendingBatch.length,\n\t\t\t\t\t\tresult.reason,\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\tcase \"retryable\":\n\t\t\t\t\tif (attempt >= this.maxRetries) {\n\t\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\t\"[Waniwani] Dropping %d event(s) after retry exhaustion: %s\",\n\t\t\t\t\t\t\tpendingBatch.length,\n\t\t\t\t\t\t\tresult.reason,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tawait this.sleep(this.backoffDelayMs(attempt));\n\t\t\t\t\tattempt += 1;\n\t\t\t\t\tcontinue;\n\t\t\t\tcase \"partial\":\n\t\t\t\t\tif (result.permanent.length > 0) {\n\t\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\t\"[Waniwani] Dropping %d event(s) rejected as permanent\",\n\t\t\t\t\t\t\tresult.permanent.length,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (result.retryable.length === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (attempt >= this.maxRetries) {\n\t\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\t\"[Waniwani] Dropping %d retryable event(s) after retry exhaustion\",\n\t\t\t\t\t\t\tresult.retryable.length,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tpendingBatch = result.retryable;\n\t\t\t\t\tawait this.sleep(this.backoffDelayMs(attempt));\n\t\t\t\t\tattempt += 1;\n\t\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async sendBatchOnce(\n\t\tevents: V2EventEnvelope[],\n\t): Promise<SendBatchResult> {\n\t\tlet response: Response;\n\n\t\ttry {\n\t\t\tresponse = await this.fetchFn(this.endpointUrl, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: this.requestHeaders(),\n\t\t\t\tbody: JSON.stringify(this.makeBatchRequest(events)),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tkind: \"retryable\",\n\t\t\t\treason: getErrorMessage(error),\n\t\t\t};\n\t\t}\n\n\t\tif (AUTH_FAILURE_STATUS.has(response.status)) {\n\t\t\treturn { kind: \"auth\", status: response.status };\n\t\t}\n\n\t\tif (RETRYABLE_STATUS.has(response.status)) {\n\t\t\treturn {\n\t\t\t\tkind: \"retryable\",\n\t\t\t\treason: `HTTP ${response.status}`,\n\t\t\t};\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\treturn {\n\t\t\t\tkind: \"permanent\",\n\t\t\t\treason: `HTTP ${response.status}`,\n\t\t\t};\n\t\t}\n\n\t\tconst data = await parseJsonResponse<V2BatchResponse>(response);\n\t\tif (!data?.rejected || data.rejected.length === 0) {\n\t\t\treturn { kind: \"success\" };\n\t\t}\n\n\t\tconst partial = this.classifyRejectedEvents(events, data.rejected);\n\t\tif (partial.retryable.length === 0 && partial.permanent.length === 0) {\n\t\t\treturn { kind: \"success\" };\n\t\t}\n\n\t\treturn {\n\t\t\tkind: \"partial\",\n\t\t\tretryable: partial.retryable,\n\t\t\tpermanent: partial.permanent,\n\t\t};\n\t}\n\n\tprivate makeBatchRequest(events: V2EventEnvelope[]): V2BatchRequest {\n\t\treturn {\n\t\t\tsentAt: this.now().toISOString(),\n\t\t\tsource: {\n\t\t\t\tsdk: SDK_NAME,\n\t\t\t\tversion: this.sdkVersion ?? \"0.0.0\",\n\t\t\t},\n\t\t\tevents,\n\t\t};\n\t}\n\n\tprivate classifyRejectedEvents(\n\t\tevents: V2EventEnvelope[],\n\t\trejected: V2BatchRejectedEvent[],\n\t): {\n\t\tretryable: V2EventEnvelope[];\n\t\tpermanent: V2EventEnvelope[];\n\t} {\n\t\tconst byId = new Map(events.map((event) => [event.id, event]));\n\t\tconst retryable: V2EventEnvelope[] = [];\n\t\tconst permanent: V2EventEnvelope[] = [];\n\n\t\tfor (const rejectedEvent of rejected) {\n\t\t\tconst event = byId.get(rejectedEvent.eventId);\n\t\t\tif (!event) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (isRetryableRejectedEvent(rejectedEvent)) {\n\t\t\t\tretryable.push(event);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpermanent.push(event);\n\t\t}\n\n\t\treturn { retryable, permanent };\n\t}\n\n\tprivate backoffDelayMs(attempt: number): number {\n\t\tconst rawDelay = this.retryBaseDelayMs * 2 ** attempt;\n\t\treturn Math.min(rawDelay, this.retryMaxDelayMs);\n\t}\n\n\tprivate stopTransportForAuthFailure(\n\t\tstatus: number,\n\t\trejectedCount: number,\n\t): void {\n\t\tthis.isStopped = true;\n\t\tconst buffered = this.buffer.length;\n\t\tthis.buffer.splice(0, buffered);\n\t\tthis.logger.error(\n\t\t\t\"[Waniwani] Auth failure (HTTP %d). Stopping tracking transport and dropping %d queued event(s)\",\n\t\t\tstatus,\n\t\t\trejectedCount + buffered,\n\t\t);\n\t}\n}\n\nfunction isRetryableRejectedEvent(\n\trejectedEvent: V2BatchRejectedEvent,\n): boolean {\n\tif (rejectedEvent.retryable === true) {\n\t\treturn true;\n\t}\n\tconst code = rejectedEvent.code.toLowerCase();\n\treturn (\n\t\tcode.includes(\"timeout\") ||\n\t\tcode.includes(\"temporary\") ||\n\t\tcode.includes(\"unavailable\") ||\n\t\tcode.includes(\"rate_limit\") ||\n\t\tcode.includes(\"transient\") ||\n\t\tcode.includes(\"server\")\n\t);\n}\n\nasync function parseJsonResponse<T>(\n\tresponse: Response,\n): Promise<T | undefined> {\n\tconst body = await response.text();\n\tif (!body) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\treturn JSON.parse(body) as T;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction joinUrl(apiUrl: string, endpointPath: string): string {\n\tconst normalizedBase = apiUrl.endsWith(\"/\") ? apiUrl : `${apiUrl}/`;\n\tconst normalizedPath = endpointPath.startsWith(\"/\")\n\t\t? endpointPath.slice(1)\n\t\t: endpointPath;\n\treturn `${normalizedBase}${normalizedPath}`;\n}\n\nfunction getErrorMessage(error: unknown): string {\n\tif (error instanceof Error) {\n\t\treturn error.message;\n\t}\n\treturn String(error);\n}\n","// Tracking Module\n\nimport type { InternalConfig } from \"../types.js\";\nimport type {\n\tTrackFn,\n\tTrackInput,\n\tTrackingClient,\n\tTrackingShutdownOptions,\n} from \"./@types.js\";\nimport { mapTrackEventToV2 } from \"./mapper.js\";\nimport { createRevenueApi } from \"./revenue.js\";\nimport { createV2BatchTransport } from \"./transport.js\";\n\n// Re-export types\nexport type {\n\tCallableTrack,\n\tComparedPriceOption,\n\tConvertedProperties,\n\tErrorCauseType,\n\tEventType,\n\tLeadQualifiedProperties,\n\tLegacyTrackEvent,\n\tLinkClickedProperties,\n\tOptionSelectedProperties,\n\tPriceShownProperties,\n\tPricesComparedProperties,\n\tRevenueConvertedInput,\n\tRevenueLeadQualifiedInput,\n\tRevenueOptionSelectedInput,\n\tRevenuePriceShownInput,\n\tRevenuePricesComparedInput,\n\tRevenueTrackingApi,\n\tSessionErrorCodeType,\n\tSessionErrorProperties,\n\tToolCalledProperties,\n\tTrackEvent,\n\tTrackFn,\n\tTrackInput,\n\tTrackingClient,\n\tTrackingConfig,\n\tTrackingShutdownOptions,\n\tTrackingShutdownResult,\n} from \"./@types.js\";\nexport { ERROR_CAUSES, EVENT_TYPES, SESSION_ERROR_CODES } from \"./@types.js\";\nexport { createEventId, mapTrackEventToV2 } from \"./mapper.js\";\nexport type {\n\tV2BatchRejectedEvent,\n\tV2BatchRequest,\n\tV2BatchResponse,\n\tV2CorrelationIds,\n\tV2EnvelopeType,\n\tV2EventEnvelope,\n} from \"./v2-types.js\";\n\nexport function createTrackingClient(config: InternalConfig): TrackingClient {\n\tconst { apiUrl, apiKey, tracking } = config;\n\n\tfunction requireApiKey(): string {\n\t\tif (!apiKey) {\n\t\t\tthrow new Error(\"WANIWANI_API_KEY is not set\");\n\t\t}\n\t\treturn apiKey;\n\t}\n\n\tconst transport = apiKey\n\t\t? createV2BatchTransport({\n\t\t\t\tapiUrl,\n\t\t\t\tapiKey,\n\t\t\t\tendpointPath: tracking.endpointPath,\n\t\t\t\tflushIntervalMs: tracking.flushIntervalMs,\n\t\t\t\tmaxBatchSize: tracking.maxBatchSize,\n\t\t\t\tmaxBufferSize: tracking.maxBufferSize,\n\t\t\t\tmaxRetries: tracking.maxRetries,\n\t\t\t\tretryBaseDelayMs: tracking.retryBaseDelayMs,\n\t\t\t\tretryMaxDelayMs: tracking.retryMaxDelayMs,\n\t\t\t\tshutdownTimeoutMs: tracking.shutdownTimeoutMs,\n\t\t\t})\n\t\t: undefined;\n\n\t// Single enqueue path shared by track(), track.* revenue helpers, and identify().\n\tfunction emit(event: TrackInput): { eventId: string } {\n\t\trequireApiKey();\n\t\tconst mappedEvent = mapTrackEventToV2(event);\n\t\t// Identity is required server-side (sessionId, externalUserId, or\n\t\t// visitorId, possibly derived from meta). Warn early: without it the\n\t\t// ingest API rejects the event even though enqueue returns an id here.\n\t\tif (\n\t\t\t!mappedEvent.correlation.sessionId &&\n\t\t\t!mappedEvent.correlation.externalUserId &&\n\t\t\t!mappedEvent.correlation.visitorId\n\t\t) {\n\t\t\tconsole.warn(\n\t\t\t\t`[waniwani] event \"${mappedEvent.name}\" has no sessionId, externalUserId, or visitorId; ` +\n\t\t\t\t\t\"the ingest API requires one and will reject it.\",\n\t\t\t);\n\t\t}\n\t\ttransport?.enqueue(mappedEvent);\n\t\treturn { eventId: mappedEvent.id };\n\t}\n\n\tconst trackOnce = async (event: TrackInput): Promise<{ eventId: string }> =>\n\t\temit(event);\n\t// Revenue helpers attach flat onto `track` (track.priceShown(), …).\n\tconst track: TrackFn = Object.assign(trackOnce, createRevenueApi(trackOnce));\n\n\tconst client: TrackingClient = {\n\t\tasync identify(\n\t\t\tuserId: string,\n\t\t\tproperties?: Record<string, unknown>,\n\t\t\tmeta?: Record<string, unknown>,\n\t\t): Promise<{ eventId: string }> {\n\t\t\trequireApiKey();\n\t\t\tconst mappedEvent = mapTrackEventToV2({\n\t\t\t\tevent: \"user.identified\",\n\t\t\t\texternalUserId: userId,\n\t\t\t\tproperties,\n\t\t\t\tmeta,\n\t\t\t});\n\t\t\ttransport?.enqueue(mappedEvent);\n\t\t\treturn { eventId: mappedEvent.id };\n\t\t},\n\t\ttrack,\n\t\tasync flush(): Promise<void> {\n\t\t\trequireApiKey();\n\t\t\tawait transport?.flush();\n\t\t},\n\t\tasync shutdown(options?: TrackingShutdownOptions) {\n\t\t\trequireApiKey();\n\t\t\treturn (\n\t\t\t\t(await transport?.shutdown({\n\t\t\t\t\ttimeoutMs: options?.timeoutMs ?? tracking.shutdownTimeoutMs,\n\t\t\t\t})) ?? { timedOut: false, pendingEvents: 0 }\n\t\t\t);\n\t\t},\n\t};\n\n\tif (transport) {\n\t\tattachShutdownHooks(client, tracking.shutdownTimeoutMs);\n\t}\n\treturn client;\n}\n\nfunction attachShutdownHooks(\n\tclient: TrackingClient,\n\tdefaultTimeoutMs: number,\n): void {\n\tif (\n\t\ttypeof process === \"undefined\" ||\n\t\ttypeof process.once !== \"function\" ||\n\t\ttypeof process.on !== \"function\"\n\t) {\n\t\treturn;\n\t}\n\n\tconst shutdown = () => {\n\t\tvoid client.shutdown({ timeoutMs: defaultTimeoutMs });\n\t};\n\n\tprocess.once(\"beforeExit\", shutdown);\n\tprocess.once(\"SIGINT\", shutdown);\n\tprocess.once(\"SIGTERM\", shutdown);\n}\n","// Waniwani SDK - Main Entry\n\nimport { createDocumentsClient } from \"./documents/client.js\";\nimport { createKbClient } from \"./kb/client.js\";\nimport {\n\tgetGlobalConfig,\n\tloadProjectConfig,\n\ttype WaniWaniProjectConfig,\n} from \"./project-config.js\";\nimport { createTrackingClient } from \"./tracking/index.js\";\nimport {\n\tDEFAULT_API_URL,\n\ttype WaniWaniClient,\n\ttype WaniWaniConfig,\n} from \"./types.js\";\n\n/**\n * Create a Waniwani SDK client\n *\n * @param config - Configuration options. When omitted, reads `waniwani.json`\n * from the current working directory, then falls back to env vars.\n * @returns A fully typed Waniwani client\n *\n * @example\n * ```typescript\n * import { waniwani } from \"@waniwani/sdk\";\n * import { toNextJsHandler } from \"@waniwani/sdk/next-js\";\n *\n * const wani = waniwani({ apiKey: \"...\" });\n *\n * // Next.js route handler\n * export const { GET, POST } = toNextJsHandler(wani, {\n * chat: { systemPrompt: \"You are a helpful assistant.\" },\n * });\n * ```\n */\nexport function waniwani(\n\tconfig?: WaniWaniConfig | WaniWaniProjectConfig,\n): WaniWaniClient {\n\tconst projectConfig = config ?? loadProjectConfig() ?? getGlobalConfig();\n\tconst effective = projectConfig as\n\t\t| (WaniWaniConfig & WaniWaniProjectConfig)\n\t\t| undefined;\n\n\tconst apiUrl =\n\t\teffective?.apiUrl ?? process.env.WANIWANI_API_URL ?? DEFAULT_API_URL;\n\tconst apiKey = effective?.apiKey ?? process.env.WANIWANI_API_KEY;\n\tconst trackingConfig = {\n\t\tendpointPath:\n\t\t\teffective?.tracking?.endpointPath ?? \"/api/mcp/events/v2/batch\",\n\t\tflushIntervalMs: effective?.tracking?.flushIntervalMs ?? 1_000,\n\t\tmaxBatchSize: effective?.tracking?.maxBatchSize ?? 20,\n\t\tmaxBufferSize: effective?.tracking?.maxBufferSize ?? 1_000,\n\t\tmaxRetries: effective?.tracking?.maxRetries ?? 3,\n\t\tretryBaseDelayMs: effective?.tracking?.retryBaseDelayMs ?? 200,\n\t\tretryMaxDelayMs: effective?.tracking?.retryMaxDelayMs ?? 2_000,\n\t\tshutdownTimeoutMs: effective?.tracking?.shutdownTimeoutMs ?? 2_000,\n\t};\n\n\tconst internalConfig = { apiUrl, apiKey, tracking: trackingConfig };\n\n\t// Compose client from modules\n\tconst trackingClient = createTrackingClient(internalConfig);\n\tconst kbClient = createKbClient(internalConfig);\n\tconst documentsClient = createDocumentsClient(internalConfig);\n\n\treturn {\n\t\t...trackingClient,\n\t\tkb: kbClient,\n\t\tdocuments: documentsClient,\n\t\t_config: internalConfig,\n\t};\n}\n","/**\n * Server-side API route handler for frontend tracking events.\n *\n * Receives V2 batch payloads from the SDK's frontend tracking client\n * (`createFrontendClient`, `useWaniwani`, `chat.track`) configured with your\n * own endpoint, and forwards them to the Waniwani backend with the secret\n * API key. Use this proxy when you do not want any Waniwani credential in\n * the browser; the direct path (public token or widget JWT) needs no route.\n *\n * @example Next.js App Router\n * ```typescript\n * // app/api/waniwani/track/route.ts\n * import { createTrackingRoute } from \"@waniwani/sdk/mcp\";\n *\n * const handler = createTrackingRoute({\n * apiKey: process.env.WANIWANI_API_KEY,\n * apiUrl: process.env.WANIWANI_API_URL,\n * });\n *\n * export { handler as POST };\n * ```\n */\n\nimport type { EventType, TrackInput } from \"../../tracking/@types.js\";\nimport type { V2EventEnvelope } from \"../../tracking/v2-types.js\";\nimport type { WaniWaniConfig } from \"../../types.js\";\nimport { waniwani } from \"../../waniwani.js\";\n\nexport interface TrackingRouteOptions {\n\t/** API key for the Waniwani backend. Defaults to WANIWANI_API_KEY env var. */\n\tapiKey?: string;\n\t/** Base URL for the Waniwani backend. Defaults to https://app.waniwani.ai. */\n\tapiUrl?: string;\n}\n\n/** Batch payload sent by the frontend tracking client (the V2 batch shape). */\ninterface BatchPayload {\n\tevents: Partial<V2EventEnvelope>[];\n\tsentAt?: string;\n}\n\n/**\n * Map an incoming V2 envelope back to the SDK's TrackInput so the server\n * client re-validates, re-maps, and batches it like any other event.\n */\nfunction mapEnvelope(envelope: Partial<V2EventEnvelope>): TrackInput {\n\treturn {\n\t\tevent: (envelope.name ?? \"widget_render\") as EventType,\n\t\tproperties: envelope.properties,\n\t\tmetadata: envelope.metadata,\n\t\tsessionId: envelope.correlation?.sessionId,\n\t\ttraceId: envelope.correlation?.traceId,\n\t\trequestId: envelope.correlation?.requestId,\n\t\tcorrelationId: envelope.correlation?.correlationId,\n\t\texternalUserId: envelope.correlation?.externalUserId,\n\t\tvisitorId: envelope.correlation?.visitorId,\n\t\teventId: envelope.id,\n\t\ttimestamp: envelope.timestamp,\n\t\tsource: envelope.source,\n\t} as TrackInput;\n}\n\n/**\n * Creates a POST handler that receives frontend tracking batches and\n * forwards them to the Waniwani backend.\n */\nexport function createTrackingRoute(options?: TrackingRouteOptions) {\n\tconst config: WaniWaniConfig = {\n\t\tapiKey: options?.apiKey,\n\t\tapiUrl: options?.apiUrl,\n\t};\n\n\t// Lazy singleton — created on first request\n\tlet client: ReturnType<typeof waniwani> | undefined;\n\n\tfunction getClient() {\n\t\tif (!client) {\n\t\t\tclient = waniwani(config);\n\t\t}\n\t\treturn client;\n\t}\n\n\treturn async function handler(request: Request): Promise<Response> {\n\t\tlet body: BatchPayload;\n\t\ttry {\n\t\t\tbody = (await request.json()) as BatchPayload;\n\t\t} catch {\n\t\t\treturn new Response(JSON.stringify({ error: \"Invalid JSON\" }), {\n\t\t\t\tstatus: 400,\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t});\n\t\t}\n\n\t\tif (!Array.isArray(body.events) || body.events.length === 0) {\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({ error: \"Missing or empty events array\" }),\n\t\t\t\t{\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\tconst c = getClient();\n\t\t\tconst results: string[] = [];\n\n\t\t\tfor (const envelope of body.events) {\n\t\t\t\tconst result = await c.track(mapEnvelope(envelope));\n\t\t\t\tresults.push(result.eventId);\n\t\t\t}\n\n\t\t\tawait c.flush();\n\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({ ok: true, accepted: results.length }),\n\t\t\t\t{\n\t\t\t\t\tstatus: 200,\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t},\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"Unknown error\";\n\t\t\treturn new Response(JSON.stringify({ error: message }), {\n\t\t\t\tstatus: 500,\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t});\n\t\t}\n\t};\n}\n","/**\n * Widget token minting — fetches short-lived JWTs from the Waniwani backend\n * so browser widgets can POST events directly.\n */\n\nimport { createLogger } from \"../../utils/logger.js\";\n\nconst log = createLogger(\"widget-token\");\n\ninterface WidgetTokenConfig {\n\tapiUrl: string;\n\tapiKey: string;\n}\n\ninterface WidgetTokenResult {\n\ttoken: string;\n\texpiresAt: string;\n}\n\ninterface CachedToken {\n\ttoken: string;\n\t/** Unix ms when the token expires */\n\texpiresAt: number;\n}\n\n/** Re-mint when < 2 minutes remain to avoid using nearly-expired tokens. */\nconst EXPIRY_BUFFER_MS = 2 * 60 * 1000;\n\nexport class WidgetTokenCache {\n\tprivate cached: CachedToken | null = null;\n\tprivate pending: Promise<string | null> | null = null;\n\tprivate readonly config: WidgetTokenConfig;\n\n\tconstructor(config: WidgetTokenConfig) {\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * Get a valid widget token. Returns a cached token if still fresh,\n\t * otherwise mints a new one. Returns `null` on failure.\n\t */\n\tasync getToken(sessionId?: string, traceId?: string): Promise<string | null> {\n\t\tif (this.cached && Date.now() < this.cached.expiresAt - EXPIRY_BUFFER_MS) {\n\t\t\treturn this.cached.token;\n\t\t}\n\n\t\t// Deduplicate concurrent requests\n\t\tif (this.pending) {\n\t\t\treturn this.pending;\n\t\t}\n\n\t\tthis.pending = this.mint(sessionId, traceId).finally(() => {\n\t\t\tthis.pending = null;\n\t\t});\n\n\t\treturn this.pending;\n\t}\n\n\tprivate async mint(\n\t\tsessionId?: string,\n\t\ttraceId?: string,\n\t): Promise<string | null> {\n\t\tconst url = joinUrl(this.config.apiUrl, \"/api/mcp/widget-tokens\");\n\t\tlog(\"minting token from\", url);\n\n\t\tconst body: Record<string, string> = {};\n\t\tif (sessionId) {\n\t\t\tbody.sessionId = sessionId;\n\t\t}\n\t\tif (traceId) {\n\t\t\tbody.traceId = traceId;\n\t\t}\n\n\t\ttry {\n\t\t\tconst response = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\tAuthorization: `Bearer ${this.config.apiKey}`,\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\tsignal: AbortSignal.timeout(5_000),\n\t\t\t});\n\t\t\tlog(\"mint response:\", response.status);\n\n\t\t\tif (!response.ok) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst json = (await response.json()) as Record<string, unknown>;\n\n\t\t\t// Support both flat { token, expiresAt } and nested { data: { token, expiresAt } }\n\t\t\tconst result = (\n\t\t\t\tjson.data && typeof json.data === \"object\" ? json.data : json\n\t\t\t) as WidgetTokenResult;\n\n\t\t\tconst expiresAtMs = new Date(result.expiresAt).getTime();\n\t\t\tif (!result.token || Number.isNaN(expiresAtMs)) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tthis.cached = {\n\t\t\t\ttoken: result.token,\n\t\t\t\texpiresAt: expiresAtMs,\n\t\t\t};\n\n\t\t\treturn result.token;\n\t\t} catch (error) {\n\t\t\tlog(\"mint failed:\", error);\n\t\t\treturn null;\n\t\t}\n\t}\n}\n\nfunction joinUrl(apiUrl: string, path: string): string {\n\tconst base = apiUrl.endsWith(\"/\") ? apiUrl.slice(0, -1) : apiUrl;\n\treturn `${base}${path}`;\n}\n","import type { FlowGraph } from \"../flows/@types\";\n\nasync function hashData(data: string): Promise<string> {\n\tif (typeof globalThis.crypto?.subtle?.digest === \"function\") {\n\t\tconst buf = await crypto.subtle.digest(\n\t\t\t\"SHA-256\",\n\t\t\tnew TextEncoder().encode(data),\n\t\t);\n\t\treturn Array.from(new Uint8Array(buf))\n\t\t\t.map((b) => b.toString(16).padStart(2, \"0\"))\n\t\t\t.join(\"\");\n\t}\n\tlet hash = 0;\n\tfor (let i = 0; i < data.length; i++) {\n\t\thash = ((hash << 5) - hash + data.charCodeAt(i)) | 0;\n\t}\n\treturn `simple-${Math.abs(hash).toString(36)}`;\n}\n\nfunction hashGraph(graph: FlowGraph): Promise<string> {\n\treturn hashData(JSON.stringify({ nodes: graph.nodes, edges: graph.edges }));\n}\n\nexport type FunnelSyncFlow = {\n\tflowId: string;\n\ttitle: string;\n\tconfigHash: string;\n\tnodes: FlowGraph[\"nodes\"];\n\tedges: FlowGraph[\"edges\"];\n};\n\nexport type FunnelSyncPayload = {\n\tcompositeHash: string;\n\tflows: FunnelSyncFlow[];\n};\n\nexport async function prepareFunnelSyncPayload(\n\tflowGraphs: FlowGraph[],\n): Promise<FunnelSyncPayload | null> {\n\tif (flowGraphs.length === 0) {\n\t\treturn null;\n\t}\n\n\tconst sorted = [...flowGraphs].sort((a, b) =>\n\t\ta.flowId.localeCompare(b.flowId),\n\t);\n\n\tconst compositeHash = await hashData(\n\t\tJSON.stringify(\n\t\t\tsorted.map((fg) => ({\n\t\t\t\tflowId: fg.flowId,\n\t\t\t\tnodes: fg.nodes,\n\t\t\t\tedges: fg.edges,\n\t\t\t})),\n\t\t),\n\t);\n\n\tconst flows = await Promise.all(\n\t\tflowGraphs.map(async (fg) => ({\n\t\t\tflowId: fg.flowId,\n\t\t\ttitle: fg.title,\n\t\t\tconfigHash: await hashGraph(fg),\n\t\t\tnodes: fg.nodes,\n\t\t\tedges: fg.edges,\n\t\t})),\n\t);\n\n\treturn { compositeHash, flows };\n}\n","import type { KbSearchTrace } from \"../../../kb/types.js\";\nimport type { ErrorCauseType } from \"../../../tracking/@types.js\";\nimport type {\n\tCallableTrack,\n\tToolCalledProperties,\n\tTrackInput,\n} from \"../../../tracking/index.js\";\nimport type { WaniWaniClient } from \"../../../types.js\";\nimport {\n\textractSessionId,\n\textractSource,\n\tFLOW_META_KEY,\n\tWIDGET_CONFIG_META_KEY,\n} from \"../utils.js\";\nimport type { WidgetTokenCache } from \"../widget-token.js\";\nimport type { FunnelSyncPayload } from \"./funnel-sync.js\";\n\ntype UnknownRecord = Record<string, unknown>;\n\nexport type WaniwaniTracker = Pick<\n\tWaniWaniClient,\n\t\"flush\" | \"identify\" | \"kb\" | \"_config\"\n> & {\n\t// with-waniwani only emits generic events, so a custom tracker need only\n\t// provide the callable `track` — not the flat `track.*` revenue helpers.\n\ttrack: CallableTrack;\n};\n\nconst SESSION_ID_KEY = \"waniwani/sessionId\";\nconst GEO_LOCATION_KEY = \"waniwani/geoLocation\";\nconst LEGACY_USER_LOCATION_KEY = \"waniwani/userLocation\";\nconst OPENAI_USER_LOCATION_KEY = \"openai/userLocation\";\n\nconst LOCATION_META_KEYS = [\n\tOPENAI_USER_LOCATION_KEY,\n\tGEO_LOCATION_KEY,\n\tLEGACY_USER_LOCATION_KEY,\n] as const;\n\nexport function stripLocationFieldsFromMeta(\n\tmeta: UnknownRecord,\n\tfields: readonly string[],\n): UnknownRecord {\n\tif (fields.length === 0) {\n\t\treturn meta;\n\t}\n\tlet next: UnknownRecord | undefined;\n\tfor (const key of LOCATION_META_KEYS) {\n\t\tconst value = meta[key];\n\t\tif (!isRecord(value)) {\n\t\t\tcontinue;\n\t\t}\n\t\tlet stripped: UnknownRecord | undefined;\n\t\tfor (const field of fields) {\n\t\t\tif (field in value) {\n\t\t\t\tif (!stripped) {\n\t\t\t\t\tstripped = { ...value };\n\t\t\t\t}\n\t\t\t\tdelete stripped[field];\n\t\t\t}\n\t\t}\n\t\tif (stripped) {\n\t\t\tif (!next) {\n\t\t\t\tnext = { ...meta };\n\t\t\t}\n\t\t\tnext[key] = stripped;\n\t\t}\n\t}\n\treturn next ?? meta;\n}\n\nexport function isRecord(value: unknown): value is UnknownRecord {\n\treturn Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nexport function extractMeta(extra: unknown): UnknownRecord | undefined {\n\tif (!isRecord(extra)) {\n\t\treturn undefined;\n\t}\n\tconst meta = extra._meta;\n\treturn isRecord(meta) ? meta : undefined;\n}\n\nexport function extractErrorText(result: unknown): string | undefined {\n\tif (!isRecord(result)) {\n\t\treturn undefined;\n\t}\n\tconst content = (result as UnknownRecord).content;\n\tif (!Array.isArray(content)) {\n\t\treturn undefined;\n\t}\n\tconst textPart = content.find(\n\t\t(c: unknown) =>\n\t\t\tisRecord(c) && c.type === \"text\" && typeof c.text === \"string\",\n\t) as UnknownRecord | undefined;\n\treturn textPart?.text as string | undefined;\n}\n\nexport function resolveToolType(\n\ttoolName: string,\n\ttoolTypeOption:\n\t\t| ToolCalledProperties[\"type\"]\n\t\t| ((toolName: string) => ToolCalledProperties[\"type\"] | undefined)\n\t\t| undefined,\n): ToolCalledProperties[\"type\"] {\n\tif (typeof toolTypeOption === \"function\") {\n\t\treturn toolTypeOption(toolName) ?? \"other\";\n\t}\n\treturn toolTypeOption ?? \"other\";\n}\n\nexport function buildTrackInput(\n\ttoolName: string,\n\textra: unknown,\n\toptions: {\n\t\ttoolType?: typeof resolveToolType extends (n: string, o: infer T) => unknown\n\t\t\t? T\n\t\t\t: never;\n\t\tmetadata?: UnknownRecord;\n\t\tstripLocationFields?: readonly string[];\n\t\tredactInput?: (input: unknown) => unknown;\n\t\tfunnelSync?: FunnelSyncPayload | null;\n\t},\n\ttiming?: {\n\t\tdurationMs: number;\n\t\tstatus: string;\n\t\terrorMessage?: string;\n\t\tcause?: ErrorCauseType;\n\t},\n\tclientInfo?: { name: string; version: string },\n\tio?: { input?: unknown; output?: unknown },\n\tkbSearch?: KbSearchTrace[],\n): TrackInput {\n\tconst toolType = resolveToolType(toolName, options.toolType);\n\n\tconst stripFields = options.stripLocationFields;\n\tconst shouldStrip = stripFields && stripFields.length > 0;\n\n\tconst rawMeta = extractMeta(extra);\n\tconst meta =\n\t\trawMeta && shouldStrip\n\t\t\t? stripLocationFieldsFromMeta(rawMeta, stripFields)\n\t\t\t: rawMeta;\n\n\tconst input =\n\t\tio?.input !== undefined && options.redactInput\n\t\t\t? options.redactInput(io.input)\n\t\t\t: io?.input;\n\n\tconst output =\n\t\tshouldStrip && isRecord(io?.output) && isRecord(io.output._meta)\n\t\t\t? {\n\t\t\t\t\t...(io.output as UnknownRecord),\n\t\t\t\t\t_meta: stripLocationFieldsFromMeta(\n\t\t\t\t\t\tio.output._meta as UnknownRecord,\n\t\t\t\t\t\tstripFields,\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t: io?.output;\n\n\tconst responseMeta =\n\t\tisRecord(io?.output) && isRecord((io.output as UnknownRecord)._meta)\n\t\t\t? ((io.output as UnknownRecord)._meta as UnknownRecord)\n\t\t\t: undefined;\n\tconst flowMeta = responseMeta?.[FLOW_META_KEY];\n\tconst baseMeta =\n\t\tflowMeta && meta\n\t\t\t? { ...meta, [FLOW_META_KEY]: flowMeta }\n\t\t\t: flowMeta\n\t\t\t\t? { [FLOW_META_KEY]: flowMeta }\n\t\t\t\t: meta;\n\n\t// Correlate to the flow session when the host carried none in `_meta` but the\n\t// caller forwarded a `sessionId` argument. On stateless hosts (Claude) widget\n\t// tool calls thread the flow session id through the LLM as a tool arg, so\n\t// without this their `tool.called` events would land in a sessionless bucket\n\t// instead of the originating flow session. This only enriches the tracked\n\t// event's meta — it does not touch `extra._meta`, so handler/flow behavior\n\t// (e.g. the flow's sessionId-echo logic) is unaffected.\n\tconst argSessionId =\n\t\tisRecord(io?.input) &&\n\t\ttypeof (io.input as UnknownRecord).sessionId === \"string\" &&\n\t\t((io.input as UnknownRecord).sessionId as string).length > 0\n\t\t\t? ((io.input as UnknownRecord).sessionId as string)\n\t\t\t: undefined;\n\tconst mergedMeta =\n\t\targSessionId && !extractSessionId(baseMeta ?? undefined)\n\t\t\t? { ...(baseMeta ?? {}), \"waniwani/sessionId\": argSessionId }\n\t\t\t: baseMeta;\n\n\treturn {\n\t\tevent: \"tool.called\",\n\t\tproperties: {\n\t\t\tname: toolName,\n\t\t\ttype: toolType,\n\t\t\t...(timing ?? {}),\n\t\t\t...(input !== undefined && { input }),\n\t\t\t...(output !== undefined && { output }),\n\t\t\t...(kbSearch && kbSearch.length > 0 && { kbSearch }),\n\t\t},\n\t\tmeta: mergedMeta,\n\t\tsource: extractSource(mergedMeta ?? meta, clientInfo),\n\t\tmetadata: {\n\t\t\t...(options.metadata ?? {}),\n\t\t\t...(clientInfo && { clientInfo }),\n\t\t\t...(options.funnelSync && { funnelSync: options.funnelSync }),\n\t\t},\n\t};\n}\n\nexport async function safeTrack(\n\ttracker: { track: CallableTrack },\n\tinput: TrackInput,\n\tonError?: (error: Error) => void,\n): Promise<void> {\n\ttry {\n\t\tawait tracker.track(input);\n\t} catch (error) {\n\t\tonError?.(toError(error));\n\t}\n}\n\nexport async function safeFlush(\n\ttracker: Pick<WaniWaniClient, \"flush\">,\n\tonError?: (error: Error) => void,\n): Promise<void> {\n\ttry {\n\t\tawait tracker.flush();\n\t} catch (error) {\n\t\tonError?.(toError(error));\n\t}\n}\n\nexport async function injectWidgetConfig(\n\tresult: unknown,\n\tcache: WidgetTokenCache | null,\n\tapiUrl: string,\n\textra?: unknown,\n\tonError?: (error: Error) => void,\n\tclientInfo?: { name?: string; version?: string },\n): Promise<void> {\n\tif (!isRecord(result)) {\n\t\treturn;\n\t}\n\n\tif (!isRecord(result._meta)) {\n\t\t(result as UnknownRecord)._meta = {};\n\t}\n\n\tconst meta = (result as UnknownRecord)._meta as UnknownRecord;\n\tconst existingWaniwaniConfig = isRecord(meta[WIDGET_CONFIG_META_KEY])\n\t\t? (meta[WIDGET_CONFIG_META_KEY] as UnknownRecord)\n\t\t: undefined;\n\tconst waniwaniConfig: UnknownRecord = {\n\t\t...(existingWaniwaniConfig ?? {}),\n\t\tendpoint:\n\t\t\texistingWaniwaniConfig?.endpoint ??\n\t\t\t`${apiUrl.replace(/\\/$/, \"\")}/api/mcp/events/v2/batch`,\n\t};\n\n\tif (cache) {\n\t\ttry {\n\t\t\tconst token = await cache.getToken();\n\t\t\tif (token) {\n\t\t\t\twaniwaniConfig.token = token;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tonError?.(toError(error));\n\t\t}\n\t}\n\n\tconst sessionId = extractSessionId(meta);\n\tif (sessionId) {\n\t\tif (!waniwaniConfig.sessionId) {\n\t\t\twaniwaniConfig.sessionId = sessionId;\n\t\t}\n\t}\n\n\tconst geoLocation = extractGeoLocation(meta);\n\tif (geoLocation !== undefined) {\n\t\tif (!waniwaniConfig.geoLocation) {\n\t\t\twaniwaniConfig.geoLocation = geoLocation;\n\t\t}\n\t}\n\n\tconst source = extractSource(extractMeta(extra), clientInfo);\n\tif (source && !waniwaniConfig.source) {\n\t\twaniwaniConfig.source = source;\n\t}\n\n\tmeta[WIDGET_CONFIG_META_KEY] = waniwaniConfig;\n}\n\n/**\n * Widget-related keys from a tool's **definition** `_meta` that the chat UI\n * and MCP hosts look for on tool call **results**. The MCP protocol exposes\n * these in `tools/list`, but chat clients that stream results via AI SDK\n * proxies (e.g. `@ai-sdk/mcp`) don't forward definition metadata to the\n * UI layer — the chat UI only sees the result's `_meta`.\n *\n * Forwarding these keys into every tool result makes widgets registered via\n * any MCP framework (skybridge, raw `@modelcontextprotocol/sdk`, etc.) render\n * in Waniwani chat without the handler having to set them manually.\n *\n * Keys:\n * - `openai/outputTemplate` — OpenAI Apps SDK widget URI (ChatGPT).\n * - `ui/resourceUri` — MCP Apps extension flat-key form (Claude, per spec).\n * - `ui` — MCP Apps extension nested form `{ resourceUri, autoHeight, ... }`.\n * - `openai/widgetAccessible`, `openai/resultCanProduceWidget`,\n * `openai/toolInvocation/invoking`, `openai/toolInvocation/invoked` —\n * additional OpenAI metadata widgets depend on.\n */\nconst WIDGET_META_KEYS = [\n\t\"openai/outputTemplate\",\n\t\"openai/widgetAccessible\",\n\t\"openai/resultCanProduceWidget\",\n\t\"openai/toolInvocation/invoking\",\n\t\"openai/toolInvocation/invoked\",\n\t\"ui/resourceUri\",\n\t\"ui\",\n] as const;\n\nexport function injectWidgetDefinitionMeta(\n\tresult: unknown,\n\tdefinitionMeta: UnknownRecord | undefined,\n): void {\n\tif (!definitionMeta || !isRecord(result)) {\n\t\treturn;\n\t}\n\n\tlet hasAnyKey = false;\n\tfor (const key of WIDGET_META_KEYS) {\n\t\tif (key in definitionMeta) {\n\t\t\thasAnyKey = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (!hasAnyKey) {\n\t\treturn;\n\t}\n\n\tif (!isRecord(result._meta)) {\n\t\t(result as UnknownRecord)._meta = {};\n\t}\n\tconst resultMeta = (result as UnknownRecord)._meta as UnknownRecord;\n\n\tfor (const key of WIDGET_META_KEYS) {\n\t\tif (!(key in definitionMeta)) {\n\t\t\tcontinue;\n\t\t}\n\t\t// Handler-set values win: never overwrite something the tool returned.\n\t\tif (key in resultMeta) {\n\t\t\tcontinue;\n\t\t}\n\t\tresultMeta[key] = definitionMeta[key];\n\t}\n}\n\nexport function injectRequestMetadata(result: unknown, extra: unknown): void {\n\tconst requestMeta = extractMeta(extra);\n\tif (!requestMeta) {\n\t\treturn;\n\t}\n\n\tif (!isRecord(result)) {\n\t\treturn;\n\t}\n\n\tif (!isRecord(result._meta)) {\n\t\t(result as UnknownRecord)._meta = {};\n\t}\n\n\tconst resultMeta = (result as UnknownRecord)._meta as UnknownRecord;\n\tconst sessionId = extractSessionId(requestMeta);\n\tif (sessionId && !resultMeta[SESSION_ID_KEY]) {\n\t\tresultMeta[SESSION_ID_KEY] = sessionId;\n\t}\n\n\tconst geoLocation = extractGeoLocation(requestMeta);\n\tif (!geoLocation) {\n\t\treturn;\n\t}\n\n\tif (!resultMeta[GEO_LOCATION_KEY]) {\n\t\tresultMeta[GEO_LOCATION_KEY] = geoLocation;\n\t}\n\n\tif (!resultMeta[LEGACY_USER_LOCATION_KEY]) {\n\t\tresultMeta[LEGACY_USER_LOCATION_KEY] = geoLocation;\n\t}\n}\n\nfunction extractGeoLocation(\n\tmeta: UnknownRecord | undefined,\n): UnknownRecord | string | undefined {\n\tif (!meta) {\n\t\treturn undefined;\n\t}\n\n\tconst geoLocation = meta[GEO_LOCATION_KEY] ?? meta[LEGACY_USER_LOCATION_KEY];\n\tif (isRecord(geoLocation) || typeof geoLocation === \"string\") {\n\t\treturn geoLocation;\n\t}\n\n\treturn undefined;\n}\n\nfunction toError(error: unknown): Error {\n\tif (error instanceof Error) {\n\t\treturn error;\n\t}\n\treturn new Error(String(error));\n}\n","type UnknownRecord = Record<string, unknown>;\n\nfunction isRecord(v: unknown): v is UnknownRecord {\n\treturn typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\n/**\n * Extract session ID from MCP transport metadata (`extra.sessionId`),\n * the raw `Mcp-Session-Id` HTTP header, or the `X-Waniwani-Session-Id`\n * header (set by stateless serverless deployments).\n */\nexport function extractTransportSessionId(\n\textra: UnknownRecord,\n): string | undefined {\n\tif (typeof extra.sessionId === \"string\" && extra.sessionId) {\n\t\treturn extra.sessionId;\n\t}\n\n\tif (isRecord(extra.requestInfo)) {\n\t\tconst rawHeaders = (extra.requestInfo as UnknownRecord).headers;\n\t\tif (isRecord(rawHeaders)) {\n\t\t\t// Normalize keys to lowercase — HTTP headers are case-insensitive\n\t\t\t// but the transport may preserve original casing (e.g. \"Mcp-Session-Id\").\n\t\t\tconst headers: UnknownRecord = {};\n\t\t\tfor (const key of Object.keys(rawHeaders)) {\n\t\t\t\theaders[key.toLowerCase()] = rawHeaders[key];\n\t\t\t}\n\t\t\tconst sid = headers[\"mcp-session-id\"];\n\t\t\tif (typeof sid === \"string\" && sid) {\n\t\t\t\treturn sid;\n\t\t\t}\n\t\t\tconst waniSid = headers[\"x-waniwani-session-id\"];\n\t\t\tif (typeof waniSid === \"string\" && waniSid) {\n\t\t\t\treturn waniSid;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn undefined;\n}\n","import {\n\ttype RetrievalCollector,\n\tretrievalCollectorStore,\n} from \"../../../kb/retrieval-context.js\";\nimport type { ToolCalledProperties } from \"../../../tracking/index.js\";\nimport { createLogger } from \"../../../utils/logger.js\";\nimport { waniwani } from \"../../../waniwani.js\";\nimport type { FlowGraph } from \"../flows/@types.js\";\nimport { REDACTED_STATE_UPDATE_FIELDS_META_KEY } from \"../flows/redacted.js\";\nimport { createScopedClient, SCOPED_CLIENT_KEY } from \"../scoped-client.js\";\nimport { classifyCause } from \"../session-errors/classify.js\";\nimport type { McpServer } from \"../types\";\nimport {\n\textractSessionId,\n\textractSource,\n\textractSourceFromHeaders,\n} from \"../utils.js\";\nimport { WidgetTokenCache } from \"../widget-token.js\";\nimport type { FunnelSyncPayload } from \"./funnel-sync.js\";\nimport { prepareFunnelSyncPayload } from \"./funnel-sync.js\";\nimport {\n\tbuildTrackInput,\n\textractErrorText,\n\textractMeta,\n\tinjectRequestMetadata,\n\tinjectWidgetConfig,\n\tinjectWidgetDefinitionMeta,\n\tisRecord,\n\tsafeFlush,\n\tsafeTrack,\n\ttype WaniwaniTracker,\n} from \"./helpers.js\";\nimport { extractTransportSessionId } from \"./transport-session.js\";\n\ntype UnknownRecord = Record<string, unknown>;\ntype RawHandler = (\n\tinput: unknown,\n\textra: unknown,\n) => Promise<unknown> | unknown;\n\ntype WrappedServer = McpServer & {\n\t__waniwaniWrapped?: true;\n};\n\nconst WRAPPED_HANDLER = Symbol.for(\"waniwani.wrappedHandler\");\n\ntype MaybeWrappedHandler = RawHandler & { [WRAPPED_HANDLER]?: true };\n\n/**\n * Options for withWaniwani().\n */\nexport type WithWaniwaniOptions = {\n\t/**\n\t * The Waniwani client instance. When omitted, a client is created\n\t * automatically from `waniwani.json` / the global config registered by\n\t * `defineConfig()`, falling back to env vars (`WANIWANI_API_KEY` and\n\t * `WANIWANI_API_URL`). Set `WANIWANI_API_URL` (e.g.\n\t * `https://eu.app.waniwani.ai`) so the auto-created client targets the\n\t * right region instead of defaulting to US.\n\t */\n\tclient?: WaniwaniTracker;\n\t/**\n\t * Optional explicit tool type. Defaults to `\"other\"`.\n\t */\n\ttoolType?:\n\t\t| ToolCalledProperties[\"type\"]\n\t\t| ((toolName: string) => ToolCalledProperties[\"type\"] | undefined);\n\t/**\n\t * Optional metadata merged into every tracked event.\n\t */\n\tmetadata?: UnknownRecord;\n\t/**\n\t * Flush tracking transport after each tool call.\n\t */\n\tflushAfterToolCall?: boolean;\n\t/**\n\t * Optional error callback for non-fatal tracking errors.\n\t */\n\tonError?: (error: Error) => void;\n\t/**\n\t * Inject widget tracking config into tool response `_meta.waniwani` so browser\n\t * widgets can send events directly to the Waniwani backend.\n\t *\n\t * Always injects `endpoint`. Injects `token` when an API key is configured\n\t * and token minting succeeds.\n\t *\n\t * @default true\n\t */\n\tinjectWidgetToken?: boolean;\n\t/**\n\t * List of field names to strip from known location `_meta` entries\n\t * (`openai/userLocation`, `waniwani/geoLocation`, `waniwani/userLocation`)\n\t * before events are sent to the Waniwani API. Applied to both the\n\t * request-level `_meta` and any `_meta` on the tool response.\n\t *\n\t * Pass e.g. `[\"latitude\", \"longitude\"]` to drop coordinates only, or\n\t * `[\"latitude\", \"longitude\", \"city\", \"region\"]` to keep just `country`.\n\t * Empty/omitted = no redaction.\n\t *\n\t * @default []\n\t */\n\tstripLocationFields?: readonly string[];\n\t/**\n\t * Replace `input.stateUpdates[field]` with `\"REDACTED\"` for any field\n\t * marked via `redacted()` on a flow state schema. When `false` (default),\n\t * the declarative markers are ignored and raw values are tracked.\n\t *\n\t * Wire this to an env var when you want real values in development logs\n\t * but redacted values in production.\n\t *\n\t * @default false\n\t */\n\tapplyFieldRedactions?: boolean;\n};\n\nconst log = createLogger(\"mcp\");\n\nconst DEFAULT_BASE_URL = \"https://app.waniwani.ai\";\n\nconst REDACTED_VALUE = \"REDACTED\";\n\nfunction buildStateUpdateRedactor(\n\tdefinitionMeta: UnknownRecord | undefined,\n): ((input: unknown) => unknown) | undefined {\n\tif (!definitionMeta) {\n\t\treturn undefined;\n\t}\n\tconst fields = definitionMeta[REDACTED_STATE_UPDATE_FIELDS_META_KEY];\n\tif (!Array.isArray(fields) || fields.length === 0) {\n\t\treturn undefined;\n\t}\n\tconst fieldSet = new Set(\n\t\tfields.filter((f): f is string => typeof f === \"string\"),\n\t);\n\tif (fieldSet.size === 0) {\n\t\treturn undefined;\n\t}\n\n\treturn (input: unknown) => {\n\t\tif (!isRecord(input)) {\n\t\t\treturn input;\n\t\t}\n\t\tconst stateUpdates = input.stateUpdates;\n\t\tif (!isRecord(stateUpdates)) {\n\t\t\treturn input;\n\t\t}\n\t\tlet changed = false;\n\t\tconst next: UnknownRecord = { ...stateUpdates };\n\t\tfor (const field of fieldSet) {\n\t\t\tif (field in next) {\n\t\t\t\tnext[field] = REDACTED_VALUE;\n\t\t\t\tchanged = true;\n\t\t\t}\n\t\t}\n\t\tif (!changed) {\n\t\t\treturn input;\n\t\t}\n\t\treturn { ...input, stateUpdates: next };\n\t};\n}\n\ntype WrapContext = {\n\tserver: McpServer;\n\ttracker: WaniwaniTracker;\n\topts: WithWaniwaniOptions;\n\ttokenCache: WidgetTokenCache | null;\n\tinjectToken: boolean;\n\tfunnelSync: FunnelSyncPayload | null;\n};\n\ntype UnknownRecordOrUndefined = UnknownRecord | undefined;\n\nfunction createWrappedHandler(\n\ttoolName: string,\n\toriginalHandler: RawHandler,\n\tctx: WrapContext,\n\tdefinitionMeta: UnknownRecordOrUndefined,\n): MaybeWrappedHandler {\n\tconst { server, tracker, opts, tokenCache, injectToken } = ctx;\n\n\tconst stateUpdateRedactor =\n\t\topts.applyFieldRedactions === true\n\t\t\t? buildStateUpdateRedactor(definitionMeta)\n\t\t\t: undefined;\n\tconst wrappedHandler: MaybeWrappedHandler = async (\n\t\tinput: unknown,\n\t\textra: unknown,\n\t) => {\n\t\tconst effectiveOpts = stateUpdateRedactor\n\t\t\t? {\n\t\t\t\t\t...opts,\n\t\t\t\t\tredactInput: stateUpdateRedactor,\n\t\t\t\t\tfunnelSync: ctx.funnelSync,\n\t\t\t\t}\n\t\t\t: { ...opts, funnelSync: ctx.funnelSync };\n\t\t// Inject scoped client into extra so createTool/flows can surface it\n\t\tconst meta = extractMeta(extra) ?? {};\n\n\t\tconst clientInfo = (\n\t\t\tserver as {\n\t\t\t\tserver?: {\n\t\t\t\t\tgetClientVersion?: () =>\n\t\t\t\t\t\t| { name: string; version: string }\n\t\t\t\t\t\t| undefined;\n\t\t\t\t};\n\t\t\t}\n\t\t).server?.getClientVersion?.();\n\n\t\t// Bridge transport-level session ID into _meta when the host doesn't\n\t\t// include one directly (e.g. Mcp-Session-Id HTTP header).\n\t\tconst existingSessionId = extractSessionId(meta);\n\t\tif (!existingSessionId && isRecord(extra)) {\n\t\t\tconst transportSid = extractTransportSessionId(extra as UnknownRecord);\n\t\t\tif (transportSid) {\n\t\t\t\tmeta[\"waniwani/sessionId\"] = transportSid;\n\t\t\t\t(extra as UnknownRecord)._meta = meta;\n\t\t\t}\n\t\t}\n\n\t\t// Resolve and stamp the caller source into _meta once, so downstream\n\t\t// consumers (flow nodes, nested tool handlers, tracking) can branch on\n\t\t// `waniwani/source` without each re-deriving it from clientInfo/headers.\n\t\t// Hosts like Claude carry no source in _meta and no transport session id;\n\t\t// clientInfo (MCP initialize) and the request headers are the only signals.\n\t\tif (!extractSource(meta) && isRecord(extra)) {\n\t\t\tconst headers = (extra as { requestInfo?: { headers?: unknown } })\n\t\t\t\t.requestInfo?.headers as Record<string, unknown> | undefined;\n\t\t\tconst resolvedSource =\n\t\t\t\textractSource(meta, clientInfo) ?? extractSourceFromHeaders(headers);\n\t\t\tif (resolvedSource) {\n\t\t\t\tmeta[\"waniwani/source\"] = resolvedSource;\n\t\t\t\t(extra as UnknownRecord)._meta = meta;\n\t\t\t}\n\t\t}\n\n\t\tconst scopedClient = createScopedClient(tracker, meta, {\n\t\t\tapiUrl: tracker._config.apiUrl,\n\t\t\tapiKey: tracker._config.apiKey,\n\t\t});\n\t\tif (isRecord(extra)) {\n\t\t\textra[SCOPED_CLIENT_KEY] = scopedClient;\n\t\t}\n\n\t\tconst retrievalCollector: RetrievalCollector = { searches: [] };\n\t\tconst startTime = performance.now();\n\t\ttry {\n\t\t\tconst result = await retrievalCollectorStore.run(retrievalCollector, () =>\n\t\t\t\toriginalHandler(input, extra),\n\t\t\t);\n\t\t\tconst durationMs = Math.round(performance.now() - startTime);\n\n\t\t\tlog(\n\t\t\t\t`tool \"${toolName}\" handler returned in ${durationMs}ms, running post-processing...`,\n\t\t\t);\n\n\t\t\tconst isErrorResult =\n\t\t\t\tisRecord(result) && (result as UnknownRecord).isError === true;\n\n\t\t\tif (isErrorResult) {\n\t\t\t\tconst errorText = extractErrorText(result);\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[waniwani] Tool \"${toolName}\" returned error${errorText ? `: ${errorText}` : \"\"}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tawait safeTrack(\n\t\t\t\ttracker,\n\t\t\t\tbuildTrackInput(\n\t\t\t\t\ttoolName,\n\t\t\t\t\textra,\n\t\t\t\t\teffectiveOpts,\n\t\t\t\t\t{\n\t\t\t\t\t\tdurationMs,\n\t\t\t\t\t\tstatus: isErrorResult ? \"error\" : \"ok\",\n\t\t\t\t\t\t...(isErrorResult && {\n\t\t\t\t\t\t\terrorMessage: extractErrorText(result) ?? \"Unknown tool error\",\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t\tclientInfo,\n\t\t\t\t\t{ input, output: result },\n\t\t\t\t\tretrievalCollector.searches,\n\t\t\t\t),\n\t\t\t\topts.onError,\n\t\t\t);\n\n\t\t\tlog(`tool \"${toolName}\" tracking done`);\n\n\t\t\tif (opts.flushAfterToolCall) {\n\t\t\t\tawait safeFlush(tracker, opts.onError);\n\t\t\t}\n\n\t\t\tinjectRequestMetadata(result, extra);\n\t\t\tinjectWidgetDefinitionMeta(result, definitionMeta);\n\n\t\t\tif (injectToken) {\n\t\t\t\tawait injectWidgetConfig(\n\t\t\t\t\tresult,\n\t\t\t\t\ttokenCache,\n\t\t\t\t\ttracker._config.apiUrl ?? DEFAULT_BASE_URL,\n\t\t\t\t\textra,\n\t\t\t\t\topts.onError,\n\t\t\t\t\tclientInfo,\n\t\t\t\t);\n\t\t\t\tlog(`tool \"${toolName}\" widget config injected`);\n\t\t\t}\n\n\t\t\tlog(`tool \"${toolName}\" post-processing complete, returning result`);\n\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\tconst durationMs = Math.round(performance.now() - startTime);\n\n\t\t\tawait safeTrack(\n\t\t\t\ttracker,\n\t\t\t\tbuildTrackInput(\n\t\t\t\t\ttoolName,\n\t\t\t\t\textra,\n\t\t\t\t\teffectiveOpts,\n\t\t\t\t\t{\n\t\t\t\t\t\tdurationMs,\n\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\terrorMessage:\n\t\t\t\t\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\t\t\t\tcause: classifyCause({ error }),\n\t\t\t\t\t},\n\t\t\t\t\tclientInfo,\n\t\t\t\t\t{ input },\n\t\t\t\t\tretrievalCollector.searches,\n\t\t\t\t),\n\t\t\t\topts.onError,\n\t\t\t);\n\n\t\t\tif (opts.flushAfterToolCall) {\n\t\t\t\tawait safeFlush(tracker, opts.onError);\n\t\t\t}\n\n\t\t\tthrow error;\n\t\t}\n\t};\n\n\twrappedHandler[WRAPPED_HANDLER] = true;\n\treturn wrappedHandler;\n}\n\n/**\n * Wrap an MCP server so tool handlers automatically emit `tool.called` events.\n *\n * The wrapper intercepts `server.registerTool(...)` for future registrations\n * and also walks `server._registeredTools` to wrap any tools already registered\n * at the time of the call. This means either call order works:\n *\n * withWaniwani(server); server.registerTool(...); // wrap then register\n * server.registerTool(...); withWaniwani(server); // register then wrap\n *\n * When `injectWidgetToken` is enabled (default), tracking config is injected\n * into tool response `_meta.waniwani` so browser widgets can post events\n * directly to the Waniwani backend without a server-side proxy.\n *\n * Widget metadata declared on the tool **definition** (e.g. skybridge's\n * `registerWidget`, raw MCP `_meta[\"ui/resourceUri\"]` / `_meta.ui.resourceUri`,\n * OpenAI's `_meta[\"openai/outputTemplate\"]`) is also forwarded into each tool\n * result's `_meta`, so chat UIs that only see tool results (and not\n * `tools/list`) can still render widgets. Handler-set keys take precedence.\n */\nexport async function withWaniwani(\n\tserver: McpServer,\n\toptions?: WithWaniwaniOptions,\n): Promise<McpServer> {\n\tconst wrappedServer = server as WrappedServer;\n\tif (wrappedServer.__waniwaniWrapped) {\n\t\treturn wrappedServer;\n\t}\n\n\twrappedServer.__waniwaniWrapped = true;\n\n\tconst opts = options ?? {};\n\tconst tracker = opts.client ?? waniwani();\n\tconst injectToken = opts.injectWidgetToken !== false;\n\n\tconst tokenCache: WidgetTokenCache | null = tracker._config.apiKey\n\t\t? new WidgetTokenCache({\n\t\t\t\tapiUrl: tracker._config.apiUrl ?? DEFAULT_BASE_URL,\n\t\t\t\tapiKey: tracker._config.apiKey,\n\t\t\t})\n\t\t: null;\n\n\tconst ctx: WrapContext = {\n\t\tserver,\n\t\ttracker,\n\t\topts,\n\t\ttokenCache,\n\t\tinjectToken,\n\t\tfunnelSync: null,\n\t};\n\n\tconst originalRegisterTool = server.registerTool.bind(server) as (\n\t\t...args: unknown[]\n\t) => unknown;\n\n\twrappedServer.registerTool = ((...args: unknown[]) => {\n\t\tconst [toolNameRaw, config, handlerRaw] = args;\n\n\t\tif (typeof handlerRaw !== \"function\") {\n\t\t\treturn originalRegisterTool(...args);\n\t\t}\n\n\t\tconst toolName =\n\t\t\ttypeof toolNameRaw === \"string\" && toolNameRaw.trim().length > 0\n\t\t\t\t? toolNameRaw\n\t\t\t\t: \"unknown\";\n\n\t\tconst definitionMeta =\n\t\t\tisRecord(config) && isRecord((config as UnknownRecord)._meta)\n\t\t\t\t? ((config as UnknownRecord)._meta as UnknownRecord)\n\t\t\t\t: undefined;\n\n\t\tconst wrapped = createWrappedHandler(\n\t\t\ttoolName,\n\t\t\thandlerRaw as RawHandler,\n\t\t\tctx,\n\t\t\tdefinitionMeta,\n\t\t);\n\t\treturn originalRegisterTool(toolNameRaw, config, wrapped);\n\t}) as McpServer[\"registerTool\"];\n\n\t// Wrap any tools that were already registered before withWaniwani() ran.\n\t// MCP SDK internal: `_registeredTools` is the dictionary used by the\n\t// `tools/call` request handler; each entry has a mutable `handler` field\n\t// that is looked up by name and invoked by reference at call time\n\t// (see @modelcontextprotocol/sdk/dist/esm/server/mcp.js:_createRegisteredTool),\n\t// so reassigning `entry.handler` safely upgrades existing tools in place.\n\t// Skybridge's McpServer subclass uses the same storage via `super.registerTool`.\n\tconst registeredTools = (\n\t\tserver as unknown as {\n\t\t\t_registeredTools?: Record<string, { handler?: unknown; _meta?: unknown }>;\n\t\t}\n\t)._registeredTools;\n\n\tif (isRecord(registeredTools)) {\n\t\tfor (const [toolName, entry] of Object.entries(registeredTools)) {\n\t\t\tif (!isRecord(entry)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst existing = entry.handler as MaybeWrappedHandler | undefined;\n\t\t\tif (typeof existing !== \"function\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (existing[WRAPPED_HANDLER]) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst definitionMeta = isRecord(entry._meta)\n\t\t\t\t? (entry._meta as UnknownRecord)\n\t\t\t\t: undefined;\n\n\t\t\tentry.handler = createWrappedHandler(\n\t\t\t\ttoolName,\n\t\t\t\texisting,\n\t\t\t\tctx,\n\t\t\t\tdefinitionMeta,\n\t\t\t);\n\t\t}\n\t}\n\n\tif (tracker._config.apiKey) {\n\t\tconst registeredToolsMap = (\n\t\t\tserver as unknown as {\n\t\t\t\t_registeredTools?: Record<string, { _meta?: unknown }>;\n\t\t\t}\n\t\t)._registeredTools;\n\n\t\tconst flowGraphs: FlowGraph[] = [];\n\t\tif (registeredToolsMap && typeof registeredToolsMap === \"object\") {\n\t\t\tfor (const entry of Object.values(registeredToolsMap)) {\n\t\t\t\tif (entry && typeof entry === \"object\") {\n\t\t\t\t\tconst meta = (entry as Record<string, unknown>)._meta;\n\t\t\t\t\tconst fg =\n\t\t\t\t\t\tmeta && typeof meta === \"object\"\n\t\t\t\t\t\t\t? ((meta as Record<string, unknown>)._flowGraph as\n\t\t\t\t\t\t\t\t\t| FlowGraph\n\t\t\t\t\t\t\t\t\t| undefined)\n\t\t\t\t\t\t\t: undefined;\n\t\t\t\t\tif (fg?.nodes?.length) {\n\t\t\t\t\t\tflowGraphs.push(fg);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (flowGraphs.length > 0) {\n\t\t\tctx.funnelSync = await prepareFunnelSyncPayload(flowGraphs);\n\t\t}\n\t}\n\n\treturn wrappedServer;\n}\n"],"mappings":"AAcA,SAASA,GAASC,EAAqD,CACtE,GAAI,SAAOA,GAAU,UAAYA,IAAU,MAG3C,OAAOA,CACR,CAEA,SAASC,GACRC,EACsC,CACtC,OAAOH,GAASG,GAAM,EAAE,CACzB,CAkBO,SAASC,GACfD,EACqB,CACrB,GAAI,CAACA,EACJ,OAED,IAAME,EAASH,GAASC,CAAI,GAAG,YAC/B,GAAI,OAAOE,GAAW,UAAYA,EAAO,OAAS,EACjD,OAAOA,EAER,IAAMC,EAAOH,EAAK,gBAAgB,EAClC,GAAI,OAAOG,GAAS,UAAYA,EAAK,OAAS,EAC7C,OAAOA,EAER,IAAMC,EAASJ,EAAK,uBAAuB,EAC3C,GAAI,OAAOI,GAAW,UAAYA,EAAO,OAAS,EACjD,OAAOA,CAGT,CAGO,SAASC,GACfL,EACU,CACV,OAAOD,GAASC,CAAI,GAAG,aAAe,EACvC,CAeO,SAASM,GAAWC,EAAmD,CAC7E,OAAOC,GAAoBD,GAAM,KAAK,CACvC,CAUO,SAASE,GAAcF,EAAwC,CACrE,OAAOD,GAAWC,CAAI,IAAM,MAC7B,CCKA,SAASG,IAAwC,CAChD,GAAI,OAAO,SAAa,KAAe,OAAO,UAAc,IAC3D,OAAO,KAGR,IAAMC,GACJ,OAAO,SAAa,IACjB,SAAmD,aACpD,UACF,OAAO,UAAc,IAClB,UAAoD,aACrD,QAEJ,OACCA,GACA,OAAQA,EAA2B,cAAiB,WAE7CA,EAED,IACR,CAGO,SAASC,IAA0B,CACzC,OAAOF,GAAiB,IAAM,IAC/B,CAEA,SAASG,IAAiC,CACzC,MAAO,CAAE,IAAK,SAAS,KAAM,MAAO,SAAS,KAAM,CACpD,CAaA,eAAsBC,GACrBC,EAC+B,CAC/B,GAAM,CAAE,SAAAC,EAAU,QAAAC,EAAS,UAAAC,EAAW,UAAAC,EAAW,UAAAC,EAAW,SAAAC,CAAS,EACpEN,EACKO,EAAMP,EAAQ,QAAU,QAExBQ,EAAKb,GAAiB,EAC5B,GAAI,CAACa,EACJ,OAAO,KAIR,IAAMC,EAAa,IAAI,gBACjBC,EAAa,IAAMD,EAAW,MAAM,EAC1C,OAAO,iBAAiB,WAAYC,CAAU,EAE9C,IAAIC,EAAW,GACTC,EAAU,IAAM,CACjBD,IAGJA,EAAW,GACX,OAAO,oBAAoB,WAAYD,CAAU,EACjDD,EAAW,MAAM,EAClB,EAQA,SAASI,GAAW,CACnB,MAAO,CAAE,UAAAV,EAAW,UAAAC,EAAW,UAAAC,EAAW,KAAMP,GAAY,CAAE,CAC/D,CAEA,eAAegB,EACdC,EACAC,EACa,CACb,IAAMC,EAAW,MAAM,MAAMhB,EAAU,CACtC,OAAQ,OACR,QAAS,CAAE,eAAgB,mBAAoB,GAAGC,CAAQ,EAC1D,KAAM,KAAK,UAAUa,CAAO,EAC5B,GAAIC,EAAS,CAAE,OAAAA,CAAO,EAAI,CAAC,CAC5B,CAAC,EACD,GAAI,CAACC,EAAS,GACb,MAAM,IAAI,MAAM,UAAUF,EAAQ,MAAM,YAAYE,EAAS,MAAM,EAAE,EAEtE,OAAQ,MAAMA,EAAS,KAAK,CAC7B,CAEA,IAAIC,EAAsB,CAAC,EAC3B,GAAI,CAKHA,GAJe,MAAMJ,EAAyB,CAC7C,GAAGD,EAAS,EACZ,OAAQ,MACT,CAAC,IACe,OAAS,CAAC,CAC3B,OAASM,EAAO,CACf,OAAAZ,EAAI,MAAM,qCAAsCY,CAAK,EACrDP,EAAQ,EACD,IACR,CAEA,IAAMQ,EAA2B,CAAC,EAClC,aAAM,QAAQ,IACbF,EAAM,IAAI,MAAOG,GAAS,CACzB,GAAI,CACH,MAAMb,EAAG,aACR,CACC,KAAMa,EAAK,KACX,YAAaA,EAAK,aAAe,GACjC,GAAIA,EAAK,YAAc,CAAE,YAAaA,EAAK,WAAY,EAAI,CAAC,EAC5D,GAAIA,EAAK,YAAc,CAAE,YAAaA,EAAK,WAAY,EAAI,CAAC,EAC5D,QAAS,MAAOC,EAAMC,IAAgB,CAIrC,IAAMC,EAAS,MAAMV,EACpB,CACC,GAAGD,EAAS,EACZ,OAAQ,OACR,KAAMQ,EAAK,KACX,UAAWC,GAAQ,CAAC,CACrB,EACAC,GAAa,MACd,EAKA,OAAIC,GAAQ,QACXlB,IAAWkB,EAAO,MAAM,EAElB,CAAE,QAASA,GAAQ,SAAW,CAAC,CAAE,CACzC,CACD,EACA,CAAE,OAAQf,EAAW,MAAO,CAC7B,EACAW,EAAW,KAAKC,CAAI,CACrB,OAASF,EAAO,CACfZ,EAAI,MAAM,+BAA+Bc,EAAK,IAAI,GAAIF,CAAK,CAC5D,CACD,CAAC,CACF,EAEAZ,EAAI,KAAK,uBAAuBa,EAAW,MAAM,eAAe,EAEzD,CACN,IAAI,OAAQ,CACX,OAAOA,CACR,EACA,QAAAR,CACD,CACD,CC1NO,SAASa,GAAeC,EAA6C,CAC3E,IAAMC,EAAQD,EAAO,UAAU,CAAC,EAChC,GAAI,CAACC,GAASA,EAAM,OAAS,QAAU,OAAOA,EAAM,MAAS,SAC5D,OAAO,KAGR,IAAIC,EACJ,GAAI,CACHA,EAAS,KAAK,MAAMD,EAAM,IAAI,CAC/B,MAAQ,CACP,OAAO,IACR,CAEA,GAAI,CAACC,GAAU,OAAOA,GAAW,UAAY,MAAM,QAAQA,CAAM,EAChE,OAAO,KAGR,IAAMC,EAAUD,EAChB,GAAIC,EAAQ,SAAW,UAAY,OAAOA,EAAQ,MAAS,SAC1D,OAAO,KAGR,IAAMC,EACLD,EAAQ,MACR,OAAOA,EAAQ,MAAS,UACxB,CAAC,MAAM,QAAQA,EAAQ,IAAI,EACvBA,EAAQ,KACT,CAAC,EAEL,MAAO,CACN,QAASA,EACT,KAAMA,EAAQ,KACd,KAAAC,EAIA,YAAaD,EAAQ,cAAgB,EACtC,CACD,CAEA,IAAME,GACL,oTAEKC,GACL,wOAEKC,GACL,kLAiBM,SAASC,GACfC,EACAC,EACyD,CACzD,GAAM,CAAE,KAAMC,EAAO,YAAaC,EAAc,GAAGC,CAAK,EAAIJ,EAAK,QACjE,MAAO,CACN,GAAGI,EACH,OAAQH,EAAW,eAAiB,qBACpC,aAAcA,EACXD,EAAK,YACJJ,GACAC,GACDC,EACJ,CACD,CCnFO,IAAMO,EAAQ,YACRC,EAAM,UAMbC,GAAY,OAAO,IAAI,yBAAyB,EAChDC,GAAS,OAAO,IAAI,sBAAsB,EAuDzC,SAASC,GACfC,EACAC,EACkB,CAClB,IAAMC,EAAUD,GAAQ,QAClBE,EAAiC,CAAC,EAExC,OAAW,CAACC,EAAKC,CAAK,IAAK,OAAO,QAAQL,CAAM,EAC/C,GAAI,OAAOK,GAAU,UAAYA,IAAU,MAAQ,aAAcA,EAAO,CACvE,IAAMC,EAAID,EASVF,EAAU,KAAK,CACd,SAAUG,EAAE,SACZ,MAAOF,EACP,YAAaE,EAAE,YACf,QAASA,EAAE,QACX,SAAUA,EAAE,QACb,CAAC,CACF,CAGD,MAAO,CACN,OAAQT,GACR,UAAAM,EACA,QAAAD,CACD,CACD,CA8BO,SAASK,GACfC,EAIAC,EACe,CACf,IAAMC,EACL,OAAOF,GAAiB,UACxBA,IAAiB,MACjB,SAAUA,EACLG,EAAOD,EAAeF,EAAa,KAAOA,EAC1CP,EAA2BS,EAC9BF,EACCC,GAAe,CAAC,EACpB,GAAKR,EAAqC,cAAgB,OAAW,CACpE,IAAMW,EAAS,OAAOD,GAAS,SAAWA,EAAOA,EAAK,GACtD,MAAM,IAAI,MACT,eAAeC,CAAM,mLAE4CA,CAAM,gCAExE,CACD,CACA,GAAM,CAAE,KAAAC,EAAM,YAAAC,EAAa,MAAAC,CAAM,EAAId,EACrC,MAAO,CACN,OAAQH,GACR,KAAM,OAAOa,GAAS,SAAWA,EAAOA,EAAK,GAC7C,GAAIE,IAAS,OAAY,CAAE,KAAAA,CAAK,EAAI,CAAC,EACrC,GAAIC,IAAgB,OAAY,CAAE,YAAAA,CAAY,EAAI,CAAC,EACnD,GAAIC,IAAU,OAAY,CAAE,MAAAA,CAAM,EAAI,CAAC,CACxC,CACD,CAEO,SAASC,GAAYX,EAA0C,CACrE,OACC,OAAOA,GAAU,UACjBA,IAAU,MACV,WAAYA,GACXA,EAA0B,SAAWR,EAExC,CAEO,SAASoB,GAASZ,EAAuC,CAC/D,OACC,OAAOA,GAAU,UACjBA,IAAU,MACV,WAAYA,GACXA,EAAuB,SAAWP,EAErC,CChNA,OAAS,KAAAoB,MAAS,MCJX,IAAMC,EAAN,cAA4B,KAAM,CACxC,YACCC,EACOC,EACN,CACD,MAAMD,CAAO,EAFN,YAAAC,EAGP,KAAK,KAAO,eACb,CACD,ECAA,IAAMC,GAAW,gBACXC,GAAe,qCAmBrB,SAASC,GAAeC,EAAcC,EAAwB,CAC7D,GAAI,CACH,IAAMC,EAAkB,KAAK,MAAMF,CAAI,EACvC,GAAIE,IAAW,MAAQ,OAAOA,GAAW,SAAU,CAClD,IAAMC,EACL,YAAaD,GAAU,OAAOA,EAAO,SAAY,SAC9CA,EAAO,QACP,GACEE,EACL,WAAYF,GAAU,OAAOA,EAAO,QAAW,SAC5CA,EAAO,OACP,GACJ,GAAIC,GAAQC,EACX,MAAO,GAAGD,CAAI,KAAKC,CAAM,GAE1B,GAAID,EACH,OAAOA,EAER,GAAIC,EACH,OAAOA,CAET,CACD,MAAQ,CAER,CACA,OAAOJ,GAAQ,6BAA6BC,CAAM,EACnD,CAEA,SAASI,GACRC,EACyC,CACzC,MAAO,eAAgBA,GAAS,OAAOA,EAAM,YAAe,QAC7D,CAGA,SAASC,GACRD,EACyB,CACzB,OAAID,GAAcC,CAAK,EACf,CAAE,WAAYA,EAAM,UAAW,EAEhC,CAAE,IAAKA,EAAM,IAAK,SAAUA,EAAM,QAAS,CACnD,CAEO,SAASE,GACfC,EACkB,CAClB,GAAM,CAAE,OAAAC,EAAQ,OAAAC,CAAO,EAAIF,EAE3B,MAAO,CACN,MAAM,QACLH,EACoC,CACpC,GAAI,CAACK,EACJ,MAAM,IAAI,MAAM,6BAA6B,EAM9C,GAAM,CAAE,aAAAC,CAAa,EAAe,KAAM,QAAO,KAAK,EAEhDC,EAAW,MAAM,MACtB,GAAGH,EAAO,QAAQ,MAAO,EAAE,CAAC,GAAGZ,EAAY,GAC3C,CACC,OAAQ,OACR,QAAS,CACR,cAAe,UAAUa,CAAM,GAC/B,eAAgB,mBAChB,iBAAkBd,EACnB,EACA,KAAM,KAAK,UAAU,CACpB,GAAGU,GAAaD,CAAK,EACrB,OAAQM,EAAaN,EAAM,MAAM,EACjC,MAAOA,EAAM,MACb,UAAWA,EAAM,UACjB,cAAeA,EAAM,aACtB,CAAC,CACF,CACD,EAEA,GAAI,CAACO,EAAS,GAAI,CACjB,IAAMC,EAAO,MAAMD,EAAS,KAAK,EAAE,MAAM,IAAM,EAAE,EACjD,MAAM,IAAIE,EACThB,GAAee,EAAMD,EAAS,MAAM,EACpCA,EAAS,MACV,CACD,CAEA,IAAMG,EAAQ,MAAMH,EAAS,KAAK,EAC5B,CAAE,OAAAI,EAAQ,UAAAC,EAAW,eAAAC,EAAgB,WAAAC,CAAW,EAAIJ,EAAK,KAE/D,MAAO,CACN,OAAQV,EAAM,OAAO,MAAMW,CAAM,EACjC,UAAAC,EACA,eAAgBC,GAAkB,KAClC,WAAAC,CACD,CACD,CACD,CACD,CCnHO,SAASC,GAAiBC,EAAgC,CAChE,MAAO,CACN,WAAY,CAAC,CAAE,OAAAC,EAAQ,SAAAC,EAAU,OAAAC,EAAQ,MAAAC,EAAO,GAAGC,CAAQ,IAC1DL,EAAK,CACJ,MAAO,cACP,WAAY,CAAE,OAAAC,EAAQ,SAAAC,EAAU,OAAAC,EAAQ,MAAAC,CAAM,EAC9C,GAAGC,CACJ,CAAC,EACF,eAAgB,CAAC,CAAE,QAAAC,EAAS,GAAGD,CAAQ,IACtCL,EAAK,CAAE,MAAO,kBAAmB,WAAY,CAAE,QAAAM,CAAQ,EAAG,GAAGD,CAAQ,CAAC,EACvE,eAAgB,CAAC,CAAE,GAAAE,EAAI,OAAAN,EAAQ,SAAAC,EAAU,GAAGG,CAAQ,IACnDL,EAAK,CACJ,MAAO,kBACP,WAAY,CAAE,GAAAO,EAAI,OAAAN,EAAQ,SAAAC,CAAS,EACnC,GAAGG,CACJ,CAAC,EACF,cAAgBG,GAAU,CACzB,GAAM,CAAE,WAAAC,EAAY,MAAAC,EAAO,KAAAC,EAAM,GAAGN,CAAQ,EAAIG,GAAS,CAAC,EAC1D,OAAOR,EAAK,CACX,MAAO,iBACP,WAAY,CAAE,WAAAS,EAAY,MAAAC,EAAO,KAAAC,CAAK,EACtC,GAAGN,CACJ,CAAC,CACF,EACA,UAAW,CAAC,CAAE,OAAAJ,EAAQ,SAAAC,EAAU,WAAAU,EAAY,GAAGP,CAAQ,IACtDL,EAAK,CACJ,MAAO,YACP,WAAY,CAAE,OAAAC,EAAQ,SAAAC,EAAU,WAAAU,CAAW,EAC3C,GAAGP,CACJ,CAAC,CACH,CACD,CCeO,IAAMQ,GAAkB,0BC3D/B,IAAMC,GAAmB,CAAC,aAAc,aAAa,EAC/CC,GAAgB,CAAC,WAAY,YAAa,WAAY,MAAM,EAC5DC,GAAkB,CACvB,YACA,aACA,WACA,YACA,cACA,cACD,EAEMC,GAAmB,WACnBC,GAAqB,2BAE3B,SAASC,EAASC,EAAkD,CACnE,OAAO,OAAOA,GAAU,UAAYA,IAAU,MAAQ,CAAC,MAAM,QAAQA,CAAK,CAC3E,CAEA,SAASC,GACRC,EACAC,EACqB,CACrB,QAAWC,KAAOD,EAAM,CACvB,IAAMH,EAAQE,EAAOE,CAAG,EACxB,GAAI,OAAOJ,GAAU,UAAYA,EAAM,OAAS,EAC/C,OAAOA,CAET,CAED,CAGA,SAASK,GAAmBC,EAA8C,CACzE,GAAI,CAACP,EAASO,CAAK,EAClB,OAGD,IAAMC,EAAaN,GAAUK,EAAOZ,EAAgB,EACpD,GAAKa,EAIL,MAAO,CACN,WAAAA,EACA,SAAUN,GAAUK,EAAOX,EAAa,GAAKE,GAC7C,UAAWI,GAAUK,EAAOV,EAAe,GAAKE,EACjD,CACD,CAEA,SAASU,GAAQC,EAAwC,CACxD,IAAMC,EAAgC,CAAC,EACjCC,EAAO,IAAI,IAEjB,QAAWL,KAAS,MAAM,QAAQG,CAAS,EAAIA,EAAY,CAACA,CAAS,EAAG,CACvE,IAAMG,EAAWP,GAAmBC,CAAK,EACrCM,GAAY,CAACD,EAAK,IAAIC,EAAS,UAAU,IAC5CD,EAAK,IAAIC,EAAS,UAAU,EAC5BF,EAAU,KAAKE,CAAQ,EAEzB,CAEA,OAAOF,CACR,CAEA,SAASG,GAAmBC,EAAuC,CAClE,GAAI,CAAC,MAAM,QAAQA,CAAQ,EAC1B,MAAO,CAAC,EAGT,IAAMC,EAAS,CAAC,GAAGD,CAAQ,EACzB,QAAQ,EACR,KAAME,GAAYjB,EAASiB,CAAO,GAAKA,EAAQ,OAAS,MAAM,EAChE,MAAI,CAACjB,EAASgB,CAAM,GAAK,CAAC,MAAM,QAAQA,EAAO,KAAK,EAC5C,CAAC,EAGFP,GACNO,EAAO,MAAM,QAASE,GACrBlB,EAASkB,CAAI,GAAKlB,EAASkB,EAAK,IAAI,EAAI,CAACA,EAAMA,EAAK,IAAI,EAAI,CAACA,CAAI,CAClE,CACD,CACD,CAmBO,SAASC,GAAsBC,EAAoC,CACzE,IAAMC,EAAOrB,EAASoB,CAAK,EAAIA,EAAQ,OAEjCE,EAAWb,GAAQY,EAAOA,EAAK,UAAYD,CAAK,EACtD,OAAIE,EAAS,OAAS,EACdA,EAGDR,GAAmBO,EAAOA,EAAK,SAAWD,CAAK,CACvD,CCjGO,IAAMG,GAAuB,uBCNpC,SAASC,EACRC,EACAC,EACqB,CACrB,QAAWC,KAAOD,EAAM,CACvB,IAAME,EAAQH,EAAKE,CAAG,EACtB,GAAI,OAAOC,GAAU,UAAYA,EAAM,OAAS,EAC/C,OAAOA,CAET,CAED,CAIA,IAAMC,GAAkB,CACvB,qBACA,mBACA,iBACA,YACA,iBACA,gBACD,EAEMC,GAAkB,CACvB,qBACA,mBACA,YACA,eACD,EAEMC,GAAgB,CACrB,mBACA,iBACA,UACA,cACA,mBACA,WACD,EAEMC,GAAwB,CAC7B,kBACA,gBACA,iBACA,SACA,SACD,EAEMC,GAAkB,CAAC,qBAAsB,WAAW,EAEpDC,GAAsB,CAAC,gBAAiB,kBAAkB,EAIhE,IAAMC,GAAmB,CAAC,sBAAuB,mBAAmB,EAE9DC,GAAgB,CACrB,MACA,MACA,eACA,cACA,WACA,UACA,aACA,WACD,EAEMC,GAAiB,CAAC,WAAY,YAAa,WAAY,MAAM,EAGtDC,EAAgB,gBAWhBC,GAAyB,kBAI/B,SAASC,EACfC,EACqB,CACrB,OAAOA,EAAOC,EAAUD,EAAME,EAAe,EAAI,MAClD,CAEO,SAASC,GACfH,EACqB,CACrB,OAAOA,EAAOC,EAAUD,EAAMI,EAAe,EAAI,MAClD,CAEO,SAASC,GACfL,EACqB,CACrB,OAAOA,EAAOC,EAAUD,EAAMM,EAAa,EAAI,MAChD,CAEO,SAASC,GACfP,EACqB,CACrB,OAAOA,EAAOC,EAAUD,EAAMQ,EAAqB,EAAI,MACxD,CAEO,SAASC,GACfT,EACqB,CACrB,OAAOA,EAAOC,EAAUD,EAAMU,EAAe,EAAI,MAClD,CAEO,SAASC,GACfX,EACqB,CACrB,OAAOA,EAAOC,EAAUD,EAAMY,EAAmB,EAAI,MACtD,CAiCA,SAASC,GAAcC,EAAkD,CACxE,OAAO,OAAOA,GAAU,UAAYA,IAAU,MAAQ,CAAC,MAAM,QAAQA,CAAK,CAC3E,CAEA,SAASC,GAAaD,EAAoC,CACzD,GAAI,SAAOA,GAAU,UAAYA,EAAM,SAAW,GAGlD,GAAI,CACH,IAAME,EAAM,IAAI,IAAIF,CAAK,EACzB,OAAOE,EAAI,WAAa,UAAYA,EAAI,WAAa,QAClDF,EACA,MACJ,MAAQ,CACP,MACD,CACD,CAEA,SAASG,GAAgBD,EAAqB,CAC7C,GAAI,CACH,IAAME,EAAW,IAAI,IAAIF,CAAG,EAAE,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO,EAC1DG,EAAOD,EAASA,EAAS,OAAS,CAAC,EACzC,OAAOC,EAAO,mBAAmBA,CAAI,EAAI,UAC1C,MAAQ,CACP,MAAO,UACR,CACD,CAEA,SAASC,GAAeC,EAA0C,CACjE,GAAI,CAACR,GAAcQ,CAAK,EACvB,OAGD,IAAML,EAAMD,GAAaO,EAAUD,EAAOE,EAAa,CAAC,EACxD,GAAI,CAACP,EACJ,OAGD,IAAMQ,EAAWF,EAAUD,EAAOI,EAAc,EAChD,MAAO,CAAE,IAAAT,EAAK,SAAUQ,GAAYP,GAAgBD,CAAG,CAAE,CAC1D,CASO,SAASU,GACfC,EACiB,CACjB,GAAI,CAACA,EACJ,MAAO,CAAC,EAGT,QAAWC,KAAOC,GAAkB,CACnC,IAAMC,EAAQC,GAAWJ,EAAKC,CAAG,CAAC,EAClC,GAAIE,EAAM,OAAS,EAClB,OAAOA,CAET,CAEA,MAAO,CAAC,CACT,CAEA,SAASC,GAAWC,EAAoC,CACvD,IAAMF,EAAwB,CAAC,EACzBG,EAAO,IAAI,IAEXC,EAAWpB,GAAyB,CACzC,GAAI,MAAM,QAAQA,CAAK,EAAG,CACzB,QAAWO,KAASP,EACnBoB,EAAQb,CAAK,EAEd,MACD,CACA,IAAMc,EAAOf,GAAeN,CAAK,EACjC,GAAIqB,EAAM,CACJF,EAAK,IAAIE,EAAK,GAAG,IACrBF,EAAK,IAAIE,EAAK,GAAG,EACjBL,EAAM,KAAKK,CAAI,GAEhB,MACD,CACA,GAAItB,GAAcC,CAAK,EACtB,QAAWO,KAAS,OAAO,OAAOP,CAAK,EACtCoB,EAAQb,CAAK,CAGhB,EAEA,OAAAa,EAAQF,CAAS,EACVF,CACR,CAEA,IAAMM,GAAqB,qBAQpB,SAASC,GACfV,EACqB,CACrB,GAAI,CAACA,EACJ,MAAO,CAAC,EAET,IAAMW,EAAMX,EAAKS,EAAkB,EACnC,OAAOG,GAAsB,MAAM,QAAQD,CAAG,EAAIA,EAAM,CAACA,CAAG,CAAC,CAC9D,CAEA,IAAME,GAAsB,CAC3B,CAAE,IAAK,mBAAoB,OAAQ,SAAU,EAC7C,CAAE,IAAK,iBAAkB,OAAQ,SAAU,CAC5C,EAaMC,GAGD,CACJ,CAAE,OAAQ,SAAU,OAAQ,QAAS,EACrC,CAAE,OAAQ,UAAW,OAAQ,SAAU,EACvC,CAAE,OAAQ,SAAU,OAAQ,SAAU,EACtC,CAAE,OAAQ,SAAU,OAAQ,QAAS,CACtC,EAOO,SAASC,EACff,EACAgB,EACqB,CACrB,GAAIhB,EAAM,CACT,IAAMiB,EAAWjB,EAAK,iBAAiB,EACvC,GAAI,OAAOiB,GAAa,UAAYA,EAAS,OAAS,EACrD,OAAOA,EAER,OAAW,CAAE,IAAAhB,EAAK,OAAAiB,CAAO,IAAKL,GAAqB,CAClD,IAAM1B,EAAQa,EAAKC,CAAG,EACtB,GAAI,OAAOd,GAAU,UAAYA,EAAM,OAAS,EAC/C,OAAO+B,CAET,CACD,CAMA,IAAMC,EAAOH,GAAY,KACzB,GAAI,OAAOG,GAAS,UAAYA,EAAK,OAAS,EAAG,CAChD,IAAMC,EAAQD,EAAK,YAAY,EAC/B,OAAW,CAAE,OAAAE,EAAQ,OAAAH,CAAO,IAAKJ,GAChC,GAAIM,EAAM,SAASC,CAAM,EACxB,OAAOH,CAGV,CAED,CASA,SAASI,GACRC,EACAtB,EACqB,CAIrB,IAAMuB,EAAWvB,EAAI,YAAY,EAC7BU,EAAMY,EAAQtB,CAAG,GAAKsB,EAAQC,CAAQ,EAC1C,GAAIb,IAAQ,QACX,QAAWc,KAAa,OAAO,KAAKF,CAAO,EAC1C,GAAIE,EAAU,YAAY,IAAMD,EAAU,CACzCb,EAAMY,EAAQE,CAAS,EACvB,KACD,EAGF,IAAMtC,EAAQ,MAAM,QAAQwB,CAAG,EAAIA,EAAI,CAAC,EAAIA,EAC5C,OAAO,OAAOxB,GAAU,UAAYA,EAAM,OAAS,EAAIA,EAAQ,MAChE,CAEO,SAASuC,GACfH,EACqB,CACrB,GAAI,CAACA,EACJ,OAED,IAAMI,EAAYL,GAAYC,EAAS,YAAY,EAC7CK,EAAkBN,GAAYC,EAAS,oBAAoB,EACjE,GACEI,GAAa,UAAU,KAAKA,CAAS,GACrCC,GAAmB,oBAAoB,KAAKA,CAAe,EAE5D,MAAO,SAIR,GAAID,GAAa,kBAAkB,KAAKA,CAAS,EAChD,MAAO,SAGT,CCpWO,IAAME,GAAoB,kBAmE1B,SAASC,GACfC,EACmC,CACnC,GAAI,OAAOA,GAAU,UAAYA,IAAU,KAC1C,OAAQA,EAAkCF,EAAiB,CAK7D,CAEO,SAASG,GACfC,EAGAC,EACAC,EACuB,CACvB,IAAMC,EAAaC,GAClBJ,EAAK,MAAM,CAAE,GAAGI,EAAO,KAAM,CAAE,GAAGH,EAAM,GAAGG,EAAM,IAAK,CAAE,CAAC,EAEpDC,EAAYC,EAAiBL,CAAI,EACjCM,EAAgBC,GAAqBP,CAAI,EACzCQ,EAAYC,GAAsB,CACvC,OAAQR,GAAQ,QAAU,QAAQ,IAAI,kBAAoBS,GAC1D,OAAQT,GAAQ,MACjB,CAAC,EAED,MAAO,CACN,UAAAG,EACA,UAAWO,GAAiBX,CAAI,EAChC,eAAgBY,GAAsBZ,CAAI,EAC1C,MAAO,OAAO,OAAOE,EAAWW,GAAiBX,CAAS,CAAC,EAC3D,SAASY,EAAQC,EAAY,CAC5B,OAAOhB,EAAK,SAASe,EAAQC,EAAYf,CAAI,CAC9C,EACA,GAAID,EAAK,GACT,cAAeiB,GAAqBhB,CAAI,EACxC,kBAAmBiB,GAAyBjB,CAAI,EAChD,UAAW,CACV,QACCkB,EACoC,CACpC,OAAOV,EAAU,QAAQ,CACxB,GAAGU,EACH,UAAWA,EAAM,WAAad,EAC9B,cAAec,EAAM,eAAiBZ,CACvC,CAAC,CACF,CACD,EACA,QAASL,CACV,CACD,CCrJA,OAAS,KAAAkB,OAAS,MAGlB,IAAMC,GAAsBD,GAAE,OAAO,CACpC,OAAQA,GAAE,OAAO,EAAE,SAAS,EAC5B,WAAYA,GAAE,OAAO,EAAE,SAAS,CACjC,CAAC,EAED,SAASE,GAAWC,EAAoC,CACvD,IAAMC,EAASH,GAAoB,UAAUE,CAAK,EAClD,GAAKC,EAAO,QAGZ,OAAOA,EAAO,KAAK,QAAUA,EAAO,KAAK,UAC1C,CAEA,IAAMC,GAAyB,CAC9B,eACA,kBACA,uCACD,EAEO,SAASC,EAAc,CAAE,MAAAH,CAAM,EAAuC,CAE5E,GAAIA,aAAiB,OAASA,EAAM,OAAS,WAC5C,MAAO,iBAGR,GACCA,aAAiB,QAChBA,EAAM,OAAS,cAAgBA,EAAM,OAAS,gBAE/C,MAAO,UAGR,IAAMI,EAASL,GAAWC,CAAK,EAC/B,OAAII,IAAW,IACP,eAEJA,IAAW,QAAaA,GAAU,IAC9B,eAEJA,IAAW,QAAaA,GAAU,IAC9B,eAIPJ,aAAiB,WACjBE,GAAuB,KAAMG,GAC5BL,EAAM,QAAQ,YAAY,EAAE,SAASK,CAAO,CAC7C,EAEO,UAGD,SACR,CChDO,SAASC,EAAmB,CAClC,SAAAC,EACA,KAAAC,EACA,MAAAC,EACA,WAAAC,CACD,EAKS,CAER,GAAI,CACH,QAAQ,MACP,6BAA6BF,CAAI,IAAIE,GAAY,MAAQ,GAAG,IAAID,CAAK,GACrE,CAAE,KAAMC,GAAY,IAAK,CAC1B,CACD,MAAQ,CAAC,CAET,GAAKH,EAKL,GAAI,CACE,QAAQ,QACZA,EAAS,MAAM,CACd,MAAO,gBACP,WAAY,CAAE,KAAAC,EAAM,MAAAC,EAAO,GAAGC,CAAW,CAC1C,CAAC,CACF,EAAE,MAAM,IAAM,CAAC,CAAC,CACjB,MAAQ,CAAC,CACV,CC1BO,SAASC,GACfC,EACmC,CACnC,IAAMC,EACLD,EAGC,MAAM,IACR,OAAIC,GAAK,OAAS,UAAYA,EAAI,MAC1BA,EAAI,MAEL,IACR,CAOO,SAASC,EACfC,EACAC,EACU,CACV,IAAMC,EAAQD,EAAK,MAAM,GAAG,EACxBE,EAAmBH,EACvB,QAAWI,KAAQF,EAAO,CACzB,GAAIC,GAAW,MAAQ,OAAOA,GAAY,SACzC,OAEDA,EAAWA,EAAoCC,CAAI,CACpD,CACA,OAAOD,CACR,CAGO,SAASE,GACfL,EACAC,EACAK,EACO,CACP,IAAMJ,EAAQD,EAAK,MAAM,GAAG,EACtBM,EAAUL,EAAM,IAAI,EAC1B,GAAI,CAACK,EACJ,OAED,IAAIJ,EAAUH,EACd,QAAWI,KAAQF,GAEjBC,EAAQC,CAAI,GAAK,MACjB,OAAOD,EAAQC,CAAI,GAAM,UACzB,MAAM,QAAQD,EAAQC,CAAI,CAAC,KAE3BD,EAAQC,CAAI,EAAI,CAAC,GAElBD,EAAUA,EAAQC,CAAI,EAEvBD,EAAQI,CAAO,EAAID,CACpB,CAGO,SAASE,GACfR,EACAC,EACO,CACP,IAAMC,EAAQD,EAAK,MAAM,GAAG,EACtBM,EAAUL,EAAM,IAAI,EAC1B,GAAI,CAACK,EACJ,OAED,IAAIJ,EAAmBH,EACvB,QAAWI,KAAQF,EAAO,CACzB,GAAIC,GAAW,MAAQ,OAAOA,GAAY,SACzC,OAEDA,EAAWA,EAAoCC,CAAI,CACpD,CACID,GAAW,MAAQ,OAAOA,GAAY,UACzC,OAAQA,EAAoCI,CAAO,CAErD,CAcO,SAASE,GACfC,EAC0B,CAC1B,IAAIC,EAAkC,CAAC,EACvC,OAAW,CAACC,EAAKN,CAAK,IAAK,OAAO,QAAQI,CAAI,EACzCE,EAAI,SAAS,GAAG,EACnBP,GAAeM,EAAQC,EAAKN,CAAK,EAEjCA,IAAU,MACV,OAAOA,GAAU,UACjB,CAAC,MAAM,QAAQA,CAAK,EAEpBK,EAASE,EAAUF,EAAQ,CAAE,CAACC,CAAG,EAAGN,CAAiC,CAAC,EAEtEK,EAAOC,CAAG,EAAIN,EAGhB,OAAOK,CACR,CAMO,SAASE,EACfC,EACAC,EAC0B,CAC1B,IAAMJ,EAAS,CAAE,GAAGG,CAAO,EAC3B,OAAW,CAACF,EAAKN,CAAK,IAAK,OAAO,QAAQS,CAAM,EAE9CT,IAAU,MACV,OAAOA,GAAU,UACjB,CAAC,MAAM,QAAQA,CAAK,GACpBK,EAAOC,CAAG,IAAM,MAChB,OAAOD,EAAOC,CAAG,GAAM,UACvB,CAAC,MAAM,QAAQD,EAAOC,CAAG,CAAC,EAE1BD,EAAOC,CAAG,EAAIC,EACbF,EAAOC,CAAG,EACVN,CACD,EAEAK,EAAOC,CAAG,EAAIN,EAGhB,OAAOK,CACR,CCpHA,SAASK,GAAQC,EAAuC,CACvD,OAAQA,EAAmC,MAAM,GAClD,CAOA,SAASC,GAAeD,EAGtB,CACD,IAAIE,EAAUF,EACVG,EAAW,GACf,QAASC,EAAI,EAAGA,EAAI,EAAGA,IAAK,CAC3B,IAAMC,EAAMN,GAAQG,CAAO,EAC3B,GAAI,CAACG,GAAK,UACT,MAED,GACCA,EAAI,OAAS,YACbA,EAAI,OAAS,YACbA,EAAI,OAAS,UACZ,CACDF,EAAW,GACXD,EAAUG,EAAI,UACd,QACD,CACA,KACD,CACA,MAAO,CAAE,MAAOH,EAAS,SAAAC,CAAS,CACnC,CAEO,SAASG,GAAgBN,EAAoC,CACnE,GAAM,CAAE,MAAAO,EAAO,SAAAJ,CAAS,EAAIF,GAAeD,CAAM,EAC3CQ,EAAcD,EAAM,aAAeP,EAAO,aAAe,OACzDK,EAAMN,GAAQQ,CAAK,EACnBE,EAAIJ,GAAK,KAETK,EAAwB,CAC7B,KAAM,UACN,GAAIF,EAAc,CAAE,YAAAA,CAAY,EAAI,CAAC,EACrC,GAAIL,EAAW,CAAE,SAAU,EAAK,EAAI,CAAC,CACtC,EAEA,OAAIM,IAAM,QAAUJ,GAAK,QACjB,CAAE,GAAGK,EAAM,KAAM,OAAQ,OAAQ,OAAO,KAAKL,EAAI,OAAO,CAAE,EAGjEI,IAAM,UACNA,IAAM,UACNA,IAAM,WACNA,IAAM,UACNA,IAAM,QAEC,CAAE,GAAGC,EAAM,KAAMD,CAAE,EAEpBC,CACR,CAOO,SAASC,GACfC,EACAC,EAC8B,CAC9B,GAAI,CAACD,GAAS,CAACC,EACd,OAED,GAAIA,EAAU,SAAS,GAAG,EAAG,CAC5B,IAAMC,EAAMD,EAAU,QAAQ,GAAG,EAC3BE,EAASF,EAAU,MAAM,EAAGC,CAAG,EAC/BE,EAAQH,EAAU,MAAMC,EAAM,CAAC,EAC/BG,EAAeL,EAAMG,CAAM,EACjC,GAAI,CAACE,EACJ,OAGD,IAAMC,EADQC,GAAeF,CAAY,IACbD,CAAK,EACjC,OAAKE,EAGEZ,GAAgBY,CAAW,EAFjC,MAGF,CACA,IAAMlB,EAASY,EAAMC,CAAS,EAC9B,GAAKb,EAGL,OAAOM,GAAgBN,CAAM,CAC9B,CC3GO,SAASoB,GAASC,EAAqB,CAC7C,OAA0BA,GAAM,MAAQA,IAAM,EAC/C,CAWA,eAAsBC,EACrBC,EACAC,EACkB,CAClB,OAAID,EAAK,OAAS,SACVA,EAAK,GAENA,EAAK,UAAUC,CAAK,CAC5B,CAaO,SAASC,GACfC,EACAC,EACAC,EACAJ,EACAK,EACyB,CAEzB,GACCH,EAAU,MAAOI,GAChBV,GAASW,EAAeP,EAAkCM,EAAE,KAAK,CAAC,CACnE,EAEA,OAAO,KAMR,IAAME,EAAaN,EACjB,OACCI,GACA,CAACV,GAASW,EAAeP,EAAkCM,EAAE,KAAK,CAAC,CACrE,EACC,IAAKA,GAAM,CACX,IAAMG,EAAcC,GAAmBL,EAAaC,EAAE,KAAK,EAC3D,OAAOG,EAAc,CAAE,GAAGH,EAAG,YAAAG,CAAY,EAAIH,CAC9C,CAAC,EAGIK,EAAWH,EAAW,SAAW,EACjCI,EAAKJ,EAAW,CAAC,EAiBvB,MAAO,CACN,QAhBAG,GAAYC,EACT,CACA,OAAQ,YACR,SAAUA,EAAG,SACb,MAAOA,EAAG,MACV,GAAIA,EAAG,YAAc,CAAE,YAAaA,EAAG,WAAY,EAAI,CAAC,EACxD,GAAIA,EAAG,YAAc,CAAE,YAAaA,EAAG,WAAY,EAAI,CAAC,EACxD,GAAIA,EAAG,SAAWT,EAAU,CAAE,QAASS,EAAG,SAAWT,CAAQ,EAAI,CAAC,CACnE,EACC,CACA,OAAQ,YACR,UAAWK,EACX,GAAIL,EAAU,CAAE,QAAAA,CAAQ,EAAI,CAAC,CAC9B,EAIF,iBAAkB,CACjB,KAAMC,EACN,MAAAJ,EACA,GAAIW,GAAYC,EAAK,CAAE,MAAOA,EAAG,KAAM,EAAI,CAAC,CAC7C,CACD,CACD,CAMA,eAAsBC,GACrBC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAhB,EAC2B,CAC3B,IAAID,EAAcU,EACdd,EAAQ,CAAE,GAAGe,CAAW,EACtBO,EAA4B,CAAC,EAG7BC,EAAiB,GACnBC,EAAa,EAEjB,KAAOA,IAAeD,GAAgB,CAErC,GAAInB,IAAgBqB,EACnB,MAAO,CACN,QAAS,CAAE,OAAQ,UAAW,EAC9B,iBAAkB,CAAE,MAAAzB,CAAM,EAC1B,aAAAsB,CACD,EAGD,IAAMI,EAAUV,EAAM,IAAIZ,CAAW,EACrC,GAAI,CAACsB,EACJ,OAAAC,EAAmB,CAClB,SAAAP,EACA,KAAM,eACN,MAAO,oBACP,WAAY,CAAE,KAAMhB,CAAY,CACjC,CAAC,EACM,CACN,QAAS,CACR,OAAQ,QACR,MAAO,kBAAkBA,CAAW,GACrC,EACA,aAAAkB,CACD,EAGID,GAAa,IAAIjB,CAAW,GAAG,gBACnCkB,EAAa,KAAK,CAAE,GAAIlB,EAAa,GAAI,IAAI,KAAK,EAAE,YAAY,CAAE,CAAC,EAGpE,GAAI,CAaH,IAAMwB,EAAS,MAAMF,EAXT,CACX,MAAA1B,EACA,KAAAmB,EACA,UAAWU,GAGX,WAAYC,GAGZ,SAAAV,CACD,CACiE,EAGjE,GAAIW,GAAYH,CAAM,EAAG,CAExB,QAAWtB,KAAKsB,EAAO,UAClBtB,EAAE,UACLY,EAAW,IAAI,GAAGd,CAAW,IAAIE,EAAE,KAAK,GAAIA,EAAE,QAAQ,EAIxD,IAAM0B,EAAkB/B,GACvB2B,EAAO,UACPA,EAAO,QACPxB,EACAJ,EACAK,CACD,EAEA,GAAI2B,EACH,MAAO,CAAE,GAAGA,EAAiB,aAAAV,CAAa,EAI3C,QAAWhB,KAAKsB,EAAO,UAAW,CACjC,IAAMK,EAAKf,EAAW,IAAI,GAAGd,CAAW,IAAIE,EAAE,KAAK,EAAE,EACrD,GAAI2B,EACH,GAAI,CACH,IAAMC,EAAQ3B,EACbP,EACAM,EAAE,KACH,EACM6B,EAAU,MAAMF,EAAGC,CAAK,EAC1BC,GAAW,OAAOA,GAAY,WACjCnC,EAAQoC,EACPpC,EACAmC,CACD,EAEF,OAASE,EAAK,CACb,IAAMC,EAAMD,aAAe,MAAQA,EAAI,QAAU,OAAOA,CAAG,EAC3DE,GAAkBvC,EAAkCM,EAAE,KAAK,EAC3D,IAAMkC,EAAqBZ,EAAO,UAAU,IAAKa,GAChDA,EAAG,QAAUnC,EAAE,MACZ,CACA,GAAGmC,EACH,QAASA,EAAG,QACT,UAAUH,CAAG;AAAA;AAAA,EAAOG,EAAG,OAAO,GAC9B,UAAUH,CAAG,EACjB,EACCG,CACJ,EACMC,EAAYzC,GACjBuC,EACAZ,EAAO,QACPxB,EACAJ,EACAK,CACD,EACA,GAAIqC,EACH,MAAO,CAAE,GAAGA,EAAW,aAAApB,CAAa,EAErC,KACD,CAEF,CAGA,IAAMvB,EAAOkB,EAAM,IAAIb,CAAW,EAClC,GAAI,CAACL,EACJ,OAAA4B,EAAmB,CAClB,SAAAP,EACA,KAAM,eACN,MAAO,gBACP,WAAY,CAAE,KAAMhB,CAAY,CACjC,CAAC,EACM,CACN,QAAS,CACR,OAAQ,QACR,MAAO,+BAA+BA,CAAW,GAClD,EACA,aAAAkB,CACD,EAEDlB,EAAc,MAAMN,EAAgBC,EAAMC,CAAK,EAC/C,QACD,CAGA,GAAI2C,GAASf,CAAM,EAAG,CACrB,IAAMgB,EAAchB,EAAO,MAC3B,GAAIgB,GAEFhD,GACCW,EAAeP,EAAkC4C,CAAW,CAC7D,EACC,CACD,IAAM7C,EAAOkB,EAAM,IAAIb,CAAW,EAClC,GAAI,CAACL,EACJ,OAAA4B,EAAmB,CAClB,SAAAP,EACA,KAAM,eACN,MAAO,gBACP,WAAY,CAAE,KAAMhB,CAAY,CACjC,CAAC,EACM,CACN,QAAS,CACR,OAAQ,QACR,MAAO,+BAA+BA,CAAW,GAClD,EACA,aAAAkB,CACD,EAEDlB,EAAc,MAAMN,EAAgBC,EAAMC,CAAK,EAC/C,QACD,CAGD,IAAM6C,EAAoBD,EACvBlC,GAAmBL,EAAauC,CAAW,EAC3C,OACGE,EAAclB,EAAO,cAAgB,GAC3C,MAAO,CACN,QAAS,CACR,OAAQ,SACR,KAAMA,EAAO,KACb,GAAIA,EAAO,OAAS,OAAY,CAAE,KAAMA,EAAO,IAAK,EAAI,CAAC,EAKzD,YAAakB,EACV,oCAAoClB,EAAO,IAAI,qDAC/C,wDAAmDA,EAAO,IAAI,iLAA4KA,EAAO,IAAI,iEACxP,YAAAkB,EACA,GAAIF,EAAc,CAAE,MAAOA,CAAY,EAAI,CAAC,EAC5C,GAAIC,EAAoB,CAAE,YAAaA,CAAkB,EAAI,CAAC,CAC/D,EACA,iBAAkB,CACjB,KAAMzC,EACN,MAAAJ,EACA,MAAO4C,EACP,SAAUhB,EAAO,IAClB,EACA,aAAAN,CACD,CACD,CAGAtB,EAAQoC,EACPpC,EACA4B,CACD,EAEA,IAAM7B,EAAOkB,EAAM,IAAIb,CAAW,EAClC,GAAI,CAACL,EACJ,OAAA4B,EAAmB,CAClB,SAAAP,EACA,KAAM,eACN,MAAO,gBACP,WAAY,CAAE,KAAMhB,CAAY,CACjC,CAAC,EACM,CACN,QAAS,CACR,OAAQ,QACR,MAAO,+BAA+BA,CAAW,GAClD,EACA,aAAAkB,CACD,EAEDlB,EAAc,MAAMN,EAAgBC,EAAMC,CAAK,CAChD,OAAS+C,EAAO,CACf,IAAMC,EAAUD,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,EACrE,OAAApB,EAAmB,CAClB,SAAAP,EACA,KAAM,eACN,MAAO6B,EAAc,CAAE,MAAAF,CAAM,CAAC,EAC9B,WAAY,CAAE,KAAM3C,CAAY,CACjC,CAAC,EACM,CACN,QAAS,CAAE,OAAQ,QAAS,MAAO4C,CAAQ,EAC3C,iBAAkB,CAAE,KAAM5C,EAAa,MAAAJ,CAAM,EAC7C,aAAAsB,CACD,CACD,CACD,CAEA,OAAAK,EAAmB,CAClB,SAAAP,EACA,KAAM,eACN,MAAO,YACP,WAAY,CAAE,KAAMhB,CAAY,CACjC,CAAC,EACM,CACN,QAAS,CACR,OAAQ,QACR,MAAO,2DACR,EACA,aAAAkB,CACD,CACD,CClWO,SAAS4B,GACfC,EAC6B,CAC7B,OACC,OAAOA,GAAU,UACjBA,IAAU,MACTA,EAA4B,WAAa,GAC1C,OAAQA,EAA4B,IAAO,UAC3C,OAAQA,EAA4B,IAAO,QAE7C,CAMA,IAAMC,GAAW,IAAI,IAErB,eAAeC,GAAUC,EAAuC,CAC/D,IAAMC,EAASH,GAAS,IAAIE,CAAS,EACrC,GAAIC,EACH,OAAOA,EAGR,IAAMC,EAAM,OAAO,KAAKF,EAAW,QAAQ,EAC3C,GAAIE,EAAI,SAAW,GAClB,MAAM,IAAI,MACT,uFACD,EAGD,IAAMC,EAAY,MAAM,WAAW,OAAO,OAAO,UAChD,MACAD,EACA,CAAE,KAAM,SAAU,EAClB,GACA,CAAC,UAAW,SAAS,CACtB,EAEA,OAAAJ,GAAS,IAAIE,EAAWG,CAAS,EAC1BA,CACR,CAMA,eAAsBC,GACrBP,EACAG,EAC6B,CAC7B,IAAMK,EAAM,MAAMN,GAAUC,CAAS,EAC/BM,EAAK,WAAW,OAAO,gBAAgB,IAAI,WAAW,EAAE,CAAC,EACzDC,EAAY,IAAI,YAAY,EAAE,OAAO,KAAK,UAAUV,CAAK,CAAC,EAE1DW,EAAa,MAAM,WAAW,OAAO,OAAO,QACjD,CAAE,KAAM,UAAW,GAAAF,CAAG,EACtBD,EACAE,CACD,EAEA,MAAO,CACN,SAAU,EACV,GAAI,OAAO,KAAKC,CAAU,EAAE,SAAS,QAAQ,EAC7C,GAAI,OAAO,KAAKF,CAAE,EAAE,SAAS,QAAQ,CACtC,CACD,CAEA,eAAsBG,GACrBC,EACAV,EACa,CACb,IAAMK,EAAM,MAAMN,GAAUC,CAAS,EAC/BW,EAAK,OAAO,KAAKD,EAAS,GAAI,QAAQ,EACtCJ,EAAK,OAAO,KAAKI,EAAS,GAAI,QAAQ,EAExCH,EACJ,GAAI,CACHA,EAAY,MAAM,WAAW,OAAO,OAAO,QAC1C,CAAE,KAAM,UAAW,GAAAD,CAAG,EACtBD,EACAM,CACD,CACD,MAAQ,CACP,MAAM,IAAI,MACT,oGACD,CACD,CAEA,OAAO,KAAK,MAAM,IAAI,YAAY,EAAE,OAAOJ,CAAS,CAAC,CACtD,CCrHA,IAAMK,GAAmC,CACxC,MAAO,EACP,KAAM,EACN,MAAO,EACP,KAAM,CACP,EAEA,SAASC,IAA2B,CACnC,IAAMC,EAAW,QAAQ,IAAI,mBAC7B,OAAIA,GAAYA,KAAYF,GACpBE,EAEJ,QAAQ,IAAI,eACR,QAED,MACR,CAaO,SAASC,EACfC,EACAC,EAC+B,CAE/B,OADeA,GAAWJ,GAAe,IAAM,QAE5C,IAAIK,IAAoB,QAAQ,IAAI,aAAaF,CAAS,IAAK,GAAGE,CAAI,EACtE,IAAM,CAAC,CACX,CCDA,IAAMC,GAAW,gBACXC,GAAmB,0BAEnBC,GAAUC,EAAa,IAAI,EAEpBC,EAAN,KAEP,CACC,IAAY,SAAkB,CAC7B,OAAQ,QAAQ,IAAI,kBAAoBH,IAAkB,QACzD,MACA,EACD,CACD,CAEA,IAAY,QAA6B,CACxC,OAAO,QAAQ,IAAI,gBACpB,CAEA,IAAY,eAAoC,CAC/C,OAAO,QAAQ,IAAI,uBACpB,CAEA,MAAM,IAAII,EAAgC,CACzC,GAAI,CAAC,KAAK,OACT,MAAM,IAAI,MACT,oEACD,EAED,IAAMC,EAAO,MAAM,KAAK,QACvB,qBACA,CAAE,IAAAD,CAAI,CACP,EACA,GAAIC,GAAQ,KACX,OAAO,KAER,GAAIC,GAAoBD,CAAI,EAAG,CAC9B,GAAI,CAAC,KAAK,cACT,MAAM,IAAI,MACT,4EACD,EAED,OAAOE,GAAgBF,EAAM,KAAK,aAAa,CAChD,CACA,OAAOA,CACR,CAEA,MAAM,IAAID,EAAaI,EAAUC,EAA4C,CAC5E,GAAI,CAAC,KAAK,OACT,MAAM,IAAI,MACT,oEACD,EAED,IAAMC,EAAS,KAAK,cACpBT,GACC,QAAQG,CAAG,uBAAkBM,EAAS,UAAY,uCAAuC,EAC1F,EACA,IAAMC,EAAUD,EACb,MAAME,GAAaJ,EAAkCE,CAAM,EAC3DF,EACH,MAAM,KAAK,QAAQ,qBAAsB,CACxC,IAAAJ,EACA,MAAOO,EACP,WAAYF,GAAS,UACtB,CAAC,CACF,CAEA,MAAM,OAAOL,EAA4B,CACxC,GAAK,KAAK,OAGV,GAAI,CACH,MAAM,KAAK,QAAQ,wBAAyB,CAAE,IAAAA,CAAI,CAAC,CACpD,OAASS,EAAO,CACf,QAAQ,MAAM,uCAAwCT,EAAKS,CAAK,CACjE,CACD,CAEA,MAAc,QAAWC,EAAcC,EAA2B,CACjE,IAAMC,EAAM,GAAG,KAAK,OAAO,GAAGF,CAAI,GAC5BG,EAAW,MAAM,MAAMD,EAAK,CACjC,OAAQ,OACR,QAAS,CACR,cAAe,UAAU,KAAK,MAAM,GACpC,eAAgB,mBAChB,iBAAkBjB,EACnB,EACA,KAAM,KAAK,UAAUgB,CAAI,CAC1B,CAAC,EAED,GAAI,CAACE,EAAS,GAAI,CACjB,IAAMC,EAAO,MAAMD,EAAS,KAAK,EAAE,MAAM,IAAM,EAAE,EACjD,MAAM,IAAI,MAAMC,GAAQ,4BAA4BD,EAAS,MAAM,EAAE,CACtE,CAGA,OADc,MAAMA,EAAS,KAAK,GACtB,IACb,CACD,EC7HO,IAAME,GAAN,KAAuE,CAC5D,IAAM,IAAI,IAE3B,MAAM,IAAIC,EAAgC,CACzC,OAAO,KAAK,IAAI,IAAIA,CAAG,GAAK,IAC7B,CAIA,MAAM,IACLA,EACAC,EACAC,EACgB,CAChB,KAAK,IAAI,IAAIF,EAAKC,CAAK,CACxB,CAEA,MAAM,OAAOD,EAA4B,CACxC,KAAK,IAAI,OAAOA,CAAG,CACpB,CACD,ECHO,IAAMG,EAAN,KAA6C,CAClC,MAAQ,IAAIC,EAE7B,IAAIC,EAA+C,CAClD,OAAO,KAAK,MAAM,IAAIA,CAAG,CAC1B,CAEA,IACCA,EACAC,EACAC,EACgB,CAChB,OAAO,KAAK,MAAM,IAAIF,EAAKC,EAAOC,CAAO,CAC1C,CAEA,OAAOF,EAA4B,CAClC,OAAO,KAAK,MAAM,OAAOA,CAAG,CAC7B,CACD,ECpCA,SAASG,GACRC,EACoC,CACpC,IAAMC,EAAMD,EAAQ,SAAS,EAC7B,OAAIC,EAAI,SAAS,YAAY,EACrB,SAEJA,EAAI,SAAS,WAAW,EACpB,YAED,QACR,CAEO,SAASC,GACfC,EACAC,EACAC,EACAC,EACY,CACZ,IAAMC,EAA8B,CAAC,EACrC,OAAW,CAACC,EAAIR,CAAO,IAAKI,EAAO,CAClC,IAAMK,EAAOH,EAAY,IAAIE,CAAE,EAC/BD,EAAW,KAAK,CACf,GAAAC,EACA,KAAMT,GAAaC,CAAO,EAC1B,MAAOS,GAAM,OAASD,EACtB,GAAIC,GAAM,eAAiB,CAAE,eAAgB,EAAK,EAAI,CAAC,CACxD,CAAC,CACF,CAEA,IAAMC,EAA8B,CAAC,EACrC,OAAW,CAACC,EAAMC,CAAI,IAAKP,EACtBO,EAAK,OAAS,SACjBF,EAAW,KAAK,CAAE,KAAAC,EAAM,GAAIC,EAAK,GAAI,KAAM,QAAS,CAAC,EAErDF,EAAW,KAAK,CAAE,KAAAC,EAAM,GAAIC,EAAK,QAAS,KAAM,aAAc,CAAC,EAIjE,MAAO,CACN,OAAQT,EAAO,GACf,MAAOA,EAAO,MACd,MAAOI,EACP,MAAOG,CACR,CACD,CC7BO,SAASG,GACfC,EACAC,EAC+B,CAC/B,GAAID,IAAU,OACb,OAGD,GAAI,OAAOA,GAAU,SAAU,CAC9B,IAAME,EAAWF,EAAM,KAAK,EAC5B,GAAI,CAACE,EACJ,MAAM,IAAI,MACT,0BAA0BD,CAAM,8FAEjC,EAED,MAAO,CAAE,SAAAC,CAAS,CACnB,CAEA,GAAI,OAAOF,GAAU,UAAYA,IAAU,KAC1C,MAAM,IAAI,MACT,0BAA0BC,CAAM,4GAEjC,EAGD,IAAMC,EACL,OAAOF,EAAM,UAAa,SAAWA,EAAM,SAAS,KAAK,EAAI,OACxDG,EACL,OAAOH,EAAM,cAAiB,SAC3BA,EAAM,aAAa,KAAK,EACxB,OAEJ,GAAI,CAACE,GAAY,CAACC,EACjB,MAAM,IAAI,MACT,0BAA0BF,CAAM,iIAEjC,EAGD,MAAO,CACN,GAAIC,EAAW,CAAE,SAAAA,CAAS,EAAI,CAAC,EAC/B,GAAIC,EAAe,CAAE,aAAAA,CAAa,EAAI,CAAC,CACxC,CACD,CChDA,IAAMC,GAIF,CACH,MAAQC,GAAWC,GAAeD,EAAO,MAAOA,EAAO,EAAE,CAC1D,EAQO,SAASE,GAAkBF,EAAuC,CACxE,IAAMG,EAAS,OAAO,QAAQJ,EAAK,EACjC,IAAI,CAAC,CAACK,EAAOC,CAAI,IAAM,CAACD,EAAOC,EAAKL,CAAM,CAAC,CAAU,EACrD,OAAO,CAAC,CAAC,CAAEM,CAAK,IAAMA,IAAU,MAAS,EAC3C,OAAO,OAAO,YAAYH,CAAM,CACjC,CAWA,eAAsBI,GAAkBC,EAOT,CAC9B,GAAM,CAAE,MAAAC,EAAO,UAAAC,EAAW,qBAAAC,EAAsB,KAAAN,CAAK,EAAIG,EAEzD,GAAI,OAAO,KAAKH,CAAI,EAAE,SAAW,EAChC,MAAO,CAAC,EAET,GAAI,CAACK,GAAa,CAACC,EAClB,OAAON,EAER,GAAI,CACH,IAAMO,EAAS,MAAMH,EAAM,IAAIC,CAAS,EACxC,OAAOE,EAAUA,EAAO,UAAY,CAAC,EAAKP,CAC3C,MAAQ,CACP,OAAOA,CACR,CACD,CAUO,SAASQ,GACfC,EACAV,EAC+D,CAC/D,GAAM,CAAE,CAACA,CAAK,EAAGE,EAAO,GAAGS,CAAK,EAAID,GAAY,CAAC,EACjD,MAAO,CAAE,MAAAR,EAAO,SAAUS,CAAK,CAChC,CAOO,SAASC,GACfC,EACAH,EACmB,CACnB,OAAI,OAAO,KAAKA,CAAQ,EAAE,SAAW,EAC7BG,EAED,CAAE,GAAGA,EAAc,SAAAH,CAAS,CACpC,CCzGA,OAAS,KAAAI,MAAS,MAUlB,IAAMC,GAAkBD,EACtB,OAAO,CACP,KAAMA,EAAE,KAAK,CACZ,OACA,SACA,SACA,UACA,SACA,QACA,SACD,CAAC,EACD,OAAQA,EAAE,MAAMA,EAAE,OAAO,CAAC,EAAE,SAAS,EACrC,YAAaA,EAAE,OAAO,EAAE,SAAS,EACjC,SAAUA,EAAE,QAAQ,EAAE,SAAS,CAChC,CAAC,EACA,SACA,qFACD,EAEKE,GAAkBF,EACtB,OAAO,CACP,SAAUA,EACR,OAAO,EACP,SAAS,EACT,SACA,qEACD,EACD,aAAcA,EACZ,OAAO,EACP,SAAS,EACT,SAAS,kDAAkD,CAC9D,CAAC,EACA,SACA,yFACD,EAEKG,GAAqBH,EACzB,OAAO,CACP,SAAUA,EAAE,OAAO,EACnB,MAAOA,EAAE,OAAO,EAChB,YAAaA,EAAE,MAAMA,EAAE,OAAO,CAAC,EAAE,SAAS,EAC1C,QAASA,EAAE,OAAO,EAAE,SAAS,EAC7B,YAAaC,GAAgB,SAAS,CACvC,CAAC,EACA,SAAS,iDAAiD,EAE/CG,GAAmB,CAC/B,MAAOF,GACL,SAAS,EACT,SACA,yHACD,EACD,OAAQF,EACN,KAAK,CAAC,YAAa,SAAU,WAAY,OAAO,CAAC,EACjD,SACA,oEACD,EACD,SAAUA,EACR,OAAO,EACP,SAAS,EACT,SAAS,2DAA2D,EACtE,MAAOA,EACL,OAAO,EACP,SAAS,EACT,SACA,uEACD,EACD,YAAaC,GACX,SAAS,EACT,SAAS,wDAAwD,EACnE,YAAaD,EACX,MAAMA,EAAE,OAAO,CAAC,EAChB,SAAS,EACT,SAAS,sDAAsD,EACjE,UAAWA,EACT,MAAMG,EAAkB,EACxB,SAAS,EACT,SAAS,8DAA8D,EACzE,QAASH,EACP,OAAO,EACP,SAAS,EACT,SAAS,gDAAgD,EAC3D,KAAMA,EACJ,OAAO,EACP,SAAS,EACT,SAAS,4CAA4C,EACvD,KAAMA,EACJ,OAAOA,EAAE,OAAO,EAAGA,EAAE,QAAQ,CAAC,EAC9B,SAAS,EACT,SAAS,wCAAwC,EACnD,YAAaA,EACX,OAAO,EACP,SAAS,EACT,SAAS,iDAAiD,EAC5D,YAAaA,EACX,QAAQ,EACR,SAAS,EACT,SACA,iEACD,EACD,UAAWA,EACT,OAAO,EACP,SAAS,EACT,SAAS,gEAAgE,EAC3E,MAAOA,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,qCAAqC,CAC5E,ECjHO,SAASK,GAAkBC,EAA4B,CAC7D,IAAMC,EAAQ,CACb,GACA,6BACA,GACA,uFACA,GACA,gEACA,wEACA,mCACA,iGACA,kGACA,qHACA,yEACA,iGACA,wGACA,yFACD,EAEA,OAAID,EAAO,eACVC,EAAM,KACL,uHACA,6FACD,EAGDA,EAAM,KACL,mFACA,mEACA,iEACA,yDACA,yHACA,qHACA,8DACA,gHACA,+HACA,iHACA,6BACA,6BACA,wGACA,yFACA,6DACA,sDACA,+GACA,kGACA,gEACA,+BACA,sGACA,2GACA,sGACA,kGACA,sGACA,uEACA,kEACA,GACA,uGACA,4GACA,yGACA,mFACA,4EACA,yEACA,yEACA,uFACA,6FACA,4EACA,gFACA,iFACA,kFACA,mEACD,EAEID,EAAO,OACVC,EAAM,KACL,yFACA,gGACA,yCACA,6FACA,kGACA,sGACA,kGACA,kCACA,8FACA,4DACD,EAGMA,EAAM,KAAK;AAAA,CAAI,CACvB,CCrFA,SAASC,GAASC,EAAwC,CACzD,MAAO,EAAQA,GAAU,OAAOA,GAAU,UAAY,CAAC,MAAM,QAAQA,CAAK,CAC3E,CAkBO,SAASC,GAA8BC,EAAc,CAC3D,IAAMC,EAAWC,GAASF,CAAM,EAC1BG,EAAWN,GAASI,EAAS,QAAQ,EAAIA,EAAS,SAAW,CAAC,EACpE,OAAQD,EAAwD,KAAK,CACpE,GAAGC,EACH,SAAU,CAAE,GAAGE,EAAU,SAAU,EAAK,CACzC,CAAC,CACF,CAEO,SAASC,GAAgBJ,EAA4B,CAE3D,IAAMG,EADOD,GAASF,CAAM,EACN,SACtB,OAAOH,GAASM,CAAQ,GAAKA,EAAS,WAAa,EACpD,CAEA,SAASD,GAASF,EAAkC,CACnD,IAAMK,EAAUL,EAA+C,KAC/D,GAAI,OAAOK,GAAW,WACrB,MAAO,CAAC,EAET,IAAMP,EAAQO,EAAO,KAAKL,CAAM,EAChC,OAAOH,GAASC,CAAK,EAAIA,EAAQ,CAAC,CACnC,CAKO,SAASQ,GACfC,EACW,CACX,GAAI,CAACA,EACJ,MAAO,CAAC,EAET,IAAMC,EAAgB,CAAC,EACvB,OAAW,CAACC,EAAMT,CAAM,IAAK,OAAO,QAAQO,CAAK,EAC5CH,GAAgBJ,CAAM,GACzBQ,EAAI,KAAKC,CAAI,EAGf,OAAOD,CACR,CAEO,IAAME,GACZ,qCxBnBD,SAASC,GAAiBC,EAGvB,CACF,IAAMC,EAAUD,EAAO,cACpB,6GACA,GAOGE,EADWF,EAAO,OAAS,OAAO,KAAKA,EAAO,KAAK,EAAE,OAAS,EAEjEG,EACC,OAAOH,EAAO,KAAkC,EAChD,QAAQ,EACR,YAAY,EACbG,EAAE,OAAOA,EAAE,OAAO,EAAGA,EAAE,QAAQ,CAAC,EAEnC,MAAO,CACN,OAAQA,EACN,KAAK,CAAC,QAAS,WAAY,OAAO,CAAC,EACnC,SACA,8KACD,EACD,OAAQA,EACN,OAAO,EACP,SAAS,EACT,SACA,2HAA2HF,CAAO,EACnI,EACD,QAASE,EACP,OAAO,EACP,SAAS,EACT,SACA,qOAAgOF,CAAO,EACxO,EACD,aAAcC,EACZ,SAAS,EACT,SACA,mOACD,EACD,UAAWC,EACT,OAAO,EACP,SAAS,EACT,SACA,yIACD,CACF,CACD,CAMA,SAASC,GAAoBC,EAA2B,CACvD,GAAI,QAAQ,IAAI,iBACf,OAAO,IAAIC,EAEZ,MAAM,IAAI,MACT,0BAA0BD,CAAM,sRAIjC,CACD,CAcO,SAASE,GACfC,EACiB,CACjB,GAAM,CAAE,OAAAR,EAAQ,MAAAS,EAAO,MAAAC,CAAM,EAAIF,EAC3BG,EAAcZ,GAAiBC,CAAM,EACrCY,EAAYC,GAAiBb,EAAQS,EAAOC,EAAOF,EAAM,WAAW,EACpEM,EAAWC,GAAkBf,CAAM,EACnCgB,EAAkB,GAAGhB,EAAO,WAAW;AAAA,EAAKc,CAAQ,GAEpDG,EAAmBT,EAAM,OAASJ,GAAoBJ,EAAO,EAAE,EAI/DkB,EAAkBC,GAAkBnB,CAAM,EAI1CoB,EAAa,IAAI,IAEvB,eAAeC,EACdC,EACAC,EACAC,EACAC,EACAC,EACuB,CAQvB,IAAMC,EAAM,CACXC,EACAC,EACAC,IAEAC,GACCH,EACAC,EACApB,EACAC,EACAU,EACAK,EACAC,EACAlB,EAAM,YACNR,EAAO,KACR,EAAE,KAAMgC,IAAY,CAAE,GAAGA,EAAQ,SAAAF,CAAS,EAAE,EAE7C,GAAIR,EAAK,SAAW,QAAS,CAK5B,IAAMW,EACL,OAAOX,EAAK,QAAW,SAAWA,EAAK,OAAO,KAAK,EAAI,OAIxD,GAHAA,EAAK,OAASW,GAAU,OAGpB,OAAOX,EAAK,SAAY,SAAU,CACrC,IAAMY,EAAUZ,EAAK,QAAQ,KAAK,EAClCA,EAAK,QAAUY,GAAW,MAC3B,CAEA,IAAMC,EAAYzB,EAAM,IAAI0B,CAAK,EACjC,GAAI,CAACD,EACJ,OAAAE,EAAmB,CAClB,SAAAX,EACA,KAAM,eACN,MAAO,gBACP,WAAY,CAAE,KAAMU,CAAM,CAC3B,CAAC,EACM,CACN,QAAS,CAAE,OAAQ,QAAkB,MAAO,eAAgB,CAC7D,EAGD,IAAMN,EAAW,MAAMQ,GAAkB,CACxC,MAAArB,EACA,UAAAM,EACA,qBAAAC,EACA,KAAMN,CACP,CAAC,EACKqB,EAAaC,GAAelB,EAAK,cAAgB,CAAC,CAAC,EACnDmB,EAAY,MAAMC,EAAgBP,EAAWI,CAAU,EAC7D,OAAOZ,EAAIc,EAAWF,EAAYT,CAAQ,CAC3C,CAEA,GAAIR,EAAK,SAAW,WAAY,CAC/B,GAAI,CAACC,EACJ,MAAO,CACN,QAAS,CACR,OAAQ,QACR,MAAO,8CACR,CACD,EAGD,IAAIoB,EACJ,GAAI,CACHA,EAAY,MAAM1B,EAAM,IAAIM,CAAS,CACtC,OAASqB,EAAK,CACb,IAAMC,EAAMD,aAAe,MAAQA,EAAI,QAAU,OAAOA,CAAG,EAC3D,OAAAP,EAAmB,CAClB,SAAAX,EACA,KAAM,kBACN,MAAOoB,EAAc,CAAE,MAAOF,CAAI,CAAC,CACpC,CAAC,EACM,CACN,QAAS,CACR,OAAQ,QACR,MAAO,uCAAuCrB,CAAS,OAAOsB,CAAG,EAClE,CACD,CACD,CAEA,GAAI,CAACF,EACJ,MAAO,CACN,QAAS,CACR,OAAQ,QACR,MAAO,qCAAqCpB,CAAS,+BACtD,CACD,EAGD,IAAMM,EAAQc,EAAU,MAClBI,EAAOJ,EAAU,KACvB,GAAI,CAACI,EACJ,MAAO,CACN,QAAS,CACR,OAAQ,QACR,MACC,0EACF,CACD,EAGD,IAAMC,EAAeC,EACpBpB,EACAW,GAAelB,EAAK,cAAgB,CAAC,CAAC,CACvC,EACMQ,EAAWa,EAAU,UAAY,CAAC,EAGxC,GAAIA,EAAU,SAAU,CACvB,IAAMO,EAAOxC,EAAM,IAAIqC,CAAI,EAC3B,GAAI,CAACG,EACJ,OAAAb,EAAmB,CAClB,SAAAX,EACA,KAAM,eACN,MAAO,gBACP,WAAY,CAAE,KAAMqB,CAAK,CAC1B,CAAC,EACM,CACN,QAAS,CACR,OAAQ,QACR,MAAO,sBAAsBA,CAAI,GAClC,CACD,EAED,IAAMI,EAAW,MAAMT,EAAgBQ,EAAMF,CAAY,EACzD,OAAOrB,EAAIwB,EAAUH,EAAclB,CAAQ,CAC5C,CAKA,OAAOH,EAAIoB,EAAMC,EAAclB,CAAQ,CACxC,CAEA,GAAIR,EAAK,SAAW,QAAS,CAC5B,GAAI,CAACC,EACJ,MAAO,CACN,QAAS,CACR,OAAQ,QACR,MAAO,2CACR,CACD,EAGD,IAAIoB,EACJ,GAAI,CACHA,EAAY,MAAM1B,EAAM,IAAIM,CAAS,CACtC,OAASqB,EAAK,CACb,IAAMC,GAAMD,aAAe,MAAQA,EAAI,QAAU,OAAOA,CAAG,EAC3D,OAAAP,EAAmB,CAClB,SAAAX,EACA,KAAM,kBACN,MAAOoB,EAAc,CAAE,MAAOF,CAAI,CAAC,CACpC,CAAC,EACM,CACN,QAAS,CACR,OAAQ,QACR,MAAO,uCAAuCrB,CAAS,OAAOsB,EAAG,EAClE,CACD,CACD,CAEA,GAAI,CAACF,EACJ,MAAO,CACN,QAAS,CACR,OAAQ,QACR,MAAO,qCAAqCpB,CAAS,oFACtD,CACD,EAGD,GAAI,CAACoB,EAAU,KACd,MAAO,CACN,QAAS,CACR,OAAQ,QACR,MACC,0EACF,CACD,EAGD,GAAI,CAACrB,EAAK,cAAgB,OAAO,KAAKA,EAAK,YAAY,EAAE,SAAW,EACnE,MAAO,CACN,QAAS,CACR,OAAQ,QACR,MACC,4EACF,CACD,EAGD,IAAMa,EAAYzB,EAAM,IAAI0B,CAAK,EACjC,GAAI,CAACD,EACJ,OAAAE,EAAmB,CAClB,SAAAX,EACA,KAAM,eACN,MAAO,gBACP,WAAY,CAAE,KAAMU,CAAM,CAC3B,CAAC,EACM,CACN,QAAS,CAAE,OAAQ,QAAkB,MAAO,eAAgB,CAC7D,EAGD,IAAMgB,EAAgBT,EAAU,MAC1BU,EAAcJ,EACnBG,EACAZ,GAAelB,EAAK,YAAY,CACjC,EAEMQ,EAAWa,EAAU,UAAY,CAAC,EAClCF,EAAY,MAAMC,EAAgBP,EAAWkB,CAAW,EAC9D,OAAO1B,EAAIc,EAAWY,EAAavB,CAAQ,CAC5C,CAEA,MAAO,CACN,QAAS,CACR,OAAQ,QACR,MAAO,oBAAoBR,EAAK,MAAM,GACvC,CACD,CACD,CAEA,IAAMgC,EAAsBC,GAC3BvD,EAAO,KACR,EACMwD,EAAa,CAClB,MAAOxD,EAAO,MACd,YAAagB,EACb,YAAAL,EACA,aAAc8C,GACd,YAAazD,EAAO,YACpB,GAAIsD,EAAoB,OAAS,GAAK,CACrC,MAAO,CACN,CAACI,EAAqC,EAAGJ,CAC1C,CACD,CACD,EAEMK,GAAe,MAAOrC,EAAqBsC,IAAmB,CACnE,IAAMC,EAAeD,EAIfE,EAAiCD,EAAa,OAAS,CAAC,EACxDE,EAAgBC,EAAiBF,CAAK,EACxCvC,EAAYwC,GAAiBzC,EAAK,UAGhCE,EAAuB,EAAQD,EAGjC,CAACA,GAAaD,EAAK,SAAW,UACjCC,EAAY,OAAO,WAAW,GAO3BA,GAAa,CAACwC,IACjBD,EAAM,oBAAoB,EAAIvC,GAG/B,IAAMG,EAAWuC,GAAoBJ,CAAY,EAE3C7B,EAAS,MAAMX,EACpBC,EACAC,EACAC,EACAsC,EACApC,CACD,EAGMwC,EACL,CAACH,GAAiBxC,EACf,CAAE,GAAGS,EAAO,QAAS,UAAAT,CAAU,EAC/BS,EAAO,QAQL,CAAE,MAAOmC,EAAO,SAAArC,CAAS,EAC9BoC,EAAW,SAAW,QACnB,CAAE,MAAO,OAAW,SAAUlC,EAAO,UAAY,CAAC,CAAE,EACpDoC,GAAkBpC,EAAO,SAAU,OAAO,EAIxCqC,EAAUF,EAAQ,CAAE,MAAAA,EAAO,GAAGD,CAAW,EAAIA,EAkBnD,GAXAJ,EAAMQ,EAAoB,EAAI,CAC7B,YACCJ,EAAW,SAAW,YAAeA,EAAW,aAAe,CAAC,EAAK,CAAC,CACxE,EAQI3C,GAAaS,EAAO,iBAAkB,CACzC,IAAMuC,GAAeC,GAAkBxC,EAAO,iBAAkBF,CAAQ,EACxE,GAAI,CACH,MAAMb,EAAM,IAAIM,EAAWgD,EAAY,CACxC,OAAS3B,EAAK,CACb,IAAMC,GAAMD,aAAe,MAAQA,EAAI,QAAU,OAAOA,CAAG,EAC3D,OAAAP,EAAmB,CAClB,SAAAX,EACA,KAAM,kBACN,MAAOoB,EAAc,CAAE,MAAOF,CAAI,CAAC,EACnC,WAAY,CAAE,KAAMZ,EAAO,iBAAiB,IAAK,CAClD,CAAC,EAcM,CACN,QAdoB,CACpB,CACC,KAAM,OACN,KAAM,KAAK,UACV,CACC,OAAQ,QACR,MAAO,0CAA0CT,CAAS,OAAOsB,EAAG,EACrE,EACA,KACA,CACD,CACD,CACD,EAGC,MAAAiB,EACA,QAAS,EACV,CACD,CACD,CAEA,IAAMW,GAAU,CACf,CACC,KAAM,OACN,KAAM,KAAK,UAAUJ,EAAS,KAAM,CAAC,CACtC,CACD,EAGA,OAAIrC,EAAO,cAAc,SACxB8B,EAAMY,CAAa,EAAI,CACtB,OAAQ1E,EAAO,GACf,aAAcgC,EAAO,YACtB,GAGM,CACN,QAAAyC,GACA,kBAAmBJ,EACnB,MAAAP,EACA,GAAI9B,EAAO,QAAQ,SAAW,QAAU,CAAE,QAAS,EAAK,EAAI,CAAC,CAC9D,CACD,GAEA,MAAO,CAEN,KAAMhC,EAAO,GACb,OAAQwD,EACR,QAASG,EAET,MAAM,SAASgB,EAAkC,CAChD,IAAMC,EAAkB,CACvB,GAAGpB,EACH,MAAO,CAAE,GAAGA,EAAW,MAAO,WAAY5C,CAAU,CACrD,EACA+D,EAAO,aACN3E,EAAO,GACP4E,EACAjB,CACD,CACD,EACA,MAAOnD,EAAM,MACb,UAAAI,CACD,CACD,CyB3hBA,SAASiE,GACRC,EACAC,EACS,CACT,IAAMC,EAAkB,CAAC,cAAc,EACvCA,EAAM,KAAK,KAAKC,CAAK,WAAW,EAChC,OAAW,CAACC,CAAI,IAAKJ,EACpBE,EAAM,KAAK,KAAKE,CAAI,IAAIA,CAAI,GAAG,EAEhCF,EAAM,KAAK,KAAKG,CAAG,SAAS,EAC5B,OAAW,CAACC,EAAMC,CAAI,IAAKN,EAC1B,GAAIM,EAAK,OAAS,SACjBL,EAAM,KAAK,KAAKI,CAAI,QAAQC,EAAK,EAAE,EAAE,MAErC,SAAWC,KAAUD,EAAK,QACzBL,EAAM,KAAK,KAAKI,CAAI,SAASE,CAAM,EAAE,EAIxC,OAAON,EAAM,KAAK;AAAA,CAAI,CACvB,CA0BO,IAAMO,EAAN,KAGL,CACO,MAAQ,IAAI,IACZ,MAAQ,IAAI,IACZ,YAAc,IAAI,IAClB,OAER,YAAYC,EAAoB,CAC/B,KAAK,OAASA,CACf,CA4BA,QACCC,EACAC,EACAC,EACqC,CACrC,IAAMC,EACL,OAAOH,GAAiB,SAAWA,EAAeA,EAAa,GAC1DI,EACL,OAAOJ,GAAiB,SACpBC,EACDD,EAAa,IACXK,EACL,OAAOL,GAAiB,SAAWE,GAAS,MAAQF,EAAa,MAC5DM,EACL,OAAON,GAAiB,SACrBE,GAAS,eACTF,EAAa,eAEjB,GAAIG,IAAOX,GAASW,IAAOT,EAC1B,MAAM,IAAI,MACT,IAAIS,CAAE,wDACP,EAED,GAAI,KAAK,MAAM,IAAIA,CAAE,EACpB,MAAM,IAAI,MAAM,SAASA,CAAE,kBAAkB,EAG9C,YAAK,MAAM,IAAIA,EAAIC,CAAG,GAClBC,IAAU,QAAaC,IAAmB,SAC7C,KAAK,YAAY,IAAIH,EAAI,CAAE,MAAOE,GAASF,EAAI,eAAAG,CAAe,CAAC,EAEzD,IACR,CAQA,QAAQX,EAA6BY,EAA+B,CACnE,GAAI,KAAK,MAAM,IAAIZ,CAAI,EACtB,MAAM,IAAI,MACT,SAASA,CAAI,uEACd,EAED,YAAK,MAAM,IAAIA,EAAM,CAAE,KAAM,SAAU,GAAAY,CAAG,CAAC,EACpC,IACR,CAqBA,mBACCZ,EACAY,EACAC,EACO,CACP,GAAI,KAAK,MAAM,IAAIb,CAAI,EACtB,MAAM,IAAI,MAAM,SAASA,CAAI,iCAAiC,EAE/D,GAAIY,EAAG,SAAW,EACjB,MAAM,IAAI,MACT,0BAA0BZ,CAAI,oDAC/B,EAED,YAAK,MAAM,IAAIA,EAAM,CACpB,KAAM,cACN,UAAWa,EACX,QAAS,CAAC,GAAGD,CAAE,CAChB,CAAC,EACM,IACR,CAqBA,OAAgB,CACf,OAAOnB,GAAkB,KAAK,MAAO,KAAK,KAAK,CAChD,CAOA,QAAQc,EAAiD,CACxD,KAAK,SAAS,EAEd,IAAMO,EAAY,IAAI,IAAI,KAAK,KAAK,EAC9BC,EAAY,IAAI,IAAI,KAAK,KAAK,EAEpC,OAAOC,GAAoB,CAC1B,OAAQ,KAAK,OACb,MAAOF,EACP,MAAOC,EACP,MAAOR,GAAS,MAChB,MAAO,IAAMd,GAAkBqB,EAAWC,CAAS,EACnD,YAAa,IAAI,IAAI,KAAK,WAAW,CACtC,CAAC,CACF,CAEQ,UAAiB,CAExB,GAAI,CAAC,KAAK,MAAM,IAAIlB,CAAK,EACxB,MAAM,IAAI,MACT,sFACD,EAID,IAAMoB,EAAY,KAAK,MAAM,IAAIpB,CAAK,EACtC,GACCoB,GAAW,OAAS,UACpBA,EAAU,KAAOlB,GACjB,CAAC,KAAK,MAAM,IAAIkB,EAAU,EAAE,EAE5B,MAAM,IAAI,MACT,6CAA6CA,EAAU,EAAE,GAC1D,EAID,OAAW,CAACjB,EAAMC,CAAI,IAAK,KAAK,MAAO,CACtC,GAAID,IAASH,GAAS,CAAC,KAAK,MAAM,IAAIG,CAAI,EACzC,MAAM,IAAI,MAAM,iCAAiCA,CAAI,GAAG,EAEzD,GACCC,EAAK,OAAS,UACdA,EAAK,KAAOF,GACZ,CAAC,KAAK,MAAM,IAAIE,EAAK,EAAE,EAEvB,MAAM,IAAI,MACT,cAAcD,CAAI,oCAAoCC,EAAK,EAAE,GAC9D,EAED,GAAIA,EAAK,OAAS,eAAiBA,EAAK,SACvC,QAAWC,KAAUD,EAAK,QACzB,GAAIC,IAAWH,GAAO,CAAC,KAAK,MAAM,IAAIG,CAAM,EAC3C,MAAM,IAAI,MACT,0BAA0BF,CAAI,oCAAoCE,CAAM,GACzE,EAIJ,CAGA,OAAW,CAACJ,CAAI,IAAK,KAAK,MACzB,GAAI,CAAC,KAAK,MAAM,IAAIA,CAAI,EACvB,MAAM,IAAI,MACT,SAASA,CAAI,kDAAkDA,CAAI,mCAAmCA,CAAI,SAC3G,CAGH,CACD,ECxPO,SAASoB,GACfC,EACsC,CACtC,OAAO,IAAIC,EAAoCD,CAAM,CACtD,CCfA,SAASE,GAAaC,EAA8C,CACnE,IAAMC,EAAUD,EAAO,QACvB,OAAO,KAAK,MAAMC,EAAQ,CAAC,GAAG,MAAQ,EAAE,CACzC,CAEA,eAAsBC,GACrBC,EACAC,EACC,CACD,IAAMC,EAAQD,GAAS,WACjBE,EAAiC,CAAC,EAClCC,EAAY,gBAAgB,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,EAAG,EAAE,CAAC,GAEnEC,EAAS,CACd,aAAc,IAAIC,IAAoB,CACrCH,EAAW,KAAKG,CAAwB,CACzC,CACD,EAEA,MAAMN,EAAK,SAASK,CAAM,EAE1B,IAAME,EAAUJ,EAAW,CAAC,IAAI,CAAC,EACjC,GAAI,CAACI,EACJ,MAAM,IAAI,MAAM,SAASP,EAAK,IAAI,8BAA8B,EAGjE,IAAMQ,EAAQ,CAAE,MAAO,CAAE,UAAAJ,CAAU,CAAE,EAErC,eAAeK,EAASC,EAA8C,CACrE,MAAO,CACN,GAAGA,EACH,aAAcR,EAAQ,MAAMA,EAAM,IAAIE,CAAS,EAAI,IACpD,CACD,CAEA,MAAO,CACN,MAAM,MACLO,EACAC,EACAC,EAC0B,CAC1B,IAAMhB,EAAU,MAAMU,EACrB,CACC,OAAQ,QACR,OAAAI,EACA,GAAIE,EAAU,CAAE,QAAAA,CAAQ,EAAI,CAAC,EAC7B,GAAID,EAAe,CAAE,aAAAA,CAAa,EAAI,CAAC,CACxC,EACAJ,CACD,EACA,OAAOC,EAASb,GAAaC,CAAM,CAAC,CACrC,EAEA,MAAM,aACLe,EAC0B,CAC1B,IAAMf,EAAU,MAAMU,EACrB,CACC,OAAQ,WACR,GAAIK,EAAe,CAAE,aAAAA,CAAa,EAAI,CAAC,CACxC,EACAJ,CACD,EACA,OAAOC,EAASb,GAAaC,CAAM,CAAC,CACrC,EAEA,MAAM,UACLe,EAC0B,CAC1B,IAAMf,EAAU,MAAMU,EACrB,CACC,OAAQ,QACR,aAAAK,CACD,EACAJ,CACD,EACA,OAAOC,EAASb,GAAaC,CAAM,CAAC,CACrC,EAEA,MAAM,WAA8C,CACnD,OAAOK,EAAQA,EAAM,IAAIE,CAAS,EAAI,IACvC,CACD,CACD,CC9GA,OAAS,qBAAAU,OAAyB,cAYlC,IAAMC,GAAkB,WAIpBC,GAAQD,GAAgB,2BACvBC,KACJA,GAAQ,IAAIF,GACZC,GAAgB,2BAA6BC,IAEvC,IAAMC,GAA0BD,GAIhC,SAASE,GAAeC,EAA4B,CAC1D,GAAI,CACHF,GAAwB,SAAS,GAAG,SAAS,KAAKE,CAAK,CACxD,MAAQ,CAAC,CACV,CCfA,IAAMC,GAAW,gBAEV,SAASC,GAAeC,EAAkC,CAChE,GAAM,CAAE,OAAAC,EAAQ,OAAAC,CAAO,EAAIF,EAE3B,SAASG,GAAwB,CAChC,GAAI,CAACD,EACJ,MAAM,IAAI,MAAM,6BAA6B,EAE9C,OAAOA,CACR,CAEA,eAAeE,EACdC,EACAC,EACAC,EACa,CACb,IAAMC,EAAML,EAAc,EACpBM,EAAM,GAAGR,EAAO,QAAQ,MAAO,EAAE,CAAC,GAAGK,CAAI,GAEzCI,EAAkC,CACvC,cAAe,UAAUF,CAAG,GAC5B,iBAAkBV,EACnB,EAEMa,EAAoB,CAAE,OAAAN,EAAQ,QAAAK,CAAQ,EAExCH,IAAS,SACZG,EAAQ,cAAc,EAAI,mBAC1BC,EAAK,KAAO,KAAK,UAAUJ,CAAI,GAGhC,IAAMK,EAAW,MAAM,MAAMH,EAAKE,CAAI,EAEtC,GAAI,CAACC,EAAS,GAAI,CACjB,IAAMC,EAAO,MAAMD,EAAS,KAAK,EAAE,MAAM,IAAM,EAAE,EACjD,MAAM,IAAIE,EACTD,GAAQ,sBAAsBD,EAAS,MAAM,GAC7CA,EAAS,MACV,CACD,CAGA,OADc,MAAMA,EAAS,KAAK,GACtB,IACb,CAEA,MAAO,CACN,MAAM,OAAOG,EAAgD,CAC5D,OAAOX,EAAwB,OAAQ,qBAAsB,CAC5D,MAAAW,CACD,CAAC,CACF,EAEA,MAAM,OACLC,EACAC,EAC0B,CAC1B,IAAMC,EAAU,MAAMd,EACrB,OACA,qBACA,CACC,MAAAY,EACA,GAAGC,CACJ,CACD,EACA,OAAAE,GAAe,CACd,MAAAH,EACA,YAAaE,EAAQ,OACrB,QAASA,EAAQ,IAAKE,IAAO,CAC5B,OAAQA,EAAE,OACV,QAASA,EAAE,QACX,MAAOA,EAAE,KACV,EAAE,CACH,CAAC,EACMF,CACR,EAEA,MAAM,SAA+B,CACpC,OAAOd,EAAoB,MAAO,qBAAqB,CACxD,CACD,CACD,CC/FA,OAAS,cAAAiB,GAAY,gBAAAC,OAAoB,KACzC,OAAS,WAAAC,OAAe,OA2CxB,IAAMC,GAAkB,gBAEpBC,EAWG,SAASC,IAAkD,CACjE,GAAID,IAAY,OACf,OAAOA,EAGR,GAAI,CACH,IAAME,EAAWJ,GAAQ,QAAQ,IAAI,EAAGC,EAAe,EACvD,GAAI,CAACH,GAAWM,CAAQ,EACvB,OAAAF,EAAU,KACH,KAER,IAAMG,EAAMN,GAAaK,EAAU,OAAO,EAC1C,OAAAF,EAAU,KAAK,MAAMG,CAAG,EACjBH,CACR,MAAQ,CACP,OAAAA,EAAU,KACH,IACR,CACD,CAeA,IAAMI,GAAa,sBAsBZ,SAASC,IAAqD,CACpE,OAAQ,WAAuCC,EAAU,CAG1D,CCxGA,IAAMC,GAAiB,gBAQhB,SAASC,GACfC,EACAC,EAAgC,CAAC,EACf,CAClB,IAAMC,EAAMD,EAAQ,MAAQ,IAAM,IAAI,MAChCE,EAAaF,EAAQ,YAAcG,GACnCC,EAAYC,GAAiBN,CAAK,EAClCO,EAAOC,GAASR,EAAM,IAAI,EAC1BS,EAAWD,GAASR,EAAM,QAAQ,EAClCU,EAAcC,GAAsBX,EAAOO,CAAI,EAC/CK,EAAUC,EAAmBb,EAAM,OAAO,GAAKG,EAAW,EAC1DW,EAAYC,GAAmBf,EAAM,UAAWE,CAAG,EACnDc,EACLH,EAAmBb,EAAM,MAAM,GAC/BiB,EAAcV,CAAI,GAClBN,EAAQ,QACRH,GACKoB,EAAYC,GAAmBnB,CAAK,EAAI,CAAE,GAAGA,CAAM,EAAI,OAEvDoB,EAA0C,CAC/C,GAAGX,CACJ,EACA,OAAI,OAAO,KAAKF,CAAI,EAAE,OAAS,IAC9Ba,EAAe,KAAOb,GAEnBW,IACHE,EAAe,UAAYF,GAGrB,CACN,GAAIN,EACJ,KAAM,YACN,KAAMP,EACN,OAAAW,EACA,UAAAF,EACA,YAAAJ,EACA,WAAYW,GAAcrB,EAAOK,CAAS,EAC1C,SAAUe,EACV,UAAAF,CACD,CACD,CAEO,SAASd,IAAwB,CACvC,OACC,OAAO,OAAW,KAClB,OAAO,OAAO,YAAe,WAEtB,OAAO,OAAO,WAAW,CAAC,GAG3B,OAAO,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,EAAG,EAAE,CAAC,IAAI,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,EACjF,CAEA,SAASiB,GACRrB,EACAK,EAC0B,CAC1B,GAAI,CAACc,GAAmBnB,CAAK,EAC5B,OAAOQ,GAASR,EAAM,UAAU,EAGjC,IAAMsB,EAAmBC,GAAoBvB,EAAOK,CAAS,EACvDmB,EAAqBhB,GAASR,EAAM,UAAU,EACpD,MAAO,CACN,GAAGsB,EACH,GAAGE,CACJ,CACD,CAEA,SAASD,GACRvB,EACAK,EAC0B,CAC1B,OAAQA,EAAW,CAClB,IAAK,cAAe,CACnB,IAAMoB,EAAsC,CAAC,EAC7C,OAAIZ,EAAmBb,EAAM,QAAQ,IACpCyB,EAAW,KAAOzB,EAAM,UAErBa,EAAmBb,EAAM,QAAQ,IACpCyB,EAAW,KAAOzB,EAAM,UAElByB,CACR,CACA,IAAK,eAAgB,CACpB,IAAMA,EAAsC,CAAC,EAC7C,OAAIZ,EAAmBb,EAAM,OAAO,IACnCyB,EAAW,IAAMzB,EAAM,SAEjByB,CACR,CACA,QACC,MAAO,CAAC,CACV,CACD,CAEA,SAASnB,GAAiBN,EAA8B,CACvD,OAAOmB,GAAmBnB,CAAK,EAAIA,EAAM,UAAYA,EAAM,KAC5D,CAEA,SAASW,GACRX,EACAO,EACmB,CACnB,IAAMmB,EACLb,EAAmBb,EAAM,SAAS,GAAK2B,GAAiBpB,CAAI,EAEvDqB,EACLf,EAAmBb,EAAM,SAAS,GAAK6B,EAAiBtB,CAAI,EAEvDuB,EAAUjB,EAAmBb,EAAM,OAAO,GAAK+B,GAAexB,CAAI,EAElEyB,EACLnB,EAAmBb,EAAM,cAAc,GAAKiC,GAAsB1B,CAAI,EAEjE2B,EACLrB,EAAmBb,EAAM,SAAS,GAAKmC,GAAiB5B,CAAI,EAEvD6B,EACLvB,EAAmBb,EAAM,aAAa,GACtCqC,GAAqB9B,CAAI,GACzBmB,EAEKhB,EAAgC,CAAC,EACvC,OAAIkB,IACHlB,EAAY,UAAYkB,GAErBE,IACHpB,EAAY,QAAUoB,GAEnBJ,IACHhB,EAAY,UAAYgB,GAErBU,IACH1B,EAAY,cAAgB0B,GAEzBJ,IACHtB,EAAY,eAAiBsB,GAE1BE,IACHxB,EAAY,UAAYwB,GAElBxB,CACR,CAEA,SAASK,GACRf,EACAE,EACS,CACT,GAAIF,aAAiB,KACpB,OAAOA,EAAM,YAAY,EAE1B,GAAI,OAAOA,GAAU,SAAU,CAC9B,IAAMsC,EAAO,IAAI,KAAKtC,CAAK,EAC3B,GAAI,CAAC,OAAO,MAAMsC,EAAK,QAAQ,CAAC,EAC/B,OAAOA,EAAK,YAAY,CAE1B,CACA,OAAOpC,EAAI,EAAE,YAAY,CAC1B,CAEA,SAASM,GAAS+B,EAAyC,CAC1D,MAAI,CAACA,GAAS,OAAOA,GAAU,UAAY,MAAM,QAAQA,CAAK,EACtD,CAAC,EAEFA,CACR,CAEA,SAAS1B,EAAmB0B,EAAoC,CAC/D,GAAI,OAAOA,GAAU,UAGjBA,EAAM,KAAK,EAAE,SAAW,EAG5B,OAAOA,CACR,CAEA,SAASpB,GAAmBnB,EAA8C,CACzE,MAAO,cAAeA,CACvB,CCxLA,IAAMwC,GAAwB,2BAQ9B,IAAMC,GAAW,gBAIjB,IAAMC,GAAsB,IAAI,IAAI,CAAC,IAAK,GAAG,CAAC,EACxCC,GAAmB,IAAI,IAAI,CAAC,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,GAAG,CAAC,EAI9DC,GAAwB,CAACC,EAAOC,IAAS,WAAW,MAAMD,EAAOC,CAAI,EAiDpE,SAASC,GACfC,EACmB,CACnB,OAAO,IAAIC,GAAoBD,CAAO,CACvC,CAEA,IAAMC,GAAN,KAAsD,CACpC,YACA,gBACA,aACA,cACA,WACA,iBACA,gBACA,kBACA,WACA,QACA,OACA,IACA,MACA,OAEA,OAA4B,CAAC,EACtC,WACA,eAAiB,GACjB,oBACA,cACA,cAAgB,EAChB,UAAY,GACZ,eAAiB,GAEzB,YAAYD,EAA6B,CACxC,KAAK,YAAcE,GAClBF,EAAQ,OACRA,EAAQ,cAAgBG,EACzB,EACA,KAAK,gBAAkBH,EAAQ,iBAAmB,IAClD,KAAK,aAAeA,EAAQ,cAAgB,GAC5C,KAAK,cAAgBA,EAAQ,eAAiB,IAC9C,KAAK,WAAaA,EAAQ,YAAc,EACxC,KAAK,iBACJA,EAAQ,kBAAoB,IAC7B,KAAK,gBACJA,EAAQ,iBAAmB,IAC5B,KAAK,kBACJA,EAAQ,mBAAqB,IAC9B,KAAK,QAAUA,EAAQ,SAAWJ,GAClC,KAAK,OAASI,EAAQ,QAAU,QAChC,KAAK,IAAMA,EAAQ,MAAQ,IAAM,IAAI,MACrC,KAAK,MACJA,EAAQ,QACNI,GAAY,IAAI,QAASC,GAAY,WAAWA,EAASD,CAAO,CAAC,GACpE,KAAK,OAASJ,EAAQ,OACtB,KAAK,WAAaA,EAAQ,WAEtB,KAAK,gBAAkB,IAC1B,KAAK,WAAa,YAAY,IAAM,CAC9B,KAAK,MAAM,CACjB,EAAG,KAAK,eAAe,GAGxB,KAAK,wBAAwB,CAC9B,CAEA,QAAQM,EAA8B,CACrC,GAAI,KAAK,WAAa,KAAK,eAAgB,CAC1C,KAAK,OAAO,KACX,8DACAA,EAAM,EACP,EACA,MACD,CAEA,GAAI,KAAK,OAAO,QAAU,KAAK,cAAe,CAC7C,IAAMC,EAAY,KAAK,OAAO,OAAS,KAAK,cAAgB,EAC5D,KAAK,OAAO,OAAO,EAAGA,CAAS,EAC/B,KAAK,OAAO,KACX,kEACAA,CACD,CACD,CAIA,GAFA,KAAK,OAAO,KAAKD,CAAK,EAElB,KAAK,OAAO,QAAU,KAAK,aAAc,CACvC,KAAK,MAAM,EAChB,MACD,CAEA,KAAK,mBAAmB,CACzB,CAEA,eAAwB,CACvB,OAAO,KAAK,OAAO,OAAS,KAAK,aAClC,CAEA,MAAM,OAAuB,CAC5B,OAAI,KAAK,cACD,KAAK,eAEb,KAAK,cAAgB,KAAK,UAAU,EAAE,QAAQ,IAAM,CACnD,KAAK,cAAgB,MACtB,CAAC,EACM,KAAK,cACb,CAEA,MAAM,SACLN,EACkC,CAClC,KAAK,eAAiB,GAClB,KAAK,aACR,cAAc,KAAK,UAAU,EAC7B,KAAK,WAAa,QAEf,KAAK,sBACR,aAAa,KAAK,mBAAmB,EACrC,KAAK,oBAAsB,OAC3B,KAAK,eAAiB,IAGvB,IAAMQ,EAAYR,GAAS,WAAa,KAAK,kBACvCS,EAAe,KAAK,MAAM,EAEhC,GAAI,CAAC,OAAO,SAASD,CAAS,GAAKA,GAAa,EAC/C,aAAMC,EACN,KAAK,UAAY,GACV,CAAE,SAAU,GAAO,cAAe,KAAK,cAAc,CAAE,EAG/D,IAAMC,EAAgB,OAAO,kBAAkB,EAM/C,OALe,MAAM,QAAQ,KAAK,CACjCD,EAAa,KAAK,IAAM,SAAkB,EAC1C,KAAK,MAAMD,CAAS,EAAE,KAAK,IAAME,CAAa,CAC/C,CAAC,IAEcA,GACd,KAAK,UAAY,GACV,CAAE,SAAU,GAAM,cAAe,KAAK,cAAc,CAAE,IAG9D,KAAK,UAAY,GACV,CAAE,SAAU,GAAO,cAAe,KAAK,cAAc,CAAE,EAC/D,CAOQ,yBAAgC,CACvC,GAAI,OAAO,SAAa,KAAe,OAAO,OAAW,IACxD,OAGD,IAAMC,EAAgB,IAAM,CACvB,SAAS,kBAAoB,UAChC,KAAK,eAAe,CAEtB,EACA,SAAS,iBAAiB,mBAAoBA,CAAa,EAC3D,OAAO,iBAAiB,WAAY,IAAM,KAAK,eAAe,CAAC,CAChE,CAGQ,gBAAuB,CAC9B,GAAI,KAAK,WAAa,KAAK,OAAO,SAAW,EAC5C,OAED,IAAMC,EAAS,KAAK,OAAO,OAAO,EAAG,KAAK,OAAO,MAAM,EACvD,KAAK,mBAAmBA,CAAM,CAC/B,CAEQ,mBAAmBA,EAAiC,CAC3D,IAAMC,EAAO,KAAK,UAAU,KAAK,iBAAiBD,CAAM,CAAC,EACzD,GAAIC,EAAK,OAAS,KAA2BD,EAAO,OAAS,EAAG,CAC/D,IAAME,EAAM,KAAK,KAAKF,EAAO,OAAS,CAAC,EACvC,KAAK,mBAAmBA,EAAO,MAAM,EAAGE,CAAG,CAAC,EAC5C,KAAK,mBAAmBF,EAAO,MAAME,CAAG,CAAC,EACzC,MACD,CACA,GAAI,CACE,KAAK,QAAQ,KAAK,YAAa,CACnC,OAAQ,OACR,QAAS,KAAK,eAAe,EAC7B,KAAAD,EACA,UAAW,EACZ,CAAC,EAAE,MAAM,IAAM,CAAC,CAAC,CAClB,MAAQ,CAER,CACD,CAEQ,gBAAyC,CAChD,IAAME,EAAkC,CACvC,eAAgB,mBAChB,iBAAkBC,EACnB,EACA,OAAI,KAAK,SACRD,EAAQ,cAAgB,UAAU,KAAK,MAAM,IAEvCA,CACR,CAEQ,oBAA2B,CAC9B,KAAK,iBAGT,KAAK,eAAiB,GACtB,KAAK,oBAAsB,WAAW,IAAM,CAC3C,KAAK,oBAAsB,OAC3B,KAAK,eAAiB,GACjB,KAAK,MAAM,CACjB,EAAG,CAAC,EACL,CAEA,MAAc,WAA2B,CACxC,KAAO,KAAK,OAAO,OAAS,GAAK,CAAC,KAAK,WAAW,CACjD,IAAME,EAAQ,KAAK,OAAO,OAAO,EAAG,KAAK,YAAY,EACrD,MAAM,KAAK,mBAAmBA,CAAK,CACpC,CACD,CAEA,MAAc,mBAAmBA,EAAyC,CACzE,IAAIC,EAAU,EACVC,EAAeF,EAEnB,KAAOE,EAAa,OAAS,GAAK,CAAC,KAAK,WAAW,CAClD,KAAK,cAAgBA,EAAa,OAClC,IAAMC,EAAS,MAAM,KAAK,cAAcD,CAAY,EAGpD,OAFA,KAAK,cAAgB,EAEbC,EAAO,KAAM,CACpB,IAAK,UACJ,OACD,IAAK,OACJ,KAAK,4BAA4BA,EAAO,OAAQD,EAAa,MAAM,EACnE,OACD,IAAK,YACJ,KAAK,OAAO,MACX,8DACAA,EAAa,OACbC,EAAO,MACR,EACA,OACD,IAAK,YACJ,GAAIF,GAAW,KAAK,WAAY,CAC/B,KAAK,OAAO,MACX,6DACAC,EAAa,OACbC,EAAO,MACR,EACA,MACD,CACA,MAAM,KAAK,MAAM,KAAK,eAAeF,CAAO,CAAC,EAC7CA,GAAW,EACX,SACD,IAAK,UAOJ,GANIE,EAAO,UAAU,OAAS,GAC7B,KAAK,OAAO,MACX,wDACAA,EAAO,UAAU,MAClB,EAEGA,EAAO,UAAU,SAAW,EAC/B,OAED,GAAIF,GAAW,KAAK,WAAY,CAC/B,KAAK,OAAO,MACX,mEACAE,EAAO,UAAU,MAClB,EACA,MACD,CACAD,EAAeC,EAAO,UACtB,MAAM,KAAK,MAAM,KAAK,eAAeF,CAAO,CAAC,EAC7CA,GAAW,EACX,QACF,CACD,CACD,CAEA,MAAc,cACbN,EAC2B,CAC3B,IAAIS,EAEJ,GAAI,CACHA,EAAW,MAAM,KAAK,QAAQ,KAAK,YAAa,CAC/C,OAAQ,OACR,QAAS,KAAK,eAAe,EAC7B,KAAM,KAAK,UAAU,KAAK,iBAAiBT,CAAM,CAAC,CACnD,CAAC,CACF,OAASU,EAAO,CACf,MAAO,CACN,KAAM,YACN,OAAQC,GAAgBD,CAAK,CAC9B,CACD,CAEA,GAAI5B,GAAoB,IAAI2B,EAAS,MAAM,EAC1C,MAAO,CAAE,KAAM,OAAQ,OAAQA,EAAS,MAAO,EAGhD,GAAI1B,GAAiB,IAAI0B,EAAS,MAAM,EACvC,MAAO,CACN,KAAM,YACN,OAAQ,QAAQA,EAAS,MAAM,EAChC,EAGD,GAAI,CAACA,EAAS,GACb,MAAO,CACN,KAAM,YACN,OAAQ,QAAQA,EAAS,MAAM,EAChC,EAGD,IAAMG,EAAO,MAAMC,GAAmCJ,CAAQ,EAC9D,GAAI,CAACG,GAAM,UAAYA,EAAK,SAAS,SAAW,EAC/C,MAAO,CAAE,KAAM,SAAU,EAG1B,IAAME,EAAU,KAAK,uBAAuBd,EAAQY,EAAK,QAAQ,EACjE,OAAIE,EAAQ,UAAU,SAAW,GAAKA,EAAQ,UAAU,SAAW,EAC3D,CAAE,KAAM,SAAU,EAGnB,CACN,KAAM,UACN,UAAWA,EAAQ,UACnB,UAAWA,EAAQ,SACpB,CACD,CAEQ,iBAAiBd,EAA2C,CACnE,MAAO,CACN,OAAQ,KAAK,IAAI,EAAE,YAAY,EAC/B,OAAQ,CACP,IAAKI,GACL,QAAS,KAAK,YAAc,OAC7B,EACA,OAAAJ,CACD,CACD,CAEQ,uBACPA,EACAe,EAIC,CACD,IAAMC,EAAO,IAAI,IAAIhB,EAAO,IAAKN,GAAU,CAACA,EAAM,GAAIA,CAAK,CAAC,CAAC,EACvDuB,EAA+B,CAAC,EAChCC,EAA+B,CAAC,EAEtC,QAAWC,KAAiBJ,EAAU,CACrC,IAAMrB,EAAQsB,EAAK,IAAIG,EAAc,OAAO,EAC5C,GAAKzB,EAGL,IAAI0B,GAAyBD,CAAa,EAAG,CAC5CF,EAAU,KAAKvB,CAAK,EACpB,QACD,CACAwB,EAAU,KAAKxB,CAAK,EACrB,CAEA,MAAO,CAAE,UAAAuB,EAAW,UAAAC,CAAU,CAC/B,CAEQ,eAAeZ,EAAyB,CAC/C,IAAMe,EAAW,KAAK,iBAAmB,GAAKf,EAC9C,OAAO,KAAK,IAAIe,EAAU,KAAK,eAAe,CAC/C,CAEQ,4BACPC,EACAC,EACO,CACP,KAAK,UAAY,GACjB,IAAMC,EAAW,KAAK,OAAO,OAC7B,KAAK,OAAO,OAAO,EAAGA,CAAQ,EAC9B,KAAK,OAAO,MACX,iGACAF,EACAC,EAAgBC,CACjB,CACD,CACD,EAEA,SAASJ,GACRD,EACU,CACV,GAAIA,EAAc,YAAc,GAC/B,MAAO,GAER,IAAMM,EAAON,EAAc,KAAK,YAAY,EAC5C,OACCM,EAAK,SAAS,SAAS,GACvBA,EAAK,SAAS,WAAW,GACzBA,EAAK,SAAS,aAAa,GAC3BA,EAAK,SAAS,YAAY,GAC1BA,EAAK,SAAS,WAAW,GACzBA,EAAK,SAAS,QAAQ,CAExB,CAEA,eAAeZ,GACdJ,EACyB,CACzB,IAAMR,EAAO,MAAMQ,EAAS,KAAK,EACjC,GAAKR,EAGL,GAAI,CACH,OAAO,KAAK,MAAMA,CAAI,CACvB,MAAQ,CACP,MACD,CACD,CAEA,SAASX,GAAQoC,EAAgBC,EAA8B,CAC9D,IAAMC,EAAiBF,EAAO,SAAS,GAAG,EAAIA,EAAS,GAAGA,CAAM,IAC1DG,EAAiBF,EAAa,WAAW,GAAG,EAC/CA,EAAa,MAAM,CAAC,EACpBA,EACH,MAAO,GAAGC,CAAc,GAAGC,CAAc,EAC1C,CAEA,SAASlB,GAAgBD,EAAwB,CAChD,OAAIA,aAAiB,MACbA,EAAM,QAEP,OAAOA,CAAK,CACpB,CC/cO,SAASoB,GAAqBC,EAAwC,CAC5E,GAAM,CAAE,OAAAC,EAAQ,OAAAC,EAAQ,SAAAC,CAAS,EAAIH,EAErC,SAASI,GAAwB,CAChC,GAAI,CAACF,EACJ,MAAM,IAAI,MAAM,6BAA6B,EAE9C,OAAOA,CACR,CAEA,IAAMG,EAAYH,EACfI,GAAuB,CACvB,OAAAL,EACA,OAAAC,EACA,aAAcC,EAAS,aACvB,gBAAiBA,EAAS,gBAC1B,aAAcA,EAAS,aACvB,cAAeA,EAAS,cACxB,WAAYA,EAAS,WACrB,iBAAkBA,EAAS,iBAC3B,gBAAiBA,EAAS,gBAC1B,kBAAmBA,EAAS,iBAC7B,CAAC,EACA,OAGH,SAASI,EAAKC,EAAwC,CACrDJ,EAAc,EACd,IAAMK,EAAcC,GAAkBF,CAAK,EAI3C,MACC,CAACC,EAAY,YAAY,WACzB,CAACA,EAAY,YAAY,gBACzB,CAACA,EAAY,YAAY,WAEzB,QAAQ,KACP,qBAAqBA,EAAY,IAAI,mGAEtC,EAEDJ,GAAW,QAAQI,CAAW,EACvB,CAAE,QAASA,EAAY,EAAG,CAClC,CAEA,IAAME,EAAY,MAAOH,GACxBD,EAAKC,CAAK,EAELI,EAAiB,OAAO,OAAOD,EAAWE,GAAiBF,CAAS,CAAC,EAErEG,EAAyB,CAC9B,MAAM,SACLC,EACAC,EACAC,EAC+B,CAC/Bb,EAAc,EACd,IAAMK,EAAcC,GAAkB,CACrC,MAAO,kBACP,eAAgBK,EAChB,WAAAC,EACA,KAAAC,CACD,CAAC,EACD,OAAAZ,GAAW,QAAQI,CAAW,EACvB,CAAE,QAASA,EAAY,EAAG,CAClC,EACA,MAAAG,EACA,MAAM,OAAuB,CAC5BR,EAAc,EACd,MAAMC,GAAW,MAAM,CACxB,EACA,MAAM,SAASa,EAAmC,CACjD,OAAAd,EAAc,EAEZ,MAAMC,GAAW,SAAS,CAC1B,UAAWa,GAAS,WAAaf,EAAS,iBAC3C,CAAC,GAAM,CAAE,SAAU,GAAO,cAAe,CAAE,CAE7C,CACD,EAEA,OAAIE,GACHc,GAAoBL,EAAQX,EAAS,iBAAiB,EAEhDW,CACR,CAEA,SAASK,GACRL,EACAM,EACO,CACP,GACC,OAAO,QAAY,KACnB,OAAO,QAAQ,MAAS,YACxB,OAAO,QAAQ,IAAO,WAEtB,OAGD,IAAMC,EAAW,IAAM,CACjBP,EAAO,SAAS,CAAE,UAAWM,CAAiB,CAAC,CACrD,EAEA,QAAQ,KAAK,aAAcC,CAAQ,EACnC,QAAQ,KAAK,SAAUA,CAAQ,EAC/B,QAAQ,KAAK,UAAWA,CAAQ,CACjC,CC7HO,SAASC,GACfC,EACiB,CAEjB,IAAMC,EADgBD,GAAUE,GAAkB,GAAKC,GAAgB,EAKjEC,EACLH,GAAW,QAAU,QAAQ,IAAI,kBAAoBI,GAChDC,EAASL,GAAW,QAAU,QAAQ,IAAI,iBAC1CM,EAAiB,CACtB,aACCN,GAAW,UAAU,cAAgB,2BACtC,gBAAiBA,GAAW,UAAU,iBAAmB,IACzD,aAAcA,GAAW,UAAU,cAAgB,GACnD,cAAeA,GAAW,UAAU,eAAiB,IACrD,WAAYA,GAAW,UAAU,YAAc,EAC/C,iBAAkBA,GAAW,UAAU,kBAAoB,IAC3D,gBAAiBA,GAAW,UAAU,iBAAmB,IACzD,kBAAmBA,GAAW,UAAU,mBAAqB,GAC9D,EAEMO,EAAiB,CAAE,OAAAJ,EAAQ,OAAAE,EAAQ,SAAUC,CAAe,EAG5DE,EAAiBC,GAAqBF,CAAc,EACpDG,EAAWC,GAAeJ,CAAc,EACxCK,EAAkBC,GAAsBN,CAAc,EAE5D,MAAO,CACN,GAAGC,EACH,GAAIE,EACJ,UAAWE,EACX,QAASL,CACV,CACD,CC3BA,SAASO,GAAYC,EAAgD,CACpE,MAAO,CACN,MAAQA,EAAS,MAAQ,gBACzB,WAAYA,EAAS,WACrB,SAAUA,EAAS,SACnB,UAAWA,EAAS,aAAa,UACjC,QAASA,EAAS,aAAa,QAC/B,UAAWA,EAAS,aAAa,UACjC,cAAeA,EAAS,aAAa,cACrC,eAAgBA,EAAS,aAAa,eACtC,UAAWA,EAAS,aAAa,UACjC,QAASA,EAAS,GAClB,UAAWA,EAAS,UACpB,OAAQA,EAAS,MAClB,CACD,CAMO,SAASC,GAAoBC,EAAgC,CACnE,IAAMC,EAAyB,CAC9B,OAAQD,GAAS,OACjB,OAAQA,GAAS,MAClB,EAGIE,EAEJ,SAASC,GAAY,CACpB,OAAKD,IACJA,EAASE,GAASH,CAAM,GAElBC,CACR,CAEA,OAAO,eAAuBG,EAAqC,CAClE,IAAIC,EACJ,GAAI,CACHA,EAAQ,MAAMD,EAAQ,KAAK,CAC5B,MAAQ,CACP,OAAO,IAAI,SAAS,KAAK,UAAU,CAAE,MAAO,cAAe,CAAC,EAAG,CAC9D,OAAQ,IACR,QAAS,CAAE,eAAgB,kBAAmB,CAC/C,CAAC,CACF,CAEA,GAAI,CAAC,MAAM,QAAQC,EAAK,MAAM,GAAKA,EAAK,OAAO,SAAW,EACzD,OAAO,IAAI,SACV,KAAK,UAAU,CAAE,MAAO,+BAAgC,CAAC,EACzD,CACC,OAAQ,IACR,QAAS,CAAE,eAAgB,kBAAmB,CAC/C,CACD,EAGD,GAAI,CACH,IAAMC,EAAIJ,EAAU,EACdK,EAAoB,CAAC,EAE3B,QAAWV,KAAYQ,EAAK,OAAQ,CACnC,IAAMG,EAAS,MAAMF,EAAE,MAAMV,GAAYC,CAAQ,CAAC,EAClDU,EAAQ,KAAKC,EAAO,OAAO,CAC5B,CAEA,aAAMF,EAAE,MAAM,EAEP,IAAI,SACV,KAAK,UAAU,CAAE,GAAI,GAAM,SAAUC,EAAQ,MAAO,CAAC,EACrD,CACC,OAAQ,IACR,QAAS,CAAE,eAAgB,kBAAmB,CAC/C,CACD,CACD,OAASE,EAAO,CACf,IAAMC,EAAUD,aAAiB,MAAQA,EAAM,QAAU,gBACzD,OAAO,IAAI,SAAS,KAAK,UAAU,CAAE,MAAOC,CAAQ,CAAC,EAAG,CACvD,OAAQ,IACR,QAAS,CAAE,eAAgB,kBAAmB,CAC/C,CAAC,CACF,CACD,CACD,CC1HA,IAAMC,GAAMC,EAAa,cAAc,EAmBjCC,GAAmB,IAAS,IAErBC,GAAN,KAAuB,CACrB,OAA6B,KAC7B,QAAyC,KAChC,OAEjB,YAAYC,EAA2B,CACtC,KAAK,OAASA,CACf,CAMA,MAAM,SAASC,EAAoBC,EAA0C,CAC5E,OAAI,KAAK,QAAU,KAAK,IAAI,EAAI,KAAK,OAAO,UAAYJ,GAChD,KAAK,OAAO,MAIhB,KAAK,QACD,KAAK,SAGb,KAAK,QAAU,KAAK,KAAKG,EAAWC,CAAO,EAAE,QAAQ,IAAM,CAC1D,KAAK,QAAU,IAChB,CAAC,EAEM,KAAK,QACb,CAEA,MAAc,KACbD,EACAC,EACyB,CACzB,IAAMC,EAAMC,GAAQ,KAAK,OAAO,OAAQ,wBAAwB,EAChER,GAAI,qBAAsBO,CAAG,EAE7B,IAAME,EAA+B,CAAC,EAClCJ,IACHI,EAAK,UAAYJ,GAEdC,IACHG,EAAK,QAAUH,GAGhB,GAAI,CACH,IAAMI,EAAW,MAAM,MAAMH,EAAK,CACjC,OAAQ,OACR,QAAS,CACR,eAAgB,mBAChB,cAAe,UAAU,KAAK,OAAO,MAAM,EAC5C,EACA,KAAM,KAAK,UAAUE,CAAI,EACzB,OAAQ,YAAY,QAAQ,GAAK,CAClC,CAAC,EAGD,GAFAT,GAAI,iBAAkBU,EAAS,MAAM,EAEjC,CAACA,EAAS,GACb,OAAO,KAGR,IAAMC,EAAQ,MAAMD,EAAS,KAAK,EAG5BE,EACLD,EAAK,MAAQ,OAAOA,EAAK,MAAS,SAAWA,EAAK,KAAOA,EAGpDE,EAAc,IAAI,KAAKD,EAAO,SAAS,EAAE,QAAQ,EACvD,MAAI,CAACA,EAAO,OAAS,OAAO,MAAMC,CAAW,EACrC,MAGR,KAAK,OAAS,CACb,MAAOD,EAAO,MACd,UAAWC,CACZ,EAEOD,EAAO,MACf,OAASE,EAAO,CACf,OAAAd,GAAI,eAAgBc,CAAK,EAClB,IACR,CACD,CACD,EAEA,SAASN,GAAQO,EAAgBC,EAAsB,CAEtD,MAAO,GADMD,EAAO,SAAS,GAAG,EAAIA,EAAO,MAAM,EAAG,EAAE,EAAIA,CAC5C,GAAGC,CAAI,EACtB,CCnHA,eAAeC,GAASC,EAA+B,CACtD,GAAI,OAAO,WAAW,QAAQ,QAAQ,QAAW,WAAY,CAC5D,IAAMC,EAAM,MAAM,OAAO,OAAO,OAC/B,UACA,IAAI,YAAY,EAAE,OAAOD,CAAI,CAC9B,EACA,OAAO,MAAM,KAAK,IAAI,WAAWC,CAAG,CAAC,EACnC,IAAKC,GAAMA,EAAE,SAAS,EAAE,EAAE,SAAS,EAAG,GAAG,CAAC,EAC1C,KAAK,EAAE,CACV,CACA,IAAIC,EAAO,EACX,QAASC,EAAI,EAAGA,EAAIJ,EAAK,OAAQI,IAChCD,GAASA,GAAQ,GAAKA,EAAOH,EAAK,WAAWI,CAAC,EAAK,EAEpD,MAAO,UAAU,KAAK,IAAID,CAAI,EAAE,SAAS,EAAE,CAAC,EAC7C,CAEA,SAASE,GAAUC,EAAmC,CACrD,OAAOP,GAAS,KAAK,UAAU,CAAE,MAAOO,EAAM,MAAO,MAAOA,EAAM,KAAM,CAAC,CAAC,CAC3E,CAeA,eAAsBC,GACrBC,EACoC,CACpC,GAAIA,EAAW,SAAW,EACzB,OAAO,KAGR,IAAMC,EAAS,CAAC,GAAGD,CAAU,EAAE,KAAK,CAACE,EAAGR,IACvCQ,EAAE,OAAO,cAAcR,EAAE,MAAM,CAChC,EAEMS,EAAgB,MAAMZ,GAC3B,KAAK,UACJU,EAAO,IAAKG,IAAQ,CACnB,OAAQA,EAAG,OACX,MAAOA,EAAG,MACV,MAAOA,EAAG,KACX,EAAE,CACH,CACD,EAEMC,EAAQ,MAAM,QAAQ,IAC3BL,EAAW,IAAI,MAAOI,IAAQ,CAC7B,OAAQA,EAAG,OACX,MAAOA,EAAG,MACV,WAAY,MAAMP,GAAUO,CAAE,EAC9B,MAAOA,EAAG,MACV,MAAOA,EAAG,KACX,EAAE,CACH,EAEA,MAAO,CAAE,cAAAD,EAAe,MAAAE,CAAM,CAC/B,CCxCA,IAAMC,GAAiB,qBACjBC,GAAmB,uBACnBC,GAA2B,wBAC3BC,GAA2B,sBAE3BC,GAAqB,CAC1BD,GACAF,GACAC,EACD,EAEO,SAASG,GACfC,EACAC,EACgB,CAChB,GAAIA,EAAO,SAAW,EACrB,OAAOD,EAER,IAAIE,EACJ,QAAWC,KAAOL,GAAoB,CACrC,IAAMM,EAAQJ,EAAKG,CAAG,EACtB,GAAI,CAACE,EAASD,CAAK,EAClB,SAED,IAAIE,EACJ,QAAWC,KAASN,EACfM,KAASH,IACPE,IACJA,EAAW,CAAE,GAAGF,CAAM,GAEvB,OAAOE,EAASC,CAAK,GAGnBD,IACEJ,IACJA,EAAO,CAAE,GAAGF,CAAK,GAElBE,EAAKC,CAAG,EAAIG,EAEd,CACA,OAAOJ,GAAQF,CAChB,CAEO,SAASK,EAASD,EAAwC,CAChE,MAAO,EAAQA,GAAU,OAAOA,GAAU,UAAY,CAAC,MAAM,QAAQA,CAAK,CAC3E,CAEO,SAASI,EAAYC,EAA2C,CACtE,GAAI,CAACJ,EAASI,CAAK,EAClB,OAED,IAAMT,EAAOS,EAAM,MACnB,OAAOJ,EAASL,CAAI,EAAIA,EAAO,MAChC,CAEO,SAASU,GAAiBC,EAAqC,CACrE,GAAI,CAACN,EAASM,CAAM,EACnB,OAED,IAAMC,EAAWD,EAAyB,QAC1C,OAAK,MAAM,QAAQC,CAAO,EAGTA,EAAQ,KACvBC,GACAR,EAASQ,CAAC,GAAKA,EAAE,OAAS,QAAU,OAAOA,EAAE,MAAS,QACxD,GACiB,KANhB,MAOF,CAEO,SAASC,GACfC,EACAC,EAI+B,CAC/B,OAAI,OAAOA,GAAmB,WACtBA,EAAeD,CAAQ,GAAK,QAE7BC,GAAkB,OAC1B,CAEO,SAASC,GACfF,EACAN,EACAS,EASAC,EAMAC,EACAC,EACAC,EACa,CACb,IAAMC,EAAWT,GAAgBC,EAAUG,EAAQ,QAAQ,EAErDM,EAAcN,EAAQ,oBACtBO,EAAcD,GAAeA,EAAY,OAAS,EAElDE,EAAUlB,EAAYC,CAAK,EAC3BT,EACL0B,GAAWD,EACR1B,GAA4B2B,EAASF,CAAW,EAChDE,EAEEC,EACLN,GAAI,QAAU,QAAaH,EAAQ,YAChCA,EAAQ,YAAYG,EAAG,KAAK,EAC5BA,GAAI,MAEFO,EACLH,GAAepB,EAASgB,GAAI,MAAM,GAAKhB,EAASgB,EAAG,OAAO,KAAK,EAC5D,CACA,GAAIA,EAAG,OACP,MAAOtB,GACNsB,EAAG,OAAO,MACVG,CACD,CACD,EACCH,GAAI,OAMFQ,GAHLxB,EAASgB,GAAI,MAAM,GAAKhB,EAAUgB,EAAG,OAAyB,KAAK,EAC9DA,EAAG,OAAyB,MAC9B,UAC4BS,CAAa,EACvCC,EACLF,GAAY7B,EACT,CAAE,GAAGA,EAAM,CAAC8B,CAAa,EAAGD,CAAS,EACrCA,EACC,CAAE,CAACC,CAAa,EAAGD,CAAS,EAC5B7B,EASCgC,EACL3B,EAASgB,GAAI,KAAK,GAClB,OAAQA,EAAG,MAAwB,WAAc,UAC/CA,EAAG,MAAwB,UAAqB,OAAS,EACtDA,EAAG,MAAwB,UAC7B,OACEY,EACLD,GAAgB,CAACE,EAAiBH,GAAY,MAAS,EACpD,CAAE,GAAIA,GAAY,CAAC,EAAI,qBAAsBC,CAAa,EAC1DD,EAEJ,MAAO,CACN,MAAO,cACP,WAAY,CACX,KAAMhB,EACN,KAAMQ,EACN,GAAIJ,GAAU,CAAC,EACf,GAAIQ,IAAU,QAAa,CAAE,MAAAA,CAAM,EACnC,GAAIC,IAAW,QAAa,CAAE,OAAAA,CAAO,EACrC,GAAIN,GAAYA,EAAS,OAAS,GAAK,CAAE,SAAAA,CAAS,CACnD,EACA,KAAMW,EACN,OAAQE,EAAcF,GAAcjC,EAAMoB,CAAU,EACpD,SAAU,CACT,GAAIF,EAAQ,UAAY,CAAC,EACzB,GAAIE,GAAc,CAAE,WAAAA,CAAW,EAC/B,GAAIF,EAAQ,YAAc,CAAE,WAAYA,EAAQ,UAAW,CAC5D,CACD,CACD,CAEA,eAAsBkB,GACrBC,EACAV,EACAW,EACgB,CAChB,GAAI,CACH,MAAMD,EAAQ,MAAMV,CAAK,CAC1B,OAASY,EAAO,CACfD,IAAUE,GAAQD,CAAK,CAAC,CACzB,CACD,CAEA,eAAsBE,GACrBJ,EACAC,EACgB,CAChB,GAAI,CACH,MAAMD,EAAQ,MAAM,CACrB,OAASE,EAAO,CACfD,IAAUE,GAAQD,CAAK,CAAC,CACzB,CACD,CAEA,eAAsBG,GACrB/B,EACAgC,EACAC,EACAnC,EACA6B,EACAlB,EACgB,CAChB,GAAI,CAACf,EAASM,CAAM,EACnB,OAGIN,EAASM,EAAO,KAAK,IACxBA,EAAyB,MAAQ,CAAC,GAGpC,IAAMX,EAAQW,EAAyB,MACjCkC,EAAyBxC,EAASL,EAAK8C,EAAsB,CAAC,EAChE9C,EAAK8C,EAAsB,EAC5B,OACGC,EAAgC,CACrC,GAAIF,GAA0B,CAAC,EAC/B,SACCA,GAAwB,UACxB,GAAGD,EAAO,QAAQ,MAAO,EAAE,CAAC,0BAC9B,EAEA,GAAID,EACH,GAAI,CACH,IAAMK,EAAQ,MAAML,EAAM,SAAS,EAC/BK,IACHD,EAAe,MAAQC,EAEzB,OAAST,EAAO,CACfD,IAAUE,GAAQD,CAAK,CAAC,CACzB,CAGD,IAAMU,EAAYf,EAAiBlC,CAAI,EACnCiD,IACEF,EAAe,YACnBA,EAAe,UAAYE,IAI7B,IAAMC,EAAcC,GAAmBnD,CAAI,EACvCkD,IAAgB,SACdH,EAAe,cACnBA,EAAe,YAAcG,IAI/B,IAAME,EAASjB,EAAc3B,EAAYC,CAAK,EAAGW,CAAU,EACvDgC,GAAU,CAACL,EAAe,SAC7BA,EAAe,OAASK,GAGzBpD,EAAK8C,EAAsB,EAAIC,CAChC,CAqBA,IAAMM,GAAmB,CACxB,wBACA,0BACA,gCACA,iCACA,gCACA,iBACA,IACD,EAEO,SAASC,GACf3C,EACA4C,EACO,CACP,GAAI,CAACA,GAAkB,CAAClD,EAASM,CAAM,EACtC,OAGD,IAAI6C,EAAY,GAChB,QAAWrD,KAAOkD,GACjB,GAAIlD,KAAOoD,EAAgB,CAC1BC,EAAY,GACZ,KACD,CAED,GAAI,CAACA,EACJ,OAGInD,EAASM,EAAO,KAAK,IACxBA,EAAyB,MAAQ,CAAC,GAEpC,IAAM8C,EAAc9C,EAAyB,MAE7C,QAAWR,KAAOkD,GACXlD,KAAOoD,IAITpD,KAAOsD,IAGXA,EAAWtD,CAAG,EAAIoD,EAAepD,CAAG,GAEtC,CAEO,SAASuD,GAAsB/C,EAAiBF,EAAsB,CAC5E,IAAMkD,EAAcnD,EAAYC,CAAK,EAKrC,GAJI,CAACkD,GAID,CAACtD,EAASM,CAAM,EACnB,OAGIN,EAASM,EAAO,KAAK,IACxBA,EAAyB,MAAQ,CAAC,GAGpC,IAAM8C,EAAc9C,EAAyB,MACvCsC,EAAYf,EAAiByB,CAAW,EAC1CV,GAAa,CAACQ,EAAW/D,EAAc,IAC1C+D,EAAW/D,EAAc,EAAIuD,GAG9B,IAAMC,EAAcC,GAAmBQ,CAAW,EAC7CT,IAIAO,EAAW9D,EAAgB,IAC/B8D,EAAW9D,EAAgB,EAAIuD,GAG3BO,EAAW7D,EAAwB,IACvC6D,EAAW7D,EAAwB,EAAIsD,GAEzC,CAEA,SAASC,GACRnD,EACqC,CACrC,GAAI,CAACA,EACJ,OAGD,IAAMkD,EAAclD,EAAKL,EAAgB,GAAKK,EAAKJ,EAAwB,EAC3E,GAAIS,EAAS6C,CAAW,GAAK,OAAOA,GAAgB,SACnD,OAAOA,CAIT,CAEA,SAASV,GAAQD,EAAuB,CACvC,OAAIA,aAAiB,MACbA,EAED,IAAI,MAAM,OAAOA,CAAK,CAAC,CAC/B,CC1ZA,SAASqB,GAASC,EAAgC,CACjD,OAAO,OAAOA,GAAM,UAAYA,IAAM,MAAQ,CAAC,MAAM,QAAQA,CAAC,CAC/D,CAOO,SAASC,GACfC,EACqB,CACrB,GAAI,OAAOA,EAAM,WAAc,UAAYA,EAAM,UAChD,OAAOA,EAAM,UAGd,GAAIH,GAASG,EAAM,WAAW,EAAG,CAChC,IAAMC,EAAcD,EAAM,YAA8B,QACxD,GAAIH,GAASI,CAAU,EAAG,CAGzB,IAAMC,EAAyB,CAAC,EAChC,QAAWC,KAAO,OAAO,KAAKF,CAAU,EACvCC,EAAQC,EAAI,YAAY,CAAC,EAAIF,EAAWE,CAAG,EAE5C,IAAMC,EAAMF,EAAQ,gBAAgB,EACpC,GAAI,OAAOE,GAAQ,UAAYA,EAC9B,OAAOA,EAER,IAAMC,EAAUH,EAAQ,uBAAuB,EAC/C,GAAI,OAAOG,GAAY,UAAYA,EAClC,OAAOA,CAET,CACD,CAGD,CCKA,IAAMC,GAAkB,OAAO,IAAI,yBAAyB,EAuEtDC,GAAMC,EAAa,KAAK,EAExBC,GAAmB,0BAEnBC,GAAiB,WAEvB,SAASC,GACRC,EAC4C,CAC5C,GAAI,CAACA,EACJ,OAED,IAAMC,EAASD,EAAeE,EAAqC,EACnE,GAAI,CAAC,MAAM,QAAQD,CAAM,GAAKA,EAAO,SAAW,EAC/C,OAED,IAAME,EAAW,IAAI,IACpBF,EAAO,OAAQG,GAAmB,OAAOA,GAAM,QAAQ,CACxD,EACA,GAAID,EAAS,OAAS,EAItB,OAAQE,GAAmB,CAC1B,GAAI,CAACC,EAASD,CAAK,EAClB,OAAOA,EAER,IAAME,EAAeF,EAAM,aAC3B,GAAI,CAACC,EAASC,CAAY,EACzB,OAAOF,EAER,IAAIG,EAAU,GACRC,EAAsB,CAAE,GAAGF,CAAa,EAC9C,QAAWG,KAASP,EACfO,KAASD,IACZA,EAAKC,CAAK,EAAIZ,GACdU,EAAU,IAGZ,OAAKA,EAGE,CAAE,GAAGH,EAAO,aAAcI,CAAK,EAF9BJ,CAGT,CACD,CAaA,SAASM,GACRC,EACAC,EACAC,EACAd,EACsB,CACtB,GAAM,CAAE,OAAAe,EAAQ,QAAAC,EAAS,KAAAC,EAAM,WAAAC,EAAY,YAAAC,CAAY,EAAIL,EAErDM,EACLH,EAAK,uBAAyB,GAC3BlB,GAAyBC,CAAc,EACvC,OACEqB,EAAsC,MAC3ChB,EACAiB,IACI,CACJ,IAAMC,EAAgBH,EACnB,CACA,GAAGH,EACH,YAAaG,EACb,WAAYN,EAAI,UACjB,EACC,CAAE,GAAGG,EAAM,WAAYH,EAAI,UAAW,EAEnCU,EAAOC,EAAYH,CAAK,GAAK,CAAC,EAE9BI,EACLX,EAOC,QAAQ,mBAAmB,EAK7B,GAAI,CADsBY,EAAiBH,CAAI,GACrBlB,EAASgB,CAAK,EAAG,CAC1C,IAAMM,EAAeC,GAA0BP,CAAsB,EACjEM,IACHJ,EAAK,oBAAoB,EAAII,EAC5BN,EAAwB,MAAQE,EAEnC,CAOA,GAAI,CAACM,EAAcN,CAAI,GAAKlB,EAASgB,CAAK,EAAG,CAC5C,IAAMS,EAAWT,EACf,aAAa,QACTU,EACLF,EAAcN,EAAME,CAAU,GAAKO,GAAyBF,CAAO,EAChEC,IACHR,EAAK,iBAAiB,EAAIQ,EACzBV,EAAwB,MAAQE,EAEnC,CAEA,IAAMU,EAAeC,GAAmBnB,EAASQ,EAAM,CACtD,OAAQR,EAAQ,QAAQ,OACxB,OAAQA,EAAQ,QAAQ,MACzB,CAAC,EACGV,EAASgB,CAAK,IACjBA,EAAMc,EAAiB,EAAIF,GAG5B,IAAMG,EAAyC,CAAE,SAAU,CAAC,CAAE,EACxDC,EAAY,YAAY,IAAI,EAClC,GAAI,CACH,IAAMC,EAAS,MAAMC,GAAwB,IAAIH,EAAoB,IACpExB,EAAgBR,EAAOiB,CAAK,CAC7B,EACMmB,EAAa,KAAK,MAAM,YAAY,IAAI,EAAIH,CAAS,EAE3D3C,GACC,SAASiB,CAAQ,yBAAyB6B,CAAU,gCACrD,EAEA,IAAMC,EACLpC,EAASiC,CAAM,GAAMA,EAAyB,UAAY,GAE3D,GAAIG,EAAe,CAClB,IAAMC,EAAYC,GAAiBL,CAAM,EACzC,QAAQ,MACP,oBAAoB3B,CAAQ,mBAAmB+B,EAAY,KAAKA,CAAS,GAAK,EAAE,EACjF,CACD,CAEA,aAAME,GACL7B,EACA8B,GACClC,EACAU,EACAC,EACA,CACC,WAAAkB,EACA,OAAQC,EAAgB,QAAU,KAClC,GAAIA,GAAiB,CACpB,aAAcE,GAAiBL,CAAM,GAAK,oBAC3C,CACD,EACAb,EACA,CAAE,MAAArB,EAAO,OAAQkC,CAAO,EACxBF,EAAmB,QACpB,EACApB,EAAK,OACN,EAEAtB,GAAI,SAASiB,CAAQ,iBAAiB,EAElCK,EAAK,oBACR,MAAM8B,GAAU/B,EAASC,EAAK,OAAO,EAGtC+B,GAAsBT,EAAQjB,CAAK,EACnC2B,GAA2BV,EAAQvC,CAAc,EAE7CmB,IACH,MAAM+B,GACLX,EACArB,EACAF,EAAQ,QAAQ,QAAUnB,GAC1ByB,EACAL,EAAK,QACLS,CACD,EACA/B,GAAI,SAASiB,CAAQ,0BAA0B,GAGhDjB,GAAI,SAASiB,CAAQ,8CAA8C,EAE5D2B,CACR,OAASY,EAAO,CACf,IAAMV,EAAa,KAAK,MAAM,YAAY,IAAI,EAAIH,CAAS,EAE3D,YAAMO,GACL7B,EACA8B,GACClC,EACAU,EACAC,EACA,CACC,WAAAkB,EACA,OAAQ,QACR,aACCU,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,EACtD,MAAOC,EAAc,CAAE,MAAAD,CAAM,CAAC,CAC/B,EACAzB,EACA,CAAE,MAAArB,CAAM,EACRgC,EAAmB,QACpB,EACApB,EAAK,OACN,EAEIA,EAAK,oBACR,MAAM8B,GAAU/B,EAASC,EAAK,OAAO,EAGhCkC,CACP,CACD,EAEA,OAAA9B,EAAe3B,EAAe,EAAI,GAC3B2B,CACR,CAsBA,eAAsBgC,GACrBtC,EACAuC,EACqB,CACrB,IAAMC,EAAgBxC,EACtB,GAAIwC,EAAc,kBACjB,OAAOA,EAGRA,EAAc,kBAAoB,GAElC,IAAMtC,EAAOqC,GAAW,CAAC,EACnBtC,EAAUC,EAAK,QAAUuC,GAAS,EAClCrC,EAAcF,EAAK,oBAAsB,GAEzCC,EAAsCF,EAAQ,QAAQ,OACzD,IAAIyC,GAAiB,CACrB,OAAQzC,EAAQ,QAAQ,QAAUnB,GAClC,OAAQmB,EAAQ,QAAQ,MACzB,CAAC,EACA,KAEGF,EAAmB,CACxB,OAAAC,EACA,QAAAC,EACA,KAAAC,EACA,WAAAC,EACA,YAAAC,EACA,WAAY,IACb,EAEMuC,EAAuB3C,EAAO,aAAa,KAAKA,CAAM,EAI5DwC,EAAc,cAAgB,IAAII,IAAoB,CACrD,GAAM,CAACC,EAAaC,EAAQC,CAAU,EAAIH,EAE1C,GAAI,OAAOG,GAAe,WACzB,OAAOJ,EAAqB,GAAGC,CAAI,EAGpC,IAAM/C,EACL,OAAOgD,GAAgB,UAAYA,EAAY,KAAK,EAAE,OAAS,EAC5DA,EACA,UAEE5D,EACLM,EAASuD,CAAM,GAAKvD,EAAUuD,EAAyB,KAAK,EACvDA,EAAyB,MAC3B,OAEEE,EAAUpD,GACfC,EACAkD,EACAhD,EACAd,CACD,EACA,OAAO0D,EAAqBE,EAAaC,EAAQE,CAAO,CACzD,GASA,IAAMC,EACLjD,EAGC,iBAEF,GAAIT,EAAS0D,CAAe,EAC3B,OAAW,CAACpD,EAAUqD,CAAK,IAAK,OAAO,QAAQD,CAAe,EAAG,CAChE,GAAI,CAAC1D,EAAS2D,CAAK,EAClB,SAED,IAAMC,EAAWD,EAAM,QAIvB,GAHI,OAAOC,GAAa,YAGpBA,EAASxE,EAAe,EAC3B,SAGD,IAAMM,EAAiBM,EAAS2D,EAAM,KAAK,EACvCA,EAAM,MACP,OAEHA,EAAM,QAAUtD,GACfC,EACAsD,EACApD,EACAd,CACD,CACD,CAGD,GAAIgB,EAAQ,QAAQ,OAAQ,CAC3B,IAAMmD,EACLpD,EAGC,iBAEIqD,EAA0B,CAAC,EACjC,GAAID,GAAsB,OAAOA,GAAuB,UACvD,QAAWF,KAAS,OAAO,OAAOE,CAAkB,EACnD,GAAIF,GAAS,OAAOA,GAAU,SAAU,CACvC,IAAMzC,EAAQyC,EAAkC,MAC1CI,EACL7C,GAAQ,OAAOA,GAAS,SACnBA,EAAiC,WAGnC,OACA6C,GAAI,OAAO,QACdD,EAAW,KAAKC,CAAE,CAEpB,EAIED,EAAW,OAAS,IACvBtD,EAAI,WAAa,MAAMwD,GAAyBF,CAAU,EAE5D,CAEA,OAAOb,CACR","names":["asRecord","value","uiObject","meta","resourceUriFromMeta","nested","flat","openai","autoHeightFromMeta","viewUriFor","tool","resourceUriFromMeta","isDisplayTool","readModelContext","candidate","supportsWebMcp","pageContext","createWebMcpBridge","options","endpoint","headers","sessionId","visitorId","channelId","onWidget","log","mc","controller","onPageHide","disposed","dispose","identity","post","request","signal","response","tools","error","registered","tool","args","execOptions","result","readWidgetStep","result","first","parsed","payload","data","WAIT_INSTRUCTIONS","SHOWN_INSTRUCTIONS","UNAVAILABLE_INSTRUCTIONS","rewriteForAgent","step","rendered","_tool","_description","rest","START","END","INTERRUPT","WIDGET","interrupt","fields","config","context","questions","key","value","q","showWidget","toolOrConfig","maybeConfig","isObjectForm","tool","toolId","data","interactive","field","isInterrupt","isWidget","z","WaniWaniError","message","status","SDK_NAME","EXTRACT_PATH","refusalMessage","body","status","parsed","code","detail","isStoredInput","input","sourceFields","createDocumentsClient","config","apiUrl","apiKey","toJSONSchema","response","text","WaniWaniError","json","fields","pageCount","pageConfidence","documentId","createRevenueApi","emit","amount","currency","itemId","label","context","options","id","input","externalId","email","name","occurredAt","DEFAULT_API_URL","DOCUMENT_ID_KEYS","FILENAME_KEYS","MEDIA_TYPE_KEYS","UNNAMED_FILENAME","UNKNOWN_MEDIA_TYPE","isRecord","value","pickFirst","source","keys","key","toAttachedDocument","entry","documentId","collect","candidate","documents","seen","document","fromLatestUserTurn","messages","latest","message","part","readAttachedDocuments","input","body","declared","SUGGESTIONS_META_KEY","pickFirst","meta","keys","key","value","SESSION_ID_KEYS","REQUEST_ID_KEYS","TRACE_ID_KEYS","EXTERNAL_USER_ID_KEYS","VISITOR_ID_KEYS","CORRELATION_ID_KEYS","FILE_PARAMS_KEYS","FILE_URL_KEYS","FILE_NAME_KEYS","FLOW_META_KEY","WIDGET_CONFIG_META_KEY","extractSessionId","meta","pickFirst","SESSION_ID_KEYS","extractRequestId","REQUEST_ID_KEYS","extractTraceId","TRACE_ID_KEYS","extractExternalUserId","EXTERNAL_USER_ID_KEYS","extractVisitorId","VISITOR_ID_KEYS","extractCorrelationId","CORRELATION_ID_KEYS","isRecordValue","value","fetchableUrl","url","filenameFromUrl","segments","last","toAttachedFile","entry","pickFirst","FILE_URL_KEYS","filename","FILE_NAME_KEYS","extractAttachedFiles","meta","key","FILE_PARAMS_KEYS","files","filesUnder","candidate","seen","collect","file","DOCUMENTS_META_KEY","extractAttachedDocuments","raw","readAttachedDocuments","SOURCE_SESSION_KEYS","CLIENT_INFO_NAME_SOURCES","extractSource","clientInfo","explicit","source","name","lower","needle","headerValue","headers","lowerKey","headerKey","extractSourceFromHeaders","userAgent","anthropicClient","SCOPED_CLIENT_KEY","extractScopedClient","extra","createScopedClient","base","meta","config","trackOnce","event","sessionId","extractSessionId","correlationId","extractCorrelationId","documents","createDocumentsClient","DEFAULT_API_URL","extractVisitorId","extractExternalUserId","createRevenueApi","userId","properties","extractAttachedFiles","extractAttachedDocuments","input","z","statusCarrierSchema","readStatus","error","parsed","NETWORK_ERROR_MESSAGES","classifyCause","status","message","reportSessionError","waniwani","code","cause","properties","getObjectShape","schema","def","getNestedValue","obj","path","parts","current","part","setNestedValue","value","lastKey","deleteNestedValue","expandDotPaths","flat","result","key","deepMerge","target","source","readDef","schema","unwrapOptional","current","optional","i","def","introspectField","inner","description","t","base","resolveFieldSchema","state","fieldPath","dot","parent","child","parentSchema","childSchema","getObjectShape","isFilled","v","resolveNextNode","edge","state","buildInterruptResult","questions","context","currentNode","stateSchema","q","getNestedValue","unanswered","fieldSchema","resolveFieldSchema","isSingle","q0","executeFrom","startNodeName","startState","nodes","edges","validators","meta","waniwani","nodeOptions","nodesVisited","MAX_ITERATIONS","iterations","END","handler","reportSessionError","result","interrupt","showWidget","isInterrupt","interruptResult","fn","value","vResult","deepMerge","err","msg","deleteNestedValue","questionsWithError","qq","errResult","isWidget","widgetField","widgetFieldSchema","interactive","error","message","classifyCause","isEncryptedEnvelope","value","keyCache","importKey","keyBase64","cached","raw","cryptoKey","encryptValue","key","iv","plaintext","ciphertext","decryptValue","envelope","ct","LEVELS","getGlobalLevel","explicit","createLogger","namespace","enabled","args","SDK_NAME","DEFAULT_BASE_URL","kvDebug","createLogger","WaniwaniKvStore","key","data","isEncryptedEnvelope","decryptValue","value","options","encKey","payload","encryptValue","error","path","body","url","response","text","MemoryKvStore","key","value","_options","WaniwaniFlowStore","WaniwaniKvStore","key","value","options","classifyNode","handler","src","extractFlowGraph","config","nodes","edges","nodeOptions","graphNodes","id","opts","graphEdges","from","edge","normalizeIntro","intro","flowId","verbatim","instructions","SEEDS","config","normalizeIntro","initInternalState","seeded","field","seed","value","loadInternalState","args","store","sessionId","sessionIsPreexisting","record","takeInternalField","internal","rest","withInternalState","tokenContent","z","flowFieldSchema","flowIntroSchema","flowQuestionSchema","flowOutputSchema","buildFlowProtocol","config","lines","isRecord","value","redacted","schema","existing","readMeta","waniwani","isFieldRedacted","metaFn","collectRedactedStateFields","state","out","name","REDACTED_STATE_UPDATE_FIELDS_META_KEY","buildInputSchema","config","piiNote","stateUpdatesSchema","z","resolveDefaultStore","flowId","WaniwaniFlowStore","compileFlow","input","nodes","edges","inputSchema","flowGraph","extractFlowGraph","protocol","buildFlowProtocol","fullDescription","store","initialInternal","initInternalState","validators","handleToolCall","args","sessionId","sessionIsPreexisting","meta","waniwani","run","node","state","internal","executeFrom","result","intent","trimmed","startEdge","START","reportSessionError","loadInternalState","startState","expandDotPaths","firstNode","resolveNextNode","flowState","err","msg","classifyCause","step","updatedState","deepMerge","edge","nextNode","existingState","mergedState","redactedStateFields","collectRedactedStateFields","toolConfig","flowOutputSchema","REDACTED_STATE_UPDATE_FIELDS_META_KEY","toolHandler","extra","requestExtra","_meta","metaSessionId","extractSessionId","extractScopedClient","contentObj","intro","takeInternalField","payload","SUGGESTIONS_META_KEY","tokenContent","withInternalState","content","FLOW_META_KEY","server","configWithGraph","buildMermaidGraph","nodes","edges","lines","START","name","END","from","edge","target","StateGraph","config","configOrName","handler","options","id","run","label","hideFromFunnel","to","condition","nodesCopy","edgesCopy","compileFlow","startEdge","createFlow","config","StateGraph","parsePayload","result","content","createFlowTestHarness","flow","options","store","registered","sessionId","server","args","handler","extra","toResult","parsed","intent","stateUpdates","context","AsyncLocalStorage","globalWithStore","store","retrievalCollectorStore","recordKbSearch","trace","SDK_NAME","createKbClient","config","apiUrl","apiKey","requireApiKey","request","method","path","body","key","url","headers","init","response","text","WaniWaniError","files","query","options","results","recordKbSearch","r","existsSync","readFileSync","resolve","CONFIG_FILENAME","_cached","loadProjectConfig","filePath","raw","GLOBAL_KEY","getGlobalConfig","GLOBAL_KEY","DEFAULT_SOURCE","mapTrackEventToV2","input","options","now","generateId","createEventId","eventName","resolveEventName","meta","toRecord","metadata","correlation","resolveCorrelationIds","eventId","takeNonEmptyString","timestamp","normalizeTimestamp","source","extractSource","rawLegacy","isLegacyTrackEvent","mappedMetadata","mapProperties","legacyProperties","mapLegacyProperties","explicitProperties","properties","requestId","extractRequestId","sessionId","extractSessionId","traceId","extractTraceId","externalUserId","extractExternalUserId","visitorId","extractVisitorId","correlationId","extractCorrelationId","date","value","DEFAULT_ENDPOINT_PATH","SDK_NAME","AUTH_FAILURE_STATUS","RETRYABLE_STATUS","defaultFetch","input","init","createV2BatchTransport","options","BatchingV2Transport","joinUrl","DEFAULT_ENDPOINT_PATH","delayMs","resolve","event","dropCount","timeoutMs","flushPromise","timeoutSignal","teardownFlush","events","body","mid","headers","SDK_NAME","batch","attempt","pendingBatch","result","response","error","getErrorMessage","data","parseJsonResponse","partial","rejected","byId","retryable","permanent","rejectedEvent","isRetryableRejectedEvent","rawDelay","status","rejectedCount","buffered","code","apiUrl","endpointPath","normalizedBase","normalizedPath","createTrackingClient","config","apiUrl","apiKey","tracking","requireApiKey","transport","createV2BatchTransport","emit","event","mappedEvent","mapTrackEventToV2","trackOnce","track","createRevenueApi","client","userId","properties","meta","options","attachShutdownHooks","defaultTimeoutMs","shutdown","waniwani","config","effective","loadProjectConfig","getGlobalConfig","apiUrl","DEFAULT_API_URL","apiKey","trackingConfig","internalConfig","trackingClient","createTrackingClient","kbClient","createKbClient","documentsClient","createDocumentsClient","mapEnvelope","envelope","createTrackingRoute","options","config","client","getClient","waniwani","request","body","c","results","result","error","message","log","createLogger","EXPIRY_BUFFER_MS","WidgetTokenCache","config","sessionId","traceId","url","joinUrl","body","response","json","result","expiresAtMs","error","apiUrl","path","hashData","data","buf","b","hash","i","hashGraph","graph","prepareFunnelSyncPayload","flowGraphs","sorted","a","compositeHash","fg","flows","SESSION_ID_KEY","GEO_LOCATION_KEY","LEGACY_USER_LOCATION_KEY","OPENAI_USER_LOCATION_KEY","LOCATION_META_KEYS","stripLocationFieldsFromMeta","meta","fields","next","key","value","isRecord","stripped","field","extractMeta","extra","extractErrorText","result","content","c","resolveToolType","toolName","toolTypeOption","buildTrackInput","options","timing","clientInfo","io","kbSearch","toolType","stripFields","shouldStrip","rawMeta","input","output","flowMeta","FLOW_META_KEY","baseMeta","argSessionId","mergedMeta","extractSessionId","extractSource","safeTrack","tracker","onError","error","toError","safeFlush","injectWidgetConfig","cache","apiUrl","existingWaniwaniConfig","WIDGET_CONFIG_META_KEY","waniwaniConfig","token","sessionId","geoLocation","extractGeoLocation","source","WIDGET_META_KEYS","injectWidgetDefinitionMeta","definitionMeta","hasAnyKey","resultMeta","injectRequestMetadata","requestMeta","isRecord","v","extractTransportSessionId","extra","rawHeaders","headers","key","sid","waniSid","WRAPPED_HANDLER","log","createLogger","DEFAULT_BASE_URL","REDACTED_VALUE","buildStateUpdateRedactor","definitionMeta","fields","REDACTED_STATE_UPDATE_FIELDS_META_KEY","fieldSet","f","input","isRecord","stateUpdates","changed","next","field","createWrappedHandler","toolName","originalHandler","ctx","server","tracker","opts","tokenCache","injectToken","stateUpdateRedactor","wrappedHandler","extra","effectiveOpts","meta","extractMeta","clientInfo","extractSessionId","transportSid","extractTransportSessionId","extractSource","headers","resolvedSource","extractSourceFromHeaders","scopedClient","createScopedClient","SCOPED_CLIENT_KEY","retrievalCollector","startTime","result","retrievalCollectorStore","durationMs","isErrorResult","errorText","extractErrorText","safeTrack","buildTrackInput","safeFlush","injectRequestMetadata","injectWidgetDefinitionMeta","injectWidgetConfig","error","classifyCause","withWaniwani","options","wrappedServer","waniwani","WidgetTokenCache","originalRegisterTool","args","toolNameRaw","config","handlerRaw","wrapped","registeredTools","entry","existing","registeredToolsMap","flowGraphs","fg","prepareFunnelSyncPayload"]}
1
+ {"version":3,"sources":["../../src/shared/view-uri.ts","../../src/webmcp/bridge.ts","../../src/webmcp/widget-step.ts","../../src/mcp/server/flows/@types.ts","../../src/mcp/server/flows/compile.ts","../../src/error.ts","../../src/documents/client.ts","../../src/tracking/revenue.ts","../../src/types.ts","../../src/documents/messages.ts","../../src/shared/meta-keys.ts","../../src/mcp/server/utils.ts","../../src/mcp/server/scoped-client.ts","../../src/mcp/server/session-errors/classify.ts","../../src/mcp/server/session-errors/report.ts","../../src/mcp/server/flows/nested.ts","../../src/mcp/server/flows/field-schema.ts","../../src/mcp/server/flows/execute.ts","../../src/mcp/server/kv/crypto.ts","../../src/utils/logger.ts","../../src/mcp/server/kv/kv-store.ts","../../src/mcp/server/kv/memory-kv-store.ts","../../src/mcp/server/flows/flow-store.ts","../../src/mcp/server/flows/graph-extract.ts","../../src/mcp/server/flows/intro.ts","../../src/mcp/server/flows/internal-state.ts","../../src/mcp/server/flows/output-schema.ts","../../src/mcp/server/flows/protocol.ts","../../src/mcp/server/flows/redacted.ts","../../src/mcp/server/flows/state-graph.ts","../../src/mcp/server/flows/create-flow.ts","../../src/mcp/server/flows/test-utils.ts","../../src/kb/retrieval-context.ts","../../src/kb/client.ts","../../src/project-config.ts","../../src/tracking/mapper.ts","../../src/tracking/transport.ts","../../src/tracking/index.ts","../../src/waniwani.ts","../../src/mcp/server/tracking-route.ts","../../src/mcp/server/widget-token.ts","../../src/mcp/server/with-waniwani/funnel-sync.ts","../../src/mcp/server/with-waniwani/helpers.ts","../../src/mcp/server/with-waniwani/transport-session.ts","../../src/mcp/server/with-waniwani/index.ts"],"sourcesContent":["/**\n * Reading a widget's view binding off MCP `_meta`.\n *\n * Kept in `shared/` because three surfaces ask the same question and none of\n * them should own the answer: the chat resolves a tool part's view before\n * rendering it, the WebMCP endpoint resolves a display tool's view before\n * handing it to the page, and both have to agree or a widget renders in one\n * place and not the other.\n *\n * The three spellings below are not alternatives to choose between. They are\n * what three host generations actually emit, and a server built on any of them\n * is a server we have to render.\n */\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn undefined;\n\t}\n\treturn value as Record<string, unknown>;\n}\n\nfunction uiObject(\n\tmeta: Record<string, unknown> | undefined,\n): Record<string, unknown> | undefined {\n\treturn asRecord(meta?.ui);\n}\n\n/** Anything carrying MCP `_meta`: a tool definition, a tool result, a resource. */\nexport type MetaCarrier = {\n\t_meta?: Record<string, unknown>;\n};\n\n/**\n * The view a `_meta` binds to, checking all three shapes in priority order:\n *\n * 1. `_meta.ui.resourceUri` — MCP Apps, nested (skybridge, ext-apps)\n * 2. `_meta[\"ui/resourceUri\"]` — MCP Apps, flat (earlier drafts, still in the wild)\n * 3. `_meta[\"openai/outputTemplate\"]` — OpenAI Apps SDK\n *\n * Order is priority, not preference: a server emitting more than one emits them\n * as aliases of the same view, and the nested form is the one the current spec\n * defines.\n */\nexport function resourceUriFromMeta(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\tif (!meta) {\n\t\treturn undefined;\n\t}\n\tconst nested = uiObject(meta)?.resourceUri;\n\tif (typeof nested === \"string\" && nested.length > 0) {\n\t\treturn nested;\n\t}\n\tconst flat = meta[\"ui/resourceUri\"];\n\tif (typeof flat === \"string\" && flat.length > 0) {\n\t\treturn flat;\n\t}\n\tconst openai = meta[\"openai/outputTemplate\"];\n\tif (typeof openai === \"string\" && openai.length > 0) {\n\t\treturn openai;\n\t}\n\treturn undefined;\n}\n\n/** Whether the host should size the frame from the view's content. */\nexport function autoHeightFromMeta(\n\tmeta: Record<string, unknown> | undefined,\n): boolean {\n\treturn uiObject(meta)?.autoHeight === true;\n}\n\n/** The `_meta` on a tool result, if it has one. */\nexport function metaOf(value: unknown): Record<string, unknown> | undefined {\n\treturn asRecord(asRecord(value)?._meta);\n}\n\n/**\n * The view a tool renders, read from its definition.\n *\n * Definition rather than result on purpose. A spec-compliant server binds the\n * view on the tool, so a result carries the binding only by accident, and\n * reading the result first means a tool whose view is declared correctly looks\n * like a tool with no view at all.\n */\nexport function viewUriFor(tool: MetaCarrier | undefined): string | undefined {\n\treturn resourceUriFromMeta(tool?._meta);\n}\n\n/**\n * Whether a tool exists to render something rather than to be reasoned about.\n *\n * The distinction matters wherever a tool list crosses a boundary. A display\n * tool advertised to a browsing agent is a tool it will try to call to make a\n * widget appear, on a surface with no widget host in it, which burns a turn and\n * strands the conversation on a render nobody performs.\n */\nexport function isDisplayTool(tool: MetaCarrier | undefined): boolean {\n\treturn viewUriFor(tool) !== undefined;\n}\n","/**\n * The WebMCP bridge.\n *\n * A browsing agent standing on a page never speaks to the MCP server. It speaks\n * to the page, through `document.modelContext`, and this is what puts the\n * server's tools there. Every `execute()` the agent calls lands on the endpoint\n * below, which stamps attribution server-side and answers.\n *\n * Nothing here discovers its own configuration. The endpoint, both ids, and the\n * widget callback are arguments, because there are two callers with two\n * different ways of knowing them: the chat embed reads them from its remote\n * config, and the standalone loader derives them from its own script tag.\n * Sniffing `document.currentScript` inside a bundle that may be loaded twenty\n * modules deep finds the wrong script or none.\n */\n\nimport type {\n\tWebMcpCallResponse,\n\tWebMcpContentBlock,\n\tWebMcpListResponse,\n\tWebMcpPageContext,\n\tWebMcpRequest,\n\tWebMcpTool,\n\tWebMcpWidgetPayload,\n} from \"./@types\";\n\n/**\n * The slice of the WebMCP browser API this uses.\n *\n * Declared structurally rather than pulled from a types package: the API is\n * still moving (the getter migrated from `Navigator` to `Document` in\n * webmcp#173, which is why both are probed below), and a dependency that\n * disagrees with the browser is worse than four lines here.\n */\ntype ModelContext = {\n\tregisterTool: (\n\t\tdescriptor: {\n\t\t\tname: string;\n\t\t\tdescription: string;\n\t\t\tinputSchema?: Record<string, unknown>;\n\t\t\tannotations?: Record<string, unknown>;\n\t\t\texecute: (\n\t\t\t\targs: Record<string, unknown>,\n\t\t\t\toptions?: { signal?: AbortSignal },\n\t\t\t) => Promise<{ content: WebMcpContentBlock[] }>;\n\t\t},\n\t\toptions?: { signal?: AbortSignal },\n\t) => Promise<unknown> | unknown;\n};\n\nexport type WebMcpBridgeOptions = {\n\t/** Absolute URL of the server's page-facing tools endpoint. */\n\tendpoint: string;\n\t/**\n\t * Extra request headers, merged over `content-type`.\n\t *\n\t * The hosted API authenticates with `Authorization: Bearer <public token>`\n\t * here, the same as every other call the embed makes. A self-hosted server\n\t * reached directly needs none.\n\t */\n\theaders?: Record<string, string>;\n\t/**\n\t * Tab-scoped id, and the key the flow engine stores its state under.\n\t *\n\t * Must be stable for the tab and must not outlive it. A value that changes\n\t * between calls silently restarts every flow; a value that persists across\n\t * days resumes a flow abandoned last week mid-question.\n\t */\n\tsessionId: string;\n\t/**\n\t * The visitor's persistent id, from `shared/visitor-id`.\n\t *\n\t * Identity rather than state. Sending it is what lets a flow started here\n\t * and a conversation continued in the chat bubble belong to one person.\n\t */\n\tvisitorId?: string;\n\t/**\n\t * The channel this embed belongs to, from the script tag or `/config`.\n\t *\n\t * Ingest drops events it cannot attribute to a channel, so a tool call sent\n\t * without this produces conversions nobody can see.\n\t */\n\tchannelId?: string;\n\t/**\n\t * Called when a tool call resolves to a widget the page should mount.\n\t *\n\t * Optional, and the surface works without it: everything the visitor needs\n\t * is also in the text the agent receives. A page with no host gets a flow\n\t * carried in words.\n\t */\n\tonWidget?: (widget: WebMcpWidgetPayload) => void;\n\t/** Defaults to `console`. */\n\tlogger?: Pick<Console, \"error\" | \"info\">;\n};\n\n/** Live for as long as the bridge is registered. */\nexport type WebMcpBridge = {\n\t/** Tools successfully registered with the browser. */\n\treadonly tools: WebMcpTool[];\n\t/** Unregister everything. Idempotent. */\n\tdispose: () => void;\n};\n\nfunction readModelContext(): ModelContext | null {\n\tif (typeof document === \"undefined\" && typeof navigator === \"undefined\") {\n\t\treturn null;\n\t}\n\t// The getter moved from Navigator to Document (webmcp#173). Probe both.\n\tconst candidate =\n\t\t(typeof document !== \"undefined\"\n\t\t\t? (document as unknown as { modelContext?: unknown }).modelContext\n\t\t\t: undefined) ??\n\t\t(typeof navigator !== \"undefined\"\n\t\t\t? (navigator as unknown as { modelContext?: unknown }).modelContext\n\t\t\t: undefined);\n\n\tif (\n\t\tcandidate &&\n\t\ttypeof (candidate as ModelContext).registerTool === \"function\"\n\t) {\n\t\treturn candidate as ModelContext;\n\t}\n\treturn null;\n}\n\n/** Whether this browser can host site tools at all. Almost every visit: no. */\nexport function supportsWebMcp(): boolean {\n\treturn readModelContext() !== null;\n}\n\nfunction pageContext(): WebMcpPageContext {\n\treturn { url: location.href, title: document.title };\n}\n\n/**\n * Register the server's tools with the browsing agent.\n *\n * Resolves once registration has been attempted for every advertised tool. A\n * tool the browser refuses is logged and skipped rather than failing the rest,\n * because a partial tool list is a working page and an exception here is a\n * blank one.\n *\n * Returns `null` when the browser has no `modelContext`, which is the common\n * case and not an error.\n */\nexport async function createWebMcpBridge(\n\toptions: WebMcpBridgeOptions,\n): Promise<WebMcpBridge | null> {\n\tconst { endpoint, headers, sessionId, visitorId, channelId, onWidget } =\n\t\toptions;\n\tconst log = options.logger ?? console;\n\n\tconst mc = readModelContext();\n\tif (!mc) {\n\t\treturn null;\n\t}\n\n\t// Unregistration is aborting this, not a method call.\n\tconst controller = new AbortController();\n\tconst onPageHide = () => controller.abort();\n\twindow.addEventListener(\"pagehide\", onPageHide);\n\n\tlet disposed = false;\n\tconst dispose = () => {\n\t\tif (disposed) {\n\t\t\treturn;\n\t\t}\n\t\tdisposed = true;\n\t\twindow.removeEventListener(\"pagehide\", onPageHide);\n\t\tcontroller.abort();\n\t};\n\n\t/**\n\t * Identity and place, attached to every request rather than left to each\n\t * call site. Read per call, because a single-page app navigates without\n\t * re-registering and `page` would otherwise name whichever URL happened to\n\t * be open when the tools were published.\n\t */\n\tfunction identity() {\n\t\treturn { sessionId, visitorId, channelId, page: pageContext() };\n\t}\n\n\tasync function post<T>(\n\t\trequest: WebMcpRequest,\n\t\tsignal?: AbortSignal,\n\t): Promise<T> {\n\t\tconst response = await fetch(endpoint, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"content-type\": \"application/json\", ...headers },\n\t\t\tbody: JSON.stringify(request),\n\t\t\t...(signal ? { signal } : {}),\n\t\t});\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`webmcp ${request.action} failed: ${response.status}`);\n\t\t}\n\t\treturn (await response.json()) as T;\n\t}\n\n\tlet tools: WebMcpTool[] = [];\n\ttry {\n\t\tconst listed = await post<WebMcpListResponse>({\n\t\t\t...identity(),\n\t\t\taction: \"list\",\n\t\t});\n\t\ttools = listed?.tools ?? [];\n\t} catch (error) {\n\t\tlog.error(\"[webmcp] could not list site tools\", error);\n\t\tdispose();\n\t\treturn null;\n\t}\n\n\tconst registered: WebMcpTool[] = [];\n\tawait Promise.all(\n\t\ttools.map(async (tool) => {\n\t\t\ttry {\n\t\t\t\tawait mc.registerTool(\n\t\t\t\t\t{\n\t\t\t\t\t\tname: tool.name,\n\t\t\t\t\t\tdescription: tool.description ?? \"\",\n\t\t\t\t\t\t...(tool.inputSchema ? { inputSchema: tool.inputSchema } : {}),\n\t\t\t\t\t\t...(tool.annotations ? { annotations: tool.annotations } : {}),\n\t\t\t\t\t\texecute: async (args, execOptions) => {\n\t\t\t\t\t\t\t// The agent's abort travels all the way to the tool, so a\n\t\t\t\t\t\t\t// visitor who navigates away mid-flow cancels the work\n\t\t\t\t\t\t\t// upstream instead of leaving it running.\n\t\t\t\t\t\t\tconst result = await post<WebMcpCallResponse>(\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t...identity(),\n\t\t\t\t\t\t\t\t\taction: \"call\",\n\t\t\t\t\t\t\t\t\tname: tool.name,\n\t\t\t\t\t\t\t\t\targuments: args ?? {},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\texecOptions?.signal,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t// A widget step arrives already resolved: the page mounts the\n\t\t\t\t\t\t\t// view, the agent is told in words that it is on screen.\n\t\t\t\t\t\t\t// Handed to a callback rather than rendered here so the host\n\t\t\t\t\t\t\t// owns presentation, and a host with none still works.\n\t\t\t\t\t\t\tif (result?.widget) {\n\t\t\t\t\t\t\t\tonWidget?.(result.widget);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn { content: result?.content ?? [] };\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{ signal: controller.signal },\n\t\t\t\t);\n\t\t\t\tregistered.push(tool);\n\t\t\t} catch (error) {\n\t\t\t\tlog.error(`[webmcp] could not register ${tool.name}`, error);\n\t\t\t}\n\t\t}),\n\t);\n\n\tlog.info(`[webmcp] registered ${registered.length} site tool(s)`);\n\n\treturn {\n\t\tget tools() {\n\t\t\treturn registered;\n\t\t},\n\t\tdispose,\n\t};\n}\n","/**\n * Widget steps on the WebMCP surface.\n *\n * In a chat host a flow's widget step is a two-call dance: the flow answers\n * `status: \"widget\"` naming a display tool, the model calls that tool, and the\n * host renders the `ui://` template it points at. None of that survives the\n * trip to a browsing agent. There is no widget host in the agent's window, the\n * display tool is not advertised on this surface, and the round trip would cost\n * a turn for a render nobody performs.\n *\n * So the step is resolved before the agent ever sees it. The server calls the\n * display tool, finds its view, and hands the page everything it needs to mount\n * the widget itself. The agent gets prose that never names a display tool. The\n * visitor gets the calendar on the page they are already looking at.\n */\n\nimport type { FlowWidgetContent } from \"../mcp/server/flows/@types\";\nimport type { WebMcpCallResult } from \"./@types\";\n\nexport type WidgetStep = {\n\t/**\n\t * The flow's own response, carried whole.\n\t *\n\t * Typed as the engine's own `FlowWidgetContent` rather than a local shape, so\n\t * a field added to a widget step reaches this file as a type change instead\n\t * of being silently dropped. That has already happened once: `intro` was\n\t * added to the payload and the surface that hand-rolled this parse kept\n\t * working only because it spread the rest through.\n\t */\n\tpayload: FlowWidgetContent;\n\ttool: string;\n\tdata: Record<string, unknown>;\n\tinteractive: boolean;\n};\n\n/**\n * A flow's widget step, or `null` for every other tool result.\n *\n * Flow tools answer with a single JSON text block, so anything that does not\n * parse as JSON with `status: \"widget\"` and a `tool` to call belongs to someone\n * else and passes through untouched. That includes ordinary tools, which is\n * most of them.\n */\nexport function readWidgetStep(result: WebMcpCallResult): WidgetStep | null {\n\tconst first = result.content?.[0];\n\tif (!first || first.type !== \"text\" || typeof first.text !== \"string\") {\n\t\treturn null;\n\t}\n\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(first.text);\n\t} catch {\n\t\treturn null;\n\t}\n\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\treturn null;\n\t}\n\n\tconst payload = parsed as Record<string, unknown>;\n\tif (payload.status !== \"widget\" || typeof payload.tool !== \"string\") {\n\t\treturn null;\n\t}\n\n\tconst data =\n\t\tpayload.data &&\n\t\ttypeof payload.data === \"object\" &&\n\t\t!Array.isArray(payload.data)\n\t\t\t? (payload.data as Record<string, unknown>)\n\t\t\t: {};\n\n\treturn {\n\t\tpayload: payload as FlowWidgetContent,\n\t\ttool: payload.tool,\n\t\tdata,\n\t\t// The protocol treats an unset `interactive` as display-only, and so does\n\t\t// this. Telling the agent to wait for an interaction that never comes\n\t\t// strands the conversation; continuing early only costs a turn.\n\t\tinteractive: payload.interactive === true,\n\t};\n}\n\nconst WAIT_INSTRUCTIONS =\n\t'The interface for this step is now open in front of the user, on the page. There is no display tool to call on this surface, so do not try. Say one short sentence about what is on screen, then stop and wait for the user to tell you what they did. When they do, call this flow again with action \"continue\".';\n\nconst SHOWN_INSTRUCTIONS =\n\t'The interface for this step is now open in front of the user, on the page. There is no display tool to call on this surface, so do not try. Summarise `data` in one short sentence, then call this flow again with action \"continue\".';\n\nconst UNAVAILABLE_INSTRUCTIONS =\n\t\"The interface for this step could not be opened on the page. Carry the step in words instead: tell the user everything in `data` yourself, then proceed as you otherwise would.\";\n\n/** The status a rewritten widget step reports in place of `\"widget\"`. */\nexport type WidgetStepStatus = \"widget_shown\" | \"widget_unavailable\";\n\n/**\n * The step as the agent should read it.\n *\n * Built by editing the flow's own payload rather than composing a new object,\n * so the fields the protocol requires the agent to echo back survive. Dropping\n * `sessionId` here breaks the flow several turns later, which is the worst kind\n * of bug to go looking for.\n *\n * `tool` and `description` are the two that must not survive. Both name a\n * display tool the agent cannot call, and leaving either in place is an\n * instruction to attempt it.\n */\nexport function rewriteForAgent(\n\tstep: WidgetStep,\n\trendered: boolean,\n): Record<string, unknown> & { status: WidgetStepStatus } {\n\tconst { tool: _tool, description: _description, ...rest } = step.payload;\n\treturn {\n\t\t...rest,\n\t\tstatus: rendered ? \"widget_shown\" : \"widget_unavailable\",\n\t\tinstructions: rendered\n\t\t\t? step.interactive\n\t\t\t\t? WAIT_INSTRUCTIONS\n\t\t\t\t: SHOWN_INSTRUCTIONS\n\t\t\t: UNAVAILABLE_INSTRUCTIONS,\n\t};\n}\n","import type { ZodRawShapeCompat } from \"@modelcontextprotocol/sdk/server/zod-compat.js\";\nimport type { RequestHandlerExtra } from \"@modelcontextprotocol/sdk/shared/protocol.js\";\nimport type {\n\tCallToolResult,\n\tServerNotification,\n\tServerRequest,\n\tToolAnnotations,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport type { z } from \"zod\";\nimport type { ScopedWaniWaniClient } from \"../scoped-client\";\nimport type { McpServer } from \"../types\";\nimport type { FieldSchemaInfo } from \"./field-schema\";\nimport type { FlowStore } from \"./flow-store\";\nimport type { FlowOutputSchema } from \"./output-schema\";\n\nexport type { FieldSchemaInfo };\n\nexport type { McpServer };\n\n/**\n * A tool object `showWidget` can render, as opposed to a bare tool name.\n *\n * Structural on purpose: anything carrying these four members satisfies it,\n * so a server that registers its tools by hand keeps working without\n * importing a factory from this package.\n */\nexport type RegisteredTool = {\n\tid: string;\n\ttitle: string;\n\tdescription: string;\n\t/** Register the tool on the server */\n\tregister: (server: McpServer) => Promise<void>;\n};\n\n// ============================================================================\n// Sentinel constants\n// ============================================================================\n\nexport const START = \"__start__\" as const;\nexport const END = \"__end__\" as const;\n\n// ============================================================================\n// Signal types — returned by node handlers to control flow behavior\n// ============================================================================\n\nconst INTERRUPT = Symbol.for(\"waniwani.flow.interrupt\");\nconst WIDGET = Symbol.for(\"waniwani.flow.widget\");\n\n/** A single question within an interrupt step */\nexport type InterruptQuestion = {\n\t/** Question to ask the user */\n\tquestion: string;\n\t/** State key where the answer will be stored */\n\tfield: string;\n\t/** Optional suggestions to present as options */\n\tsuggestions?: string[];\n\t/** Hidden context/instructions for this specific question (not shown to user directly) */\n\tcontext?: string;\n\t/** Validation function — runs after the user answers, before advancing to the next node */\n\t// biome-ignore lint/suspicious/noConfusingVoidType: void is needed so `async () => {}` compiles\n\tvalidate?: (value: unknown) => MaybePromise<Record<string, unknown> | void>;\n};\n\n/**\n * Interrupt signal — pauses the flow and asks the user one or more questions.\n * Single-question and multi-question interrupts use the same type.\n */\nexport type InterruptSignal = {\n\treadonly __type: typeof INTERRUPT;\n\t/** Questions to ask — ask all in one conversational message */\n\tquestions: InterruptQuestion[];\n\t/** Overall hidden context/instructions for the assistant (not shown to user directly) */\n\tcontext?: string;\n};\n\nexport type WidgetSignal = {\n\treadonly __type: typeof WIDGET;\n\t/** The id of the display tool to delegate rendering to */\n\ttool: string;\n\t/** Data to pass to the display tool */\n\tdata?: Record<string, unknown>;\n\t/**\n\t * Whether the user is expected to interact with the widget before the flow continues.\n\t * Defaults to true. Set to false for informational widgets that should render and then\n\t * immediately advance to the next flow step.\n\t */\n\tinteractive?: boolean;\n\t/**\n\t * State key this widget fills — enables auto-skip when the field is already in state.\n\t * Pass this so the engine can skip the widget step when the answer is already known.\n\t */\n\tfield?: string;\n};\n\n/**\n * Create an interrupt signal — pauses the flow and asks the user a question.\n * Used internally by the engine. Flow authors use the typed `interrupt` from the node context.\n *\n * Accepts an object where each key is a field name and the value describes the question.\n * The only reserved key is `context` (string) for overall hidden AI instructions.\n */\nexport function interrupt(\n\tfields: Record<string, unknown>,\n\tconfig?: { context?: string },\n): InterruptSignal {\n\tconst context = config?.context;\n\tconst questions: InterruptQuestion[] = [];\n\n\tfor (const [key, value] of Object.entries(fields)) {\n\t\tif (typeof value === \"object\" && value !== null && \"question\" in value) {\n\t\t\tconst q = value as {\n\t\t\t\tquestion: string;\n\t\t\t\tsuggestions?: string[];\n\t\t\t\tcontext?: string;\n\t\t\t\tvalidate?: (\n\t\t\t\t\tvalue: unknown,\n\t\t\t\t\t// biome-ignore lint/suspicious/noConfusingVoidType: void is needed so `async () => {}` compiles\n\t\t\t\t) => MaybePromise<Record<string, unknown> | void>;\n\t\t\t};\n\t\t\tquestions.push({\n\t\t\t\tquestion: q.question,\n\t\t\t\tfield: key,\n\t\t\t\tsuggestions: q.suggestions,\n\t\t\t\tcontext: q.context,\n\t\t\t\tvalidate: q.validate,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn {\n\t\t__type: INTERRUPT,\n\t\tquestions,\n\t\tcontext,\n\t};\n}\n\ntype ShowWidgetConfig = {\n\tdata?: Record<string, unknown>;\n\t/**\n\t * @deprecated REMOVED. The engine now emits a standardized instruction telling\n\t * the AI to call the widget tool. Return any per-widget description from the\n\t * widget tool's own response instead. Typed as `never` so passing it is a\n\t * compile-time error; the runtime check below catches JS callers as a safety net.\n\t */\n\tdescription?: never;\n\tinteractive?: boolean;\n\tfield?: string;\n};\n\n/**\n * Create a widget signal — pauses the flow and delegates rendering to a display tool.\n * Used internally by the engine. Flow authors use the typed `showWidget` from the node context.\n */\nexport function showWidget(\n\tconfig: ShowWidgetConfig & { tool: RegisteredTool | string },\n): WidgetSignal;\n/**\n * @deprecated Use the object form: `showWidget({ tool, data, field, interactive })`.\n * The positional form will be removed in v0.13.0\n */\nexport function showWidget(\n\ttool: RegisteredTool | string,\n\tconfig: ShowWidgetConfig,\n): WidgetSignal;\nexport function showWidget(\n\ttoolOrConfig:\n\t\t| RegisteredTool\n\t\t| string\n\t\t| (ShowWidgetConfig & { tool: RegisteredTool | string }),\n\tmaybeConfig?: ShowWidgetConfig,\n): WidgetSignal {\n\tconst isObjectForm =\n\t\ttypeof toolOrConfig === \"object\" &&\n\t\ttoolOrConfig !== null &&\n\t\t\"tool\" in toolOrConfig;\n\tconst tool = isObjectForm ? toolOrConfig.tool : toolOrConfig;\n\tconst config: ShowWidgetConfig = isObjectForm\n\t\t? toolOrConfig\n\t\t: (maybeConfig ?? {});\n\tif ((config as { description?: unknown }).description !== undefined) {\n\t\tconst toolId = typeof tool === \"string\" ? tool : tool.id;\n\t\tthrow new Error(\n\t\t\t`showWidget(\"${toolId}\", { description }) is no longer supported. ` +\n\t\t\t\t`The engine now emits a standardized instruction telling the AI to call ` +\n\t\t\t\t`the widget tool. Return any per-widget description from the \"${toolId}\" ` +\n\t\t\t\t`tool's own response instead.`,\n\t\t);\n\t}\n\tconst { data, interactive, field } = config;\n\treturn {\n\t\t__type: WIDGET,\n\t\ttool: typeof tool === \"string\" ? tool : tool.id,\n\t\t...(data !== undefined ? { data } : {}),\n\t\t...(interactive !== undefined ? { interactive } : {}),\n\t\t...(field !== undefined ? { field } : {}),\n\t};\n}\n\nexport function isInterrupt(value: unknown): value is InterruptSignal {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\t\"__type\" in value &&\n\t\t(value as InterruptSignal).__type === INTERRUPT\n\t);\n}\n\nexport function isWidget(value: unknown): value is WidgetSignal {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\t\"__type\" in value &&\n\t\t(value as WidgetSignal).__type === WIDGET\n\t);\n}\n\n// ============================================================================\n// Node context & handler\n// ============================================================================\n\nexport type MaybePromise<T> = T | Promise<T>;\n\n// ============================================================================\n// Nested state utility types\n// ============================================================================\n\n/** Deep partial — allows partial updates at any nesting level (for z.object state fields). */\nexport type DeepPartial<T> = {\n\t[K in keyof T]?: T[K] extends Record<string, unknown>\n\t\t? DeepPartial<T[K]>\n\t\t: T[K];\n};\n\n/**\n * Extract known (non-index-signature) string keys from a type.\n * Zod v4's z.object() adds `[key: string]: unknown` to inferred types,\n * so we filter those out to get only the declared field names.\n */\ntype KnownStringKeys<T> = Extract<\n\tkeyof {\n\t\t[K in keyof T as string extends K\n\t\t\t? never\n\t\t\t: number extends K\n\t\t\t\t? never\n\t\t\t\t: K]: T[K];\n\t},\n\tstring\n>;\n\n/**\n * Union of all valid field paths for a state type.\n * - Flat fields produce their key: `\"email\"`\n * - `z.object()` fields produce dot-paths to sub-fields: `\"driver.name\"`, `\"driver.license\"`\n * - Arrays and general records (`z.record()`) are treated as flat fields.\n * - Only 1 level of nesting is supported.\n */\nexport type FieldPaths<TState> = {\n\t[K in Extract<keyof TState, string>]: NonNullable<TState[K]> extends unknown[]\n\t\t? K\n\t\t: NonNullable<TState[K]> extends Record<string, unknown>\n\t\t\t? KnownStringKeys<NonNullable<TState[K]>> extends never\n\t\t\t\t? K\n\t\t\t\t: K | `${K}.${KnownStringKeys<NonNullable<TState[K]>>}`\n\t\t\t: K;\n}[Extract<keyof TState, string>];\n\n/** Resolve a dot-path to the value type at that path in TState. */\nexport type ResolveFieldType<\n\tTState,\n\tP extends string,\n> = P extends `${infer Parent}.${infer Child}`\n\t? Parent extends keyof TState\n\t\t? Child extends keyof NonNullable<TState[Parent]>\n\t\t\t? NonNullable<TState[Parent]>[Child]\n\t\t\t: never\n\t\t: never\n\t: P extends keyof TState\n\t\t? TState[P]\n\t\t: never;\n\n// ============================================================================\n// Typed interrupt & showWidget\n// ============================================================================\n\n/**\n * Typed interrupt function — available on the node context.\n *\n * First argument: an object where each key is a field path and each value\n * describes the question for that field. Use dot-paths for nested state fields.\n * `validate` receives the field's value typed from the Zod schema.\n *\n * Second argument (optional): config with overall hidden AI instructions.\n *\n * @example\n * ```ts\n * // Flat field\n * interrupt({ breed: { question: \"What breed is your pet?\" } })\n *\n * // Nested field (z.object in state)\n * interrupt({ \"driver.name\": { question: \"Driver's name?\" } })\n *\n * // Multiple questions with context\n * interrupt(\n * {\n * \"driver.name\": { question: \"Name?\" },\n * \"driver.license\": { question: \"License?\" },\n * },\n * { context: \"Ask both questions naturally.\" },\n * )\n * ```\n */\nexport type TypedInterrupt<TState> = (\n\tfields: {\n\t\t[P in FieldPaths<TState>]?: {\n\t\t\tquestion: string;\n\t\t\tvalidate?: (\n\t\t\t\tvalue: ResolveFieldType<TState, P>,\n\t\t\t\t// biome-ignore lint/suspicious/noConfusingVoidType: void is needed so `async () => {}` compiles\n\t\t\t) => MaybePromise<DeepPartial<TState> | void>;\n\t\t\tsuggestions?: string[];\n\t\t\tcontext?: string;\n\t\t};\n\t},\n\tconfig?: {\n\t\t/** Overall hidden context/instructions for the assistant (not shown to user directly) */\n\t\tcontext?: string;\n\t},\n) => InterruptSignal;\n\n/**\n * Typed showWidget function — available on the node context.\n * The `field` parameter accepts field paths (flat or dot-path for nested state).\n *\n * Prefer the object form: `showWidget({ tool, data, field, interactive })`.\n * The positional form `showWidget(tool, config)` is deprecated.\n */\nexport type TypedShowWidgetConfig<TState> = {\n\tdata?: Record<string, unknown>;\n\t/**\n\t * @deprecated REMOVED. The engine now emits a standardized instruction telling\n\t * the AI to call the widget tool. Return any per-widget description from the\n\t * widget tool's own response instead. Typed as `never` so passing it is a\n\t * compile-time error pointing authors at the new channel.\n\t */\n\tdescription?: never;\n\tinteractive?: boolean;\n\tfield?: FieldPaths<TState>;\n};\n\nexport type TypedShowWidget<TState> = {\n\t(\n\t\tconfig: TypedShowWidgetConfig<TState> & { tool: RegisteredTool | string },\n\t): WidgetSignal;\n\t/**\n\t * @deprecated Use the object form: `showWidget({ tool, data, field, interactive })`.\n\t * The positional form will be removed in v0.13.0\n\t */\n\t(\n\t\ttool: RegisteredTool | string,\n\t\tconfig: TypedShowWidgetConfig<TState>,\n\t): WidgetSignal;\n};\n\n/**\n * Context object passed to node handlers.\n * Provides state, metadata, and typed helper functions for creating signals.\n */\nexport type NodeContext<TState> = {\n\t/** Current flow state (partial — fields are filled as the flow progresses) */\n\tstate: Partial<TState>;\n\t/** Request metadata from the MCP call */\n\tmeta?: Record<string, unknown>;\n\t/** Create an interrupt signal — pause and ask the user questions */\n\tinterrupt: TypedInterrupt<TState>;\n\t/** Create a widget signal — pause and show a UI widget */\n\tshowWidget: TypedShowWidget<TState>;\n\t/** Session-scoped Waniwani client — available when the server is wrapped with withWaniwani() */\n\twaniwani?: ScopedWaniWaniClient;\n};\n\n/**\n * Node handler — receives a context object and returns a signal or state updates.\n * The return value determines behavior:\n * - `Partial<TState>` → action node (state merged, auto-advance)\n * - `InterruptSignal` → interrupt (pause, ask user one or more questions)\n * - `WidgetSignal` → widget step (pause, show widget)\n */\nexport type NodeHandler<TState> = (\n\tctx: NodeContext<TState>,\n) => MaybePromise<DeepPartial<TState> | InterruptSignal | WidgetSignal>;\n\n/**\n * Condition function for conditional edges.\n * Receives current state, returns the name of the next node.\n */\nexport type ConditionFn<TState> = (\n\tstate: Partial<TState>,\n) => string | Promise<string>;\n\nexport type Edge<TState> =\n\t| { type: \"direct\"; to: string }\n\t| {\n\t\t\ttype: \"conditional\";\n\t\t\tcondition: ConditionFn<TState>;\n\t\t\t/**\n\t\t\t * The branch targets declared on the edge. The condition can only return\n\t\t\t * one of these, so graph introspection (funnel sync, Mermaid output) reads\n\t\t\t * them directly — no source parsing, correct by construction.\n\t\t\t */\n\t\t\ttargets: string[];\n\t };\n\nexport type NodeOptions = {\n\t/** Human-readable label for this node (used in funnel visualization and Graphs). */\n\tlabel: string;\n\t/** When true, this node is excluded from funnel analytics. */\n\thideFromFunnel?: boolean;\n};\n\n/**\n * Config for the object form of `.addNode({ id, run, label?, hideFromFunnel? })`.\n *\n * Preferred over the positional form `.addNode(id, run, options?)` — metadata\n * sits at the top where the eye lands, and the handler is a named field.\n */\nexport type AddNodeConfig<TState, TName extends string = string> = {\n\t/** Unique node id within the flow. */\n\tid: TName;\n\t/** Node handler — receives ctx, returns state updates or a signal. */\n\trun: NodeHandler<TState>;\n\t/** Human-readable label for funnel visualization and graphs. Defaults to `id`. */\n\tlabel?: string;\n\t/** When true, this node is excluded from funnel analytics. */\n\thideFromFunnel?: boolean;\n};\n\nexport type FlowGraphNode = {\n\tid: string;\n\ttype: \"widget\" | \"interrupt\" | \"action\";\n\tlabel: string;\n\thideFromFunnel?: boolean;\n};\n\nexport type FlowGraphEdge =\n\t| { from: string; to: string; type: \"direct\" }\n\t| { from: string; to: string[]; type: \"conditional\" };\n\nexport type FlowGraph = {\n\tflowId: string;\n\ttitle: string;\n\tnodes: FlowGraphNode[];\n\tedges: FlowGraphEdge[];\n};\n\n// ============================================================================\n// Flow config & compiled output\n// ============================================================================\n\n/**\n * The intro delivered the first time a session touches the flow.\n *\n * A bare string is shorthand for `{ verbatim: \"…\" }`.\n *\n * - `verbatim` — reproduced word for word by the assistant. Use it for legal\n * notices (GDPR, data controller, retention) that must survive intact.\n * - `instructions` — guidance the assistant writes its own opening from.\n *\n * Set both and the assistant writes the surrounding prose from\n * `instructions` while keeping the `verbatim` text untouched.\n */\nexport type FlowIntro =\n\t| string\n\t| {\n\t\t\t/** Text the assistant must reproduce word for word. */\n\t\t\tverbatim?: string;\n\t\t\t/** How the assistant should write the introduction around it. */\n\t\t\tinstructions?: string;\n\t };\n\n/** Normalized intro as it appears in the flow tool response. */\nexport type FlowIntroPayload = {\n\t/** Deliver word for word — no paraphrase, no translation, no reformatting. */\n\tverbatim?: string;\n\t/** Guidance for the assistant's own wording. */\n\tinstructions?: string;\n};\n\nexport type FlowConfig = {\n\t/** Unique identifier for the flow (becomes the MCP tool name) */\n\tid: string;\n\t/** Display title */\n\ttitle: string;\n\t/** Description for the AI (explains when to use this flow) */\n\tdescription: string;\n\t/**\n\t * Opening message for the session's first flow response — an introduction,\n\t * a GDPR notice, or both. Attached to whichever response the engine returns\n\t * first, so it lands even when pre-filled state skips the flow's opening\n\t * nodes, and it is never repeated once delivered.\n\t *\n\t * @example\n\t * ```ts\n\t * intro: \"Acme is the data controller. Your answers are used for this quote only.\"\n\t * ```\n\t *\n\t * @example\n\t * ```ts\n\t * intro: {\n\t * verbatim: \"Acme is the data controller. Your answers are deleted after 30 days.\",\n\t * instructions: \"Introduce yourself as Léa from Acme in one short sentence first.\",\n\t * }\n\t * ```\n\t */\n\tintro?: FlowIntro;\n\t/**\n\t * Define the flow's state — each field the flow collects.\n\t * Keys are the field names used in `interrupt({ field })`,\n\t * values are Zod schemas with `.describe()`.\n\t *\n\t * The state definition serves two purposes:\n\t * 1. Type inference — `TState` is automatically derived, no explicit generic needed\n\t * 2. AI protocol — field names, types, and descriptions are included in the tool\n\t * description so the AI can pre-fill answers via `_meta.flow.state`\n\t *\n\t * @example\n\t * ```ts\n\t * state: {\n\t * country: z.string().describe(\"Country the business is based in\"),\n\t * status: z.enum([\"registered\", \"unregistered\"]).describe(\"Business registration status\"),\n\t * }\n\t * ```\n\t */\n\tstate: Record<string, z.ZodType>;\n\t/**\n\t * If true, instruct the agent to omit PII (names, emails, phones, addresses,\n\t * IDs, ages, birthdates) from the `intent` and `context` fields. Only adds a\n\t * guidance line to the tool description — does not redact server-side.\n\t */\n\tomitIntentPII?: boolean;\n\t/**\n\t * Optional tool annotations, passed through to `registerTool` as-is.\n\t *\n\t * Includes MCP's `title` — a human-readable name for the tool. Claude's\n\t * Connectors Directory requires it on every tool and flags the server at\n\t * submission without it; the top-level `title` does not satisfy that.\n\t */\n\tannotations?: ToolAnnotations;\n};\n\n/**\n * Infer the runtime state type from a flow's state schema definition.\n *\n * @example\n * ```ts\n * const config = {\n * state: {\n * country: z.enum([\"FR\", \"DE\"]),\n * status: z.enum([\"registered\", \"unregistered\"]),\n * }\n * };\n * type MyState = InferFlowState<typeof config.state>;\n * // { country: \"FR\" | \"DE\"; status: \"registered\" | \"unregistered\" }\n * ```\n */\nexport type InferFlowState<T extends Record<string, z.ZodType>> = {\n\t[K in keyof T]: z.infer<T[K]>;\n};\n\n/**\n * Flow tool handler — uses shared MCP types (`RequestHandlerExtra`, `CallToolResult`)\n * so it's assignable to both MCP SDK's `ToolCallback` and Skybridge's `ToolHandler`.\n */\nexport type FlowToolHandler = (\n\targs: Record<string, unknown>,\n\textra: RequestHandlerExtra<ServerRequest, ServerNotification>,\n) => CallToolResult | Promise<CallToolResult>;\n\n/**\n * A compiled flow — can be registered on an McpServer.\n *\n * Exposes MCP-compatible `name`, `config`, and `handler` so it can be\n * registered directly: `server.registerTool(flow.name, flow.config, flow.handler)`\n */\nexport type RegisteredFlow = {\n\t/** Tool name — pass to `server.registerTool(flow.name, flow.config, flow.handler)`. */\n\tname: string;\n\t/** Tool config object — pass to `server.registerTool(flow.name, flow.config, flow.handler)`. */\n\tconfig: {\n\t\ttitle: string;\n\t\tdescription: string;\n\t\tinputSchema: ZodRawShapeCompat;\n\t\t/**\n\t\t * JSON Schema for the structured payload returned in every flow tool\n\t\t * call. Baked in so MCP clients render the flow protocol as a typed\n\t\t * contract instead of an opaque JSON string.\n\t\t */\n\t\toutputSchema: FlowOutputSchema;\n\t\tannotations?: ToolAnnotations;\n\t};\n\t/** Tool callback — pass to `server.registerTool(flow.name, flow.config, flow.handler)`. */\n\thandler: FlowToolHandler;\n\t/** Register this flow on an MCP server. Shorthand for `server.registerTool(flow.name, flow.config, flow.handler)`. */\n\tregister: (server: McpServer) => Promise<void>;\n\t/** Returns a Mermaid `flowchart TD` diagram of the flow graph. */\n\tgraph: () => string;\n\tflowGraph: FlowGraph;\n};\n\nexport interface CompileInput<TState extends Record<string, unknown>> {\n\tconfig: FlowConfig;\n\tnodes: Map<string, NodeHandler<TState>>;\n\tedges: Map<string, Edge<TState>>;\n\tstore?: FlowStore;\n\tgraph: () => string;\n\tnodeOptions: Map<string, NodeOptions>;\n}\n\nexport type FlowToolInput = {\n\taction: \"start\" | \"continue\" | \"reset\";\n\t/** Required when `action` is `\"start\"`. */\n\tintent?: string;\n\t/** Optional when `action` is `\"start\"`. Describes the situation/environment that led the user to start this flow. */\n\tcontext?: string;\n\tstateUpdates?: Record<string, unknown>;\n\t/** Session identifier echoed in the response. Pass it back on \"continue\" and \"reset\" calls. */\n\tsessionId?: string;\n};\n\n/**\n * The engine's own state for a session, persisted in the same record as the\n * flow's state and kept strictly apart from it.\n *\n * Not flow state: nothing here is declared by the author, reaches a node, or\n * can be written through `stateUpdates`.\n *\n * One optional field per engine feature that carries something across calls. A\n * field is seeded when the session starts and taken off once the engine has\n * used it, so presence means \"still pending\" and absence means \"done\". Seeding,\n * reading and persisting all go through `internal-state.ts`, which carries\n * unrecognized fields through untouched: a field a newer version wrote survives\n * a call served by an older one, and adding a field never strands sessions\n * across a deploy.\n */\nexport type FlowInternalState = {\n\t/**\n\t * The flow's intro, waiting to ride on the session's first response. Gone\n\t * once delivered, so later responses in the same conversation leave it out.\n\t */\n\tintro?: FlowIntroPayload;\n};\n\nexport type FlowTokenContent = {\n\tstep?: string;\n\tstate: Record<string, unknown>;\n\tfield?: string;\n\twidgetId?: string;\n\t/** The engine's own state for this session. Never flow state. */\n\tinternal?: FlowInternalState;\n};\n\nexport type InterruptQuestionData = {\n\tquestion: string;\n\tfield: string;\n\tsuggestions?: string[];\n\tcontext?: string;\n\t/**\n\t * JIT schema fragment for the field, derived from the flow's state schema.\n\t * The LLM uses this to know the field's type, allowed values, and meaning\n\t * at the exact moment it needs to fill it — without consulting a global dump.\n\t */\n\tfieldSchema?: FieldSchemaInfo;\n};\n\nexport type FlowInterruptContent = {\n\tstatus: \"interrupt\";\n\t/** Opening message for the session's first flow response. */\n\tintro?: FlowIntroPayload;\n\t/** Single-question shorthand */\n\tquestion?: string;\n\tfield?: string;\n\tsuggestions?: string[];\n\t/** JIT schema fragment for the single-question shorthand. */\n\tfieldSchema?: FieldSchemaInfo;\n\t/** Multi-question */\n\tquestions?: InterruptQuestionData[];\n\tcontext?: string;\n};\n\nexport type FlowWidgetContent = {\n\tstatus: \"widget\";\n\t/** Opening message for the session's first flow response. */\n\tintro?: FlowIntroPayload;\n\t/** Display tool to call */\n\ttool: string;\n\t/** Data to pass to the display tool */\n\tdata?: Record<string, unknown>;\n\t/**\n\t * Standardized instruction to the AI to call the widget tool. Auto-generated\n\t * by the engine from the tool id; the value previously passed via\n\t * `showWidget({ description })` is ignored.\n\t */\n\tdescription?: string;\n\t/** Whether the widget expects user interaction before continuing */\n\tinteractive?: boolean;\n\t/** State field the widget fills, when declared via `showWidget({ field })`. */\n\tfield?: string;\n\t/** JIT schema fragment for the field the widget fills. */\n\tfieldSchema?: FieldSchemaInfo;\n};\n\nexport type FlowCompleteContent = {\n\tstatus: \"complete\";\n\t/** Opening message for the session's first flow response. */\n\tintro?: FlowIntroPayload;\n};\n\nexport type FlowErrorContent = {\n\tstatus: \"error\";\n\terror: string;\n};\n\n/** Parsed response text from a flow tool call */\nexport type FlowContent =\n\t| FlowInterruptContent\n\t| FlowWidgetContent\n\t| FlowCompleteContent\n\t| FlowErrorContent;\n\n/** A single node visit during flow execution. `at` is the ISO timestamp of when the node was entered. */\nexport type NodeVisit = {\n\tid: string;\n\tat: string;\n};\n\nexport type ExecutionResult = {\n\tcontent: FlowContent;\n\tflowTokenContent?: FlowTokenContent;\n\t/** Nodes visited during this execution (excludes nodes with hideFromFunnel). */\n\tnodesVisited?: NodeVisit[];\n};\n","import type { ToolCallback } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { RequestHandlerExtra } from \"@modelcontextprotocol/sdk/shared/protocol.js\";\nimport type {\n\tServerNotification,\n\tServerRequest,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { z } from \"zod\";\nimport type { ScopedWaniWaniClient } from \"../scoped-client\";\nimport { extractScopedClient } from \"../scoped-client\";\nimport { classifyCause } from \"../session-errors/classify\";\nimport { reportSessionError } from \"../session-errors/report\";\nimport {\n\textractSessionId,\n\tFLOW_META_KEY,\n\tSUGGESTIONS_META_KEY,\n\ttype SuggestionsMeta,\n} from \"../utils\";\nimport type {\n\tCompileInput,\n\tExecutionResult,\n\tFlowInternalState,\n\tFlowToolHandler,\n\tFlowToolInput,\n\tMcpServer,\n\tRegisteredFlow,\n} from \"./@types\";\nimport { START } from \"./@types\";\nimport { executeFrom, resolveNextNode, type ValidateFn } from \"./execute\";\nimport { type FlowStore, WaniwaniFlowStore } from \"./flow-store\";\nimport { extractFlowGraph } from \"./graph-extract\";\nimport {\n\tinitInternalState,\n\tloadInternalState,\n\ttakeInternalField,\n\twithInternalState,\n} from \"./internal-state\";\nimport { deepMerge, expandDotPaths } from \"./nested\";\nimport { flowOutputSchema } from \"./output-schema\";\nimport { buildNextStep } from \"./protocol\";\nimport {\n\tcollectRedactedStateFields,\n\tREDACTED_STATE_UPDATE_FIELDS_META_KEY,\n} from \"./redacted\";\n\n// ============================================================================\n// Input schema\n// ============================================================================\n\nfunction buildInputSchema(config: {\n\tomitIntentPII?: boolean;\n\tstate?: Record<string, z.ZodType>;\n}) {\n\tconst piiNote = config.omitIntentPII\n\t\t? \" Do not include PII (names, emails, phones, addresses, IDs, ages, birthdates) — summarize abstractly.\"\n\t\t: \"\";\n\n\t// When the flow declares state fields, expose them as typed (optional) keys\n\t// on `stateUpdates` so the LLM sees field names, types, and descriptions in\n\t// the tool's JSON Schema. `.passthrough()` preserves unknown keys (e.g.\n\t// dot-paths like \"driver.name\" for nested state, plus forward-compat keys).\n\tconst hasState = config.state && Object.keys(config.state).length > 0;\n\tconst stateUpdatesSchema = hasState\n\t\t? z\n\t\t\t\t.object(config.state as Record<string, z.ZodType>)\n\t\t\t\t.partial()\n\t\t\t\t.passthrough()\n\t\t: z.record(z.string(), z.unknown());\n\n\treturn {\n\t\taction: z\n\t\t\t.enum([\"start\", \"continue\", \"reset\"])\n\t\t\t.describe(\n\t\t\t\t'\"start\" to begin the flow, \"continue\" to resume after a pause (interrupt or widget), \"reset\" to restart from the beginning with a correction to a previously-collected field',\n\t\t\t),\n\t\tintent: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.describe(\n\t\t\t\t`Required when action is \"start\". A brief summary of the user's goal for this flow, taken from what the user said rather than inferred.${piiNote}`,\n\t\t\t),\n\t\tcontext: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.describe(\n\t\t\t\t`Optional when action is \"start\". The situation that led the user here — the page they are on, what they were doing, or what triggered the request. Omitted when there is nothing genuinely relevant to report.${piiNote}`,\n\t\t\t),\n\t\tstateUpdates: stateUpdatesSchema\n\t\t\t.optional()\n\t\t\t.describe(\n\t\t\t\t'State field values to set before processing the next node: the user\\'s answer keyed by the `field` from the response, plus any other values the user stated. Fields already filled here are skipped by the engine. For nested state fields, use dot-paths like \"driver.name\".',\n\t\t\t),\n\t\tsessionId: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.describe(\n\t\t\t\t'Session identifier. If the response includes a `sessionId`, pass it back on every subsequent \"continue\" and \"reset\" call for this flow.',\n\t\t\t),\n\t};\n}\n\n// ============================================================================\n// Default store resolution\n// ============================================================================\n\nfunction resolveDefaultStore(flowId: string): FlowStore {\n\tif (process.env.WANIWANI_API_KEY) {\n\t\treturn new WaniwaniFlowStore();\n\t}\n\tthrow new Error(\n\t\t`[waniwani] createFlow \"${flowId}\": no flow store configured. ` +\n\t\t\t`Pass { store } to .compile() — use MemoryKvStore from \"@waniwani/sdk/mcp\" for ` +\n\t\t\t`local development, or plug in a Redis/Upstash/Cloudflare KV adapter for production. ` +\n\t\t\t`Alternatively, set WANIWANI_API_KEY to use hosted flow state on app.waniwani.ai.`,\n\t);\n}\n\n// ============================================================================\n// Compile\n// ============================================================================\n\n/**\n * One handled tool call, plus the internal state this session arrived with. The\n * response assembler reads it to decide what to attach, then persists what is\n * left. Branches that fail before the engine runs return no `internal`, which\n * reads as \"nothing pending\".\n */\ntype HandledCall = ExecutionResult & { internal?: FlowInternalState };\n\nexport function compileFlow<TState extends Record<string, unknown>>(\n\tinput: CompileInput<TState>,\n): RegisteredFlow {\n\tconst { config, nodes, edges } = input;\n\tconst inputSchema = buildInputSchema(config);\n\tconst flowGraph = extractFlowGraph(config, nodes, edges, input.nodeOptions);\n\t// The description says what the tool is and nothing about what the\n\t// assistant should do next. Operating instructions in a tool description\n\t// trip prompt-injection classifiers (see `protocol.ts`); they ride the\n\t// response instead, as `nextStep`.\n\tconst fullDescription = `${config.description}\\n\\nThis tool runs a multi-step flow. Each call returns the flow's current status, the data for that step, and a \\`nextStep\\` field describing what the following call contains.`;\n\n\tconst store: FlowStore = input.store ?? resolveDefaultStore(config.id);\n\n\t// The internal state every new session starts from. Built once at compile\n\t// time so a malformed config fails at startup, not on a live conversation.\n\tconst initialInternal = initInternalState(config);\n\n\t// Validator storage — populated when handlers return interrupts with validate functions.\n\t// Keyed by \"nodeName:fieldName\", persists across tool calls within the same server.\n\tconst validators = new Map<string, ValidateFn>();\n\n\tasync function handleToolCall(\n\t\targs: FlowToolInput,\n\t\tsessionId: string | undefined,\n\t\tsessionIsPreexisting: boolean,\n\t\tmeta?: Record<string, unknown>,\n\t\twaniwani?: ScopedWaniWaniClient,\n\t): Promise<HandledCall> {\n\t\t/**\n\t\t * Run the engine from `node`, carrying this session's internal state\n\t\t * through to the response assembler. Every branch below resumes the same\n\t\t * graph with the same per-call context, so the only things that vary are\n\t\t * where execution starts, the state it starts from, and where the internal\n\t\t * state was read.\n\t\t */\n\t\tconst run = (\n\t\t\tnode: string,\n\t\t\tstate: TState,\n\t\t\tinternal: FlowInternalState,\n\t\t): Promise<HandledCall> =>\n\t\t\texecuteFrom(\n\t\t\t\tnode,\n\t\t\t\tstate,\n\t\t\t\tnodes,\n\t\t\t\tedges,\n\t\t\t\tvalidators,\n\t\t\t\tmeta,\n\t\t\t\twaniwani,\n\t\t\t\tinput.nodeOptions,\n\t\t\t\tconfig.state,\n\t\t\t).then((result) => ({ ...result, internal }));\n\n\t\tif (args.action === \"start\") {\n\t\t\t// `intent` is observational: the schema asks for it on start, but nothing\n\t\t\t// in the engine reads it (it never reaches a node, the store, or an\n\t\t\t// event). A missing value therefore costs a conversation turn and buys\n\t\t\t// nothing, so trim it and carry on instead of failing the call.\n\t\t\tconst intent =\n\t\t\t\ttypeof args.intent === \"string\" ? args.intent.trim() : undefined;\n\t\t\targs.intent = intent || undefined;\n\n\t\t\t// Trim context if provided (optional field, no error if missing)\n\t\t\tif (typeof args.context === \"string\") {\n\t\t\t\tconst trimmed = args.context.trim();\n\t\t\t\targs.context = trimmed || undefined;\n\t\t\t}\n\n\t\t\tconst startEdge = edges.get(START);\n\t\t\tif (!startEdge) {\n\t\t\t\treportSessionError({\n\t\t\t\t\twaniwani,\n\t\t\t\t\tcode: \"agent_failed\",\n\t\t\t\t\tcause: \"flow_dead_end\",\n\t\t\t\t\tproperties: { node: START },\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\tcontent: { status: \"error\" as const, error: \"No start edge\" },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst internal = await loadInternalState({\n\t\t\t\tstore,\n\t\t\t\tsessionId,\n\t\t\t\tsessionIsPreexisting,\n\t\t\t\tseed: initialInternal,\n\t\t\t});\n\t\t\tconst startState = expandDotPaths(args.stateUpdates ?? {}) as TState;\n\t\t\tconst firstNode = await resolveNextNode(startEdge, startState);\n\t\t\treturn run(firstNode, startState, internal);\n\t\t}\n\n\t\tif (args.action === \"continue\") {\n\t\t\tif (!sessionId) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror: \"No session ID available for continue action.\",\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tlet flowState: Awaited<ReturnType<typeof store.get>>;\n\t\t\ttry {\n\t\t\t\tflowState = await store.get(sessionId);\n\t\t\t} catch (err) {\n\t\t\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\t\t\treportSessionError({\n\t\t\t\t\twaniwani,\n\t\t\t\t\tcode: \"upstream_failed\",\n\t\t\t\t\tcause: classifyCause({ error: err }),\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror: `Failed to load flow state (session \"${sessionId}\"): ${msg}`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!flowState) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror: `Flow state not found for session \"${sessionId}\". The flow may have expired.`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst state = flowState.state as TState;\n\t\t\tconst step = flowState.step;\n\t\t\tif (!step) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror:\n\t\t\t\t\t\t\t'This flow has already completed. Use action \"start\" to begin a new flow.',\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst updatedState = deepMerge(\n\t\t\t\tstate as Record<string, unknown>,\n\t\t\t\texpandDotPaths(args.stateUpdates ?? {}),\n\t\t\t) as TState;\n\t\t\tconst internal = flowState.internal ?? {};\n\n\t\t\t// Widget continue: advance past the widget step (don't re-show it)\n\t\t\tif (flowState.widgetId) {\n\t\t\t\tconst edge = edges.get(step);\n\t\t\t\tif (!edge) {\n\t\t\t\t\treportSessionError({\n\t\t\t\t\t\twaniwani,\n\t\t\t\t\t\tcode: \"agent_failed\",\n\t\t\t\t\t\tcause: \"flow_dead_end\",\n\t\t\t\t\t\tproperties: { node: step },\n\t\t\t\t\t});\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\t\terror: `No edge from step \"${step}\"`,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tconst nextNode = await resolveNextNode(edge, updatedState);\n\t\t\t\treturn run(nextNode, updatedState, internal);\n\t\t\t}\n\n\t\t\t// Interrupt continue: re-execute from current step.\n\t\t\t// The handler re-runs, filters answered questions, and runs\n\t\t\t// validators if all questions are filled.\n\t\t\treturn run(step, updatedState, internal);\n\t\t}\n\n\t\tif (args.action === \"reset\") {\n\t\t\tif (!sessionId) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror: \"No session ID available for reset action.\",\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tlet flowState: Awaited<ReturnType<typeof store.get>>;\n\t\t\ttry {\n\t\t\t\tflowState = await store.get(sessionId);\n\t\t\t} catch (err) {\n\t\t\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\t\t\treportSessionError({\n\t\t\t\t\twaniwani,\n\t\t\t\t\tcode: \"upstream_failed\",\n\t\t\t\t\tcause: classifyCause({ error: err }),\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror: `Failed to load flow state (session \"${sessionId}\"): ${msg}`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!flowState) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror: `Flow state not found for session \"${sessionId}\". The flow may have completed or expired. Use action \"start\" to begin a new flow.`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!flowState.step) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror:\n\t\t\t\t\t\t\t'This flow has already completed. Use action \"start\" to begin a new flow.',\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!args.stateUpdates || Object.keys(args.stateUpdates).length === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\terror:\n\t\t\t\t\t\t\t'Missing \"stateUpdates\" for action \"reset\". Include the corrected field(s).',\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst startEdge = edges.get(START);\n\t\t\tif (!startEdge) {\n\t\t\t\treportSessionError({\n\t\t\t\t\twaniwani,\n\t\t\t\t\tcode: \"agent_failed\",\n\t\t\t\t\tcause: \"flow_dead_end\",\n\t\t\t\t\tproperties: { node: START },\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\tcontent: { status: \"error\" as const, error: \"No start edge\" },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst existingState = flowState.state as TState;\n\t\t\tconst mergedState = deepMerge(\n\t\t\t\texistingState as Record<string, unknown>,\n\t\t\t\texpandDotPaths(args.stateUpdates),\n\t\t\t) as TState;\n\n\t\t\tconst internal = flowState.internal ?? {};\n\t\t\tconst firstNode = await resolveNextNode(startEdge, mergedState);\n\t\t\treturn run(firstNode, mergedState, internal);\n\t\t}\n\n\t\treturn {\n\t\t\tcontent: {\n\t\t\t\tstatus: \"error\" as const,\n\t\t\t\terror: `Unknown action: \"${args.action}\"`,\n\t\t\t},\n\t\t};\n\t}\n\n\tconst redactedStateFields = collectRedactedStateFields(\n\t\tconfig.state as Record<string, z.ZodType> | undefined,\n\t);\n\tconst toolConfig = {\n\t\ttitle: config.title,\n\t\tdescription: fullDescription,\n\t\tinputSchema,\n\t\toutputSchema: flowOutputSchema,\n\t\tannotations: config.annotations,\n\t\t...(redactedStateFields.length > 0 && {\n\t\t\t_meta: {\n\t\t\t\t[REDACTED_STATE_UPDATE_FIELDS_META_KEY]: redactedStateFields,\n\t\t\t},\n\t\t}),\n\t};\n\n\tconst toolHandler = (async (args: FlowToolInput, extra: unknown) => {\n\t\tconst requestExtra = extra as RequestHandlerExtra<\n\t\t\tServerRequest,\n\t\t\tServerNotification\n\t\t>;\n\t\tconst _meta: Record<string, unknown> = requestExtra._meta ?? {};\n\t\tconst metaSessionId = extractSessionId(_meta);\n\t\tlet sessionId = metaSessionId ?? args.sessionId;\n\t\t// A session id the caller supplied may already carry flow history; one we\n\t\t// generate below cannot.\n\t\tconst sessionIsPreexisting = Boolean(sessionId);\n\n\t\t// Auto-generate session ID for clients that don't provide one (e.g. Claude Code)\n\t\tif (!sessionId && args.action === \"start\") {\n\t\t\tsessionId = crypto.randomUUID();\n\t\t}\n\n\t\t// Bridge the resolved sessionId into _meta when the transport didn't\n\t\t// supply one (auto-generated on start, or echoed back via args.sessionId\n\t\t// on continue/reset). Without this, downstream tracking, scoped-client,\n\t\t// and source detection see an empty _meta on every turn after start.\n\t\tif (sessionId && !metaSessionId) {\n\t\t\t_meta[\"waniwani/sessionId\"] = sessionId;\n\t\t}\n\n\t\tconst waniwani = extractScopedClient(requestExtra);\n\n\t\tconst result = await handleToolCall(\n\t\t\targs,\n\t\t\tsessionId,\n\t\t\tsessionIsPreexisting,\n\t\t\t_meta,\n\t\t\twaniwani,\n\t\t);\n\n\t\t// Echo sessionId in response when not sourced from _meta (client must pass it back)\n\t\tconst contentObj =\n\t\t\t!metaSessionId && sessionId\n\t\t\t\t? { ...result.content, sessionId }\n\t\t\t\t: result.content;\n\n\t\t// The intro goes on whichever response the engine returns first for a\n\t\t// session, so it lands even when pre-filled state skips the flow's opening\n\t\t// nodes, and taking it off the internal state is what keeps it to once per\n\t\t// conversation. Error responses keep it pending: the assistant may never\n\t\t// surface one to the user, so delivering it there would spend it on a\n\t\t// message nobody reads.\n\t\tconst { value: intro, internal } =\n\t\t\tcontentObj.status === \"error\"\n\t\t\t\t? { value: undefined, internal: result.internal ?? {} }\n\t\t\t\t: takeInternalField(result.internal, \"intro\");\n\n\t\t// `intro` first so it reads as the opening instruction rather than a\n\t\t// trailing detail after the question and its schema. `nextStep` last:\n\t\t// it is the protocol for this one status, and it reads after the data\n\t\t// it applies to.\n\t\tconst nextStep = buildNextStep({\n\t\t\tstatus: contentObj.status,\n\t\t\ttool: \"tool\" in contentObj ? contentObj.tool : undefined,\n\t\t\tinteractive:\n\t\t\t\t\"interactive\" in contentObj ? contentObj.interactive : undefined,\n\t\t\thasIntro: Boolean(intro),\n\t\t\techoSessionId: !metaSessionId && Boolean(sessionId),\n\t\t});\n\t\tconst payload = {\n\t\t\t...(intro ? { intro } : {}),\n\t\t\t...contentObj,\n\t\t\t...(nextStep ? { nextStep } : {}),\n\t\t};\n\n\t\t// Authoritative for the turn: an empty array clears pills an earlier flow\n\t\t// call in the same turn set. Only the single-open-question shorthand\n\t\t// carries a top-level `suggestions`. Set before both return paths below\n\t\t// so every flow tool result — including the state-persistence failure —\n\t\t// carries the key.\n\t\t_meta[SUGGESTIONS_META_KEY] = {\n\t\t\tsuggestions:\n\t\t\t\tcontentObj.status === \"interrupt\" ? (contentObj.suggestions ?? []) : [],\n\t\t} satisfies SuggestionsMeta;\n\n\t\t// Persist flow state under session ID. On completion we store the final\n\t\t// `{ state }` (no `step`) so customers can read the final state until\n\t\t// the KV TTL expires; a stale `continue` falls into the \"already\n\t\t// completed\" branch at the loader since `step` is undefined.\n\t\t// TODO: expose a `deleteOnComplete` compile option for customers who\n\t\t// want the prior behavior (drop the session as soon as END is reached).\n\t\tif (sessionId && result.flowTokenContent) {\n\t\t\tconst tokenContent = withInternalState(result.flowTokenContent, internal);\n\t\t\ttry {\n\t\t\t\tawait store.set(sessionId, tokenContent);\n\t\t\t} catch (err) {\n\t\t\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\t\t\treportSessionError({\n\t\t\t\t\twaniwani,\n\t\t\t\t\tcode: \"upstream_failed\",\n\t\t\t\t\tcause: classifyCause({ error: err }),\n\t\t\t\t\tproperties: { node: result.flowTokenContent.step },\n\t\t\t\t});\n\t\t\t\tconst errorContent = [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: JSON.stringify(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\t\t\terror: `Flow state failed to persist (session \"${sessionId}\"): ${msg}`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t),\n\t\t\t\t\t},\n\t\t\t\t];\n\t\t\t\treturn {\n\t\t\t\t\tcontent: errorContent,\n\t\t\t\t\t_meta,\n\t\t\t\t\tisError: true,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst content = [\n\t\t\t{\n\t\t\t\ttype: \"text\" as const,\n\t\t\t\ttext: JSON.stringify(payload, null, 2),\n\t\t\t},\n\t\t];\n\n\t\t// Attach flow execution path to _meta so it's captured in the tool.called event\n\t\tif (result.nodesVisited?.length) {\n\t\t\t_meta[FLOW_META_KEY] = {\n\t\t\t\tflowId: config.id,\n\t\t\t\tnodesVisited: result.nodesVisited,\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tcontent,\n\t\t\tstructuredContent: payload as Record<string, unknown>,\n\t\t\t_meta,\n\t\t\t...(result.content.status === \"error\" ? { isError: true } : {}),\n\t\t};\n\t}) satisfies ToolCallback<typeof inputSchema>;\n\n\treturn {\n\t\t// MCP-compatible — server.registerTool(flow.name, flow.config, flow.handler)\n\t\tname: config.id,\n\t\tconfig: toolConfig,\n\t\thandler: toolHandler as unknown as FlowToolHandler,\n\n\t\tasync register(server: McpServer): Promise<void> {\n\t\t\tconst configWithGraph = {\n\t\t\t\t...toolConfig,\n\t\t\t\t_meta: { ...toolConfig._meta, _flowGraph: flowGraph },\n\t\t\t};\n\t\t\tserver.registerTool(\n\t\t\t\tconfig.id,\n\t\t\t\tconfigWithGraph as typeof toolConfig,\n\t\t\t\ttoolHandler,\n\t\t\t);\n\t\t},\n\t\tgraph: input.graph,\n\t\tflowGraph,\n\t};\n}\n","// Waniwani SDK - Errors\n\nexport class WaniWaniError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic status: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"WaniWaniError\";\n\t}\n}\n","import { WaniWaniError } from \"../error.js\";\nimport type { InternalConfig } from \"../types.js\";\nimport type {\n\tDocumentExtractInput,\n\tDocumentExtractResult,\n\tDocumentExtractStoredInput,\n\tDocumentSchema,\n\tDocumentsClient,\n} from \"./types.js\";\n\nconst SDK_NAME = \"@waniwani/sdk\";\nconst EXTRACT_PATH = \"/api/mcp/modules/documents/extract\";\n\n/** Method syntax makes the parameter bivariant, so a structural schema is accepted without an assertion. */\ninterface ZodModule {\n\ttoJSONSchema(schema: DocumentSchema<unknown>): Record<string, unknown>;\n}\n\ninterface ExtractWireResponse {\n\tfields: unknown;\n\tpageCount: number;\n\tpageConfidence: number | null;\n\tdocumentId: string;\n}\n\n/**\n * The platform puts the machine-readable code on `message` and the human reason\n * on `detail`, so a refusal reads as \"UNSUPPORTED_DOCUMENT_TYPE: photo.heic is\n * not one of ...\". The raw body is the fallback.\n */\nfunction refusalMessage(body: string, status: number): string {\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(body);\n\t\tif (parsed !== null && typeof parsed === \"object\") {\n\t\t\tconst code =\n\t\t\t\t\"message\" in parsed && typeof parsed.message === \"string\"\n\t\t\t\t\t? parsed.message\n\t\t\t\t\t: \"\";\n\t\t\tconst detail =\n\t\t\t\t\"detail\" in parsed && typeof parsed.detail === \"string\"\n\t\t\t\t\t? parsed.detail\n\t\t\t\t\t: \"\";\n\t\t\tif (code && detail) {\n\t\t\t\treturn `${code}: ${detail}`;\n\t\t\t}\n\t\t\tif (code) {\n\t\t\t\treturn code;\n\t\t\t}\n\t\t\tif (detail) {\n\t\t\t\treturn detail;\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// A non-JSON body is surfaced as-is below.\n\t}\n\treturn body || `Documents API error: HTTP ${status}`;\n}\n\nfunction isStoredInput<T>(\n\tinput: DocumentExtractInput<T>,\n): input is DocumentExtractStoredInput<T> {\n\treturn \"documentId\" in input && typeof input.documentId === \"string\";\n}\n\n/** The platform's body is a union, so each branch's fields must travel alone. */\nfunction sourceFields<T>(\n\tinput: DocumentExtractInput<T>,\n): Record<string, string> {\n\tif (isStoredInput(input)) {\n\t\treturn { documentId: input.documentId };\n\t}\n\treturn { url: input.url, filename: input.filename };\n}\n\nexport function createDocumentsClient(\n\tconfig: Pick<InternalConfig, \"apiUrl\" | \"apiKey\">,\n): DocumentsClient {\n\tconst { apiUrl, apiKey } = config;\n\n\treturn {\n\t\tasync extract<T>(\n\t\t\tinput: DocumentExtractInput<T>,\n\t\t): Promise<DocumentExtractResult<T>> {\n\t\t\tif (!apiKey) {\n\t\t\t\tthrow new Error(\"WANIWANI_API_KEY is not set\");\n\t\t\t}\n\n\t\t\t// zod is an optional peer dependency and this module is reachable from the\n\t\t\t// package root, so a top-level import would break every consumer that only\n\t\t\t// uses tracking.\n\t\t\tconst { toJSONSchema }: ZodModule = await import(\"zod\");\n\n\t\t\tconst response = await fetch(\n\t\t\t\t`${apiUrl.replace(/\\/$/, \"\")}${EXTRACT_PATH}`,\n\t\t\t\t{\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t\"X-WaniWani-SDK\": SDK_NAME,\n\t\t\t\t\t},\n\t\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\t\t...sourceFields(input),\n\t\t\t\t\t\tschema: toJSONSchema(input.schema),\n\t\t\t\t\t\tpages: input.pages,\n\t\t\t\t\t\tsessionId: input.sessionId,\n\t\t\t\t\t\tcorrelationId: input.correlationId,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tif (!response.ok) {\n\t\t\t\tconst text = await response.text().catch(() => \"\");\n\t\t\t\tthrow new WaniWaniError(\n\t\t\t\t\trefusalMessage(text, response.status),\n\t\t\t\t\tresponse.status,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst json = (await response.json()) as { data: ExtractWireResponse };\n\t\t\tconst { fields, pageCount, pageConfidence, documentId } = json.data;\n\n\t\t\treturn {\n\t\t\t\tfields: input.schema.parse(fields),\n\t\t\t\tpageCount,\n\t\t\t\tpageConfidence: pageConfidence ?? null,\n\t\t\t\tdocumentId,\n\t\t\t};\n\t\t},\n\t};\n}\n","import type { RevenueTrackingApi, TrackInput } from \"./@types.js\";\n\n/** Enqueue one event and resolve to its id. */\ntype Emit = (event: TrackInput) => Promise<{ eventId: string }>;\n\n/**\n * Build the flat `track.*` revenue helpers over an `emit` function. Shared\n * by the top-level client and the request-scoped client (which injects meta\n * into `emit`) so both expose the same revenue API (WAN-386).\n *\n * Each helper maps its flat input to a typed first-class revenue event; the\n * remaining tracking context (sessionId / externalUserId / meta) is forwarded\n * as-is. Identity (one of those, or meta-derived) is required by the ingest\n * API — see the warning emitted from the client's `emit`.\n */\nexport function createRevenueApi(emit: Emit): RevenueTrackingApi {\n\treturn {\n\t\tpriceShown: ({ amount, currency, itemId, label, ...context }) =>\n\t\t\temit({\n\t\t\t\tevent: \"price_shown\",\n\t\t\t\tproperties: { amount, currency, itemId, label },\n\t\t\t\t...context,\n\t\t\t}),\n\t\tpricesCompared: ({ options, ...context }) =>\n\t\t\temit({ event: \"prices_compared\", properties: { options }, ...context }),\n\t\toptionSelected: ({ id, amount, currency, ...context }) =>\n\t\t\temit({\n\t\t\t\tevent: \"option_selected\",\n\t\t\t\tproperties: { id, amount, currency },\n\t\t\t\t...context,\n\t\t\t}),\n\t\tleadQualified: (input) => {\n\t\t\tconst { externalId, email, name, ...context } = input ?? {};\n\t\t\treturn emit({\n\t\t\t\tevent: \"lead_qualified\",\n\t\t\t\tproperties: { externalId, email, name },\n\t\t\t\t...context,\n\t\t\t});\n\t\t},\n\t\tconverted: ({ amount, currency, occurredAt, ...context }) =>\n\t\t\temit({\n\t\t\t\tevent: \"converted\",\n\t\t\t\tproperties: { amount, currency, occurredAt },\n\t\t\t\t...context,\n\t\t\t}),\n\t};\n}\n","// Waniwani SDK - Core Types\n\nimport type { DocumentsClient } from \"./documents/types.js\";\nimport type { KbClient } from \"./kb/types.js\";\nimport type { TrackingClient, TrackingConfig } from \"./tracking/@types.js\";\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nexport interface WaniWaniConfig {\n\t/**\n\t * Your MCP environment API key\n\t *\n\t * Defaults to process.env.WANIWANI_API_KEY if not provided\n\t *\n\t * To create one, visit:\n\t * https://app.waniwani.com/mcp/environments\n\t */\n\tapiKey?: string;\n\t/**\n\t * The base URL of the Waniwani API\n\t *\n\t * Defaults to process.env.WANIWANI_API_URL if not provided, then to\n\t * https://app.waniwani.ai. Set WANIWANI_API_URL (e.g. to\n\t * https://eu.app.waniwani.ai) to keep .env as the single source of truth\n\t * and avoid pointing at the wrong region.\n\t */\n\tapiUrl?: string;\n\t/**\n\t * Tracking transport behavior.\n\t */\n\ttracking?: TrackingConfig;\n}\n\n// ============================================================================\n// Client (composed from modules)\n// ============================================================================\n\n/**\n * Waniwani SDK Client\n *\n * Extends with each module:\n * - TrackingClient: track(), flush(), shutdown()\n *\n * Pass this client to framework adapters:\n * - `toNextJsHandler(wani, { ... })` for Next.js route handlers\n */\nexport interface WaniWaniClient extends TrackingClient {\n\t/** @internal Resolved config — used by framework adapters */\n\treadonly _config: InternalConfig;\n\t/** Knowledge base client for ingestion, search, and source listing */\n\treadonly kb: KbClient;\n\t/** Documents client for reading a file into typed fields */\n\treadonly documents: DocumentsClient;\n}\n\n// ============================================================================\n// Internal\n// ============================================================================\n\nexport const DEFAULT_API_URL = \"https://app.waniwani.ai\";\n\nexport interface InternalConfig {\n\tapiUrl: string;\n\tapiKey: string | undefined;\n\ttracking: Required<TrackingConfig>;\n}\n","import type { AttachedDocument } from \"./types.js\";\n\nconst DOCUMENT_ID_KEYS = [\"documentId\", \"document_id\"] as const;\nconst FILENAME_KEYS = [\"filename\", \"file_name\", \"fileName\", \"name\"] as const;\nconst MEDIA_TYPE_KEYS = [\n\t\"mediaType\",\n\t\"media_type\",\n\t\"mimeType\",\n\t\"mime_type\",\n\t\"contentType\",\n\t\"content_type\",\n] as const;\n\nconst UNNAMED_FILENAME = \"document\";\nconst UNKNOWN_MEDIA_TYPE = \"application/octet-stream\";\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction pickFirst(\n\tsource: Record<string, unknown>,\n\tkeys: readonly string[],\n): string | undefined {\n\tfor (const key of keys) {\n\t\tconst value = source[key];\n\t\tif (typeof value === \"string\" && value.length > 0) {\n\t\t\treturn value;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/** Only the id is load-bearing: it is what `extract()` needs, so an entry missing the descriptive two is still usable. */\nfunction toAttachedDocument(entry: unknown): AttachedDocument | undefined {\n\tif (!isRecord(entry)) {\n\t\treturn undefined;\n\t}\n\n\tconst documentId = pickFirst(entry, DOCUMENT_ID_KEYS);\n\tif (!documentId) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\tdocumentId,\n\t\tfilename: pickFirst(entry, FILENAME_KEYS) ?? UNNAMED_FILENAME,\n\t\tmediaType: pickFirst(entry, MEDIA_TYPE_KEYS) ?? UNKNOWN_MEDIA_TYPE,\n\t};\n}\n\nfunction collect(candidate: unknown): AttachedDocument[] {\n\tconst documents: AttachedDocument[] = [];\n\tconst seen = new Set<string>();\n\n\tfor (const entry of Array.isArray(candidate) ? candidate : [candidate]) {\n\t\tconst document = toAttachedDocument(entry);\n\t\tif (document && !seen.has(document.documentId)) {\n\t\t\tseen.add(document.documentId);\n\t\t\tdocuments.push(document);\n\t\t}\n\t}\n\n\treturn documents;\n}\n\nfunction fromLatestUserTurn(messages: unknown): AttachedDocument[] {\n\tif (!Array.isArray(messages)) {\n\t\treturn [];\n\t}\n\n\tconst latest = [...messages]\n\t\t.reverse()\n\t\t.find((message) => isRecord(message) && message.role === \"user\");\n\tif (!isRecord(latest) || !Array.isArray(latest.parts)) {\n\t\treturn [];\n\t}\n\n\treturn collect(\n\t\tlatest.parts.flatMap((part: unknown) =>\n\t\t\tisRecord(part) && isRecord(part.data) ? [part, part.data] : [part],\n\t\t),\n\t);\n}\n\n/**\n * The documents a visitor attached on the latest user turn, for a chat route you\n * run yourself. Pass the parsed request body: the widget sends them in a\n * top-level `documents` field, outside `messages`, so the bytes stay out of the\n * transcript that is reposted on every later turn.\n *\n * ```ts\n * const body = await request.json();\n * for (const { documentId } of readAttachedDocuments(body)) {\n * const { fields } = await wani.documents.extract({ documentId, schema });\n * }\n * ```\n *\n * Also accepts the `documents` array alone, or a `messages` array whose latest\n * user message carries the ids in its parts. Unparseable input yields an empty\n * array; repeated ids collapse to one.\n */\nexport function readAttachedDocuments(input: unknown): AttachedDocument[] {\n\tconst body = isRecord(input) ? input : undefined;\n\n\tconst declared = collect(body ? body.documents : input);\n\tif (declared.length > 0) {\n\t\treturn declared;\n\t}\n\n\treturn fromLatestUserTurn(body ? body.messages : input);\n}\n","/**\n * `_meta` keys shared between the MCP server surface and the browser chat\n * bundle. Kept in `shared/` so both import the same string literal instead of\n * duplicating it, without the chat bundle pulling in server code.\n */\n\n/**\n * Suggested answers for the flow's current step. Every flow tool result\n * carries this key — a non-empty array only when the step has exactly one\n * open question (the only case where a single pill is an unambiguous\n * answer), an empty array otherwise. The chat widget renders a non-empty\n * array as pills and treats the key's presence, empty or not, as\n * authoritative for the turn; other MCP hosts ignore the key.\n */\nexport const SUGGESTIONS_META_KEY = \"waniwani/suggestions\" as const;\n\n/** Shape stored under {@link SUGGESTIONS_META_KEY}. */\nexport type SuggestionsMeta = {\n\tsuggestions: string[];\n};\n","import { readAttachedDocuments } from \"../../documents/messages.js\";\nimport type { AttachedDocument } from \"../../documents/types.js\";\n\n// ============================================================================\n// Meta key extraction helpers\n// ============================================================================\n\n/** Pick the first non-empty string value from `meta` matching the given keys. */\nfunction pickFirst(\n\tmeta: Record<string, unknown>,\n\tkeys: readonly string[],\n): string | undefined {\n\tfor (const key of keys) {\n\t\tconst value = meta[key];\n\t\tif (typeof value === \"string\" && value.length > 0) {\n\t\t\treturn value;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n// --- Key lists (ordered by priority) ---\n\nconst SESSION_ID_KEYS = [\n\t\"waniwani/sessionId\",\n\t\"openai/sessionId\",\n\t\"openai/session\",\n\t\"sessionId\",\n\t\"conversationId\",\n\t\"mcp-session-id\",\n] as const;\n\nconst REQUEST_ID_KEYS = [\n\t\"waniwani/requestId\",\n\t\"openai/requestId\",\n\t\"requestId\",\n\t\"mcp/requestId\",\n] as const;\n\nconst TRACE_ID_KEYS = [\n\t\"waniwani/traceId\",\n\t\"openai/traceId\",\n\t\"traceId\",\n\t\"mcp/traceId\",\n\t\"openai/requestId\",\n\t\"requestId\",\n] as const;\n\nconst EXTERNAL_USER_ID_KEYS = [\n\t\"waniwani/userId\",\n\t\"openai/userId\",\n\t\"externalUserId\",\n\t\"userId\",\n\t\"actorId\",\n] as const;\n\nconst VISITOR_ID_KEYS = [\"waniwani/visitorId\", \"visitorId\"] as const;\n\nconst CORRELATION_ID_KEYS = [\"correlationId\", \"openai/requestId\"] as const;\n\nconst TURN_COUNT_KEYS = [\"waniwani/turnCount\"] as const;\n\nconst FILE_PARAMS_KEYS = [\"waniwani/fileParams\", \"openai/fileParams\"] as const;\n\nconst FILE_URL_KEYS = [\n\t\"url\",\n\t\"uri\",\n\t\"download_url\",\n\t\"downloadUrl\",\n\t\"file_url\",\n\t\"fileUrl\",\n\t\"signed_url\",\n\t\"signedUrl\",\n] as const;\n\nconst FILE_NAME_KEYS = [\"filename\", \"file_name\", \"fileName\", \"name\"] as const;\n\n/** Meta key for flow execution path (nodesVisited, flowId). */\nexport const FLOW_META_KEY = \"waniwani/flow\" as const;\n\nexport type { SuggestionsMeta } from \"../../shared/meta-keys\";\nexport { SUGGESTIONS_META_KEY } from \"../../shared/meta-keys\";\n\n/**\n * Meta key under which `withWaniwani` injects the widget tracking config\n * (`{ endpoint, token, sessionId, source, geoLocation }`) into tool response\n * `_meta`. The frontend client reads it to track without any manual wiring.\n * Older SDKs wrote the same object under the bare `waniwani` key.\n */\nexport const WIDGET_CONFIG_META_KEY = \"waniwani/widget\" as const;\n\n// --- Extractors ---\n\nexport function extractSessionId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, SESSION_ID_KEYS) : undefined;\n}\n\nexport function extractRequestId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, REQUEST_ID_KEYS) : undefined;\n}\n\nexport function extractTraceId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, TRACE_ID_KEYS) : undefined;\n}\n\nexport function extractExternalUserId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, EXTERNAL_USER_ID_KEYS) : undefined;\n}\n\nexport function extractVisitorId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, VISITOR_ID_KEYS) : undefined;\n}\n\nexport function extractCorrelationId(\n\tmeta: Record<string, unknown> | undefined,\n): string | undefined {\n\treturn meta ? pickFirst(meta, CORRELATION_ID_KEYS) : undefined;\n}\n\n/**\n * Number of user messages in the current chat session, as counted by the\n * Waniwani app before dispatching the MCP request. Useful for MCPs that\n * gate behavior on conversation length (e.g. compulsory email verification\n * after N turns) without each MCP having to track turn state itself.\n *\n * Forwarded as `waniwani/turnCount` (non-negative integer).\n */\nexport function extractTurnCount(\n\tmeta: Record<string, unknown> | undefined,\n): number | undefined {\n\tif (!meta) {\n\t\treturn undefined;\n\t}\n\tfor (const key of TURN_COUNT_KEYS) {\n\t\tconst value = meta[key];\n\t\tif (typeof value === \"number\" && Number.isFinite(value) && value >= 0) {\n\t\t\treturn value;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/** A file the host bound to this tool call, resolved to something fetchable. */\nexport interface AttachedFile {\n\turl: string;\n\tfilename: string;\n}\n\nexport type { AttachedDocument };\n\nfunction isRecordValue(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction fetchableUrl(value: unknown): string | undefined {\n\tif (typeof value !== \"string\" || value.length === 0) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\tconst url = new URL(value);\n\t\treturn url.protocol === \"https:\" || url.protocol === \"http:\"\n\t\t\t? value\n\t\t\t: undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction filenameFromUrl(url: string): string {\n\ttry {\n\t\tconst segments = new URL(url).pathname.split(\"/\").filter(Boolean);\n\t\tconst last = segments[segments.length - 1];\n\t\treturn last ? decodeURIComponent(last) : \"document\";\n\t} catch {\n\t\treturn \"document\";\n\t}\n}\n\nfunction toAttachedFile(entry: unknown): AttachedFile | undefined {\n\tif (!isRecordValue(entry)) {\n\t\treturn undefined;\n\t}\n\n\tconst url = fetchableUrl(pickFirst(entry, FILE_URL_KEYS));\n\tif (!url) {\n\t\treturn undefined;\n\t}\n\n\tconst filename = pickFirst(entry, FILE_NAME_KEYS);\n\treturn { url, filename: filename ?? filenameFromUrl(url) };\n}\n\n/**\n * Files the host attached to this tool call, from the first of\n * `waniwani/fileParams` or `openai/fileParams` (ChatGPT) that carries any. The\n * container may be a list or an object keyed by parameter name, and entries name\n * their URL differently per host, so every plausible spelling is read and\n * anything without a fetchable URL is skipped.\n */\nexport function extractAttachedFiles(\n\tmeta: Record<string, unknown> | undefined,\n): AttachedFile[] {\n\tif (!meta) {\n\t\treturn [];\n\t}\n\n\tfor (const key of FILE_PARAMS_KEYS) {\n\t\tconst files = filesUnder(meta[key]);\n\t\tif (files.length > 0) {\n\t\t\treturn files;\n\t\t}\n\t}\n\n\treturn [];\n}\n\nfunction filesUnder(candidate: unknown): AttachedFile[] {\n\tconst files: AttachedFile[] = [];\n\tconst seen = new Set<string>();\n\n\tconst collect = (value: unknown): void => {\n\t\tif (Array.isArray(value)) {\n\t\t\tfor (const entry of value) {\n\t\t\t\tcollect(entry);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tconst file = toAttachedFile(value);\n\t\tif (file) {\n\t\t\tif (!seen.has(file.url)) {\n\t\t\t\tseen.add(file.url);\n\t\t\t\tfiles.push(file);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (isRecordValue(value)) {\n\t\t\tfor (const entry of Object.values(value)) {\n\t\t\t\tcollect(entry);\n\t\t\t}\n\t\t}\n\t};\n\n\tcollect(candidate);\n\treturn files;\n}\n\nconst DOCUMENTS_META_KEY = \"waniwani/documents\";\n\n/**\n * Documents the visitor attached to this chat turn, stamped by the Waniwani chat\n * route under `waniwani/documents`. The bytes stay on the platform, so each entry\n * is a handle: spread one straight into `documents.extract({ documentId, schema })`.\n * Empty on hosts that upload nothing, which is every host but the web widget.\n */\nexport function extractAttachedDocuments(\n\tmeta: Record<string, unknown> | undefined,\n): AttachedDocument[] {\n\tif (!meta) {\n\t\treturn [];\n\t}\n\tconst raw = meta[DOCUMENTS_META_KEY];\n\treturn readAttachedDocuments(Array.isArray(raw) ? raw : [raw]);\n}\n\nconst SOURCE_SESSION_KEYS = [\n\t{ key: \"openai/sessionId\", source: \"chatgpt\" },\n\t{ key: \"openai/session\", source: \"chatgpt\" },\n] as const;\n\n/**\n * Client identifiers advertised via MCP `initialize` → `clientInfo.name` that\n * the SDK maps to a known source. Match is case-insensitive substring on the\n * advertised name, so this catches \"Claude\", \"Claude Code\", \"claude-ai\", etc.\n * without having to enumerate every surface.\n *\n * ChatGPT is primarily identified by its `openai/*` session key (see\n * SOURCE_SESSION_KEYS), but a session without that key still resolves here from\n * the advertised client name so assistant traffic is attributed consistently.\n * First match wins, so order the needles most-specific first.\n */\nconst CLIENT_INFO_NAME_SOURCES: ReadonlyArray<{\n\tneedle: string;\n\tsource: string;\n}> = [\n\t{ needle: \"claude\", source: \"claude\" },\n\t{ needle: \"chatgpt\", source: \"chatgpt\" },\n\t{ needle: \"openai\", source: \"chatgpt\" },\n\t{ needle: \"gemini\", source: \"gemini\" },\n];\n\nexport type ExtractSourceClientInfo = {\n\tname?: string;\n\tversion?: string;\n};\n\nexport function extractSource(\n\tmeta: Record<string, unknown> | undefined,\n\tclientInfo?: ExtractSourceClientInfo,\n): string | undefined {\n\tif (meta) {\n\t\tconst explicit = meta[\"waniwani/source\"];\n\t\tif (typeof explicit === \"string\" && explicit.length > 0) {\n\t\t\treturn explicit;\n\t\t}\n\t\tfor (const { key, source } of SOURCE_SESSION_KEYS) {\n\t\t\tconst value = meta[key];\n\t\t\tif (typeof value === \"string\" && value.length > 0) {\n\t\t\t\treturn source;\n\t\t\t}\n\t\t}\n\t}\n\t// Fall back to MCP `initialize` clientInfo.name when no _meta key matches.\n\t// Claude surfaces (Code, Desktop, claude.ai connector, Anthropic API MCP\n\t// connector) don't expose a namespaced session id in _meta but do advertise\n\t// themselves in the handshake. Per-MCP-session clientInfo is captured by the\n\t// MCP transport and exposed via server.getClientVersion().\n\tconst name = clientInfo?.name;\n\tif (typeof name === \"string\" && name.length > 0) {\n\t\tconst lower = name.toLowerCase();\n\t\tfor (const { needle, source } of CLIENT_INFO_NAME_SOURCES) {\n\t\t\tif (lower.includes(needle)) {\n\t\t\t\treturn source;\n\t\t\t}\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * HTTP request headers that identify a known caller when neither `_meta` nor\n * `clientInfo` carries a source. This is the most robust Claude signal: every\n * Claude surface sends `User-Agent: Claude-User` and `x-anthropic-client` on\n * MCP HTTP requests, whereas the MCP transport carries no session id and the\n * `initialize` `clientInfo` may not be surfaced on stateless deployments.\n */\nfunction headerValue(\n\theaders: Record<string, unknown>,\n\tkey: string,\n): string | undefined {\n\t// HTTP header names are case-insensitive; the transport may preserve the\n\t// original casing, so match the key case-insensitively rather than assuming\n\t// it was lowercased.\n\tconst lowerKey = key.toLowerCase();\n\tlet raw = headers[key] ?? headers[lowerKey];\n\tif (raw === undefined) {\n\t\tfor (const headerKey of Object.keys(headers)) {\n\t\t\tif (headerKey.toLowerCase() === lowerKey) {\n\t\t\t\traw = headers[headerKey];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\tconst value = Array.isArray(raw) ? raw[0] : raw;\n\treturn typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nexport function extractSourceFromHeaders(\n\theaders: Record<string, unknown> | undefined,\n): string | undefined {\n\tif (!headers) {\n\t\treturn undefined;\n\t}\n\tconst userAgent = headerValue(headers, \"user-agent\");\n\tconst anthropicClient = headerValue(headers, \"x-anthropic-client\");\n\tif (\n\t\t(userAgent && /claude/i.test(userAgent)) ||\n\t\t(anthropicClient && /claude|anthropic/i.test(anthropicClient))\n\t) {\n\t\treturn \"claude\";\n\t}\n\t// ChatGPT / OpenAI surfaces without an `openai/*` session key still send an\n\t// OpenAI user-agent (e.g. `ChatGPT-User`), so attribute them here too.\n\tif (userAgent && /chatgpt|openai/i.test(userAgent)) {\n\t\treturn \"chatgpt\";\n\t}\n\treturn undefined;\n}\n","import { createDocumentsClient } from \"../../documents/client.js\";\nimport type {\n\tDocumentExtractInput,\n\tDocumentExtractResult,\n\tDocumentsClient,\n} from \"../../documents/types.js\";\nimport type { KbClient } from \"../../kb/types.js\";\nimport type {\n\tCallableTrack,\n\tTrackFn,\n\tTrackInput,\n\tTrackingClient,\n} from \"../../tracking/@types.js\";\nimport { createRevenueApi } from \"../../tracking/revenue.js\";\nimport { DEFAULT_API_URL } from \"../../types.js\";\nimport {\n\ttype AttachedDocument,\n\ttype AttachedFile,\n\textractAttachedDocuments,\n\textractAttachedFiles,\n\textractCorrelationId,\n\textractExternalUserId,\n\textractSessionId,\n\textractVisitorId,\n} from \"./utils.js\";\n\n/**\n * Well-known key used to attach the scoped client to the MCP `extra` object.\n * Read by `createTool` and flow compilation to surface it in handler contexts.\n */\nexport const SCOPED_CLIENT_KEY = \"waniwani/client\";\n\n/**\n * A request-scoped Waniwani client with meta pre-attached.\n *\n * Available as `context.waniwani` inside `createTool` handlers and flow nodes\n * when the server is wrapped with `withWaniwani()`.\n */\nexport interface ScopedWaniWaniClient {\n\t/**\n\t * The session id this request's events correlate to, resolved from the\n\t * request meta. Read it to link the session to your own records (store it\n\t * on a lead, then send an off-platform `converted` with the same\n\t * `sessionId` later). `undefined` when the host provided no session id.\n\t */\n\treadonly sessionId?: string;\n\t/**\n\t * The anonymous visitor id, resolved from the request meta. Present on the\n\t * web chat surface (a persisted per-device id threaded through as\n\t * `waniwani/visitorId`); `undefined` on hosts that do not supply one, such\n\t * as Claude. Ingest accepts it as an identity in its own right, so use it\n\t * to correlate events before a session or user id exists.\n\t */\n\treadonly visitorId?: string;\n\t/**\n\t * The external user id, resolved from the request meta (e.g. `openai/userId`\n\t * for ChatGPT's anonymous users, or a host-provided `externalUserId`). Read\n\t * it alongside `sessionId`/`visitorId` — whichever the current host provides.\n\t * `undefined` when the host supplied no user id.\n\t */\n\treadonly externalUserId?: string;\n\t/**\n\t * Track an event — request meta is automatically merged. Also exposes the\n\t * revenue helpers flat (`track.priceShown()`, `track.converted()`, …), which\n\t * inherit the same scoped meta (so identity is carried from the request).\n\t */\n\ttrack: TrackFn;\n\t/** Identify a user — request meta is automatically merged. */\n\tidentify(\n\t\tuserId: string,\n\t\tproperties?: Record<string, unknown>,\n\t): Promise<{ eventId: string }>;\n\t/** Knowledge base client (no meta needed). */\n\treadonly kb: KbClient;\n\t/**\n\t * Files the host attached to this tool call, each with a fetchable `url` and\n\t * a `filename` — spread one straight into `documents.extract()`. Empty on\n\t * hosts that attach nothing.\n\t */\n\treadonly attachedFiles: AttachedFile[];\n\t/**\n\t * Documents the visitor uploaded through the chat widget on this turn. The\n\t * platform holds the bytes, so each entry is a handle: spread one straight\n\t * into `documents.extract({ documentId, schema })`. Empty on hosts that\n\t * upload nothing.\n\t */\n\treadonly attachedDocuments: AttachedDocument[];\n\t/** Documents client; `sessionId` and `correlationId` are carried from the request. */\n\treadonly documents: DocumentsClient;\n\t/** @internal Resolved API config from withWaniwani(). */\n\treadonly _config?: { apiUrl?: string; apiKey?: string };\n}\n\n/**\n * Extract the scoped client from the MCP `extra` object.\n * Returns undefined if `withWaniwani()` is not wrapping the server.\n */\nexport function extractScopedClient(\n\textra: unknown,\n): ScopedWaniWaniClient | undefined {\n\tif (typeof extra === \"object\" && extra !== null) {\n\t\treturn (extra as Record<string, unknown>)[SCOPED_CLIENT_KEY] as\n\t\t\t| ScopedWaniWaniClient\n\t\t\t| undefined;\n\t}\n\treturn undefined;\n}\n\nexport function createScopedClient(\n\tbase: { track: CallableTrack; identify: TrackingClient[\"identify\"] } & {\n\t\treadonly kb: KbClient;\n\t},\n\tmeta: Record<string, unknown>,\n\tconfig?: { apiUrl?: string; apiKey?: string },\n): ScopedWaniWaniClient {\n\tconst trackOnce = (event: TrackInput): Promise<{ eventId: string }> =>\n\t\tbase.track({ ...event, meta: { ...meta, ...event.meta } });\n\n\tconst sessionId = extractSessionId(meta);\n\tconst correlationId = extractCorrelationId(meta);\n\tconst documents = createDocumentsClient({\n\t\tapiUrl: config?.apiUrl ?? process.env.WANIWANI_API_URL ?? DEFAULT_API_URL,\n\t\tapiKey: config?.apiKey,\n\t});\n\n\treturn {\n\t\tsessionId,\n\t\tvisitorId: extractVisitorId(meta),\n\t\texternalUserId: extractExternalUserId(meta),\n\t\ttrack: Object.assign(trackOnce, createRevenueApi(trackOnce)),\n\t\tidentify(userId, properties) {\n\t\t\treturn base.identify(userId, properties, meta);\n\t\t},\n\t\tkb: base.kb,\n\t\tattachedFiles: extractAttachedFiles(meta),\n\t\tattachedDocuments: extractAttachedDocuments(meta),\n\t\tdocuments: {\n\t\t\textract<T>(\n\t\t\t\tinput: DocumentExtractInput<T>,\n\t\t\t): Promise<DocumentExtractResult<T>> {\n\t\t\t\treturn documents.extract({\n\t\t\t\t\t...input,\n\t\t\t\t\tsessionId: input.sessionId ?? sessionId,\n\t\t\t\t\tcorrelationId: input.correlationId ?? correlationId,\n\t\t\t\t});\n\t\t\t},\n\t\t},\n\t\t_config: config,\n\t};\n}\n","import { z } from \"zod\";\nimport type { ErrorCauseType } from \"../../../tracking/@types.js\";\n\nconst statusCarrierSchema = z.object({\n\tstatus: z.number().optional(),\n\tstatusCode: z.number().optional(),\n});\n\nfunction readStatus(error: unknown): number | undefined {\n\tconst parsed = statusCarrierSchema.safeParse(error);\n\tif (!parsed.success) {\n\t\treturn undefined;\n\t}\n\treturn parsed.data.status ?? parsed.data.statusCode;\n}\n\nconst NETWORK_ERROR_MESSAGES = [\n\t\"fetch failed\",\n\t\"failed to fetch\",\n\t\"networkerror when attempting to fetch\",\n];\n\nexport function classifyCause({ error }: { error: unknown }): ErrorCauseType {\n\t// zod is an optional peer dep; a nested copy fails instanceof across realms.\n\tif (error instanceof Error && error.name === \"ZodError\") {\n\t\treturn \"invalid_output\";\n\t}\n\n\tif (\n\t\terror instanceof Error &&\n\t\t(error.name === \"AbortError\" || error.name === \"TimeoutError\")\n\t) {\n\t\treturn \"timeout\";\n\t}\n\n\tconst status = readStatus(error);\n\tif (status === 429) {\n\t\treturn \"rate_limited\";\n\t}\n\tif (status !== undefined && status >= 500) {\n\t\treturn \"upstream_5xx\";\n\t}\n\tif (status !== undefined && status >= 400) {\n\t\treturn \"upstream_4xx\";\n\t}\n\n\tif (\n\t\terror instanceof TypeError &&\n\t\tNETWORK_ERROR_MESSAGES.some((message) =>\n\t\t\terror.message.toLowerCase().includes(message),\n\t\t)\n\t) {\n\t\treturn \"network\";\n\t}\n\n\treturn \"unknown\";\n}\n","import type {\n\tErrorCauseType,\n\tSessionErrorCodeType,\n\tSessionErrorProperties,\n} from \"../../../tracking/@types.js\";\nimport type { ScopedWaniWaniClient } from \"../scoped-client.js\";\n\n/** Fire-and-forget: never throws, so a failing caller keeps its own error. */\nexport function reportSessionError({\n\twaniwani,\n\tcode,\n\tcause,\n\tproperties,\n}: {\n\twaniwani?: ScopedWaniWaniClient;\n\tcode: SessionErrorCodeType;\n\tcause: ErrorCauseType;\n\tproperties?: Omit<SessionErrorProperties, \"code\" | \"cause\">;\n}): void {\n\t// Hosts patch console; a logger that throws must not cost us the event either.\n\ttry {\n\t\tconsole.error(\n\t\t\t`[waniwani][session-error] ${code}:${properties?.tool ?? \"-\"}:${cause}`,\n\t\t\t{ node: properties?.node },\n\t\t);\n\t} catch {}\n\n\tif (!waniwani) {\n\t\treturn;\n\t}\n\n\t// track() is a public interface: it can throw synchronously or return a non-promise.\n\ttry {\n\t\tvoid Promise.resolve(\n\t\t\twaniwani.track({\n\t\t\t\tevent: \"session.error\",\n\t\t\t\tproperties: { code, cause, ...properties },\n\t\t\t}),\n\t\t).catch(() => {});\n\t} catch {}\n}\n","import type { z } from \"zod\";\n\n// ============================================================================\n// Zod object schema detection\n// ============================================================================\n\n/** Check whether a Zod schema is a `z.object(...)`. */\nexport function isObjectSchema(schema: z.ZodType): boolean {\n\tconst def = (schema as unknown as { _zod?: { def?: { type?: string } } })._zod\n\t\t?.def;\n\treturn def?.type === \"object\";\n}\n\n/** Extract the shape of a `z.object(...)` schema, or null if not an object. */\nexport function getObjectShape(\n\tschema: z.ZodType,\n): Record<string, z.ZodType> | null {\n\tconst def = (\n\t\tschema as unknown as {\n\t\t\t_zod?: { def?: { type?: string; shape?: Record<string, z.ZodType> } };\n\t\t}\n\t)._zod?.def;\n\tif (def?.type === \"object\" && def.shape) {\n\t\treturn def.shape;\n\t}\n\treturn null;\n}\n\n// ============================================================================\n// Dot-path value access\n// ============================================================================\n\n/** Resolve a dot-path like `\"driver.name\"` to its value in a nested object. */\nexport function getNestedValue(\n\tobj: Record<string, unknown>,\n\tpath: string,\n): unknown {\n\tconst parts = path.split(\".\");\n\tlet current: unknown = obj;\n\tfor (const part of parts) {\n\t\tif (current == null || typeof current !== \"object\") {\n\t\t\treturn undefined;\n\t\t}\n\t\tcurrent = (current as Record<string, unknown>)[part];\n\t}\n\treturn current;\n}\n\n/** Set a value at a dot-path, creating intermediate objects as needed. */\nexport function setNestedValue(\n\tobj: Record<string, unknown>,\n\tpath: string,\n\tvalue: unknown,\n): void {\n\tconst parts = path.split(\".\");\n\tconst lastKey = parts.pop();\n\tif (!lastKey) {\n\t\treturn;\n\t}\n\tlet current = obj;\n\tfor (const part of parts) {\n\t\tif (\n\t\t\tcurrent[part] == null ||\n\t\t\ttypeof current[part] !== \"object\" ||\n\t\t\tArray.isArray(current[part])\n\t\t) {\n\t\t\tcurrent[part] = {};\n\t\t}\n\t\tcurrent = current[part] as Record<string, unknown>;\n\t}\n\tcurrent[lastKey] = value;\n}\n\n/** Delete a value at a dot-path. Only removes the leaf key. */\nexport function deleteNestedValue(\n\tobj: Record<string, unknown>,\n\tpath: string,\n): void {\n\tconst parts = path.split(\".\");\n\tconst lastKey = parts.pop();\n\tif (!lastKey) {\n\t\treturn;\n\t}\n\tlet current: unknown = obj;\n\tfor (const part of parts) {\n\t\tif (current == null || typeof current !== \"object\") {\n\t\t\treturn;\n\t\t}\n\t\tcurrent = (current as Record<string, unknown>)[part];\n\t}\n\tif (current != null && typeof current === \"object\") {\n\t\tdelete (current as Record<string, unknown>)[lastKey];\n\t}\n}\n\n// ============================================================================\n// State merging\n// ============================================================================\n\n/**\n * Expand dot-path keys into nested objects.\n * `{ \"driver.name\": \"John\", \"email\": \"a@b.com\" }` → `{ driver: { name: \"John\" }, email: \"a@b.com\" }`\n *\n * Mixed input where a dot-key and its nested sibling share a parent\n * (e.g. `{ \"driver.name\": \"John\", driver: { license: \"ABC\" } }`) is deep-merged\n * regardless of key order, so neither side clobbers the other.\n */\nexport function expandDotPaths(\n\tflat: Record<string, unknown>,\n): Record<string, unknown> {\n\tlet result: Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(flat)) {\n\t\tif (key.includes(\".\")) {\n\t\t\tsetNestedValue(result, key, value);\n\t\t} else if (\n\t\t\tvalue !== null &&\n\t\t\ttypeof value === \"object\" &&\n\t\t\t!Array.isArray(value)\n\t\t) {\n\t\t\tresult = deepMerge(result, { [key]: value as Record<string, unknown> });\n\t\t} else {\n\t\t\tresult[key] = value;\n\t\t}\n\t}\n\treturn result;\n}\n\n/**\n * Deep-merge source into target. Preserves existing nested keys.\n * Only merges plain objects — arrays and primitives are overwritten.\n */\nexport function deepMerge(\n\ttarget: Record<string, unknown>,\n\tsource: Record<string, unknown>,\n): Record<string, unknown> {\n\tconst result = { ...target };\n\tfor (const [key, value] of Object.entries(source)) {\n\t\tif (\n\t\t\tvalue !== null &&\n\t\t\ttypeof value === \"object\" &&\n\t\t\t!Array.isArray(value) &&\n\t\t\tresult[key] !== null &&\n\t\t\ttypeof result[key] === \"object\" &&\n\t\t\t!Array.isArray(result[key])\n\t\t) {\n\t\t\tresult[key] = deepMerge(\n\t\t\t\tresult[key] as Record<string, unknown>,\n\t\t\t\tvalue as Record<string, unknown>,\n\t\t\t);\n\t\t} else {\n\t\t\tresult[key] = value;\n\t\t}\n\t}\n\treturn result;\n}\n","import type { z } from \"zod\";\nimport { getObjectShape } from \"./nested\";\n\n/**\n * Compact, JSON-serializable schema fragment for a single state field.\n * Emitted inside interrupt/widget responses so the LLM gets focused, just-in-time\n * guidance for the field it is being asked to write — without needing to consult\n * a global schema dump.\n */\nexport type FieldSchemaInfo = {\n\ttype:\n\t\t| \"enum\"\n\t\t| \"string\"\n\t\t| \"number\"\n\t\t| \"boolean\"\n\t\t| \"object\"\n\t\t| \"array\"\n\t\t| \"unknown\";\n\t/** Allowed values for enum fields. */\n\tvalues?: string[];\n\t/** Field description from `.describe()` (or the parent group description if applicable). */\n\tdescription?: string;\n\t/** True when the underlying schema is optional/nullable/has a default. */\n\toptional?: boolean;\n};\n\ntype ZodDef = {\n\ttype?: string;\n\tentries?: Record<string, string>;\n\tinnerType?: z.ZodType;\n};\n\ntype ZodInternals = {\n\t_zod?: {\n\t\tdef?: ZodDef;\n\t};\n};\n\nfunction readDef(schema: z.ZodType): ZodDef | undefined {\n\treturn (schema as unknown as ZodInternals)._zod?.def;\n}\n\n/**\n * Unwrap optional/nullable/default wrappers to reach the underlying schema.\n * Returns whether at least one wrapper marked the field as optional from the\n * caller's perspective (optional/nullable/default all mean the LLM may omit it).\n */\nfunction unwrapOptional(schema: z.ZodType): {\n\tinner: z.ZodType;\n\toptional: boolean;\n} {\n\tlet current = schema;\n\tlet optional = false;\n\tfor (let i = 0; i < 8; i++) {\n\t\tconst def = readDef(current);\n\t\tif (!def?.innerType) {\n\t\t\tbreak;\n\t\t}\n\t\tif (\n\t\t\tdef.type === \"optional\" ||\n\t\t\tdef.type === \"nullable\" ||\n\t\t\tdef.type === \"default\"\n\t\t) {\n\t\t\toptional = true;\n\t\t\tcurrent = def.innerType;\n\t\t\tcontinue;\n\t\t}\n\t\tbreak;\n\t}\n\treturn { inner: current, optional };\n}\n\nexport function introspectField(schema: z.ZodType): FieldSchemaInfo {\n\tconst { inner, optional } = unwrapOptional(schema);\n\tconst description = inner.description ?? schema.description ?? undefined;\n\tconst def = readDef(inner);\n\tconst t = def?.type;\n\n\tconst base: FieldSchemaInfo = {\n\t\ttype: \"unknown\",\n\t\t...(description ? { description } : {}),\n\t\t...(optional ? { optional: true } : {}),\n\t};\n\n\tif (t === \"enum\" && def?.entries) {\n\t\treturn { ...base, type: \"enum\", values: Object.keys(def.entries) };\n\t}\n\tif (\n\t\tt === \"string\" ||\n\t\tt === \"number\" ||\n\t\tt === \"boolean\" ||\n\t\tt === \"object\" ||\n\t\tt === \"array\"\n\t) {\n\t\treturn { ...base, type: t };\n\t}\n\treturn base;\n}\n\n/**\n * Resolve a field path (flat key or `parent.child` dot-path) to its schema info,\n * using the flow's declared state schema. Returns `undefined` when the path is\n * not declared — the engine then simply omits `fieldSchema` from the response.\n */\nexport function resolveFieldSchema(\n\tstate: Record<string, z.ZodType> | undefined,\n\tfieldPath: string,\n): FieldSchemaInfo | undefined {\n\tif (!state || !fieldPath) {\n\t\treturn undefined;\n\t}\n\tif (fieldPath.includes(\".\")) {\n\t\tconst dot = fieldPath.indexOf(\".\");\n\t\tconst parent = fieldPath.slice(0, dot);\n\t\tconst child = fieldPath.slice(dot + 1);\n\t\tconst parentSchema = state[parent];\n\t\tif (!parentSchema) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst shape = getObjectShape(parentSchema);\n\t\tconst childSchema = shape?.[child];\n\t\tif (!childSchema) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn introspectField(childSchema);\n\t}\n\tconst schema = state[fieldPath];\n\tif (!schema) {\n\t\treturn undefined;\n\t}\n\treturn introspectField(schema);\n}\n","import type { z } from \"zod\";\nimport type { ScopedWaniWaniClient } from \"../scoped-client\";\nimport { classifyCause } from \"../session-errors/classify\";\nimport { reportSessionError } from \"../session-errors/report\";\nimport type {\n\tEdge,\n\tExecutionResult,\n\tInterruptQuestionData,\n\tMaybePromise,\n\tNodeHandler,\n\tNodeOptions,\n\tNodeVisit,\n} from \"./@types\";\nimport { END, interrupt, isInterrupt, isWidget, showWidget } from \"./@types\";\nimport { resolveFieldSchema } from \"./field-schema\";\nimport { deepMerge, deleteNestedValue, getNestedValue } from \"./nested\";\n\nexport type StateSchema = Record<string, z.ZodType>;\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Check whether a state value counts as \"filled\" (not empty/missing). */\nexport function isFilled(v: unknown): boolean {\n\treturn v !== undefined && v !== null && v !== \"\";\n}\n\nexport type ValidateFn = (\n\tvalue: unknown,\n\t// biome-ignore lint/suspicious/noConfusingVoidType: void needed for async () => {} validators\n) => MaybePromise<Record<string, unknown> | void>;\n\n// ============================================================================\n// Edge resolution\n// ============================================================================\n\nexport async function resolveNextNode<TState extends Record<string, unknown>>(\n\tedge: Edge<TState>,\n\tstate: Partial<TState>,\n): Promise<string> {\n\tif (edge.type === \"direct\") {\n\t\treturn edge.to;\n\t}\n\treturn edge.condition(state);\n}\n\n// ============================================================================\n// Interrupt result builder\n// ============================================================================\n\n/**\n * Build an interrupt ExecutionResult from a list of questions and current state.\n * Filters out already-answered questions and caches the full question list in\n * flowMeta so partial-answer continues can filter without re-executing the handler.\n *\n * Returns `null` when all questions are already filled (caller should advance).\n */\nexport function buildInterruptResult<TState extends Record<string, unknown>>(\n\tquestions: InterruptQuestionData[],\n\tcontext: string | undefined,\n\tcurrentNode: string,\n\tstate: TState,\n\tstateSchema?: StateSchema,\n): ExecutionResult | null {\n\t// All filled — caller should advance to the next node\n\tif (\n\t\tquestions.every((q) =>\n\t\t\tisFilled(getNestedValue(state as Record<string, unknown>, q.field)),\n\t\t)\n\t) {\n\t\treturn null;\n\t}\n\n\t// Filter out questions whose fields are already answered, and attach JIT\n\t// schema fragments so the LLM sees the field's type/values/description at\n\t// the moment it needs to fill it.\n\tconst unanswered = questions\n\t\t.filter(\n\t\t\t(q) =>\n\t\t\t\t!isFilled(getNestedValue(state as Record<string, unknown>, q.field)),\n\t\t)\n\t\t.map((q) => {\n\t\t\tconst fieldSchema = resolveFieldSchema(stateSchema, q.field);\n\t\t\treturn fieldSchema ? { ...q, fieldSchema } : q;\n\t\t});\n\n\t// Single-question shorthand: unwrap for cleaner AI payload\n\tconst isSingle = unanswered.length === 1;\n\tconst q0 = unanswered[0];\n\tconst payload =\n\t\tisSingle && q0\n\t\t\t? {\n\t\t\t\t\tstatus: \"interrupt\" as const,\n\t\t\t\t\tquestion: q0.question,\n\t\t\t\t\tfield: q0.field,\n\t\t\t\t\t...(q0.suggestions ? { suggestions: q0.suggestions } : {}),\n\t\t\t\t\t...(q0.fieldSchema ? { fieldSchema: q0.fieldSchema } : {}),\n\t\t\t\t\t...(q0.context || context ? { context: q0.context ?? context } : {}),\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tstatus: \"interrupt\" as const,\n\t\t\t\t\tquestions: unanswered,\n\t\t\t\t\t...(context ? { context } : {}),\n\t\t\t\t};\n\n\treturn {\n\t\tcontent: payload,\n\t\tflowTokenContent: {\n\t\t\tstep: currentNode,\n\t\t\tstate,\n\t\t\t...(isSingle && q0 ? { field: q0.field } : {}),\n\t\t},\n\t};\n}\n\n// ============================================================================\n// Execution engine\n// ============================================================================\n\nexport async function executeFrom<TState extends Record<string, unknown>>(\n\tstartNodeName: string,\n\tstartState: TState,\n\tnodes: Map<string, NodeHandler<TState>>,\n\tedges: Map<string, Edge<TState>>,\n\tvalidators: Map<string, ValidateFn>,\n\tmeta?: Record<string, unknown>,\n\twaniwani?: ScopedWaniWaniClient,\n\tnodeOptions?: Map<string, NodeOptions>,\n\tstateSchema?: StateSchema,\n): Promise<ExecutionResult> {\n\tlet currentNode = startNodeName;\n\tlet state = { ...startState };\n\tconst nodesVisited: NodeVisit[] = [];\n\n\t// Safety limit to prevent infinite loops\n\tconst MAX_ITERATIONS = 50;\n\tlet iterations = 0;\n\n\twhile (iterations++ < MAX_ITERATIONS) {\n\t\t// Reached END\n\t\tif (currentNode === END) {\n\t\t\treturn {\n\t\t\t\tcontent: { status: \"complete\" },\n\t\t\t\tflowTokenContent: { state },\n\t\t\t\tnodesVisited,\n\t\t\t};\n\t\t}\n\n\t\tconst handler = nodes.get(currentNode);\n\t\tif (!handler) {\n\t\t\treportSessionError({\n\t\t\t\twaniwani,\n\t\t\t\tcode: \"agent_failed\",\n\t\t\t\tcause: \"flow_unknown_node\",\n\t\t\t\tproperties: { node: currentNode },\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tcontent: {\n\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\terror: `Unknown node: \"${currentNode}\"`,\n\t\t\t\t},\n\t\t\t\tnodesVisited,\n\t\t\t};\n\t\t}\n\n\t\tif (!nodeOptions?.get(currentNode)?.hideFromFunnel) {\n\t\t\tnodesVisited.push({ id: currentNode, at: new Date().toISOString() });\n\t\t}\n\n\t\ttry {\n\t\t\t// Build context object for the handler\n\t\t\tconst ctx = {\n\t\t\t\tstate,\n\t\t\t\tmeta,\n\t\t\t\tinterrupt: interrupt as NodeHandler<TState> extends never\n\t\t\t\t\t? never\n\t\t\t\t\t: typeof interrupt,\n\t\t\t\tshowWidget: showWidget as NodeHandler<TState> extends never\n\t\t\t\t\t? never\n\t\t\t\t\t: typeof showWidget,\n\t\t\t\twaniwani,\n\t\t\t};\n\t\t\tconst result = await handler(ctx as Parameters<typeof handler>[0]);\n\n\t\t\t// Interrupt signal — pause and ask the user one or more questions\n\t\t\tif (isInterrupt(result)) {\n\t\t\t\t// Extract and store any validate functions from the interrupt questions\n\t\t\t\tfor (const q of result.questions) {\n\t\t\t\t\tif (q.validate) {\n\t\t\t\t\t\tvalidators.set(`${currentNode}:${q.field}`, q.validate);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst interruptResult = buildInterruptResult(\n\t\t\t\t\tresult.questions,\n\t\t\t\t\tresult.context,\n\t\t\t\t\tcurrentNode,\n\t\t\t\t\tstate,\n\t\t\t\t\tstateSchema,\n\t\t\t\t);\n\n\t\t\t\tif (interruptResult) {\n\t\t\t\t\treturn { ...interruptResult, nodesVisited };\n\t\t\t\t}\n\n\t\t\t\t// All questions filled — run validators before advancing\n\t\t\t\tfor (const q of result.questions) {\n\t\t\t\t\tconst fn = validators.get(`${currentNode}:${q.field}`);\n\t\t\t\t\tif (fn) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst value = getNestedValue(\n\t\t\t\t\t\t\t\tstate as Record<string, unknown>,\n\t\t\t\t\t\t\t\tq.field,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tconst vResult = await fn(value);\n\t\t\t\t\t\t\tif (vResult && typeof vResult === \"object\") {\n\t\t\t\t\t\t\t\tstate = deepMerge(\n\t\t\t\t\t\t\t\t\tstate as Record<string, unknown>,\n\t\t\t\t\t\t\t\t\tvResult as Record<string, unknown>,\n\t\t\t\t\t\t\t\t) as TState;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tdeleteNestedValue(state as Record<string, unknown>, q.field);\n\t\t\t\t\t\t\tconst questionsWithError = result.questions.map((qq) =>\n\t\t\t\t\t\t\t\tqq.field === q.field\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\t...qq,\n\t\t\t\t\t\t\t\t\t\t\tcontext: qq.context\n\t\t\t\t\t\t\t\t\t\t\t\t? `ERROR: ${msg}\\n\\n${qq.context}`\n\t\t\t\t\t\t\t\t\t\t\t\t: `ERROR: ${msg}`,\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: qq,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tconst errResult = buildInterruptResult(\n\t\t\t\t\t\t\t\tquestionsWithError,\n\t\t\t\t\t\t\t\tresult.context,\n\t\t\t\t\t\t\t\tcurrentNode,\n\t\t\t\t\t\t\t\tstate,\n\t\t\t\t\t\t\t\tstateSchema,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (errResult) {\n\t\t\t\t\t\t\t\treturn { ...errResult, nodesVisited };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// All questions filled and validated — advance to next node\n\t\t\t\tconst edge = edges.get(currentNode);\n\t\t\t\tif (!edge) {\n\t\t\t\t\treportSessionError({\n\t\t\t\t\t\twaniwani,\n\t\t\t\t\t\tcode: \"agent_failed\",\n\t\t\t\t\t\tcause: \"flow_dead_end\",\n\t\t\t\t\t\tproperties: { node: currentNode },\n\t\t\t\t\t});\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\t\terror: `No outgoing edge from node \"${currentNode}\"`,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tnodesVisited,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tcurrentNode = await resolveNextNode(edge, state);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Widget signal — delegate to display tool\n\t\t\tif (isWidget(result)) {\n\t\t\t\tconst widgetField = result.field;\n\t\t\t\tif (widgetField) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tisFilled(\n\t\t\t\t\t\t\tgetNestedValue(state as Record<string, unknown>, widgetField),\n\t\t\t\t\t\t)\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst edge = edges.get(currentNode);\n\t\t\t\t\t\tif (!edge) {\n\t\t\t\t\t\t\treportSessionError({\n\t\t\t\t\t\t\t\twaniwani,\n\t\t\t\t\t\t\t\tcode: \"agent_failed\",\n\t\t\t\t\t\t\t\tcause: \"flow_dead_end\",\n\t\t\t\t\t\t\t\tproperties: { node: currentNode },\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\t\t\t\terror: `No outgoing edge from node \"${currentNode}\"`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tnodesVisited,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentNode = await resolveNextNode(edge, state);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst widgetFieldSchema = widgetField\n\t\t\t\t\t? resolveFieldSchema(stateSchema, widgetField)\n\t\t\t\t\t: undefined;\n\t\t\t\tconst interactive = result.interactive !== false;\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"widget\",\n\t\t\t\t\t\ttool: result.tool,\n\t\t\t\t\t\t...(result.data !== undefined ? { data: result.data } : {}),\n\t\t\t\t\t\t// Non-interactive widgets are the most-skipped step: the model\n\t\t\t\t\t\t// sees \"display then continue\" and jumps straight to continue,\n\t\t\t\t\t\t// dropping the visible render. Spell out the order and the\n\t\t\t\t\t\t// failure mode so the display call cannot be shortcut.\n\t\t\t\t\t\tdescription: interactive\n\t\t\t\t\t\t\t? `IMPORTANT: You MUST now call the ${result.tool} tool to display the widget. Do NOT skip this step`\n\t\t\t\t\t\t\t: `IMPORTANT — two ordered steps. STEP 1: call the ${result.tool} tool RIGHT NOW to display this widget to the user. This renders UI the user must see — do NOT skip it and do NOT jump straight to action:\"continue\". STEP 2: ONLY AFTER ${result.tool} has been called, call this flow again with action:\"continue\".`,\n\t\t\t\t\t\tinteractive,\n\t\t\t\t\t\t...(widgetField ? { field: widgetField } : {}),\n\t\t\t\t\t\t...(widgetFieldSchema ? { fieldSchema: widgetFieldSchema } : {}),\n\t\t\t\t\t},\n\t\t\t\t\tflowTokenContent: {\n\t\t\t\t\t\tstep: currentNode,\n\t\t\t\t\t\tstate,\n\t\t\t\t\t\tfield: widgetField,\n\t\t\t\t\t\twidgetId: result.tool,\n\t\t\t\t\t},\n\t\t\t\t\tnodesVisited,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Action node — deep-merge state (preserves nested object siblings)\n\t\t\tstate = deepMerge(\n\t\t\t\tstate as Record<string, unknown>,\n\t\t\t\tresult as Record<string, unknown>,\n\t\t\t) as TState;\n\n\t\t\tconst edge = edges.get(currentNode);\n\t\t\tif (!edge) {\n\t\t\t\treportSessionError({\n\t\t\t\t\twaniwani,\n\t\t\t\t\tcode: \"agent_failed\",\n\t\t\t\t\tcause: \"flow_dead_end\",\n\t\t\t\t\tproperties: { node: currentNode },\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\terror: `No outgoing edge from node \"${currentNode}\"`,\n\t\t\t\t\t},\n\t\t\t\t\tnodesVisited,\n\t\t\t\t};\n\t\t\t}\n\t\t\tcurrentNode = await resolveNextNode(edge, state);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\treportSessionError({\n\t\t\t\twaniwani,\n\t\t\t\tcode: \"agent_failed\",\n\t\t\t\tcause: classifyCause({ error }),\n\t\t\t\tproperties: { node: currentNode },\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tcontent: { status: \"error\", error: message },\n\t\t\t\tflowTokenContent: { step: currentNode, state },\n\t\t\t\tnodesVisited,\n\t\t\t};\n\t\t}\n\t}\n\n\treportSessionError({\n\t\twaniwani,\n\t\tcode: \"agent_failed\",\n\t\tcause: \"flow_loop\",\n\t\tproperties: { node: currentNode },\n\t});\n\treturn {\n\t\tcontent: {\n\t\t\tstatus: \"error\",\n\t\t\terror: \"Flow exceeded maximum iterations (possible infinite loop)\",\n\t\t},\n\t\tnodesVisited,\n\t};\n}\n","/**\n * AES-256-GCM encryption for KV store values.\n *\n * When `WANIWANI_ENCRYPTION_KEY` is set, values are encrypted before\n * being sent to the Waniwani API and decrypted on read. The server\n * never sees plaintext flow state.\n *\n * Key format: base64-encoded 32-byte (256-bit) key.\n * Generate with: `openssl rand -base64 32`\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Encrypted value envelope stored as JSONB in the KV store. */\nexport type EncryptedEnvelope = {\n\t/** Version marker — allows future algorithm changes. */\n\t__ww_enc: 1;\n\t/** Base64-encoded ciphertext (includes GCM auth tag). */\n\tct: string;\n\t/** Base64-encoded 12-byte IV. */\n\tiv: string;\n};\n\n// ============================================================================\n// Type guard\n// ============================================================================\n\nexport function isEncryptedEnvelope(\n\tvalue: unknown,\n): value is EncryptedEnvelope {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\t(value as EncryptedEnvelope).__ww_enc === 1 &&\n\t\ttypeof (value as EncryptedEnvelope).ct === \"string\" &&\n\t\ttypeof (value as EncryptedEnvelope).iv === \"string\"\n\t);\n}\n\n// ============================================================================\n// Key cache\n// ============================================================================\n\nconst keyCache = new Map<string, CryptoKey>();\n\nasync function importKey(keyBase64: string): Promise<CryptoKey> {\n\tconst cached = keyCache.get(keyBase64);\n\tif (cached) {\n\t\treturn cached;\n\t}\n\n\tconst raw = Buffer.from(keyBase64, \"base64\");\n\tif (raw.length !== 32) {\n\t\tthrow new Error(\n\t\t\t\"[Waniwani KV] WANIWANI_ENCRYPTION_KEY must be a base64-encoded 32-byte (256-bit) key.\",\n\t\t);\n\t}\n\n\tconst cryptoKey = await globalThis.crypto.subtle.importKey(\n\t\t\"raw\",\n\t\traw,\n\t\t{ name: \"AES-GCM\" },\n\t\tfalse,\n\t\t[\"encrypt\", \"decrypt\"],\n\t);\n\n\tkeyCache.set(keyBase64, cryptoKey);\n\treturn cryptoKey;\n}\n\n// ============================================================================\n// Encrypt / Decrypt\n// ============================================================================\n\nexport async function encryptValue(\n\tvalue: Record<string, unknown>,\n\tkeyBase64: string,\n): Promise<EncryptedEnvelope> {\n\tconst key = await importKey(keyBase64);\n\tconst iv = globalThis.crypto.getRandomValues(new Uint8Array(12));\n\tconst plaintext = new TextEncoder().encode(JSON.stringify(value));\n\n\tconst ciphertext = await globalThis.crypto.subtle.encrypt(\n\t\t{ name: \"AES-GCM\", iv },\n\t\tkey,\n\t\tplaintext,\n\t);\n\n\treturn {\n\t\t__ww_enc: 1,\n\t\tct: Buffer.from(ciphertext).toString(\"base64\"),\n\t\tiv: Buffer.from(iv).toString(\"base64\"),\n\t};\n}\n\nexport async function decryptValue<T = Record<string, unknown>>(\n\tenvelope: EncryptedEnvelope,\n\tkeyBase64: string,\n): Promise<T> {\n\tconst key = await importKey(keyBase64);\n\tconst ct = Buffer.from(envelope.ct, \"base64\");\n\tconst iv = Buffer.from(envelope.iv, \"base64\");\n\n\tlet plaintext: ArrayBuffer;\n\ttry {\n\t\tplaintext = await globalThis.crypto.subtle.decrypt(\n\t\t\t{ name: \"AES-GCM\", iv },\n\t\t\tkey,\n\t\t\tct,\n\t\t);\n\t} catch {\n\t\tthrow new Error(\n\t\t\t\"[Waniwani KV] Decryption failed. The encryption key may be incorrect or the data may be corrupted.\",\n\t\t);\n\t}\n\n\treturn JSON.parse(new TextDecoder().decode(plaintext)) as T;\n}\n","type LogLevel = \"debug\" | \"warn\" | \"error\" | \"none\";\n\nconst LEVELS: Record<LogLevel, number> = {\n\tdebug: 0,\n\twarn: 1,\n\terror: 2,\n\tnone: 3,\n};\n\nfunction getGlobalLevel(): LogLevel {\n\tconst explicit = process.env.WANIWANI_LOG_LEVEL as LogLevel | undefined;\n\tif (explicit && explicit in LEVELS) {\n\t\treturn explicit;\n\t}\n\tif (process.env.WANIWANI_DEBUG) {\n\t\treturn \"debug\";\n\t}\n\treturn \"none\";\n}\n\n/**\n * Creates a namespaced logger that writes to console when the log level permits.\n *\n * When `enabled` is omitted the logger checks `WANIWANI_LOG_LEVEL` (or falls\n * back to `WANIWANI_DEBUG` for backward compat). Pass an explicit boolean to\n * override the env-var check (e.g. from a user-facing `debug` option).\n *\n * @example\n * const log = createLogger(\"chat\"); // env-var driven\n * const log = createLogger(\"chat\", debug); // explicit override\n */\nexport function createLogger(\n\tnamespace: string,\n\tenabled?: boolean,\n): (...args: unknown[]) => void {\n\tconst active = enabled ?? getGlobalLevel() === \"debug\";\n\treturn active\n\t\t? (...args: unknown[]) => console.log(`[waniwani:${namespace}]`, ...args)\n\t\t: () => {};\n}\n","/**\n * Generic key-value store backed by the Waniwani API.\n *\n * Values are stored as JSON objects (`Record<string, unknown>`) in the\n * `/api/mcp/redis/*` endpoints. Tenant isolation is handled by the API key.\n *\n * Config is read from env vars:\n * - `WANIWANI_API_KEY` (required)\n * - `WANIWANI_API_URL` (optional, defaults to https://app.waniwani.ai)\n * - `WANIWANI_ENCRYPTION_KEY` (optional, base64-encoded 32-byte key for AES-256-GCM encryption)\n */\n\nimport { createLogger } from \"../../../utils/logger.js\";\nimport { decryptValue, encryptValue, isEncryptedEnvelope } from \"./crypto\";\n\n// ============================================================================\n// Interface\n// ============================================================================\n\nexport interface KvStoreSetOptions {\n\t/**\n\t * Time-to-live in seconds, mirroring Redis `SET ... EX`. When omitted the\n\t * server applies its default retention window. The value is kept until it\n\t * expires, however far in the future.\n\t */\n\tttlSeconds?: number;\n}\n\nexport interface KvStore<T = Record<string, unknown>> {\n\tget(key: string): Promise<T | null>;\n\tset(key: string, value: T, options?: KvStoreSetOptions): Promise<void>;\n\tdelete(key: string): Promise<void>;\n}\n\n// ============================================================================\n// Waniwani API implementation\n// ============================================================================\n\nconst SDK_NAME = \"@waniwani/sdk\";\nconst DEFAULT_BASE_URL = \"https://app.waniwani.ai\";\n\nconst kvDebug = createLogger(\"kv\");\n\nexport class WaniwaniKvStore<T = Record<string, unknown>>\n\timplements KvStore<T>\n{\n\tprivate get baseUrl(): string {\n\t\treturn (process.env.WANIWANI_API_URL ?? DEFAULT_BASE_URL).replace(\n\t\t\t/\\/$/,\n\t\t\t\"\",\n\t\t);\n\t}\n\n\tprivate get apiKey(): string | undefined {\n\t\treturn process.env.WANIWANI_API_KEY;\n\t}\n\n\tprivate get encryptionKey(): string | undefined {\n\t\treturn process.env.WANIWANI_ENCRYPTION_KEY;\n\t}\n\n\tasync get(key: string): Promise<T | null> {\n\t\tif (!this.apiKey) {\n\t\t\tthrow new Error(\n\t\t\t\t\"[Waniwani KV] No API key configured. Set WANIWANI_API_KEY env var.\",\n\t\t\t);\n\t\t}\n\t\tconst data = await this.request<Record<string, unknown> | null>(\n\t\t\t\"/api/mcp/redis/get\",\n\t\t\t{ key },\n\t\t);\n\t\tif (data == null) {\n\t\t\treturn null;\n\t\t}\n\t\tif (isEncryptedEnvelope(data)) {\n\t\t\tif (!this.encryptionKey) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"[Waniwani KV] Encrypted data found but WANIWANI_ENCRYPTION_KEY is not set.\",\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn decryptValue<T>(data, this.encryptionKey);\n\t\t}\n\t\treturn data as T;\n\t}\n\n\tasync set(key: string, value: T, options?: KvStoreSetOptions): Promise<void> {\n\t\tif (!this.apiKey) {\n\t\t\tthrow new Error(\n\t\t\t\t\"[Waniwani KV] No API key configured. Set WANIWANI_API_KEY env var.\",\n\t\t\t);\n\t\t}\n\t\tconst encKey = this.encryptionKey;\n\t\tkvDebug(\n\t\t\t`set \"${key}\" — encryption ${encKey ? \"enabled\" : \"disabled (no WANIWANI_ENCRYPTION_KEY)\"}`,\n\t\t);\n\t\tconst payload = encKey\n\t\t\t? await encryptValue(value as Record<string, unknown>, encKey)\n\t\t\t: value;\n\t\tawait this.request(\"/api/mcp/redis/set\", {\n\t\t\tkey,\n\t\t\tvalue: payload,\n\t\t\tttlSeconds: options?.ttlSeconds,\n\t\t});\n\t}\n\n\tasync delete(key: string): Promise<void> {\n\t\tif (!this.apiKey) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait this.request(\"/api/mcp/redis/delete\", { key });\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[Waniwani KV] delete failed for key:\", key, error);\n\t\t}\n\t}\n\n\tprivate async request<R>(path: string, body: unknown): Promise<R> {\n\t\tconst url = `${this.baseUrl}${path}`;\n\t\tconst response = await fetch(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${this.apiKey}`,\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\"X-WaniWani-SDK\": SDK_NAME,\n\t\t\t},\n\t\t\tbody: JSON.stringify(body),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst text = await response.text().catch(() => \"\");\n\t\t\tthrow new Error(text || `KV store API error: HTTP ${response.status}`);\n\t\t}\n\n\t\tconst json = (await response.json()) as { data: R };\n\t\treturn json.data;\n\t}\n}\n","/**\n * In-memory KvStore implementation.\n *\n * State lives in a `Map` for the lifetime of the process — nothing is\n * persisted, nothing is shared across instances. Use for local development\n * and tests. For production, plug in a Redis/Upstash/CF-KV adapter or set\n * `WANIWANI_API_KEY` to use the hosted store.\n */\n\nimport type { KvStore, KvStoreSetOptions } from \"./kv-store\";\n\nexport class MemoryKvStore<T = Record<string, unknown>> implements KvStore<T> {\n\tprivate readonly map = new Map<string, T>();\n\n\tasync get(key: string): Promise<T | null> {\n\t\treturn this.map.get(key) ?? null;\n\t}\n\n\t// In-memory storage has no expiry; `options` (e.g. `ttlSeconds`) is accepted\n\t// for parity with WaniwaniKvStore but ignored.\n\tasync set(\n\t\tkey: string,\n\t\tvalue: T,\n\t\t_options?: KvStoreSetOptions,\n\t): Promise<void> {\n\t\tthis.map.set(key, value);\n\t}\n\n\tasync delete(key: string): Promise<void> {\n\t\tthis.map.delete(key);\n\t}\n}\n","/**\n * Server-side flow state store.\n *\n * Flow state is stored via the Waniwani API, keyed by session ID.\n * Config comes from env vars (WANIWANI_API_KEY, WANIWANI_API_URL).\n */\n\nimport { type KvStoreSetOptions, WaniwaniKvStore } from \"../kv\";\nimport type { FlowTokenContent } from \"./@types\";\n\n// ============================================================================\n// Interface\n// ============================================================================\n\nexport interface FlowStore {\n\tget(key: string): Promise<FlowTokenContent | null>;\n\tset(\n\t\tkey: string,\n\t\tvalue: FlowTokenContent,\n\t\toptions?: KvStoreSetOptions,\n\t): Promise<void>;\n\tdelete(key: string): Promise<void>;\n}\n\n// ============================================================================\n// Waniwani API implementation\n// ============================================================================\n\nexport class WaniwaniFlowStore implements FlowStore {\n\tprivate readonly store = new WaniwaniKvStore<FlowTokenContent>();\n\n\tget(key: string): Promise<FlowTokenContent | null> {\n\t\treturn this.store.get(key);\n\t}\n\n\tset(\n\t\tkey: string,\n\t\tvalue: FlowTokenContent,\n\t\toptions?: KvStoreSetOptions,\n\t): Promise<void> {\n\t\treturn this.store.set(key, value, options);\n\t}\n\n\tdelete(key: string): Promise<void> {\n\t\treturn this.store.delete(key);\n\t}\n}\n","import type {\n\tEdge,\n\tFlowConfig,\n\tFlowGraph,\n\tFlowGraphEdge,\n\tFlowGraphNode,\n\tNodeHandler,\n\tNodeOptions,\n} from \"./@types\";\n\nfunction classifyNode<TState extends Record<string, unknown>>(\n\thandler: NodeHandler<TState>,\n): \"widget\" | \"interrupt\" | \"action\" {\n\tconst src = handler.toString();\n\tif (src.includes(\"showWidget\")) {\n\t\treturn \"widget\";\n\t}\n\tif (src.includes(\"interrupt\")) {\n\t\treturn \"interrupt\";\n\t}\n\treturn \"action\";\n}\n\nexport function extractFlowGraph<TState extends Record<string, unknown>>(\n\tconfig: FlowConfig,\n\tnodes: Map<string, NodeHandler<TState>>,\n\tedges: Map<string, Edge<TState>>,\n\tnodeOptions: Map<string, NodeOptions>,\n): FlowGraph {\n\tconst graphNodes: FlowGraphNode[] = [];\n\tfor (const [id, handler] of nodes) {\n\t\tconst opts = nodeOptions.get(id);\n\t\tgraphNodes.push({\n\t\t\tid,\n\t\t\ttype: classifyNode(handler),\n\t\t\tlabel: opts?.label ?? id,\n\t\t\t...(opts?.hideFromFunnel ? { hideFromFunnel: true } : {}),\n\t\t});\n\t}\n\n\tconst graphEdges: FlowGraphEdge[] = [];\n\tfor (const [from, edge] of edges) {\n\t\tif (edge.type === \"direct\") {\n\t\t\tgraphEdges.push({ from, to: edge.to, type: \"direct\" });\n\t\t} else {\n\t\t\tgraphEdges.push({ from, to: edge.targets, type: \"conditional\" });\n\t\t}\n\t}\n\n\treturn {\n\t\tflowId: config.id,\n\t\ttitle: config.title,\n\t\tnodes: graphNodes,\n\t\tedges: graphEdges,\n\t};\n}\n","/**\n * Flow intro — the first thing the assistant says when a session first\n * touches the flow.\n *\n * A flow can be entered at any node: when the user's opening message already\n * answers the first questions, the engine skips those steps and the\n * conversation starts mid-flow. So the intro is not tied to a node. It is\n * seeded into the session's internal state at `start` and rides along with\n * whatever response the engine returns first, which delivers the introduction\n * and any legal notice exactly once, wherever the flow happens to land,\n * without costing an extra conversation turn.\n *\n * This file only normalizes what the author declared. Where it lives and when\n * it goes out is `internal-state.ts` and the response assembler in\n * `compile.ts`.\n */\n\nimport type { FlowIntro, FlowIntroPayload } from \"./@types\";\n\n/**\n * Normalize an author-supplied `intro` into the payload shape sent on the wire.\n * Called once at compile time so a malformed intro fails at startup rather\n * than on a live conversation.\n *\n * Returns `undefined` when the flow declares no intro.\n */\nexport function normalizeIntro(\n\tintro: FlowIntro | undefined,\n\tflowId: string,\n): FlowIntroPayload | undefined {\n\tif (intro === undefined) {\n\t\treturn undefined;\n\t}\n\n\tif (typeof intro === \"string\") {\n\t\tconst verbatim = intro.trim();\n\t\tif (!verbatim) {\n\t\t\tthrow new Error(\n\t\t\t\t`[waniwani] createFlow \"${flowId}\": \\`intro\\` is an empty string. ` +\n\t\t\t\t\t`Pass the text to deliver word for word, or drop the option.`,\n\t\t\t);\n\t\t}\n\t\treturn { verbatim };\n\t}\n\n\tif (typeof intro !== \"object\" || intro === null) {\n\t\tthrow new Error(\n\t\t\t`[waniwani] createFlow \"${flowId}\": \\`intro\\` must be a string (delivered verbatim) ` +\n\t\t\t\t`or an object with \\`verbatim\\` and/or \\`instructions\\`.`,\n\t\t);\n\t}\n\n\tconst verbatim =\n\t\ttypeof intro.verbatim === \"string\" ? intro.verbatim.trim() : undefined;\n\tconst instructions =\n\t\ttypeof intro.instructions === \"string\"\n\t\t\t? intro.instructions.trim()\n\t\t\t: undefined;\n\n\tif (!verbatim && !instructions) {\n\t\tthrow new Error(\n\t\t\t`[waniwani] createFlow \"${flowId}\": \\`intro\\` needs \\`verbatim\\` (text delivered ` +\n\t\t\t\t`word for word), \\`instructions\\` (guidance the assistant writes from), or both.`,\n\t\t);\n\t}\n\n\treturn {\n\t\t...(verbatim ? { verbatim } : {}),\n\t\t...(instructions ? { instructions } : {}),\n\t};\n}\n","/**\n * The session's internal state: what the engine carries for a session, as\n * opposed to the state the flow collects.\n *\n * A field is seeded when the session starts, read where the engine needs it,\n * and taken off once used. Everything that seeds, loads, takes from, or\n * persists internal state lives here, so the rest of the engine just reads\n * `internal.<field>`.\n */\n\nimport type { FlowConfig, FlowInternalState, FlowTokenContent } from \"./@types\";\nimport type { FlowStore } from \"./flow-store\";\nimport { normalizeIntro } from \"./intro\";\n\n/**\n * How each internal field starts life. One entry per field: it reads the flow\n * config and returns the value a fresh session begins with, or `undefined`\n * when the flow does not use that feature.\n *\n * Adding an engine feature that remembers something at init means adding a\n * field to `FlowInternalState` and its seed here. Nothing else changes.\n */\nconst SEEDS: {\n\t[K in keyof FlowInternalState]-?: (\n\t\tconfig: FlowConfig,\n\t) => FlowInternalState[K];\n} = {\n\tintro: (config) => normalizeIntro(config.intro, config.id),\n};\n\n/**\n * Build the internal state a fresh session begins with.\n *\n * Called once at compile time, so a malformed config fails at startup rather\n * than on a live conversation, and every new session copies the same seed.\n */\nexport function initInternalState(config: FlowConfig): FlowInternalState {\n\tconst seeded = Object.entries(SEEDS)\n\t\t.map(([field, seed]) => [field, seed(config)] as const)\n\t\t.filter(([, value]) => value !== undefined);\n\treturn Object.fromEntries(seeded) as FlowInternalState;\n}\n\n/**\n * Resolve the internal state a `start` call should run with: what the session\n * has left if it is already underway, the seed if it is not.\n *\n * The store read is skipped when the seed is empty (a flow using none of these\n * features has nothing to look up) and when the session id was minted by this\n * very call (there is no record behind it yet). A failed read falls back to the\n * seed: repeating at worst an introduction beats refusing the call.\n */\nexport async function loadInternalState(args: {\n\tstore: FlowStore;\n\tsessionId: string | undefined;\n\t/** False when the engine minted the session id in this call. */\n\tsessionIsPreexisting: boolean;\n\t/** What a session that has not started yet begins with. */\n\tseed: FlowInternalState;\n}): Promise<FlowInternalState> {\n\tconst { store, sessionId, sessionIsPreexisting, seed } = args;\n\n\tif (Object.keys(seed).length === 0) {\n\t\treturn {};\n\t}\n\tif (!sessionId || !sessionIsPreexisting) {\n\t\treturn seed;\n\t}\n\ttry {\n\t\tconst record = await store.get(sessionId);\n\t\treturn record ? (record.internal ?? {}) : seed;\n\t} catch {\n\t\treturn seed;\n\t}\n}\n\n/**\n * Take one field off the session's internal state: the value to use on this\n * response, plus the state to persist without it. Absence then means the field\n * has been used, so a later call in the same conversation leaves it out.\n *\n * Fields this version does not recognize are carried through untouched, so a\n * record written by a newer engine survives a call served by an older one.\n */\nexport function takeInternalField<K extends keyof FlowInternalState>(\n\tinternal: FlowInternalState | undefined,\n\tfield: K,\n): { value: FlowInternalState[K]; internal: FlowInternalState } {\n\tconst { [field]: value, ...rest } = internal ?? {};\n\treturn { value, internal: rest };\n}\n\n/**\n * Combine the record the engine wants to persist with the session's internal\n * state. An empty bag is left off entirely, so a flow using none of this\n * writes the same record it always did.\n */\nexport function withInternalState(\n\ttokenContent: FlowTokenContent,\n\tinternal: FlowInternalState,\n): FlowTokenContent {\n\tif (Object.keys(internal).length === 0) {\n\t\treturn tokenContent;\n\t}\n\treturn { ...tokenContent, internal };\n}\n","import { z } from \"zod\";\n\n// ============================================================================\n// Output schema — the JSON shape returned in `structuredContent` for every\n// flow tool call. Mirrors `FlowContent` plus the optional `sessionId` echo.\n//\n// Baked into the compiled tool config so MCP clients (Claude, ChatGPT, etc.)\n// can present a typed, validated contract instead of an opaque string payload.\n// ============================================================================\n\nconst flowFieldSchema = z\n\t.object({\n\t\ttype: z.enum([\n\t\t\t\"enum\",\n\t\t\t\"string\",\n\t\t\t\"number\",\n\t\t\t\"boolean\",\n\t\t\t\"object\",\n\t\t\t\"array\",\n\t\t\t\"unknown\",\n\t\t]),\n\t\tvalues: z.array(z.string()).optional(),\n\t\tdescription: z.string().optional(),\n\t\toptional: z.boolean().optional(),\n\t})\n\t.describe(\n\t\t\"JIT schema fragment for a state field — type, allowed values, and description.\",\n\t);\n\nconst flowIntroSchema = z\n\t.object({\n\t\tverbatim: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.describe(\n\t\t\t\t\"Text to reproduce word for word, before anything else in the reply.\",\n\t\t\t),\n\t\tinstructions: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.describe(\"How to write the introduction in your own words.\"),\n\t})\n\t.describe(\n\t\t\"Opening message for the session's first flow response. Delivered once per conversation.\",\n\t);\n\nconst flowQuestionSchema = z\n\t.object({\n\t\tquestion: z.string(),\n\t\tfield: z.string(),\n\t\tsuggestions: z.array(z.string()).optional(),\n\t\tcontext: z.string().optional(),\n\t\tfieldSchema: flowFieldSchema.optional(),\n\t})\n\t.describe(\"One question within a multi-question interrupt.\");\n\nexport const flowOutputSchema = {\n\tintro: flowIntroSchema\n\t\t.optional()\n\t\t.describe(\n\t\t\t\"Present only on the session's first flow response. Deliver it at the very start of your next message, then never again.\",\n\t\t),\n\tstatus: z\n\t\t.enum([\"interrupt\", \"widget\", \"complete\", \"error\"])\n\t\t.describe(\n\t\t\t\"Current flow status and the next action the assistant should take.\",\n\t\t),\n\tquestion: z\n\t\t.string()\n\t\t.optional()\n\t\t.describe(\"Single question to ask the user when status is interrupt.\"),\n\tfield: z\n\t\t.string()\n\t\t.optional()\n\t\t.describe(\n\t\t\t\"State field to fill with the user's answer on the next continue call.\",\n\t\t),\n\tfieldSchema: flowFieldSchema\n\t\t.optional()\n\t\t.describe(\"JIT schema fragment for the single-question shorthand.\"),\n\tsuggestions: z\n\t\t.array(z.string())\n\t\t.optional()\n\t\t.describe(\"Suggested answers for the single-question shorthand.\"),\n\tquestions: z\n\t\t.array(flowQuestionSchema)\n\t\t.optional()\n\t\t.describe(\"Multiple questions to ask the user when status is interrupt.\"),\n\tcontext: z\n\t\t.string()\n\t\t.optional()\n\t\t.describe(\"Private instruction context for the assistant.\"),\n\ttool: z\n\t\t.string()\n\t\t.optional()\n\t\t.describe(\"Widget tool to call when status is widget.\"),\n\tdata: z\n\t\t.record(z.string(), z.unknown())\n\t\t.optional()\n\t\t.describe(\"Input data to pass to the widget tool.\"),\n\tdescription: z\n\t\t.string()\n\t\t.optional()\n\t\t.describe(\"Instruction for rendering the requested widget.\"),\n\tinteractive: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe(\n\t\t\t\"Whether the widget requires user interaction before continuing.\",\n\t\t),\n\tsessionId: z\n\t\t.string()\n\t\t.optional()\n\t\t.describe(\"Session identifier to pass on future continue and reset calls.\"),\n\terror: z.string().optional().describe(\"Error message when status is error.\"),\n\tnextStep: z\n\t\t.string()\n\t\t.optional()\n\t\t.describe(\n\t\t\t\"How to handle this response and what the next call to this tool contains. Scoped to the status returned above.\",\n\t\t),\n};\n\nexport type FlowOutputSchema = typeof flowOutputSchema;\n","/**\n * Flow protocol — the operating instructions the assistant needs to drive a\n * multi-step flow.\n *\n * These live in the tool's *response*, not in its description. A tool\n * description that prescribes conversation behavior, tool sequencing, required\n * follow-up calls, or policy outcomes is what prompt-injection classifiers\n * look for: ChatGPT's connector consent card raises \"Suspicious Instruction\"\n * on one, and the user is asked to approve a tool that looks like it is trying\n * to steer the model. From the outside there is no way to tell an honest\n * protocol from an injected one when both arrive in the same field.\n *\n * A tool result is the ordinary place for control flow, and it is not\n * classified that way. So the description carries what the tool *is*, the\n * input schema carries what each argument *means*, and everything about what\n * to do next rides back on the response, scoped to the status that actually\n * came out. The assistant sees less text per turn and only the branch it is\n * standing in.\n */\n\nimport type { FlowContent } from \"./@types\";\n\n/** Shape the response assembler passes in. A subset of `FlowContent`. */\nexport interface NextStepInput {\n\tstatus: FlowContent[\"status\"];\n\t/** Widget tool to render, when `status` is `widget`. */\n\ttool?: string;\n\t/** Whether that widget takes user input before the flow resumes. */\n\tinteractive?: boolean;\n\t/** Whether this response carries the once-per-conversation `intro`. */\n\thasIntro?: boolean;\n\t/** Whether the response echoes a `sessionId` the caller must pass back. */\n\techoSessionId?: boolean;\n}\n\nconst CORRECTION =\n\t'To change a field the user already answered (\"actually my email is X\"), call `action: \"reset\"` with the corrected `stateUpdates`. The flow restarts with every existing answer preserved and filled steps skipped, and may take a different path if the corrected value affects routing. The question on the table is a `continue`, not a `reset`.';\n\nconst INTRO =\n\t\"`intro` opens your next message, ahead of the question, the widget call, and the result. `intro.verbatim` is reproduced word for word — no paraphrase, translation, trimming, expansion, or reformatting. `intro.instructions` is how to write the surrounding prose in your own words. It appears once per conversation and is not repeated from earlier turns.\";\n\nfunction interruptSteps(): string[] {\n\treturn [\n\t\t\"The flow is paused on the user.\",\n\t\t\"- One question: ask `question`, and return the answer as `stateUpdates` keyed by `field`.\",\n\t\t\"- Several questions: ask every entry of `questions` in a single conversational message, then return all the answers keyed by their own `field`.\",\n\t\t'- `fieldSchema`, when present, describes the accepted value: match enum `values` exactly, send a number for `type: \"number\"`.',\n\t\t\"- `context`, when present, shapes your wording and is not shown to the user.\",\n\t\t'Resume with `action: \"continue\"`. Send back what the user actually said, including values they volunteered for other fields — those steps get skipped. Anything they left unanswered stays out and the flow asks again.',\n\t];\n}\n\nfunction widgetSteps(input: NextStepInput): string[] {\n\tconst tool = input.tool ? `\\`${input.tool}\\`` : \"the tool named in `tool`\";\n\tif (input.interactive) {\n\t\treturn [\n\t\t\t`Render the widget by calling ${tool} with the \\`data\\` object as its input.`,\n\t\t\t'The widget takes user input, so the flow waits there. Once the user has responded, resume with `action: \"continue\"` and their selection in `stateUpdates` under `field`.',\n\t\t];\n\t}\n\treturn [\n\t\t`Render the widget by calling ${tool} with the \\`data\\` object as its input. It is display-only, and it is still a step the user sees, so it happens before the flow moves on.`,\n\t\t'Once it is rendered, resume with `action: \"continue\"`. There is nothing to wait for.',\n\t];\n}\n\n/**\n * Build the `nextStep` line(s) for one response. Returns `undefined` when the\n * status speaks for itself and the schema descriptions already cover it.\n */\nexport function buildNextStep(input: NextStepInput): string | undefined {\n\tconst parts: string[] = [];\n\n\tswitch (input.status) {\n\t\tcase \"interrupt\":\n\t\t\tparts.push(...interruptSteps(), CORRECTION);\n\t\t\tbreak;\n\t\tcase \"widget\":\n\t\t\tparts.push(...widgetSteps(input), CORRECTION);\n\t\t\tbreak;\n\t\tcase \"complete\":\n\t\t\tparts.push(\"The flow is finished. Present the result to the user.\");\n\t\t\tbreak;\n\t\tcase \"error\":\n\t\t\tparts.push(\"`error` holds what went wrong.\");\n\t\t\tbreak;\n\t}\n\n\tif (input.echoSessionId && input.status !== \"complete\") {\n\t\tparts.push(\n\t\t\t\"`sessionId` comes back on every `continue` and `reset` call for this flow.\",\n\t\t);\n\t}\n\n\tif (input.hasIntro) {\n\t\tparts.unshift(INTRO);\n\t}\n\n\treturn parts.length > 0 ? parts.join(\"\\n\") : undefined;\n}\n","import type { z } from \"zod\";\n\ntype UnknownRecord = Record<string, unknown>;\n\nfunction isRecord(value: unknown): value is UnknownRecord {\n\treturn Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/**\n * Mark a Zod schema as PII — its value will be replaced with `\"REDACTED\"` in\n * any `tool.called` event payload sent to the Waniwani API. The handler still\n * receives the original value; only the tracked copy is scrubbed.\n *\n * Apply at the end of the schema chain so the marker is attached to the final\n * schema:\n *\n * ```ts\n * ages: redacted(z.string().describe(\"Comma-separated ages\")),\n * zipcode: redacted(z.string().describe(\"Spanish postal code\")),\n * ```\n *\n * Uses Zod v4's `.meta()` registry, so the marker is preserved across schema\n * clones (`.optional()`, `.default()`, etc.).\n */\nexport function redacted<T extends z.ZodType>(schema: T): T {\n\tconst existing = readMeta(schema);\n\tconst waniwani = isRecord(existing.waniwani) ? existing.waniwani : {};\n\treturn (schema as unknown as { meta: (m: UnknownRecord) => T }).meta({\n\t\t...existing,\n\t\twaniwani: { ...waniwani, redacted: true },\n\t});\n}\n\nexport function isFieldRedacted(schema: z.ZodType): boolean {\n\tconst meta = readMeta(schema);\n\tconst waniwani = meta.waniwani;\n\treturn isRecord(waniwani) && waniwani.redacted === true;\n}\n\nfunction readMeta(schema: z.ZodType): UnknownRecord {\n\tconst metaFn = (schema as unknown as { meta?: () => unknown }).meta;\n\tif (typeof metaFn !== \"function\") {\n\t\treturn {};\n\t}\n\tconst value = metaFn.call(schema);\n\treturn isRecord(value) ? value : {};\n}\n\n/**\n * Walk a flow state schema and return the field names marked via `redacted()`.\n */\nexport function collectRedactedStateFields(\n\tstate: Record<string, z.ZodType> | undefined,\n): string[] {\n\tif (!state) {\n\t\treturn [];\n\t}\n\tconst out: string[] = [];\n\tfor (const [name, schema] of Object.entries(state)) {\n\t\tif (isFieldRedacted(schema)) {\n\t\t\tout.push(name);\n\t\t}\n\t}\n\treturn out;\n}\n\nexport const REDACTED_STATE_UPDATE_FIELDS_META_KEY =\n\t\"waniwani/redactedStateUpdateFields\";\n","import type {\n\tAddNodeConfig,\n\tConditionFn,\n\tEdge,\n\tFlowConfig,\n\tNodeHandler,\n\tNodeOptions,\n\tRegisteredFlow,\n} from \"./@types\";\nimport { END, START } from \"./@types\";\nimport { compileFlow } from \"./compile\";\nimport type { FlowStore } from \"./flow-store\";\n\nfunction buildMermaidGraph(\n\tnodes: Map<string, unknown>,\n\tedges: Map<string, Edge<unknown>>,\n): string {\n\tconst lines: string[] = [\"flowchart TD\"];\n\tlines.push(` ${START}((Start))`);\n\tfor (const [name] of nodes) {\n\t\tlines.push(` ${name}[${name}]`);\n\t}\n\tlines.push(` ${END}((End))`);\n\tfor (const [from, edge] of edges) {\n\t\tif (edge.type === \"direct\") {\n\t\t\tlines.push(` ${from} --> ${edge.to}`);\n\t\t} else {\n\t\t\tfor (const target of edge.targets) {\n\t\t\t\tlines.push(` ${from} -.-> ${target}`);\n\t\t\t}\n\t\t}\n\t}\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * A LangGraph-inspired state graph builder for MCP tools.\n *\n * @example\n * ```ts\n * const flow = new StateGraph<MyState>({\n * id: \"onboarding\",\n * title: \"User Onboarding\",\n * description: \"Guides users through onboarding\",\n * })\n * .addNode({\n * id: \"ask_name\",\n * run: ({ interrupt }) => interrupt({ question: \"What's your name?\", field: \"name\" }),\n * })\n * .addNode({\n * id: \"greet\",\n * run: ({ state }) => ({ greeting: `Hello ${state.name}!` }),\n * })\n * .addEdge(START, \"ask_name\")\n * .addEdge(\"ask_name\", \"greet\")\n * .addEdge(\"greet\", END)\n * .compile();\n * ```\n */\nexport class StateGraph<\n\tTState extends Record<string, unknown>,\n\tTNodes extends string = never,\n> {\n\tprivate nodes = new Map<string, NodeHandler<TState>>();\n\tprivate edges = new Map<string, Edge<TState>>();\n\tprivate nodeOptions = new Map<string, NodeOptions>();\n\tprivate config: FlowConfig;\n\n\tconstructor(config: FlowConfig) {\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * Add a node with a handler.\n\t *\n\t * The handler receives a context object with `state`, `meta`, `interrupt`, and `showWidget`.\n\t *\n\t * @example\n\t * ```ts\n\t * .addNode({\n\t * id: \"ask_name\",\n\t * label: \"Ask for name\",\n\t * run: ({ interrupt }) => interrupt({ question: \"What's your name?\", field: \"name\" }),\n\t * })\n\t * ```\n\t */\n\taddNode<TName extends string>(\n\t\tconfig: AddNodeConfig<TState, TName>,\n\t): StateGraph<TState, TNodes | TName>;\n\t/**\n\t * @deprecated Use the object form: `.addNode({ id, run, label? })`.\n\t * The positional form will be removed in v0.13.0\n\t */\n\taddNode<TName extends string>(\n\t\tname: TName,\n\t\thandler: NodeHandler<TState>,\n\t\toptions?: NodeOptions,\n\t): StateGraph<TState, TNodes | TName>;\n\taddNode<TName extends string>(\n\t\tconfigOrName: TName | AddNodeConfig<TState, TName>,\n\t\thandler?: NodeHandler<TState>,\n\t\toptions?: NodeOptions,\n\t): StateGraph<TState, TNodes | TName> {\n\t\tconst id =\n\t\t\ttypeof configOrName === \"string\" ? configOrName : configOrName.id;\n\t\tconst run =\n\t\t\ttypeof configOrName === \"string\"\n\t\t\t\t? (handler as NodeHandler<TState>)\n\t\t\t\t: configOrName.run;\n\t\tconst label =\n\t\t\ttypeof configOrName === \"string\" ? options?.label : configOrName.label;\n\t\tconst hideFromFunnel =\n\t\t\ttypeof configOrName === \"string\"\n\t\t\t\t? options?.hideFromFunnel\n\t\t\t\t: configOrName.hideFromFunnel;\n\n\t\tif (id === START || id === END) {\n\t\t\tthrow new Error(\n\t\t\t\t`\"${id}\" is a reserved name and cannot be used as a node name`,\n\t\t\t);\n\t\t}\n\t\tif (this.nodes.has(id)) {\n\t\t\tthrow new Error(`Node \"${id}\" already exists`);\n\t\t}\n\n\t\tthis.nodes.set(id, run);\n\t\tif (label !== undefined || hideFromFunnel !== undefined) {\n\t\t\tthis.nodeOptions.set(id, { label: label ?? id, hideFromFunnel });\n\t\t}\n\t\treturn this as unknown as StateGraph<TState, TNodes | TName>;\n\t}\n\n\t/**\n\t * Add a direct edge between two nodes.\n\t *\n\t * Use `START` as `from` to set the entry point.\n\t * Use `END` as `to` to mark a terminal node.\n\t */\n\taddEdge(from: typeof START | TNodes, to: TNodes | typeof END): this {\n\t\tif (this.edges.has(from)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Node \"${from}\" already has an outgoing edge. Use addConditionalEdge for branching.`,\n\t\t\t);\n\t\t}\n\t\tthis.edges.set(from, { type: \"direct\", to });\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add a conditional edge from a node.\n\t *\n\t * Declare every node the branch can lead to in `to`, then `condition`\n\t * receives the current state and returns which of those nodes to go to next.\n\t * The condition's return type is constrained to `to`, so it can never route\n\t * somewhere undeclared — and graph introspection (funnel sync, Mermaid\n\t * output, the platform's flow graph) reads `to` directly, correct by\n\t * construction.\n\t *\n\t * @example\n\t * ```ts\n\t * .addConditionalEdge(\n\t * \"analyze_email\",\n\t * [\"done\", \"ask_company\"],\n\t * (state) => (state.isCompanyEmail ? \"done\" : \"ask_company\"),\n\t * )\n\t * ```\n\t */\n\taddConditionalEdge<const TTo extends TNodes | typeof END>(\n\t\tfrom: TNodes,\n\t\tto: readonly TTo[],\n\t\tcondition: (state: Partial<TState>) => NoInfer<TTo> | Promise<NoInfer<TTo>>,\n\t): this {\n\t\tif (this.edges.has(from)) {\n\t\t\tthrow new Error(`Node \"${from}\" already has an outgoing edge.`);\n\t\t}\n\t\tif (to.length === 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`Conditional edge from \"${from}\" must declare at least one target node in \\`to\\`.`,\n\t\t\t);\n\t\t}\n\t\tthis.edges.set(from, {\n\t\t\ttype: \"conditional\",\n\t\t\tcondition: condition as ConditionFn<TState>,\n\t\t\ttargets: [...to],\n\t\t});\n\t\treturn this;\n\t}\n\n\t/**\n\t * Generate a Mermaid `flowchart TD` diagram of the graph.\n\t *\n\t * Direct edges use solid arrows. Conditional edges render one dashed arrow\n\t * per declared target.\n\t *\n\t * @example\n\t * ```ts\n\t * console.log(graph.graph());\n\t * // flowchart TD\n\t * // __start__((Start))\n\t * // ask_name[ask_name]\n\t * // greet[greet]\n\t * // __end__((End))\n\t * // __start__ --> ask_name\n\t * // ask_name --> greet\n\t * // greet --> __end__\n\t * ```\n\t */\n\tgraph(): string {\n\t\treturn buildMermaidGraph(this.nodes, this.edges);\n\t}\n\n\t/**\n\t * Compile the graph into a RegisteredFlow that can be registered on an McpServer.\n\t *\n\t * Validates the graph structure and returns a registration-compatible object.\n\t */\n\tcompile(options?: { store?: FlowStore }): RegisteredFlow {\n\t\tthis.validate();\n\n\t\tconst nodesCopy = new Map(this.nodes);\n\t\tconst edgesCopy = new Map(this.edges);\n\n\t\treturn compileFlow<TState>({\n\t\t\tconfig: this.config,\n\t\t\tnodes: nodesCopy,\n\t\t\tedges: edgesCopy,\n\t\t\tstore: options?.store,\n\t\t\tgraph: () => buildMermaidGraph(nodesCopy, edgesCopy),\n\t\t\tnodeOptions: new Map(this.nodeOptions),\n\t\t});\n\t}\n\n\tprivate validate(): void {\n\t\t// Must have a START edge\n\t\tif (!this.edges.has(START)) {\n\t\t\tthrow new Error(\n\t\t\t\t'Flow must have an entry point. Add an edge from START: .addEdge(START, \"first_node\")',\n\t\t\t);\n\t\t}\n\n\t\t// START edge target must exist\n\t\tconst startEdge = this.edges.get(START);\n\t\tif (\n\t\t\tstartEdge?.type === \"direct\" &&\n\t\t\tstartEdge.to !== END &&\n\t\t\t!this.nodes.has(startEdge.to)\n\t\t) {\n\t\t\tthrow new Error(\n\t\t\t\t`START edge references non-existent node: \"${startEdge.to}\"`,\n\t\t\t);\n\t\t}\n\n\t\t// All static edge targets must reference existing nodes (or END)\n\t\tfor (const [from, edge] of this.edges) {\n\t\t\tif (from !== START && !this.nodes.has(from)) {\n\t\t\t\tthrow new Error(`Edge from non-existent node: \"${from}\"`);\n\t\t\t}\n\t\t\tif (\n\t\t\t\tedge.type === \"direct\" &&\n\t\t\t\tedge.to !== END &&\n\t\t\t\t!this.nodes.has(edge.to)\n\t\t\t) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Edge from \"${from}\" references non-existent node: \"${edge.to}\"`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (edge.type === \"conditional\" && edge.targets) {\n\t\t\t\tfor (const target of edge.targets) {\n\t\t\t\t\tif (target !== END && !this.nodes.has(target)) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`Conditional edge from \"${from}\" declares non-existent target: \"${target}\"`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Every node must have an outgoing edge\n\t\tfor (const [name] of this.nodes) {\n\t\t\tif (!this.edges.has(name)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Node \"${name}\" has no outgoing edge. Add one with .addEdge(\"${name}\", ...) or .addConditionalEdge(\"${name}\", ...)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n}\n","import type { z } from \"zod\";\nimport type { FlowConfig, InferFlowState } from \"./@types\";\nimport { StateGraph } from \"./state-graph\";\n\n/**\n * Create a new flow graph — convenience factory for `new StateGraph()`.\n *\n * The state type is automatically inferred from the `state` definition —\n * no explicit generic parameter needed.\n *\n * @example\n * ```ts\n * import { createFlow, interrupt, START, END } from \"@waniwani/sdk/mcp\";\n * import { z } from \"zod\";\n *\n * const flow = createFlow({\n * id: \"onboarding\",\n * title: \"User Onboarding\",\n * description: \"Guides users through onboarding. Use when a user wants to get started.\",\n * state: {\n * name: z.string().describe(\"The user's name\"),\n * email: z.string().describe(\"The user's email address\"),\n * },\n * })\n * .addNode({\n * id: \"ask_name\",\n * run: () => interrupt({ question: \"What's your name?\", field: \"name\" }),\n * })\n * .addNode({\n * id: \"ask_email\",\n * run: () => interrupt({ question: \"What's your email?\", field: \"email\" }),\n * })\n * .addEdge(START, \"ask_name\")\n * .addEdge(\"ask_name\", \"ask_email\")\n * .addEdge(\"ask_email\", END)\n * .compile();\n * ```\n */\nexport function createFlow<const TSchema extends Record<string, z.ZodType>>(\n\tconfig: Omit<FlowConfig, \"state\"> & { state: TSchema },\n): StateGraph<InferFlowState<TSchema>> {\n\treturn new StateGraph<InferFlowState<TSchema>>(config);\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type {\n\tFlowCompleteContent,\n\tFlowContent,\n\tFlowErrorContent,\n\tFlowInterruptContent,\n\tFlowTokenContent,\n\tFlowWidgetContent,\n\tRegisteredFlow,\n} from \"./@types\";\nimport type { FlowStore } from \"./flow-store\";\n\n// ============================================================================\n// Test harness for compiled flows\n// ============================================================================\n\ntype WithDecodedState = { decodedState: FlowTokenContent | null };\n\nexport type FlowTestResult =\n\t| (FlowInterruptContent & WithDecodedState)\n\t| (FlowWidgetContent & WithDecodedState)\n\t| (FlowCompleteContent & WithDecodedState)\n\t| (FlowErrorContent & WithDecodedState);\n\ntype Handler = (input: unknown, extra: unknown) => Promise<unknown>;\ntype RegisterToolArgs = [string, Record<string, unknown>, Handler];\n\nfunction parsePayload(result: Record<string, unknown>): FlowContent {\n\tconst content = result.content as Array<{ type: string; text?: string }>;\n\treturn JSON.parse(content[0]?.text ?? \"\") as FlowContent;\n}\n\nexport async function createFlowTestHarness(\n\tflow: RegisteredFlow,\n\toptions?: { stateStore?: FlowStore },\n) {\n\tconst store = options?.stateStore;\n\tconst registered: RegisterToolArgs[] = [];\n\tconst sessionId = `test-session-${Math.random().toString(36).slice(2, 10)}`;\n\n\tconst server = {\n\t\tregisterTool: (...args: unknown[]) => {\n\t\t\tregistered.push(args as RegisterToolArgs);\n\t\t},\n\t} as unknown as McpServer;\n\n\tawait flow.register(server);\n\n\tconst handler = registered[0]?.[2];\n\tif (!handler) {\n\t\tthrow new Error(`Flow \"${flow.name}\" did not register a handler`);\n\t}\n\n\tconst extra = { _meta: { sessionId } };\n\n\tasync function toResult(parsed: FlowContent): Promise<FlowTestResult> {\n\t\treturn {\n\t\t\t...parsed,\n\t\t\tdecodedState: store ? await store.get(sessionId) : null,\n\t\t} satisfies FlowTestResult;\n\t}\n\n\treturn {\n\t\tasync start(\n\t\t\tintent: string,\n\t\t\tstateUpdates?: Record<string, unknown>,\n\t\t\tcontext?: string,\n\t\t): Promise<FlowTestResult> {\n\t\t\tconst result = (await handler(\n\t\t\t\t{\n\t\t\t\t\taction: \"start\",\n\t\t\t\t\tintent,\n\t\t\t\t\t...(context ? { context } : {}),\n\t\t\t\t\t...(stateUpdates ? { stateUpdates } : {}),\n\t\t\t\t},\n\t\t\t\textra,\n\t\t\t)) as Record<string, unknown>;\n\t\t\treturn toResult(parsePayload(result));\n\t\t},\n\n\t\tasync continueWith(\n\t\t\tstateUpdates?: Record<string, unknown>,\n\t\t): Promise<FlowTestResult> {\n\t\t\tconst result = (await handler(\n\t\t\t\t{\n\t\t\t\t\taction: \"continue\",\n\t\t\t\t\t...(stateUpdates ? { stateUpdates } : {}),\n\t\t\t\t},\n\t\t\t\textra,\n\t\t\t)) as Record<string, unknown>;\n\t\t\treturn toResult(parsePayload(result));\n\t\t},\n\n\t\tasync resetWith(\n\t\t\tstateUpdates: Record<string, unknown>,\n\t\t): Promise<FlowTestResult> {\n\t\t\tconst result = (await handler(\n\t\t\t\t{\n\t\t\t\t\taction: \"reset\",\n\t\t\t\t\tstateUpdates,\n\t\t\t\t},\n\t\t\t\textra,\n\t\t\t)) as Record<string, unknown>;\n\t\t\treturn toResult(parsePayload(result));\n\t\t},\n\n\t\tasync lastState(): Promise<FlowTokenContent | null> {\n\t\t\treturn store ? store.get(sessionId) : null;\n\t\t},\n\t};\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { KbSearchTrace } from \"./types.js\";\n\n// Per-request bucket the tool wrapper opens; kb.search records into it.\nexport interface RetrievalCollector {\n\tsearches: KbSearchTrace[];\n}\n\n// Global slot, not a module-level instance: core (kb.search) and mcp\n// (withWaniwani) ship as separate bundles, so only globalThis keeps a single\n// shared AsyncLocalStorage per process. Two SDK copies in one process share\n// this slot, so RetrievalCollector's shape must stay backward-compatible.\nconst globalWithStore = globalThis as typeof globalThis & {\n\t__waniwaniKbRetrievalStore?: AsyncLocalStorage<RetrievalCollector>;\n};\n\nlet store = globalWithStore.__waniwaniKbRetrievalStore;\nif (!store) {\n\tstore = new AsyncLocalStorage<RetrievalCollector>();\n\tglobalWithStore.__waniwaniKbRetrievalStore = store;\n}\nexport const retrievalCollectorStore = store;\n\n// Best-effort: recording runs inside kb.search(), so it must never throw back\n// into a user's search (e.g. a corrupted collector from a mixed SDK version).\nexport function recordKbSearch(trace: KbSearchTrace): void {\n\ttry {\n\t\tretrievalCollectorStore.getStore()?.searches.push(trace);\n\t} catch {}\n}\n","// KB Client — thin HTTP wrapper for knowledge base API\n\nimport { WaniWaniError } from \"../error.js\";\nimport type { InternalConfig } from \"../types.js\";\nimport { recordKbSearch } from \"./retrieval-context.js\";\nimport type {\n\tKbClient,\n\tKbIngestFile,\n\tKbIngestResult,\n\tKbSearchOptions,\n\tKbSource,\n\tSearchResult,\n} from \"./types.js\";\n\nconst SDK_NAME = \"@waniwani/sdk\";\n\nexport function createKbClient(config: InternalConfig): KbClient {\n\tconst { apiUrl, apiKey } = config;\n\n\tfunction requireApiKey(): string {\n\t\tif (!apiKey) {\n\t\t\tthrow new Error(\"WANIWANI_API_KEY is not set\");\n\t\t}\n\t\treturn apiKey;\n\t}\n\n\tasync function request<T>(\n\t\tmethod: \"GET\" | \"POST\",\n\t\tpath: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tconst key = requireApiKey();\n\t\tconst url = `${apiUrl.replace(/\\/$/, \"\")}${path}`;\n\n\t\tconst headers: Record<string, string> = {\n\t\t\tAuthorization: `Bearer ${key}`,\n\t\t\t\"X-WaniWani-SDK\": SDK_NAME,\n\t\t};\n\n\t\tconst init: RequestInit = { method, headers };\n\n\t\tif (body !== undefined) {\n\t\t\theaders[\"Content-Type\"] = \"application/json\";\n\t\t\tinit.body = JSON.stringify(body);\n\t\t}\n\n\t\tconst response = await fetch(url, init);\n\n\t\tif (!response.ok) {\n\t\t\tconst text = await response.text().catch(() => \"\");\n\t\t\tthrow new WaniWaniError(\n\t\t\t\ttext || `KB API error: HTTP ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t);\n\t\t}\n\n\t\tconst json = (await response.json()) as { data: T };\n\t\treturn json.data;\n\t}\n\n\treturn {\n\t\tasync ingest(files: KbIngestFile[]): Promise<KbIngestResult> {\n\t\t\treturn request<KbIngestResult>(\"POST\", \"/api/mcp/kb/ingest\", {\n\t\t\t\tfiles,\n\t\t\t});\n\t\t},\n\n\t\tasync search(\n\t\t\tquery: string,\n\t\t\toptions?: KbSearchOptions,\n\t\t): Promise<SearchResult[]> {\n\t\t\tconst results = await request<SearchResult[]>(\n\t\t\t\t\"POST\",\n\t\t\t\t\"/api/mcp/kb/search\",\n\t\t\t\t{\n\t\t\t\t\tquery,\n\t\t\t\t\t...options,\n\t\t\t\t},\n\t\t\t);\n\t\t\trecordKbSearch({\n\t\t\t\tquery,\n\t\t\t\tresultCount: results.length,\n\t\t\t\tresults: results.map((r) => ({\n\t\t\t\t\tsource: r.source,\n\t\t\t\t\theading: r.heading,\n\t\t\t\t\tscore: r.score,\n\t\t\t\t})),\n\t\t\t});\n\t\t\treturn results;\n\t\t},\n\n\t\tasync sources(): Promise<KbSource[]> {\n\t\t\treturn request<KbSource[]>(\"GET\", \"/api/mcp/kb/sources\");\n\t\t},\n\t};\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\n/**\n * Project-level configuration for Waniwani MCP projects.\n *\n * Mirrors the JSON Schema hosted at https://docs.waniwani.ai/waniwani.json.\n * The canonical config file is `waniwani.json` at the project root:\n *\n * ```json\n * {\n * \"$schema\": \"https://docs.waniwani.ai/waniwani.json\",\n * \"orgId\": \"...\",\n * \"projectId\": \"...\"\n * }\n * ```\n *\n * `waniwani()` and the CLI both read this file automatically — no\n * explicit import is required.\n */\nexport interface WaniWaniProjectConfig {\n\t/** URL of the JSON Schema for editor autocomplete. Ignored at runtime. */\n\t$schema?: string;\n\t/** Waniwani organization ID this project belongs to. */\n\torgId?: string;\n\t/** Waniwani MCP project ID. */\n\tprojectId?: string;\n\t/**\n\t * The base URL of the Waniwani API.\n\t * Falls back to the `WANIWANI_API_URL` env var when omitted here, then to\n\t * `https://app.waniwani.ai`.\n\t */\n\tapiUrl?: string;\n\t/**\n\t * Local port the MCP listens on during `waniwani dev`. Overridden by\n\t * `--port`. Defaults to 3000.\n\t */\n\tdevPort?: number;\n}\n\n// ---------------------------------------------------------------------------\n// waniwani.json loader\n// ---------------------------------------------------------------------------\n\nconst CONFIG_FILENAME = \"waniwani.json\";\n\nlet _cached: WaniWaniProjectConfig | null | undefined;\n\n/**\n * Load `waniwani.json` from the current working directory.\n *\n * Returns `null` if the file doesn't exist or the runtime doesn't support\n * synchronous filesystem reads (e.g. edge / worker runtimes). Cached after\n * the first call.\n *\n * @internal\n */\nexport function loadProjectConfig(): WaniWaniProjectConfig | null {\n\tif (_cached !== undefined) {\n\t\treturn _cached;\n\t}\n\n\ttry {\n\t\tconst filePath = resolve(process.cwd(), CONFIG_FILENAME);\n\t\tif (!existsSync(filePath)) {\n\t\t\t_cached = null;\n\t\t\treturn null;\n\t\t}\n\t\tconst raw = readFileSync(filePath, \"utf-8\");\n\t\t_cached = JSON.parse(raw) as WaniWaniProjectConfig;\n\t\treturn _cached;\n\t} catch {\n\t\t_cached = null;\n\t\treturn null;\n\t}\n}\n\n/**\n * Reset the cached config. Test-only.\n *\n * @internal\n */\nexport function resetProjectConfigCache(): void {\n\t_cached = undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Legacy: defineConfig + globalThis registration\n// ---------------------------------------------------------------------------\n\nconst GLOBAL_KEY = \"__waniwani_config__\" as const;\n\n/**\n * Register a Waniwani project configuration on `globalThis`.\n *\n * @deprecated Create a `waniwani.json` at the project root instead. The SDK\n * and CLI both auto-load that file — no `defineConfig` call required.\n * See https://docs.waniwani.ai/waniwani.json for the schema.\n */\nexport function defineConfig(\n\tconfig: WaniWaniProjectConfig,\n): WaniWaniProjectConfig {\n\t(globalThis as Record<string, unknown>)[GLOBAL_KEY] = config;\n\treturn config;\n}\n\n/**\n * Retrieve the globally registered config (set by `defineConfig`).\n *\n * @deprecated Use `loadProjectConfig()` instead. `defineConfig` is going away.\n * @internal\n */\nexport function getGlobalConfig(): WaniWaniProjectConfig | undefined {\n\treturn (globalThis as Record<string, unknown>)[GLOBAL_KEY] as\n\t\t| WaniWaniProjectConfig\n\t\t| undefined;\n}\n","import {\n\textractCorrelationId,\n\textractExternalUserId,\n\textractRequestId,\n\textractSessionId,\n\textractSource,\n\textractTraceId,\n\textractVisitorId,\n} from \"../mcp/server/utils.js\";\nimport type { EventType, LegacyTrackEvent, TrackInput } from \"./@types.js\";\nimport type { V2CorrelationIds, V2EventEnvelope } from \"./v2-types.js\";\n\nconst DEFAULT_SOURCE = \"@waniwani/sdk\";\n\nexport interface MapTrackEventOptions {\n\tnow?: () => Date;\n\tgenerateId?: () => string;\n\tsource?: string;\n}\n\nexport function mapTrackEventToV2(\n\tinput: TrackInput,\n\toptions: MapTrackEventOptions = {},\n): V2EventEnvelope {\n\tconst now = options.now ?? (() => new Date());\n\tconst generateId = options.generateId ?? createEventId;\n\tconst eventName = resolveEventName(input);\n\tconst meta = toRecord(input.meta);\n\tconst metadata = toRecord(input.metadata);\n\tconst correlation = resolveCorrelationIds(input, meta);\n\tconst eventId = takeNonEmptyString(input.eventId) ?? generateId();\n\tconst timestamp = normalizeTimestamp(input.timestamp, now);\n\tconst source =\n\t\ttakeNonEmptyString(input.source) ??\n\t\textractSource(meta) ??\n\t\toptions.source ??\n\t\tDEFAULT_SOURCE;\n\tconst rawLegacy = isLegacyTrackEvent(input) ? { ...input } : undefined;\n\n\tconst mappedMetadata: Record<string, unknown> = {\n\t\t...metadata,\n\t};\n\tif (Object.keys(meta).length > 0) {\n\t\tmappedMetadata.meta = meta;\n\t}\n\tif (rawLegacy) {\n\t\tmappedMetadata.rawLegacy = rawLegacy;\n\t}\n\n\treturn {\n\t\tid: eventId,\n\t\ttype: \"mcp.event\",\n\t\tname: eventName,\n\t\tsource,\n\t\ttimestamp,\n\t\tcorrelation,\n\t\tproperties: mapProperties(input, eventName),\n\t\tmetadata: mappedMetadata,\n\t\trawLegacy,\n\t};\n}\n\nexport function createEventId(): string {\n\tif (\n\t\ttypeof crypto !== \"undefined\" &&\n\t\ttypeof crypto.randomUUID === \"function\"\n\t) {\n\t\treturn `evt_${crypto.randomUUID()}`;\n\t}\n\n\treturn `evt_${Math.random().toString(36).slice(2, 10)}_${Date.now().toString(36)}`;\n}\n\nfunction mapProperties(\n\tinput: TrackInput,\n\teventName: EventType,\n): Record<string, unknown> {\n\tif (!isLegacyTrackEvent(input)) {\n\t\treturn toRecord(input.properties);\n\t}\n\n\tconst legacyProperties = mapLegacyProperties(input, eventName);\n\tconst explicitProperties = toRecord(input.properties);\n\treturn {\n\t\t...legacyProperties,\n\t\t...explicitProperties,\n\t};\n}\n\nfunction mapLegacyProperties(\n\tinput: LegacyTrackEvent,\n\teventName: EventType,\n): Record<string, unknown> {\n\tswitch (eventName) {\n\t\tcase \"tool.called\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (takeNonEmptyString(input.toolName)) {\n\t\t\t\tproperties.name = input.toolName;\n\t\t\t}\n\t\t\tif (takeNonEmptyString(input.toolType)) {\n\t\t\t\tproperties.type = input.toolType;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tcase \"link.clicked\": {\n\t\t\tconst properties: Record<string, unknown> = {};\n\t\t\tif (takeNonEmptyString(input.linkUrl)) {\n\t\t\t\tproperties.url = input.linkUrl;\n\t\t\t}\n\t\t\treturn properties;\n\t\t}\n\t\tdefault:\n\t\t\treturn {};\n\t}\n}\n\nfunction resolveEventName(input: TrackInput): EventType {\n\treturn isLegacyTrackEvent(input) ? input.eventType : input.event;\n}\n\nfunction resolveCorrelationIds(\n\tinput: TrackInput,\n\tmeta: Record<string, unknown>,\n): V2CorrelationIds {\n\tconst requestId =\n\t\ttakeNonEmptyString(input.requestId) ?? extractRequestId(meta);\n\n\tconst sessionId =\n\t\ttakeNonEmptyString(input.sessionId) ?? extractSessionId(meta);\n\n\tconst traceId = takeNonEmptyString(input.traceId) ?? extractTraceId(meta);\n\n\tconst externalUserId =\n\t\ttakeNonEmptyString(input.externalUserId) ?? extractExternalUserId(meta);\n\n\tconst visitorId =\n\t\ttakeNonEmptyString(input.visitorId) ?? extractVisitorId(meta);\n\n\tconst correlationId =\n\t\ttakeNonEmptyString(input.correlationId) ??\n\t\textractCorrelationId(meta) ??\n\t\trequestId;\n\n\tconst correlation: V2CorrelationIds = {};\n\tif (sessionId) {\n\t\tcorrelation.sessionId = sessionId;\n\t}\n\tif (traceId) {\n\t\tcorrelation.traceId = traceId;\n\t}\n\tif (requestId) {\n\t\tcorrelation.requestId = requestId;\n\t}\n\tif (correlationId) {\n\t\tcorrelation.correlationId = correlationId;\n\t}\n\tif (externalUserId) {\n\t\tcorrelation.externalUserId = externalUserId;\n\t}\n\tif (visitorId) {\n\t\tcorrelation.visitorId = visitorId;\n\t}\n\treturn correlation;\n}\n\nfunction normalizeTimestamp(\n\tinput: string | Date | undefined,\n\tnow: () => Date,\n): string {\n\tif (input instanceof Date) {\n\t\treturn input.toISOString();\n\t}\n\tif (typeof input === \"string\") {\n\t\tconst date = new Date(input);\n\t\tif (!Number.isNaN(date.getTime())) {\n\t\t\treturn date.toISOString();\n\t\t}\n\t}\n\treturn now().toISOString();\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\treturn {};\n\t}\n\treturn value as Record<string, unknown>;\n}\n\nfunction takeNonEmptyString(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") {\n\t\treturn undefined;\n\t}\n\tif (value.trim().length === 0) {\n\t\treturn undefined;\n\t}\n\treturn value;\n}\n\nfunction isLegacyTrackEvent(input: TrackInput): input is LegacyTrackEvent {\n\treturn \"eventType\" in input;\n}\n","import type {\n\tTrackingShutdownOptions,\n\tTrackingShutdownResult,\n} from \"./@types.js\";\nimport type {\n\tV2BatchRejectedEvent,\n\tV2BatchRequest,\n\tV2BatchResponse,\n\tV2EventEnvelope,\n} from \"./v2-types.js\";\n\ntype FetchFn = (\n\tinput: URL | RequestInfo,\n\tinit?: RequestInit,\n) => Promise<Response>;\n\nconst DEFAULT_ENDPOINT_PATH = \"/api/mcp/events/v2/batch\";\nconst DEFAULT_FLUSH_INTERVAL_MS = 1_000;\nconst DEFAULT_MAX_BATCH_SIZE = 20;\nconst DEFAULT_MAX_BUFFER_SIZE = 1_000;\nconst DEFAULT_MAX_RETRIES = 3;\nconst DEFAULT_RETRY_BASE_DELAY_MS = 200;\nconst DEFAULT_RETRY_MAX_DELAY_MS = 2_000;\nconst DEFAULT_SHUTDOWN_TIMEOUT_MS = 2_000;\nconst SDK_NAME = \"@waniwani/sdk\";\n// Keepalive requests are capped by browsers (64KiB in-flight quota); stay under it.\nconst TEARDOWN_MAX_BODY_BYTES = 60_000;\n\nconst AUTH_FAILURE_STATUS = new Set([401, 403]);\nconst RETRYABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504]);\n\n// `fetch` brand-checks its receiver, so it cannot be stored on an instance field.\n// Resolve it off `globalThis` per call so a host swapping `fetch` later is honoured.\nconst defaultFetch: FetchFn = (input, init) => globalThis.fetch(input, init);\n\ninterface Logger {\n\twarn: (message: string, ...args: unknown[]) => void;\n\terror: (message: string, ...args: unknown[]) => void;\n}\n\nexport interface V2TransportOptions {\n\tapiUrl: string;\n\t/**\n\t * Bearer credential for the ingest endpoint: secret key (`wwk_`), public\n\t * token (`wwp_`), or widget JWT. Omit only for unauthenticated proxies.\n\t */\n\tapiKey?: string;\n\tendpointPath?: string;\n\tflushIntervalMs?: number;\n\tmaxBatchSize?: number;\n\tmaxBufferSize?: number;\n\tmaxRetries?: number;\n\tretryBaseDelayMs?: number;\n\tretryMaxDelayMs?: number;\n\tshutdownTimeoutMs?: number;\n\tsdkVersion?: string;\n\tfetchFn?: FetchFn;\n\tlogger?: Logger;\n\tnow?: () => Date;\n\tsleep?: (delayMs: number) => Promise<void>;\n}\n\nexport interface V2BatchTransport {\n\tenqueue: (event: V2EventEnvelope) => void;\n\tflush: () => Promise<void>;\n\tshutdown: (\n\t\toptions?: TrackingShutdownOptions,\n\t) => Promise<TrackingShutdownResult>;\n\tpendingEvents: () => number;\n}\n\ntype SendBatchResult =\n\t| { kind: \"success\" }\n\t| { kind: \"retryable\"; reason: string }\n\t| { kind: \"permanent\"; reason: string }\n\t| { kind: \"auth\"; status: number }\n\t| {\n\t\t\tkind: \"partial\";\n\t\t\tretryable: V2EventEnvelope[];\n\t\t\tpermanent: V2EventEnvelope[];\n\t };\n\nexport function createV2BatchTransport(\n\toptions: V2TransportOptions,\n): V2BatchTransport {\n\treturn new BatchingV2Transport(options);\n}\n\nclass BatchingV2Transport implements V2BatchTransport {\n\tprivate readonly endpointUrl: string;\n\tprivate readonly flushIntervalMs: number;\n\tprivate readonly maxBatchSize: number;\n\tprivate readonly maxBufferSize: number;\n\tprivate readonly maxRetries: number;\n\tprivate readonly retryBaseDelayMs: number;\n\tprivate readonly retryMaxDelayMs: number;\n\tprivate readonly shutdownTimeoutMs: number;\n\tprivate readonly sdkVersion?: string;\n\tprivate readonly fetchFn: FetchFn;\n\tprivate readonly logger: Logger;\n\tprivate readonly now: () => Date;\n\tprivate readonly sleep: (delayMs: number) => Promise<void>;\n\tprivate readonly apiKey?: string;\n\n\tprivate readonly buffer: V2EventEnvelope[] = [];\n\tprivate flushTimer: ReturnType<typeof setInterval> | undefined;\n\tprivate flushScheduled = false;\n\tprivate flushScheduledTimer: ReturnType<typeof setTimeout> | undefined;\n\tprivate flushInFlight: Promise<void> | undefined;\n\tprivate inFlightCount = 0;\n\tprivate isStopped = false;\n\tprivate isShuttingDown = false;\n\n\tconstructor(options: V2TransportOptions) {\n\t\tthis.endpointUrl = joinUrl(\n\t\t\toptions.apiUrl,\n\t\t\toptions.endpointPath ?? DEFAULT_ENDPOINT_PATH,\n\t\t);\n\t\tthis.flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;\n\t\tthis.maxBatchSize = options.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;\n\t\tthis.maxBufferSize = options.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE;\n\t\tthis.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;\n\t\tthis.retryBaseDelayMs =\n\t\t\toptions.retryBaseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS;\n\t\tthis.retryMaxDelayMs =\n\t\t\toptions.retryMaxDelayMs ?? DEFAULT_RETRY_MAX_DELAY_MS;\n\t\tthis.shutdownTimeoutMs =\n\t\t\toptions.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;\n\t\tthis.fetchFn = options.fetchFn ?? defaultFetch;\n\t\tthis.logger = options.logger ?? console;\n\t\tthis.now = options.now ?? (() => new Date());\n\t\tthis.sleep =\n\t\t\toptions.sleep ??\n\t\t\t((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)));\n\t\tthis.apiKey = options.apiKey;\n\t\tthis.sdkVersion = options.sdkVersion;\n\n\t\tif (this.flushIntervalMs > 0) {\n\t\t\tthis.flushTimer = setInterval(() => {\n\t\t\t\tvoid this.flush();\n\t\t\t}, this.flushIntervalMs);\n\t\t}\n\n\t\tthis.registerBrowserTeardown();\n\t}\n\n\tenqueue(event: V2EventEnvelope): void {\n\t\tif (this.isStopped || this.isShuttingDown) {\n\t\t\tthis.logger.warn(\n\t\t\t\t\"[Waniwani] Tracking transport is stopped, dropping event %s\",\n\t\t\t\tevent.id,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.buffer.length >= this.maxBufferSize) {\n\t\t\tconst dropCount = this.buffer.length - this.maxBufferSize + 1;\n\t\t\tthis.buffer.splice(0, dropCount);\n\t\t\tthis.logger.warn(\n\t\t\t\t\"[Waniwani] Tracking buffer overflow, dropped %d oldest event(s)\",\n\t\t\t\tdropCount,\n\t\t\t);\n\t\t}\n\n\t\tthis.buffer.push(event);\n\n\t\tif (this.buffer.length >= this.maxBatchSize) {\n\t\t\tvoid this.flush();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.scheduleMicroFlush();\n\t}\n\n\tpendingEvents(): number {\n\t\treturn this.buffer.length + this.inFlightCount;\n\t}\n\n\tasync flush(): Promise<void> {\n\t\tif (this.flushInFlight) {\n\t\t\treturn this.flushInFlight;\n\t\t}\n\t\tthis.flushInFlight = this.flushLoop().finally(() => {\n\t\t\tthis.flushInFlight = undefined;\n\t\t});\n\t\treturn this.flushInFlight;\n\t}\n\n\tasync shutdown(\n\t\toptions?: TrackingShutdownOptions,\n\t): Promise<TrackingShutdownResult> {\n\t\tthis.isShuttingDown = true;\n\t\tif (this.flushTimer) {\n\t\t\tclearInterval(this.flushTimer);\n\t\t\tthis.flushTimer = undefined;\n\t\t}\n\t\tif (this.flushScheduledTimer) {\n\t\t\tclearTimeout(this.flushScheduledTimer);\n\t\t\tthis.flushScheduledTimer = undefined;\n\t\t\tthis.flushScheduled = false;\n\t\t}\n\n\t\tconst timeoutMs = options?.timeoutMs ?? this.shutdownTimeoutMs;\n\t\tconst flushPromise = this.flush();\n\n\t\tif (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n\t\t\tawait flushPromise;\n\t\t\tthis.isStopped = true;\n\t\t\treturn { timedOut: false, pendingEvents: this.pendingEvents() };\n\t\t}\n\n\t\tconst timeoutSignal = Symbol(\"shutdown-timeout\");\n\t\tconst result = await Promise.race([\n\t\t\tflushPromise.then(() => \"flushed\" as const),\n\t\t\tthis.sleep(timeoutMs).then(() => timeoutSignal),\n\t\t]);\n\n\t\tif (result === timeoutSignal) {\n\t\t\tthis.isStopped = true;\n\t\t\treturn { timedOut: true, pendingEvents: this.pendingEvents() };\n\t\t}\n\n\t\tthis.isStopped = true;\n\t\treturn { timedOut: false, pendingEvents: this.pendingEvents() };\n\t}\n\n\t/**\n\t * In browsers, drain the buffer with keepalive fetches when the page is\n\t * hidden or unloading. The regular async flush loop does not survive\n\t * navigation; `keepalive` requests do. No-op outside a DOM environment.\n\t */\n\tprivate registerBrowserTeardown(): void {\n\t\tif (typeof document === \"undefined\" || typeof window === \"undefined\") {\n\t\t\treturn;\n\t\t}\n\n\t\tconst teardownFlush = () => {\n\t\t\tif (document.visibilityState === \"hidden\") {\n\t\t\t\tthis.keepaliveFlush();\n\t\t\t}\n\t\t};\n\t\tdocument.addEventListener(\"visibilitychange\", teardownFlush);\n\t\twindow.addEventListener(\"pagehide\", () => this.keepaliveFlush());\n\t}\n\n\t/** Fire-and-forget flush of everything buffered, chunked under the keepalive body cap. */\n\tprivate keepaliveFlush(): void {\n\t\tif (this.isStopped || this.buffer.length === 0) {\n\t\t\treturn;\n\t\t}\n\t\tconst events = this.buffer.splice(0, this.buffer.length);\n\t\tthis.sendKeepaliveChunk(events);\n\t}\n\n\tprivate sendKeepaliveChunk(events: V2EventEnvelope[]): void {\n\t\tconst body = JSON.stringify(this.makeBatchRequest(events));\n\t\tif (body.length > TEARDOWN_MAX_BODY_BYTES && events.length > 1) {\n\t\t\tconst mid = Math.ceil(events.length / 2);\n\t\t\tthis.sendKeepaliveChunk(events.slice(0, mid));\n\t\t\tthis.sendKeepaliveChunk(events.slice(mid));\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tvoid this.fetchFn(this.endpointUrl, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: this.requestHeaders(),\n\t\t\t\tbody,\n\t\t\t\tkeepalive: true,\n\t\t\t}).catch(() => {});\n\t\t} catch {\n\t\t\t// Teardown is best-effort; never throw during page unload.\n\t\t}\n\t}\n\n\tprivate requestHeaders(): Record<string, string> {\n\t\tconst headers: Record<string, string> = {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\"X-WaniWani-SDK\": SDK_NAME,\n\t\t};\n\t\tif (this.apiKey) {\n\t\t\theaders.Authorization = `Bearer ${this.apiKey}`;\n\t\t}\n\t\treturn headers;\n\t}\n\n\tprivate scheduleMicroFlush(): void {\n\t\tif (this.flushScheduled) {\n\t\t\treturn;\n\t\t}\n\t\tthis.flushScheduled = true;\n\t\tthis.flushScheduledTimer = setTimeout(() => {\n\t\t\tthis.flushScheduledTimer = undefined;\n\t\t\tthis.flushScheduled = false;\n\t\t\tvoid this.flush();\n\t\t}, 0);\n\t}\n\n\tprivate async flushLoop(): Promise<void> {\n\t\twhile (this.buffer.length > 0 && !this.isStopped) {\n\t\t\tconst batch = this.buffer.splice(0, this.maxBatchSize);\n\t\t\tawait this.sendBatchWithRetry(batch);\n\t\t}\n\t}\n\n\tprivate async sendBatchWithRetry(batch: V2EventEnvelope[]): Promise<void> {\n\t\tlet attempt = 0;\n\t\tlet pendingBatch = batch;\n\n\t\twhile (pendingBatch.length > 0 && !this.isStopped) {\n\t\t\tthis.inFlightCount = pendingBatch.length;\n\t\t\tconst result = await this.sendBatchOnce(pendingBatch);\n\t\t\tthis.inFlightCount = 0;\n\n\t\t\tswitch (result.kind) {\n\t\t\t\tcase \"success\":\n\t\t\t\t\treturn;\n\t\t\t\tcase \"auth\":\n\t\t\t\t\tthis.stopTransportForAuthFailure(result.status, pendingBatch.length);\n\t\t\t\t\treturn;\n\t\t\t\tcase \"permanent\":\n\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\"[Waniwani] Dropping %d event(s) after permanent failure: %s\",\n\t\t\t\t\t\tpendingBatch.length,\n\t\t\t\t\t\tresult.reason,\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\tcase \"retryable\":\n\t\t\t\t\tif (attempt >= this.maxRetries) {\n\t\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\t\"[Waniwani] Dropping %d event(s) after retry exhaustion: %s\",\n\t\t\t\t\t\t\tpendingBatch.length,\n\t\t\t\t\t\t\tresult.reason,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tawait this.sleep(this.backoffDelayMs(attempt));\n\t\t\t\t\tattempt += 1;\n\t\t\t\t\tcontinue;\n\t\t\t\tcase \"partial\":\n\t\t\t\t\tif (result.permanent.length > 0) {\n\t\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\t\"[Waniwani] Dropping %d event(s) rejected as permanent\",\n\t\t\t\t\t\t\tresult.permanent.length,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (result.retryable.length === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (attempt >= this.maxRetries) {\n\t\t\t\t\t\tthis.logger.error(\n\t\t\t\t\t\t\t\"[Waniwani] Dropping %d retryable event(s) after retry exhaustion\",\n\t\t\t\t\t\t\tresult.retryable.length,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tpendingBatch = result.retryable;\n\t\t\t\t\tawait this.sleep(this.backoffDelayMs(attempt));\n\t\t\t\t\tattempt += 1;\n\t\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async sendBatchOnce(\n\t\tevents: V2EventEnvelope[],\n\t): Promise<SendBatchResult> {\n\t\tlet response: Response;\n\n\t\ttry {\n\t\t\tresponse = await this.fetchFn(this.endpointUrl, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: this.requestHeaders(),\n\t\t\t\tbody: JSON.stringify(this.makeBatchRequest(events)),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tkind: \"retryable\",\n\t\t\t\treason: getErrorMessage(error),\n\t\t\t};\n\t\t}\n\n\t\tif (AUTH_FAILURE_STATUS.has(response.status)) {\n\t\t\treturn { kind: \"auth\", status: response.status };\n\t\t}\n\n\t\tif (RETRYABLE_STATUS.has(response.status)) {\n\t\t\treturn {\n\t\t\t\tkind: \"retryable\",\n\t\t\t\treason: `HTTP ${response.status}`,\n\t\t\t};\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\treturn {\n\t\t\t\tkind: \"permanent\",\n\t\t\t\treason: `HTTP ${response.status}`,\n\t\t\t};\n\t\t}\n\n\t\tconst data = await parseJsonResponse<V2BatchResponse>(response);\n\t\tif (!data?.rejected || data.rejected.length === 0) {\n\t\t\treturn { kind: \"success\" };\n\t\t}\n\n\t\tconst partial = this.classifyRejectedEvents(events, data.rejected);\n\t\tif (partial.retryable.length === 0 && partial.permanent.length === 0) {\n\t\t\treturn { kind: \"success\" };\n\t\t}\n\n\t\treturn {\n\t\t\tkind: \"partial\",\n\t\t\tretryable: partial.retryable,\n\t\t\tpermanent: partial.permanent,\n\t\t};\n\t}\n\n\tprivate makeBatchRequest(events: V2EventEnvelope[]): V2BatchRequest {\n\t\treturn {\n\t\t\tsentAt: this.now().toISOString(),\n\t\t\tsource: {\n\t\t\t\tsdk: SDK_NAME,\n\t\t\t\tversion: this.sdkVersion ?? \"0.0.0\",\n\t\t\t},\n\t\t\tevents,\n\t\t};\n\t}\n\n\tprivate classifyRejectedEvents(\n\t\tevents: V2EventEnvelope[],\n\t\trejected: V2BatchRejectedEvent[],\n\t): {\n\t\tretryable: V2EventEnvelope[];\n\t\tpermanent: V2EventEnvelope[];\n\t} {\n\t\tconst byId = new Map(events.map((event) => [event.id, event]));\n\t\tconst retryable: V2EventEnvelope[] = [];\n\t\tconst permanent: V2EventEnvelope[] = [];\n\n\t\tfor (const rejectedEvent of rejected) {\n\t\t\tconst event = byId.get(rejectedEvent.eventId);\n\t\t\tif (!event) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (isRetryableRejectedEvent(rejectedEvent)) {\n\t\t\t\tretryable.push(event);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpermanent.push(event);\n\t\t}\n\n\t\treturn { retryable, permanent };\n\t}\n\n\tprivate backoffDelayMs(attempt: number): number {\n\t\tconst rawDelay = this.retryBaseDelayMs * 2 ** attempt;\n\t\treturn Math.min(rawDelay, this.retryMaxDelayMs);\n\t}\n\n\tprivate stopTransportForAuthFailure(\n\t\tstatus: number,\n\t\trejectedCount: number,\n\t): void {\n\t\tthis.isStopped = true;\n\t\tconst buffered = this.buffer.length;\n\t\tthis.buffer.splice(0, buffered);\n\t\tthis.logger.error(\n\t\t\t\"[Waniwani] Auth failure (HTTP %d). Stopping tracking transport and dropping %d queued event(s)\",\n\t\t\tstatus,\n\t\t\trejectedCount + buffered,\n\t\t);\n\t}\n}\n\nfunction isRetryableRejectedEvent(\n\trejectedEvent: V2BatchRejectedEvent,\n): boolean {\n\tif (rejectedEvent.retryable === true) {\n\t\treturn true;\n\t}\n\tconst code = rejectedEvent.code.toLowerCase();\n\treturn (\n\t\tcode.includes(\"timeout\") ||\n\t\tcode.includes(\"temporary\") ||\n\t\tcode.includes(\"unavailable\") ||\n\t\tcode.includes(\"rate_limit\") ||\n\t\tcode.includes(\"transient\") ||\n\t\tcode.includes(\"server\")\n\t);\n}\n\nasync function parseJsonResponse<T>(\n\tresponse: Response,\n): Promise<T | undefined> {\n\tconst body = await response.text();\n\tif (!body) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\treturn JSON.parse(body) as T;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction joinUrl(apiUrl: string, endpointPath: string): string {\n\tconst normalizedBase = apiUrl.endsWith(\"/\") ? apiUrl : `${apiUrl}/`;\n\tconst normalizedPath = endpointPath.startsWith(\"/\")\n\t\t? endpointPath.slice(1)\n\t\t: endpointPath;\n\treturn `${normalizedBase}${normalizedPath}`;\n}\n\nfunction getErrorMessage(error: unknown): string {\n\tif (error instanceof Error) {\n\t\treturn error.message;\n\t}\n\treturn String(error);\n}\n","// Tracking Module\n\nimport type { InternalConfig } from \"../types.js\";\nimport type {\n\tTrackFn,\n\tTrackInput,\n\tTrackingClient,\n\tTrackingShutdownOptions,\n} from \"./@types.js\";\nimport { mapTrackEventToV2 } from \"./mapper.js\";\nimport { createRevenueApi } from \"./revenue.js\";\nimport { createV2BatchTransport } from \"./transport.js\";\n\n// Re-export types\nexport type {\n\tCallableTrack,\n\tComparedPriceOption,\n\tConvertedProperties,\n\tErrorCauseType,\n\tEventType,\n\tLeadQualifiedProperties,\n\tLegacyTrackEvent,\n\tLinkClickedProperties,\n\tOptionSelectedProperties,\n\tPriceShownProperties,\n\tPricesComparedProperties,\n\tRevenueConvertedInput,\n\tRevenueLeadQualifiedInput,\n\tRevenueOptionSelectedInput,\n\tRevenuePriceShownInput,\n\tRevenuePricesComparedInput,\n\tRevenueTrackingApi,\n\tSessionErrorCodeType,\n\tSessionErrorProperties,\n\tToolCalledProperties,\n\tTrackEvent,\n\tTrackFn,\n\tTrackInput,\n\tTrackingClient,\n\tTrackingConfig,\n\tTrackingShutdownOptions,\n\tTrackingShutdownResult,\n} from \"./@types.js\";\nexport { ERROR_CAUSES, EVENT_TYPES, SESSION_ERROR_CODES } from \"./@types.js\";\nexport { createEventId, mapTrackEventToV2 } from \"./mapper.js\";\nexport type {\n\tV2BatchRejectedEvent,\n\tV2BatchRequest,\n\tV2BatchResponse,\n\tV2CorrelationIds,\n\tV2EnvelopeType,\n\tV2EventEnvelope,\n} from \"./v2-types.js\";\n\nexport function createTrackingClient(config: InternalConfig): TrackingClient {\n\tconst { apiUrl, apiKey, tracking } = config;\n\n\tfunction requireApiKey(): string {\n\t\tif (!apiKey) {\n\t\t\tthrow new Error(\"WANIWANI_API_KEY is not set\");\n\t\t}\n\t\treturn apiKey;\n\t}\n\n\tconst transport = apiKey\n\t\t? createV2BatchTransport({\n\t\t\t\tapiUrl,\n\t\t\t\tapiKey,\n\t\t\t\tendpointPath: tracking.endpointPath,\n\t\t\t\tflushIntervalMs: tracking.flushIntervalMs,\n\t\t\t\tmaxBatchSize: tracking.maxBatchSize,\n\t\t\t\tmaxBufferSize: tracking.maxBufferSize,\n\t\t\t\tmaxRetries: tracking.maxRetries,\n\t\t\t\tretryBaseDelayMs: tracking.retryBaseDelayMs,\n\t\t\t\tretryMaxDelayMs: tracking.retryMaxDelayMs,\n\t\t\t\tshutdownTimeoutMs: tracking.shutdownTimeoutMs,\n\t\t\t})\n\t\t: undefined;\n\n\t// Single enqueue path shared by track(), track.* revenue helpers, and identify().\n\tfunction emit(event: TrackInput): { eventId: string } {\n\t\trequireApiKey();\n\t\tconst mappedEvent = mapTrackEventToV2(event);\n\t\t// Identity is required server-side (sessionId, externalUserId, or\n\t\t// visitorId, possibly derived from meta). Warn early: without it the\n\t\t// ingest API rejects the event even though enqueue returns an id here.\n\t\tif (\n\t\t\t!mappedEvent.correlation.sessionId &&\n\t\t\t!mappedEvent.correlation.externalUserId &&\n\t\t\t!mappedEvent.correlation.visitorId\n\t\t) {\n\t\t\tconsole.warn(\n\t\t\t\t`[waniwani] event \"${mappedEvent.name}\" has no sessionId, externalUserId, or visitorId; ` +\n\t\t\t\t\t\"the ingest API requires one and will reject it.\",\n\t\t\t);\n\t\t}\n\t\ttransport?.enqueue(mappedEvent);\n\t\treturn { eventId: mappedEvent.id };\n\t}\n\n\tconst trackOnce = async (event: TrackInput): Promise<{ eventId: string }> =>\n\t\temit(event);\n\t// Revenue helpers attach flat onto `track` (track.priceShown(), …).\n\tconst track: TrackFn = Object.assign(trackOnce, createRevenueApi(trackOnce));\n\n\tconst client: TrackingClient = {\n\t\tasync identify(\n\t\t\tuserId: string,\n\t\t\tproperties?: Record<string, unknown>,\n\t\t\tmeta?: Record<string, unknown>,\n\t\t): Promise<{ eventId: string }> {\n\t\t\trequireApiKey();\n\t\t\tconst mappedEvent = mapTrackEventToV2({\n\t\t\t\tevent: \"user.identified\",\n\t\t\t\texternalUserId: userId,\n\t\t\t\tproperties,\n\t\t\t\tmeta,\n\t\t\t});\n\t\t\ttransport?.enqueue(mappedEvent);\n\t\t\treturn { eventId: mappedEvent.id };\n\t\t},\n\t\ttrack,\n\t\tasync flush(): Promise<void> {\n\t\t\trequireApiKey();\n\t\t\tawait transport?.flush();\n\t\t},\n\t\tasync shutdown(options?: TrackingShutdownOptions) {\n\t\t\trequireApiKey();\n\t\t\treturn (\n\t\t\t\t(await transport?.shutdown({\n\t\t\t\t\ttimeoutMs: options?.timeoutMs ?? tracking.shutdownTimeoutMs,\n\t\t\t\t})) ?? { timedOut: false, pendingEvents: 0 }\n\t\t\t);\n\t\t},\n\t};\n\n\tif (transport) {\n\t\tattachShutdownHooks(client, tracking.shutdownTimeoutMs);\n\t}\n\treturn client;\n}\n\nfunction attachShutdownHooks(\n\tclient: TrackingClient,\n\tdefaultTimeoutMs: number,\n): void {\n\tif (\n\t\ttypeof process === \"undefined\" ||\n\t\ttypeof process.once !== \"function\" ||\n\t\ttypeof process.on !== \"function\"\n\t) {\n\t\treturn;\n\t}\n\n\tconst shutdown = () => {\n\t\tvoid client.shutdown({ timeoutMs: defaultTimeoutMs });\n\t};\n\n\tprocess.once(\"beforeExit\", shutdown);\n\tprocess.once(\"SIGINT\", shutdown);\n\tprocess.once(\"SIGTERM\", shutdown);\n}\n","// Waniwani SDK - Main Entry\n\nimport { createDocumentsClient } from \"./documents/client.js\";\nimport { createKbClient } from \"./kb/client.js\";\nimport {\n\tgetGlobalConfig,\n\tloadProjectConfig,\n\ttype WaniWaniProjectConfig,\n} from \"./project-config.js\";\nimport { createTrackingClient } from \"./tracking/index.js\";\nimport {\n\tDEFAULT_API_URL,\n\ttype WaniWaniClient,\n\ttype WaniWaniConfig,\n} from \"./types.js\";\n\n/**\n * Create a Waniwani SDK client\n *\n * @param config - Configuration options. When omitted, reads `waniwani.json`\n * from the current working directory, then falls back to env vars.\n * @returns A fully typed Waniwani client\n *\n * @example\n * ```typescript\n * import { waniwani } from \"@waniwani/sdk\";\n * import { toNextJsHandler } from \"@waniwani/sdk/next-js\";\n *\n * const wani = waniwani({ apiKey: \"...\" });\n *\n * // Next.js route handler\n * export const { GET, POST } = toNextJsHandler(wani, {\n * chat: { systemPrompt: \"You are a helpful assistant.\" },\n * });\n * ```\n */\nexport function waniwani(\n\tconfig?: WaniWaniConfig | WaniWaniProjectConfig,\n): WaniWaniClient {\n\tconst projectConfig = config ?? loadProjectConfig() ?? getGlobalConfig();\n\tconst effective = projectConfig as\n\t\t| (WaniWaniConfig & WaniWaniProjectConfig)\n\t\t| undefined;\n\n\tconst apiUrl =\n\t\teffective?.apiUrl ?? process.env.WANIWANI_API_URL ?? DEFAULT_API_URL;\n\tconst apiKey = effective?.apiKey ?? process.env.WANIWANI_API_KEY;\n\tconst trackingConfig = {\n\t\tendpointPath:\n\t\t\teffective?.tracking?.endpointPath ?? \"/api/mcp/events/v2/batch\",\n\t\tflushIntervalMs: effective?.tracking?.flushIntervalMs ?? 1_000,\n\t\tmaxBatchSize: effective?.tracking?.maxBatchSize ?? 20,\n\t\tmaxBufferSize: effective?.tracking?.maxBufferSize ?? 1_000,\n\t\tmaxRetries: effective?.tracking?.maxRetries ?? 3,\n\t\tretryBaseDelayMs: effective?.tracking?.retryBaseDelayMs ?? 200,\n\t\tretryMaxDelayMs: effective?.tracking?.retryMaxDelayMs ?? 2_000,\n\t\tshutdownTimeoutMs: effective?.tracking?.shutdownTimeoutMs ?? 2_000,\n\t};\n\n\tconst internalConfig = { apiUrl, apiKey, tracking: trackingConfig };\n\n\t// Compose client from modules\n\tconst trackingClient = createTrackingClient(internalConfig);\n\tconst kbClient = createKbClient(internalConfig);\n\tconst documentsClient = createDocumentsClient(internalConfig);\n\n\treturn {\n\t\t...trackingClient,\n\t\tkb: kbClient,\n\t\tdocuments: documentsClient,\n\t\t_config: internalConfig,\n\t};\n}\n","/**\n * Server-side API route handler for frontend tracking events.\n *\n * Receives V2 batch payloads from the SDK's frontend tracking client\n * (`createFrontendClient`, `useWaniwani`, `chat.track`) configured with your\n * own endpoint, and forwards them to the Waniwani backend with the secret\n * API key. Use this proxy when you do not want any Waniwani credential in\n * the browser; the direct path (public token or widget JWT) needs no route.\n *\n * @example Next.js App Router\n * ```typescript\n * // app/api/waniwani/track/route.ts\n * import { createTrackingRoute } from \"@waniwani/sdk/mcp\";\n *\n * const handler = createTrackingRoute({\n * apiKey: process.env.WANIWANI_API_KEY,\n * apiUrl: process.env.WANIWANI_API_URL,\n * });\n *\n * export { handler as POST };\n * ```\n */\n\nimport type { EventType, TrackInput } from \"../../tracking/@types.js\";\nimport type { V2EventEnvelope } from \"../../tracking/v2-types.js\";\nimport type { WaniWaniConfig } from \"../../types.js\";\nimport { waniwani } from \"../../waniwani.js\";\n\nexport interface TrackingRouteOptions {\n\t/** API key for the Waniwani backend. Defaults to WANIWANI_API_KEY env var. */\n\tapiKey?: string;\n\t/** Base URL for the Waniwani backend. Defaults to https://app.waniwani.ai. */\n\tapiUrl?: string;\n}\n\n/** Batch payload sent by the frontend tracking client (the V2 batch shape). */\ninterface BatchPayload {\n\tevents: Partial<V2EventEnvelope>[];\n\tsentAt?: string;\n}\n\n/**\n * Map an incoming V2 envelope back to the SDK's TrackInput so the server\n * client re-validates, re-maps, and batches it like any other event.\n */\nfunction mapEnvelope(envelope: Partial<V2EventEnvelope>): TrackInput {\n\treturn {\n\t\tevent: (envelope.name ?? \"widget_render\") as EventType,\n\t\tproperties: envelope.properties,\n\t\tmetadata: envelope.metadata,\n\t\tsessionId: envelope.correlation?.sessionId,\n\t\ttraceId: envelope.correlation?.traceId,\n\t\trequestId: envelope.correlation?.requestId,\n\t\tcorrelationId: envelope.correlation?.correlationId,\n\t\texternalUserId: envelope.correlation?.externalUserId,\n\t\tvisitorId: envelope.correlation?.visitorId,\n\t\teventId: envelope.id,\n\t\ttimestamp: envelope.timestamp,\n\t\tsource: envelope.source,\n\t} as TrackInput;\n}\n\n/**\n * Creates a POST handler that receives frontend tracking batches and\n * forwards them to the Waniwani backend.\n */\nexport function createTrackingRoute(options?: TrackingRouteOptions) {\n\tconst config: WaniWaniConfig = {\n\t\tapiKey: options?.apiKey,\n\t\tapiUrl: options?.apiUrl,\n\t};\n\n\t// Lazy singleton — created on first request\n\tlet client: ReturnType<typeof waniwani> | undefined;\n\n\tfunction getClient() {\n\t\tif (!client) {\n\t\t\tclient = waniwani(config);\n\t\t}\n\t\treturn client;\n\t}\n\n\treturn async function handler(request: Request): Promise<Response> {\n\t\tlet body: BatchPayload;\n\t\ttry {\n\t\t\tbody = (await request.json()) as BatchPayload;\n\t\t} catch {\n\t\t\treturn new Response(JSON.stringify({ error: \"Invalid JSON\" }), {\n\t\t\t\tstatus: 400,\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t});\n\t\t}\n\n\t\tif (!Array.isArray(body.events) || body.events.length === 0) {\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({ error: \"Missing or empty events array\" }),\n\t\t\t\t{\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\tconst c = getClient();\n\t\t\tconst results: string[] = [];\n\n\t\t\tfor (const envelope of body.events) {\n\t\t\t\tconst result = await c.track(mapEnvelope(envelope));\n\t\t\t\tresults.push(result.eventId);\n\t\t\t}\n\n\t\t\tawait c.flush();\n\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({ ok: true, accepted: results.length }),\n\t\t\t\t{\n\t\t\t\t\tstatus: 200,\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t},\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"Unknown error\";\n\t\t\treturn new Response(JSON.stringify({ error: message }), {\n\t\t\t\tstatus: 500,\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t});\n\t\t}\n\t};\n}\n","/**\n * Widget token minting — fetches short-lived JWTs from the Waniwani backend\n * so browser widgets can POST events directly.\n */\n\nimport { createLogger } from \"../../utils/logger.js\";\n\nconst log = createLogger(\"widget-token\");\n\ninterface WidgetTokenConfig {\n\tapiUrl: string;\n\tapiKey: string;\n}\n\ninterface WidgetTokenResult {\n\ttoken: string;\n\texpiresAt: string;\n}\n\ninterface CachedToken {\n\ttoken: string;\n\t/** Unix ms when the token expires */\n\texpiresAt: number;\n}\n\n/** Re-mint when < 2 minutes remain to avoid using nearly-expired tokens. */\nconst EXPIRY_BUFFER_MS = 2 * 60 * 1000;\n\nexport class WidgetTokenCache {\n\tprivate cached: CachedToken | null = null;\n\tprivate pending: Promise<string | null> | null = null;\n\tprivate readonly config: WidgetTokenConfig;\n\n\tconstructor(config: WidgetTokenConfig) {\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * Get a valid widget token. Returns a cached token if still fresh,\n\t * otherwise mints a new one. Returns `null` on failure.\n\t */\n\tasync getToken(sessionId?: string, traceId?: string): Promise<string | null> {\n\t\tif (this.cached && Date.now() < this.cached.expiresAt - EXPIRY_BUFFER_MS) {\n\t\t\treturn this.cached.token;\n\t\t}\n\n\t\t// Deduplicate concurrent requests\n\t\tif (this.pending) {\n\t\t\treturn this.pending;\n\t\t}\n\n\t\tthis.pending = this.mint(sessionId, traceId).finally(() => {\n\t\t\tthis.pending = null;\n\t\t});\n\n\t\treturn this.pending;\n\t}\n\n\tprivate async mint(\n\t\tsessionId?: string,\n\t\ttraceId?: string,\n\t): Promise<string | null> {\n\t\tconst url = joinUrl(this.config.apiUrl, \"/api/mcp/widget-tokens\");\n\t\tlog(\"minting token from\", url);\n\n\t\tconst body: Record<string, string> = {};\n\t\tif (sessionId) {\n\t\t\tbody.sessionId = sessionId;\n\t\t}\n\t\tif (traceId) {\n\t\t\tbody.traceId = traceId;\n\t\t}\n\n\t\ttry {\n\t\t\tconst response = await fetch(url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\tAuthorization: `Bearer ${this.config.apiKey}`,\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\tsignal: AbortSignal.timeout(5_000),\n\t\t\t});\n\t\t\tlog(\"mint response:\", response.status);\n\n\t\t\tif (!response.ok) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst json = (await response.json()) as Record<string, unknown>;\n\n\t\t\t// Support both flat { token, expiresAt } and nested { data: { token, expiresAt } }\n\t\t\tconst result = (\n\t\t\t\tjson.data && typeof json.data === \"object\" ? json.data : json\n\t\t\t) as WidgetTokenResult;\n\n\t\t\tconst expiresAtMs = new Date(result.expiresAt).getTime();\n\t\t\tif (!result.token || Number.isNaN(expiresAtMs)) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tthis.cached = {\n\t\t\t\ttoken: result.token,\n\t\t\t\texpiresAt: expiresAtMs,\n\t\t\t};\n\n\t\t\treturn result.token;\n\t\t} catch (error) {\n\t\t\tlog(\"mint failed:\", error);\n\t\t\treturn null;\n\t\t}\n\t}\n}\n\nfunction joinUrl(apiUrl: string, path: string): string {\n\tconst base = apiUrl.endsWith(\"/\") ? apiUrl.slice(0, -1) : apiUrl;\n\treturn `${base}${path}`;\n}\n","import type { FlowGraph } from \"../flows/@types\";\n\nasync function hashData(data: string): Promise<string> {\n\tif (typeof globalThis.crypto?.subtle?.digest === \"function\") {\n\t\tconst buf = await crypto.subtle.digest(\n\t\t\t\"SHA-256\",\n\t\t\tnew TextEncoder().encode(data),\n\t\t);\n\t\treturn Array.from(new Uint8Array(buf))\n\t\t\t.map((b) => b.toString(16).padStart(2, \"0\"))\n\t\t\t.join(\"\");\n\t}\n\tlet hash = 0;\n\tfor (let i = 0; i < data.length; i++) {\n\t\thash = ((hash << 5) - hash + data.charCodeAt(i)) | 0;\n\t}\n\treturn `simple-${Math.abs(hash).toString(36)}`;\n}\n\nfunction hashGraph(graph: FlowGraph): Promise<string> {\n\treturn hashData(JSON.stringify({ nodes: graph.nodes, edges: graph.edges }));\n}\n\nexport type FunnelSyncFlow = {\n\tflowId: string;\n\ttitle: string;\n\tconfigHash: string;\n\tnodes: FlowGraph[\"nodes\"];\n\tedges: FlowGraph[\"edges\"];\n};\n\nexport type FunnelSyncPayload = {\n\tcompositeHash: string;\n\tflows: FunnelSyncFlow[];\n};\n\nexport async function prepareFunnelSyncPayload(\n\tflowGraphs: FlowGraph[],\n): Promise<FunnelSyncPayload | null> {\n\tif (flowGraphs.length === 0) {\n\t\treturn null;\n\t}\n\n\tconst sorted = [...flowGraphs].sort((a, b) =>\n\t\ta.flowId.localeCompare(b.flowId),\n\t);\n\n\tconst compositeHash = await hashData(\n\t\tJSON.stringify(\n\t\t\tsorted.map((fg) => ({\n\t\t\t\tflowId: fg.flowId,\n\t\t\t\tnodes: fg.nodes,\n\t\t\t\tedges: fg.edges,\n\t\t\t})),\n\t\t),\n\t);\n\n\tconst flows = await Promise.all(\n\t\tflowGraphs.map(async (fg) => ({\n\t\t\tflowId: fg.flowId,\n\t\t\ttitle: fg.title,\n\t\t\tconfigHash: await hashGraph(fg),\n\t\t\tnodes: fg.nodes,\n\t\t\tedges: fg.edges,\n\t\t})),\n\t);\n\n\treturn { compositeHash, flows };\n}\n","import type { KbSearchTrace } from \"../../../kb/types.js\";\nimport type { ErrorCauseType } from \"../../../tracking/@types.js\";\nimport type {\n\tCallableTrack,\n\tToolCalledProperties,\n\tTrackInput,\n} from \"../../../tracking/index.js\";\nimport type { WaniWaniClient } from \"../../../types.js\";\nimport {\n\textractSessionId,\n\textractSource,\n\tFLOW_META_KEY,\n\tWIDGET_CONFIG_META_KEY,\n} from \"../utils.js\";\nimport type { WidgetTokenCache } from \"../widget-token.js\";\nimport type { FunnelSyncPayload } from \"./funnel-sync.js\";\n\ntype UnknownRecord = Record<string, unknown>;\n\nexport type WaniwaniTracker = Pick<\n\tWaniWaniClient,\n\t\"flush\" | \"identify\" | \"kb\" | \"_config\"\n> & {\n\t// with-waniwani only emits generic events, so a custom tracker need only\n\t// provide the callable `track` — not the flat `track.*` revenue helpers.\n\ttrack: CallableTrack;\n};\n\nconst SESSION_ID_KEY = \"waniwani/sessionId\";\nconst GEO_LOCATION_KEY = \"waniwani/geoLocation\";\nconst LEGACY_USER_LOCATION_KEY = \"waniwani/userLocation\";\nconst OPENAI_USER_LOCATION_KEY = \"openai/userLocation\";\n\nconst LOCATION_META_KEYS = [\n\tOPENAI_USER_LOCATION_KEY,\n\tGEO_LOCATION_KEY,\n\tLEGACY_USER_LOCATION_KEY,\n] as const;\n\nexport function stripLocationFieldsFromMeta(\n\tmeta: UnknownRecord,\n\tfields: readonly string[],\n): UnknownRecord {\n\tif (fields.length === 0) {\n\t\treturn meta;\n\t}\n\tlet next: UnknownRecord | undefined;\n\tfor (const key of LOCATION_META_KEYS) {\n\t\tconst value = meta[key];\n\t\tif (!isRecord(value)) {\n\t\t\tcontinue;\n\t\t}\n\t\tlet stripped: UnknownRecord | undefined;\n\t\tfor (const field of fields) {\n\t\t\tif (field in value) {\n\t\t\t\tif (!stripped) {\n\t\t\t\t\tstripped = { ...value };\n\t\t\t\t}\n\t\t\t\tdelete stripped[field];\n\t\t\t}\n\t\t}\n\t\tif (stripped) {\n\t\t\tif (!next) {\n\t\t\t\tnext = { ...meta };\n\t\t\t}\n\t\t\tnext[key] = stripped;\n\t\t}\n\t}\n\treturn next ?? meta;\n}\n\nexport function isRecord(value: unknown): value is UnknownRecord {\n\treturn Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nexport function extractMeta(extra: unknown): UnknownRecord | undefined {\n\tif (!isRecord(extra)) {\n\t\treturn undefined;\n\t}\n\tconst meta = extra._meta;\n\treturn isRecord(meta) ? meta : undefined;\n}\n\nexport function extractErrorText(result: unknown): string | undefined {\n\tif (!isRecord(result)) {\n\t\treturn undefined;\n\t}\n\tconst content = (result as UnknownRecord).content;\n\tif (!Array.isArray(content)) {\n\t\treturn undefined;\n\t}\n\tconst textPart = content.find(\n\t\t(c: unknown) =>\n\t\t\tisRecord(c) && c.type === \"text\" && typeof c.text === \"string\",\n\t) as UnknownRecord | undefined;\n\treturn textPart?.text as string | undefined;\n}\n\nexport function resolveToolType(\n\ttoolName: string,\n\ttoolTypeOption:\n\t\t| ToolCalledProperties[\"type\"]\n\t\t| ((toolName: string) => ToolCalledProperties[\"type\"] | undefined)\n\t\t| undefined,\n): ToolCalledProperties[\"type\"] {\n\tif (typeof toolTypeOption === \"function\") {\n\t\treturn toolTypeOption(toolName) ?? \"other\";\n\t}\n\treturn toolTypeOption ?? \"other\";\n}\n\nexport function buildTrackInput(\n\ttoolName: string,\n\textra: unknown,\n\toptions: {\n\t\ttoolType?: typeof resolveToolType extends (n: string, o: infer T) => unknown\n\t\t\t? T\n\t\t\t: never;\n\t\tmetadata?: UnknownRecord;\n\t\tstripLocationFields?: readonly string[];\n\t\tredactInput?: (input: unknown) => unknown;\n\t\tfunnelSync?: FunnelSyncPayload | null;\n\t},\n\ttiming?: {\n\t\tdurationMs: number;\n\t\tstatus: string;\n\t\terrorMessage?: string;\n\t\tcause?: ErrorCauseType;\n\t},\n\tclientInfo?: { name: string; version: string },\n\tio?: { input?: unknown; output?: unknown },\n\tkbSearch?: KbSearchTrace[],\n): TrackInput {\n\tconst toolType = resolveToolType(toolName, options.toolType);\n\n\tconst stripFields = options.stripLocationFields;\n\tconst shouldStrip = stripFields && stripFields.length > 0;\n\n\tconst rawMeta = extractMeta(extra);\n\tconst meta =\n\t\trawMeta && shouldStrip\n\t\t\t? stripLocationFieldsFromMeta(rawMeta, stripFields)\n\t\t\t: rawMeta;\n\n\tconst input =\n\t\tio?.input !== undefined && options.redactInput\n\t\t\t? options.redactInput(io.input)\n\t\t\t: io?.input;\n\n\tconst output =\n\t\tshouldStrip && isRecord(io?.output) && isRecord(io.output._meta)\n\t\t\t? {\n\t\t\t\t\t...(io.output as UnknownRecord),\n\t\t\t\t\t_meta: stripLocationFieldsFromMeta(\n\t\t\t\t\t\tio.output._meta as UnknownRecord,\n\t\t\t\t\t\tstripFields,\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t: io?.output;\n\n\tconst responseMeta =\n\t\tisRecord(io?.output) && isRecord((io.output as UnknownRecord)._meta)\n\t\t\t? ((io.output as UnknownRecord)._meta as UnknownRecord)\n\t\t\t: undefined;\n\tconst flowMeta = responseMeta?.[FLOW_META_KEY];\n\tconst baseMeta =\n\t\tflowMeta && meta\n\t\t\t? { ...meta, [FLOW_META_KEY]: flowMeta }\n\t\t\t: flowMeta\n\t\t\t\t? { [FLOW_META_KEY]: flowMeta }\n\t\t\t\t: meta;\n\n\t// Correlate to the flow session when the host carried none in `_meta` but the\n\t// caller forwarded a `sessionId` argument. On stateless hosts (Claude) widget\n\t// tool calls thread the flow session id through the LLM as a tool arg, so\n\t// without this their `tool.called` events would land in a sessionless bucket\n\t// instead of the originating flow session. This only enriches the tracked\n\t// event's meta — it does not touch `extra._meta`, so handler/flow behavior\n\t// (e.g. the flow's sessionId-echo logic) is unaffected.\n\tconst argSessionId =\n\t\tisRecord(io?.input) &&\n\t\ttypeof (io.input as UnknownRecord).sessionId === \"string\" &&\n\t\t((io.input as UnknownRecord).sessionId as string).length > 0\n\t\t\t? ((io.input as UnknownRecord).sessionId as string)\n\t\t\t: undefined;\n\tconst mergedMeta =\n\t\targSessionId && !extractSessionId(baseMeta ?? undefined)\n\t\t\t? { ...(baseMeta ?? {}), \"waniwani/sessionId\": argSessionId }\n\t\t\t: baseMeta;\n\n\treturn {\n\t\tevent: \"tool.called\",\n\t\tproperties: {\n\t\t\tname: toolName,\n\t\t\ttype: toolType,\n\t\t\t...(timing ?? {}),\n\t\t\t...(input !== undefined && { input }),\n\t\t\t...(output !== undefined && { output }),\n\t\t\t...(kbSearch && kbSearch.length > 0 && { kbSearch }),\n\t\t},\n\t\tmeta: mergedMeta,\n\t\tsource: extractSource(mergedMeta ?? meta, clientInfo),\n\t\tmetadata: {\n\t\t\t...(options.metadata ?? {}),\n\t\t\t...(clientInfo && { clientInfo }),\n\t\t\t...(options.funnelSync && { funnelSync: options.funnelSync }),\n\t\t},\n\t};\n}\n\nexport async function safeTrack(\n\ttracker: { track: CallableTrack },\n\tinput: TrackInput,\n\tonError?: (error: Error) => void,\n): Promise<void> {\n\ttry {\n\t\tawait tracker.track(input);\n\t} catch (error) {\n\t\tonError?.(toError(error));\n\t}\n}\n\nexport async function safeFlush(\n\ttracker: Pick<WaniWaniClient, \"flush\">,\n\tonError?: (error: Error) => void,\n): Promise<void> {\n\ttry {\n\t\tawait tracker.flush();\n\t} catch (error) {\n\t\tonError?.(toError(error));\n\t}\n}\n\nexport async function injectWidgetConfig(\n\tresult: unknown,\n\tcache: WidgetTokenCache | null,\n\tapiUrl: string,\n\textra?: unknown,\n\tonError?: (error: Error) => void,\n\tclientInfo?: { name?: string; version?: string },\n): Promise<void> {\n\tif (!isRecord(result)) {\n\t\treturn;\n\t}\n\n\tif (!isRecord(result._meta)) {\n\t\t(result as UnknownRecord)._meta = {};\n\t}\n\n\tconst meta = (result as UnknownRecord)._meta as UnknownRecord;\n\tconst existingWaniwaniConfig = isRecord(meta[WIDGET_CONFIG_META_KEY])\n\t\t? (meta[WIDGET_CONFIG_META_KEY] as UnknownRecord)\n\t\t: undefined;\n\tconst waniwaniConfig: UnknownRecord = {\n\t\t...(existingWaniwaniConfig ?? {}),\n\t\tendpoint:\n\t\t\texistingWaniwaniConfig?.endpoint ??\n\t\t\t`${apiUrl.replace(/\\/$/, \"\")}/api/mcp/events/v2/batch`,\n\t};\n\n\tif (cache) {\n\t\ttry {\n\t\t\tconst token = await cache.getToken();\n\t\t\tif (token) {\n\t\t\t\twaniwaniConfig.token = token;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tonError?.(toError(error));\n\t\t}\n\t}\n\n\tconst sessionId = extractSessionId(meta);\n\tif (sessionId) {\n\t\tif (!waniwaniConfig.sessionId) {\n\t\t\twaniwaniConfig.sessionId = sessionId;\n\t\t}\n\t}\n\n\tconst geoLocation = extractGeoLocation(meta);\n\tif (geoLocation !== undefined) {\n\t\tif (!waniwaniConfig.geoLocation) {\n\t\t\twaniwaniConfig.geoLocation = geoLocation;\n\t\t}\n\t}\n\n\tconst source = extractSource(extractMeta(extra), clientInfo);\n\tif (source && !waniwaniConfig.source) {\n\t\twaniwaniConfig.source = source;\n\t}\n\n\tmeta[WIDGET_CONFIG_META_KEY] = waniwaniConfig;\n}\n\n/**\n * Widget-related keys from a tool's **definition** `_meta` that the chat UI\n * and MCP hosts look for on tool call **results**. The MCP protocol exposes\n * these in `tools/list`, but chat clients that stream results via AI SDK\n * proxies (e.g. `@ai-sdk/mcp`) don't forward definition metadata to the\n * UI layer — the chat UI only sees the result's `_meta`.\n *\n * Forwarding these keys into every tool result makes widgets registered via\n * any MCP framework (skybridge, raw `@modelcontextprotocol/sdk`, etc.) render\n * in Waniwani chat without the handler having to set them manually.\n *\n * Keys:\n * - `openai/outputTemplate` — OpenAI Apps SDK widget URI (ChatGPT).\n * - `ui/resourceUri` — MCP Apps extension flat-key form (Claude, per spec).\n * - `ui` — MCP Apps extension nested form `{ resourceUri, autoHeight, ... }`.\n * - `openai/widgetAccessible`, `openai/resultCanProduceWidget`,\n * `openai/toolInvocation/invoking`, `openai/toolInvocation/invoked` —\n * additional OpenAI metadata widgets depend on.\n */\nconst WIDGET_META_KEYS = [\n\t\"openai/outputTemplate\",\n\t\"openai/widgetAccessible\",\n\t\"openai/resultCanProduceWidget\",\n\t\"openai/toolInvocation/invoking\",\n\t\"openai/toolInvocation/invoked\",\n\t\"ui/resourceUri\",\n\t\"ui\",\n] as const;\n\nexport function injectWidgetDefinitionMeta(\n\tresult: unknown,\n\tdefinitionMeta: UnknownRecord | undefined,\n): void {\n\tif (!definitionMeta || !isRecord(result)) {\n\t\treturn;\n\t}\n\n\tlet hasAnyKey = false;\n\tfor (const key of WIDGET_META_KEYS) {\n\t\tif (key in definitionMeta) {\n\t\t\thasAnyKey = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (!hasAnyKey) {\n\t\treturn;\n\t}\n\n\tif (!isRecord(result._meta)) {\n\t\t(result as UnknownRecord)._meta = {};\n\t}\n\tconst resultMeta = (result as UnknownRecord)._meta as UnknownRecord;\n\n\tfor (const key of WIDGET_META_KEYS) {\n\t\tif (!(key in definitionMeta)) {\n\t\t\tcontinue;\n\t\t}\n\t\t// Handler-set values win: never overwrite something the tool returned.\n\t\tif (key in resultMeta) {\n\t\t\tcontinue;\n\t\t}\n\t\tresultMeta[key] = definitionMeta[key];\n\t}\n}\n\nexport function injectRequestMetadata(result: unknown, extra: unknown): void {\n\tconst requestMeta = extractMeta(extra);\n\tif (!requestMeta) {\n\t\treturn;\n\t}\n\n\tif (!isRecord(result)) {\n\t\treturn;\n\t}\n\n\tif (!isRecord(result._meta)) {\n\t\t(result as UnknownRecord)._meta = {};\n\t}\n\n\tconst resultMeta = (result as UnknownRecord)._meta as UnknownRecord;\n\tconst sessionId = extractSessionId(requestMeta);\n\tif (sessionId && !resultMeta[SESSION_ID_KEY]) {\n\t\tresultMeta[SESSION_ID_KEY] = sessionId;\n\t}\n\n\tconst geoLocation = extractGeoLocation(requestMeta);\n\tif (!geoLocation) {\n\t\treturn;\n\t}\n\n\tif (!resultMeta[GEO_LOCATION_KEY]) {\n\t\tresultMeta[GEO_LOCATION_KEY] = geoLocation;\n\t}\n\n\tif (!resultMeta[LEGACY_USER_LOCATION_KEY]) {\n\t\tresultMeta[LEGACY_USER_LOCATION_KEY] = geoLocation;\n\t}\n}\n\nfunction extractGeoLocation(\n\tmeta: UnknownRecord | undefined,\n): UnknownRecord | string | undefined {\n\tif (!meta) {\n\t\treturn undefined;\n\t}\n\n\tconst geoLocation = meta[GEO_LOCATION_KEY] ?? meta[LEGACY_USER_LOCATION_KEY];\n\tif (isRecord(geoLocation) || typeof geoLocation === \"string\") {\n\t\treturn geoLocation;\n\t}\n\n\treturn undefined;\n}\n\nfunction toError(error: unknown): Error {\n\tif (error instanceof Error) {\n\t\treturn error;\n\t}\n\treturn new Error(String(error));\n}\n","type UnknownRecord = Record<string, unknown>;\n\nfunction isRecord(v: unknown): v is UnknownRecord {\n\treturn typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\n/**\n * Extract session ID from MCP transport metadata (`extra.sessionId`),\n * the raw `Mcp-Session-Id` HTTP header, or the `X-Waniwani-Session-Id`\n * header (set by stateless serverless deployments).\n */\nexport function extractTransportSessionId(\n\textra: UnknownRecord,\n): string | undefined {\n\tif (typeof extra.sessionId === \"string\" && extra.sessionId) {\n\t\treturn extra.sessionId;\n\t}\n\n\tif (isRecord(extra.requestInfo)) {\n\t\tconst rawHeaders = (extra.requestInfo as UnknownRecord).headers;\n\t\tif (isRecord(rawHeaders)) {\n\t\t\t// Normalize keys to lowercase — HTTP headers are case-insensitive\n\t\t\t// but the transport may preserve original casing (e.g. \"Mcp-Session-Id\").\n\t\t\tconst headers: UnknownRecord = {};\n\t\t\tfor (const key of Object.keys(rawHeaders)) {\n\t\t\t\theaders[key.toLowerCase()] = rawHeaders[key];\n\t\t\t}\n\t\t\tconst sid = headers[\"mcp-session-id\"];\n\t\t\tif (typeof sid === \"string\" && sid) {\n\t\t\t\treturn sid;\n\t\t\t}\n\t\t\tconst waniSid = headers[\"x-waniwani-session-id\"];\n\t\t\tif (typeof waniSid === \"string\" && waniSid) {\n\t\t\t\treturn waniSid;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn undefined;\n}\n","import {\n\ttype RetrievalCollector,\n\tretrievalCollectorStore,\n} from \"../../../kb/retrieval-context.js\";\nimport type { ToolCalledProperties } from \"../../../tracking/index.js\";\nimport { createLogger } from \"../../../utils/logger.js\";\nimport { waniwani } from \"../../../waniwani.js\";\nimport type { FlowGraph } from \"../flows/@types.js\";\nimport { REDACTED_STATE_UPDATE_FIELDS_META_KEY } from \"../flows/redacted.js\";\nimport { createScopedClient, SCOPED_CLIENT_KEY } from \"../scoped-client.js\";\nimport { classifyCause } from \"../session-errors/classify.js\";\nimport type { McpServer } from \"../types\";\nimport {\n\textractSessionId,\n\textractSource,\n\textractSourceFromHeaders,\n} from \"../utils.js\";\nimport { WidgetTokenCache } from \"../widget-token.js\";\nimport type { FunnelSyncPayload } from \"./funnel-sync.js\";\nimport { prepareFunnelSyncPayload } from \"./funnel-sync.js\";\nimport {\n\tbuildTrackInput,\n\textractErrorText,\n\textractMeta,\n\tinjectRequestMetadata,\n\tinjectWidgetConfig,\n\tinjectWidgetDefinitionMeta,\n\tisRecord,\n\tsafeFlush,\n\tsafeTrack,\n\ttype WaniwaniTracker,\n} from \"./helpers.js\";\nimport { extractTransportSessionId } from \"./transport-session.js\";\n\ntype UnknownRecord = Record<string, unknown>;\ntype RawHandler = (\n\tinput: unknown,\n\textra: unknown,\n) => Promise<unknown> | unknown;\n\ntype WrappedServer = McpServer & {\n\t__waniwaniWrapped?: true;\n};\n\nconst WRAPPED_HANDLER = Symbol.for(\"waniwani.wrappedHandler\");\n\ntype MaybeWrappedHandler = RawHandler & { [WRAPPED_HANDLER]?: true };\n\n/**\n * Options for withWaniwani().\n */\nexport type WithWaniwaniOptions = {\n\t/**\n\t * The Waniwani client instance. When omitted, a client is created\n\t * automatically from `waniwani.json` / the global config registered by\n\t * `defineConfig()`, falling back to env vars (`WANIWANI_API_KEY` and\n\t * `WANIWANI_API_URL`). Set `WANIWANI_API_URL` (e.g.\n\t * `https://eu.app.waniwani.ai`) so the auto-created client targets the\n\t * right region instead of defaulting to US.\n\t */\n\tclient?: WaniwaniTracker;\n\t/**\n\t * Optional explicit tool type. Defaults to `\"other\"`.\n\t */\n\ttoolType?:\n\t\t| ToolCalledProperties[\"type\"]\n\t\t| ((toolName: string) => ToolCalledProperties[\"type\"] | undefined);\n\t/**\n\t * Optional metadata merged into every tracked event.\n\t */\n\tmetadata?: UnknownRecord;\n\t/**\n\t * Flush tracking transport after each tool call.\n\t */\n\tflushAfterToolCall?: boolean;\n\t/**\n\t * Optional error callback for non-fatal tracking errors.\n\t */\n\tonError?: (error: Error) => void;\n\t/**\n\t * Inject widget tracking config into tool response `_meta.waniwani` so browser\n\t * widgets can send events directly to the Waniwani backend.\n\t *\n\t * Always injects `endpoint`. Injects `token` when an API key is configured\n\t * and token minting succeeds.\n\t *\n\t * @default true\n\t */\n\tinjectWidgetToken?: boolean;\n\t/**\n\t * List of field names to strip from known location `_meta` entries\n\t * (`openai/userLocation`, `waniwani/geoLocation`, `waniwani/userLocation`)\n\t * before events are sent to the Waniwani API. Applied to both the\n\t * request-level `_meta` and any `_meta` on the tool response.\n\t *\n\t * Pass e.g. `[\"latitude\", \"longitude\"]` to drop coordinates only, or\n\t * `[\"latitude\", \"longitude\", \"city\", \"region\"]` to keep just `country`.\n\t * Empty/omitted = no redaction.\n\t *\n\t * @default []\n\t */\n\tstripLocationFields?: readonly string[];\n\t/**\n\t * Replace `input.stateUpdates[field]` with `\"REDACTED\"` for any field\n\t * marked via `redacted()` on a flow state schema. When `false` (default),\n\t * the declarative markers are ignored and raw values are tracked.\n\t *\n\t * Wire this to an env var when you want real values in development logs\n\t * but redacted values in production.\n\t *\n\t * @default false\n\t */\n\tapplyFieldRedactions?: boolean;\n};\n\nconst log = createLogger(\"mcp\");\n\nconst DEFAULT_BASE_URL = \"https://app.waniwani.ai\";\n\nconst REDACTED_VALUE = \"REDACTED\";\n\nfunction buildStateUpdateRedactor(\n\tdefinitionMeta: UnknownRecord | undefined,\n): ((input: unknown) => unknown) | undefined {\n\tif (!definitionMeta) {\n\t\treturn undefined;\n\t}\n\tconst fields = definitionMeta[REDACTED_STATE_UPDATE_FIELDS_META_KEY];\n\tif (!Array.isArray(fields) || fields.length === 0) {\n\t\treturn undefined;\n\t}\n\tconst fieldSet = new Set(\n\t\tfields.filter((f): f is string => typeof f === \"string\"),\n\t);\n\tif (fieldSet.size === 0) {\n\t\treturn undefined;\n\t}\n\n\treturn (input: unknown) => {\n\t\tif (!isRecord(input)) {\n\t\t\treturn input;\n\t\t}\n\t\tconst stateUpdates = input.stateUpdates;\n\t\tif (!isRecord(stateUpdates)) {\n\t\t\treturn input;\n\t\t}\n\t\tlet changed = false;\n\t\tconst next: UnknownRecord = { ...stateUpdates };\n\t\tfor (const field of fieldSet) {\n\t\t\tif (field in next) {\n\t\t\t\tnext[field] = REDACTED_VALUE;\n\t\t\t\tchanged = true;\n\t\t\t}\n\t\t}\n\t\tif (!changed) {\n\t\t\treturn input;\n\t\t}\n\t\treturn { ...input, stateUpdates: next };\n\t};\n}\n\ntype WrapContext = {\n\tserver: McpServer;\n\ttracker: WaniwaniTracker;\n\topts: WithWaniwaniOptions;\n\ttokenCache: WidgetTokenCache | null;\n\tinjectToken: boolean;\n\tfunnelSync: FunnelSyncPayload | null;\n};\n\ntype UnknownRecordOrUndefined = UnknownRecord | undefined;\n\nfunction createWrappedHandler(\n\ttoolName: string,\n\toriginalHandler: RawHandler,\n\tctx: WrapContext,\n\tdefinitionMeta: UnknownRecordOrUndefined,\n): MaybeWrappedHandler {\n\tconst { server, tracker, opts, tokenCache, injectToken } = ctx;\n\n\tconst stateUpdateRedactor =\n\t\topts.applyFieldRedactions === true\n\t\t\t? buildStateUpdateRedactor(definitionMeta)\n\t\t\t: undefined;\n\tconst wrappedHandler: MaybeWrappedHandler = async (\n\t\tinput: unknown,\n\t\textra: unknown,\n\t) => {\n\t\tconst effectiveOpts = stateUpdateRedactor\n\t\t\t? {\n\t\t\t\t\t...opts,\n\t\t\t\t\tredactInput: stateUpdateRedactor,\n\t\t\t\t\tfunnelSync: ctx.funnelSync,\n\t\t\t\t}\n\t\t\t: { ...opts, funnelSync: ctx.funnelSync };\n\t\t// Inject scoped client into extra so createTool/flows can surface it\n\t\tconst meta = extractMeta(extra) ?? {};\n\n\t\tconst clientInfo = (\n\t\t\tserver as {\n\t\t\t\tserver?: {\n\t\t\t\t\tgetClientVersion?: () =>\n\t\t\t\t\t\t| { name: string; version: string }\n\t\t\t\t\t\t| undefined;\n\t\t\t\t};\n\t\t\t}\n\t\t).server?.getClientVersion?.();\n\n\t\t// Bridge transport-level session ID into _meta when the host doesn't\n\t\t// include one directly (e.g. Mcp-Session-Id HTTP header).\n\t\tconst existingSessionId = extractSessionId(meta);\n\t\tif (!existingSessionId && isRecord(extra)) {\n\t\t\tconst transportSid = extractTransportSessionId(extra as UnknownRecord);\n\t\t\tif (transportSid) {\n\t\t\t\tmeta[\"waniwani/sessionId\"] = transportSid;\n\t\t\t\t(extra as UnknownRecord)._meta = meta;\n\t\t\t}\n\t\t}\n\n\t\t// Resolve and stamp the caller source into _meta once, so downstream\n\t\t// consumers (flow nodes, nested tool handlers, tracking) can branch on\n\t\t// `waniwani/source` without each re-deriving it from clientInfo/headers.\n\t\t// Hosts like Claude carry no source in _meta and no transport session id;\n\t\t// clientInfo (MCP initialize) and the request headers are the only signals.\n\t\tif (!extractSource(meta) && isRecord(extra)) {\n\t\t\tconst headers = (extra as { requestInfo?: { headers?: unknown } })\n\t\t\t\t.requestInfo?.headers as Record<string, unknown> | undefined;\n\t\t\tconst resolvedSource =\n\t\t\t\textractSource(meta, clientInfo) ?? extractSourceFromHeaders(headers);\n\t\t\tif (resolvedSource) {\n\t\t\t\tmeta[\"waniwani/source\"] = resolvedSource;\n\t\t\t\t(extra as UnknownRecord)._meta = meta;\n\t\t\t}\n\t\t}\n\n\t\tconst scopedClient = createScopedClient(tracker, meta, {\n\t\t\tapiUrl: tracker._config.apiUrl,\n\t\t\tapiKey: tracker._config.apiKey,\n\t\t});\n\t\tif (isRecord(extra)) {\n\t\t\textra[SCOPED_CLIENT_KEY] = scopedClient;\n\t\t}\n\n\t\tconst retrievalCollector: RetrievalCollector = { searches: [] };\n\t\tconst startTime = performance.now();\n\t\ttry {\n\t\t\tconst result = await retrievalCollectorStore.run(retrievalCollector, () =>\n\t\t\t\toriginalHandler(input, extra),\n\t\t\t);\n\t\t\tconst durationMs = Math.round(performance.now() - startTime);\n\n\t\t\tlog(\n\t\t\t\t`tool \"${toolName}\" handler returned in ${durationMs}ms, running post-processing...`,\n\t\t\t);\n\n\t\t\tconst isErrorResult =\n\t\t\t\tisRecord(result) && (result as UnknownRecord).isError === true;\n\n\t\t\tif (isErrorResult) {\n\t\t\t\tconst errorText = extractErrorText(result);\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[waniwani] Tool \"${toolName}\" returned error${errorText ? `: ${errorText}` : \"\"}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tawait safeTrack(\n\t\t\t\ttracker,\n\t\t\t\tbuildTrackInput(\n\t\t\t\t\ttoolName,\n\t\t\t\t\textra,\n\t\t\t\t\teffectiveOpts,\n\t\t\t\t\t{\n\t\t\t\t\t\tdurationMs,\n\t\t\t\t\t\tstatus: isErrorResult ? \"error\" : \"ok\",\n\t\t\t\t\t\t...(isErrorResult && {\n\t\t\t\t\t\t\terrorMessage: extractErrorText(result) ?? \"Unknown tool error\",\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t\tclientInfo,\n\t\t\t\t\t{ input, output: result },\n\t\t\t\t\tretrievalCollector.searches,\n\t\t\t\t),\n\t\t\t\topts.onError,\n\t\t\t);\n\n\t\t\tlog(`tool \"${toolName}\" tracking done`);\n\n\t\t\tif (opts.flushAfterToolCall) {\n\t\t\t\tawait safeFlush(tracker, opts.onError);\n\t\t\t}\n\n\t\t\tinjectRequestMetadata(result, extra);\n\t\t\tinjectWidgetDefinitionMeta(result, definitionMeta);\n\n\t\t\tif (injectToken) {\n\t\t\t\tawait injectWidgetConfig(\n\t\t\t\t\tresult,\n\t\t\t\t\ttokenCache,\n\t\t\t\t\ttracker._config.apiUrl ?? DEFAULT_BASE_URL,\n\t\t\t\t\textra,\n\t\t\t\t\topts.onError,\n\t\t\t\t\tclientInfo,\n\t\t\t\t);\n\t\t\t\tlog(`tool \"${toolName}\" widget config injected`);\n\t\t\t}\n\n\t\t\tlog(`tool \"${toolName}\" post-processing complete, returning result`);\n\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\tconst durationMs = Math.round(performance.now() - startTime);\n\n\t\t\tawait safeTrack(\n\t\t\t\ttracker,\n\t\t\t\tbuildTrackInput(\n\t\t\t\t\ttoolName,\n\t\t\t\t\textra,\n\t\t\t\t\teffectiveOpts,\n\t\t\t\t\t{\n\t\t\t\t\t\tdurationMs,\n\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\terrorMessage:\n\t\t\t\t\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\t\t\t\tcause: classifyCause({ error }),\n\t\t\t\t\t},\n\t\t\t\t\tclientInfo,\n\t\t\t\t\t{ input },\n\t\t\t\t\tretrievalCollector.searches,\n\t\t\t\t),\n\t\t\t\topts.onError,\n\t\t\t);\n\n\t\t\tif (opts.flushAfterToolCall) {\n\t\t\t\tawait safeFlush(tracker, opts.onError);\n\t\t\t}\n\n\t\t\tthrow error;\n\t\t}\n\t};\n\n\twrappedHandler[WRAPPED_HANDLER] = true;\n\treturn wrappedHandler;\n}\n\n/**\n * Wrap an MCP server so tool handlers automatically emit `tool.called` events.\n *\n * The wrapper intercepts `server.registerTool(...)` for future registrations\n * and also walks `server._registeredTools` to wrap any tools already registered\n * at the time of the call. This means either call order works:\n *\n * withWaniwani(server); server.registerTool(...); // wrap then register\n * server.registerTool(...); withWaniwani(server); // register then wrap\n *\n * When `injectWidgetToken` is enabled (default), tracking config is injected\n * into tool response `_meta.waniwani` so browser widgets can post events\n * directly to the Waniwani backend without a server-side proxy.\n *\n * Widget metadata declared on the tool **definition** (e.g. skybridge's\n * `registerWidget`, raw MCP `_meta[\"ui/resourceUri\"]` / `_meta.ui.resourceUri`,\n * OpenAI's `_meta[\"openai/outputTemplate\"]`) is also forwarded into each tool\n * result's `_meta`, so chat UIs that only see tool results (and not\n * `tools/list`) can still render widgets. Handler-set keys take precedence.\n */\nexport async function withWaniwani(\n\tserver: McpServer,\n\toptions?: WithWaniwaniOptions,\n): Promise<McpServer> {\n\tconst wrappedServer = server as WrappedServer;\n\tif (wrappedServer.__waniwaniWrapped) {\n\t\treturn wrappedServer;\n\t}\n\n\twrappedServer.__waniwaniWrapped = true;\n\n\tconst opts = options ?? {};\n\tconst tracker = opts.client ?? waniwani();\n\tconst injectToken = opts.injectWidgetToken !== false;\n\n\tconst tokenCache: WidgetTokenCache | null = tracker._config.apiKey\n\t\t? new WidgetTokenCache({\n\t\t\t\tapiUrl: tracker._config.apiUrl ?? DEFAULT_BASE_URL,\n\t\t\t\tapiKey: tracker._config.apiKey,\n\t\t\t})\n\t\t: null;\n\n\tconst ctx: WrapContext = {\n\t\tserver,\n\t\ttracker,\n\t\topts,\n\t\ttokenCache,\n\t\tinjectToken,\n\t\tfunnelSync: null,\n\t};\n\n\tconst originalRegisterTool = server.registerTool.bind(server) as (\n\t\t...args: unknown[]\n\t) => unknown;\n\n\twrappedServer.registerTool = ((...args: unknown[]) => {\n\t\tconst [toolNameRaw, config, handlerRaw] = args;\n\n\t\tif (typeof handlerRaw !== \"function\") {\n\t\t\treturn originalRegisterTool(...args);\n\t\t}\n\n\t\tconst toolName =\n\t\t\ttypeof toolNameRaw === \"string\" && toolNameRaw.trim().length > 0\n\t\t\t\t? toolNameRaw\n\t\t\t\t: \"unknown\";\n\n\t\tconst definitionMeta =\n\t\t\tisRecord(config) && isRecord((config as UnknownRecord)._meta)\n\t\t\t\t? ((config as UnknownRecord)._meta as UnknownRecord)\n\t\t\t\t: undefined;\n\n\t\tconst wrapped = createWrappedHandler(\n\t\t\ttoolName,\n\t\t\thandlerRaw as RawHandler,\n\t\t\tctx,\n\t\t\tdefinitionMeta,\n\t\t);\n\t\treturn originalRegisterTool(toolNameRaw, config, wrapped);\n\t}) as McpServer[\"registerTool\"];\n\n\t// Wrap any tools that were already registered before withWaniwani() ran.\n\t// MCP SDK internal: `_registeredTools` is the dictionary used by the\n\t// `tools/call` request handler; each entry has a mutable `handler` field\n\t// that is looked up by name and invoked by reference at call time\n\t// (see @modelcontextprotocol/sdk/dist/esm/server/mcp.js:_createRegisteredTool),\n\t// so reassigning `entry.handler` safely upgrades existing tools in place.\n\t// Skybridge's McpServer subclass uses the same storage via `super.registerTool`.\n\tconst registeredTools = (\n\t\tserver as unknown as {\n\t\t\t_registeredTools?: Record<string, { handler?: unknown; _meta?: unknown }>;\n\t\t}\n\t)._registeredTools;\n\n\tif (isRecord(registeredTools)) {\n\t\tfor (const [toolName, entry] of Object.entries(registeredTools)) {\n\t\t\tif (!isRecord(entry)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst existing = entry.handler as MaybeWrappedHandler | undefined;\n\t\t\tif (typeof existing !== \"function\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (existing[WRAPPED_HANDLER]) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst definitionMeta = isRecord(entry._meta)\n\t\t\t\t? (entry._meta as UnknownRecord)\n\t\t\t\t: undefined;\n\n\t\t\tentry.handler = createWrappedHandler(\n\t\t\t\ttoolName,\n\t\t\t\texisting,\n\t\t\t\tctx,\n\t\t\t\tdefinitionMeta,\n\t\t\t);\n\t\t}\n\t}\n\n\tif (tracker._config.apiKey) {\n\t\tconst registeredToolsMap = (\n\t\t\tserver as unknown as {\n\t\t\t\t_registeredTools?: Record<string, { _meta?: unknown }>;\n\t\t\t}\n\t\t)._registeredTools;\n\n\t\tconst flowGraphs: FlowGraph[] = [];\n\t\tif (registeredToolsMap && typeof registeredToolsMap === \"object\") {\n\t\t\tfor (const entry of Object.values(registeredToolsMap)) {\n\t\t\t\tif (entry && typeof entry === \"object\") {\n\t\t\t\t\tconst meta = (entry as Record<string, unknown>)._meta;\n\t\t\t\t\tconst fg =\n\t\t\t\t\t\tmeta && typeof meta === \"object\"\n\t\t\t\t\t\t\t? ((meta as Record<string, unknown>)._flowGraph as\n\t\t\t\t\t\t\t\t\t| FlowGraph\n\t\t\t\t\t\t\t\t\t| undefined)\n\t\t\t\t\t\t\t: undefined;\n\t\t\t\t\tif (fg?.nodes?.length) {\n\t\t\t\t\t\tflowGraphs.push(fg);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (flowGraphs.length > 0) {\n\t\t\tctx.funnelSync = await prepareFunnelSyncPayload(flowGraphs);\n\t\t}\n\t}\n\n\treturn wrappedServer;\n}\n"],"mappings":"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","names":["asRecord","value","uiObject","meta","resourceUriFromMeta","nested","flat","openai","autoHeightFromMeta","viewUriFor","tool","resourceUriFromMeta","isDisplayTool","readModelContext","candidate","supportsWebMcp","pageContext","createWebMcpBridge","options","endpoint","headers","sessionId","visitorId","channelId","onWidget","log","mc","controller","onPageHide","disposed","dispose","identity","post","request","signal","response","tools","error","registered","tool","args","execOptions","result","readWidgetStep","result","first","parsed","payload","data","WAIT_INSTRUCTIONS","SHOWN_INSTRUCTIONS","UNAVAILABLE_INSTRUCTIONS","rewriteForAgent","step","rendered","_tool","_description","rest","START","END","INTERRUPT","WIDGET","interrupt","fields","config","context","questions","key","value","q","showWidget","toolOrConfig","maybeConfig","isObjectForm","tool","toolId","data","interactive","field","isInterrupt","isWidget","z","WaniWaniError","message","status","SDK_NAME","EXTRACT_PATH","refusalMessage","body","status","parsed","code","detail","isStoredInput","input","sourceFields","createDocumentsClient","config","apiUrl","apiKey","toJSONSchema","response","text","WaniWaniError","json","fields","pageCount","pageConfidence","documentId","createRevenueApi","emit","amount","currency","itemId","label","context","options","id","input","externalId","email","name","occurredAt","DEFAULT_API_URL","DOCUMENT_ID_KEYS","FILENAME_KEYS","MEDIA_TYPE_KEYS","UNNAMED_FILENAME","UNKNOWN_MEDIA_TYPE","isRecord","value","pickFirst","source","keys","key","toAttachedDocument","entry","documentId","collect","candidate","documents","seen","document","fromLatestUserTurn","messages","latest","message","part","readAttachedDocuments","input","body","declared","SUGGESTIONS_META_KEY","pickFirst","meta","keys","key","value","SESSION_ID_KEYS","REQUEST_ID_KEYS","TRACE_ID_KEYS","EXTERNAL_USER_ID_KEYS","VISITOR_ID_KEYS","CORRELATION_ID_KEYS","FILE_PARAMS_KEYS","FILE_URL_KEYS","FILE_NAME_KEYS","FLOW_META_KEY","WIDGET_CONFIG_META_KEY","extractSessionId","meta","pickFirst","SESSION_ID_KEYS","extractRequestId","REQUEST_ID_KEYS","extractTraceId","TRACE_ID_KEYS","extractExternalUserId","EXTERNAL_USER_ID_KEYS","extractVisitorId","VISITOR_ID_KEYS","extractCorrelationId","CORRELATION_ID_KEYS","isRecordValue","value","fetchableUrl","url","filenameFromUrl","segments","last","toAttachedFile","entry","pickFirst","FILE_URL_KEYS","filename","FILE_NAME_KEYS","extractAttachedFiles","meta","key","FILE_PARAMS_KEYS","files","filesUnder","candidate","seen","collect","file","DOCUMENTS_META_KEY","extractAttachedDocuments","raw","readAttachedDocuments","SOURCE_SESSION_KEYS","CLIENT_INFO_NAME_SOURCES","extractSource","clientInfo","explicit","source","name","lower","needle","headerValue","headers","lowerKey","headerKey","extractSourceFromHeaders","userAgent","anthropicClient","SCOPED_CLIENT_KEY","extractScopedClient","extra","createScopedClient","base","meta","config","trackOnce","event","sessionId","extractSessionId","correlationId","extractCorrelationId","documents","createDocumentsClient","DEFAULT_API_URL","extractVisitorId","extractExternalUserId","createRevenueApi","userId","properties","extractAttachedFiles","extractAttachedDocuments","input","z","statusCarrierSchema","readStatus","error","parsed","NETWORK_ERROR_MESSAGES","classifyCause","status","message","reportSessionError","waniwani","code","cause","properties","getObjectShape","schema","def","getNestedValue","obj","path","parts","current","part","setNestedValue","value","lastKey","deleteNestedValue","expandDotPaths","flat","result","key","deepMerge","target","source","readDef","schema","unwrapOptional","current","optional","i","def","introspectField","inner","description","t","base","resolveFieldSchema","state","fieldPath","dot","parent","child","parentSchema","childSchema","getObjectShape","isFilled","v","resolveNextNode","edge","state","buildInterruptResult","questions","context","currentNode","stateSchema","q","getNestedValue","unanswered","fieldSchema","resolveFieldSchema","isSingle","q0","executeFrom","startNodeName","startState","nodes","edges","validators","meta","waniwani","nodeOptions","nodesVisited","MAX_ITERATIONS","iterations","END","handler","reportSessionError","result","interrupt","showWidget","isInterrupt","interruptResult","fn","value","vResult","deepMerge","err","msg","deleteNestedValue","questionsWithError","qq","errResult","isWidget","widgetField","widgetFieldSchema","interactive","error","message","classifyCause","isEncryptedEnvelope","value","keyCache","importKey","keyBase64","cached","raw","cryptoKey","encryptValue","key","iv","plaintext","ciphertext","decryptValue","envelope","ct","LEVELS","getGlobalLevel","explicit","createLogger","namespace","enabled","args","SDK_NAME","DEFAULT_BASE_URL","kvDebug","createLogger","WaniwaniKvStore","key","data","isEncryptedEnvelope","decryptValue","value","options","encKey","payload","encryptValue","error","path","body","url","response","text","MemoryKvStore","key","value","_options","WaniwaniFlowStore","WaniwaniKvStore","key","value","options","classifyNode","handler","src","extractFlowGraph","config","nodes","edges","nodeOptions","graphNodes","id","opts","graphEdges","from","edge","normalizeIntro","intro","flowId","verbatim","instructions","SEEDS","config","normalizeIntro","initInternalState","seeded","field","seed","value","loadInternalState","args","store","sessionId","sessionIsPreexisting","record","takeInternalField","internal","rest","withInternalState","tokenContent","z","flowFieldSchema","flowIntroSchema","flowQuestionSchema","flowOutputSchema","CORRECTION","INTRO","interruptSteps","widgetSteps","input","tool","buildNextStep","parts","isRecord","value","redacted","schema","existing","readMeta","waniwani","isFieldRedacted","metaFn","collectRedactedStateFields","state","out","name","REDACTED_STATE_UPDATE_FIELDS_META_KEY","buildInputSchema","config","piiNote","stateUpdatesSchema","z","resolveDefaultStore","flowId","WaniwaniFlowStore","compileFlow","input","nodes","edges","inputSchema","flowGraph","extractFlowGraph","fullDescription","store","initialInternal","initInternalState","validators","handleToolCall","args","sessionId","sessionIsPreexisting","meta","waniwani","run","node","state","internal","executeFrom","result","intent","trimmed","startEdge","START","reportSessionError","loadInternalState","startState","expandDotPaths","firstNode","resolveNextNode","flowState","err","msg","classifyCause","step","updatedState","deepMerge","edge","nextNode","existingState","mergedState","redactedStateFields","collectRedactedStateFields","toolConfig","flowOutputSchema","REDACTED_STATE_UPDATE_FIELDS_META_KEY","toolHandler","extra","requestExtra","_meta","metaSessionId","extractSessionId","extractScopedClient","contentObj","intro","takeInternalField","nextStep","buildNextStep","payload","SUGGESTIONS_META_KEY","tokenContent","withInternalState","content","FLOW_META_KEY","server","configWithGraph","buildMermaidGraph","nodes","edges","lines","START","name","END","from","edge","target","StateGraph","config","configOrName","handler","options","id","run","label","hideFromFunnel","to","condition","nodesCopy","edgesCopy","compileFlow","startEdge","createFlow","config","StateGraph","parsePayload","result","content","createFlowTestHarness","flow","options","store","registered","sessionId","server","args","handler","extra","toResult","parsed","intent","stateUpdates","context","AsyncLocalStorage","globalWithStore","store","retrievalCollectorStore","recordKbSearch","trace","SDK_NAME","createKbClient","config","apiUrl","apiKey","requireApiKey","request","method","path","body","key","url","headers","init","response","text","WaniWaniError","files","query","options","results","recordKbSearch","r","existsSync","readFileSync","resolve","CONFIG_FILENAME","_cached","loadProjectConfig","filePath","raw","GLOBAL_KEY","getGlobalConfig","GLOBAL_KEY","DEFAULT_SOURCE","mapTrackEventToV2","input","options","now","generateId","createEventId","eventName","resolveEventName","meta","toRecord","metadata","correlation","resolveCorrelationIds","eventId","takeNonEmptyString","timestamp","normalizeTimestamp","source","extractSource","rawLegacy","isLegacyTrackEvent","mappedMetadata","mapProperties","legacyProperties","mapLegacyProperties","explicitProperties","properties","requestId","extractRequestId","sessionId","extractSessionId","traceId","extractTraceId","externalUserId","extractExternalUserId","visitorId","extractVisitorId","correlationId","extractCorrelationId","date","value","DEFAULT_ENDPOINT_PATH","SDK_NAME","AUTH_FAILURE_STATUS","RETRYABLE_STATUS","defaultFetch","input","init","createV2BatchTransport","options","BatchingV2Transport","joinUrl","DEFAULT_ENDPOINT_PATH","delayMs","resolve","event","dropCount","timeoutMs","flushPromise","timeoutSignal","teardownFlush","events","body","mid","headers","SDK_NAME","batch","attempt","pendingBatch","result","response","error","getErrorMessage","data","parseJsonResponse","partial","rejected","byId","retryable","permanent","rejectedEvent","isRetryableRejectedEvent","rawDelay","status","rejectedCount","buffered","code","apiUrl","endpointPath","normalizedBase","normalizedPath","createTrackingClient","config","apiUrl","apiKey","tracking","requireApiKey","transport","createV2BatchTransport","emit","event","mappedEvent","mapTrackEventToV2","trackOnce","track","createRevenueApi","client","userId","properties","meta","options","attachShutdownHooks","defaultTimeoutMs","shutdown","waniwani","config","effective","loadProjectConfig","getGlobalConfig","apiUrl","DEFAULT_API_URL","apiKey","trackingConfig","internalConfig","trackingClient","createTrackingClient","kbClient","createKbClient","documentsClient","createDocumentsClient","mapEnvelope","envelope","createTrackingRoute","options","config","client","getClient","waniwani","request","body","c","results","result","error","message","log","createLogger","EXPIRY_BUFFER_MS","WidgetTokenCache","config","sessionId","traceId","url","joinUrl","body","response","json","result","expiresAtMs","error","apiUrl","path","hashData","data","buf","b","hash","i","hashGraph","graph","prepareFunnelSyncPayload","flowGraphs","sorted","a","compositeHash","fg","flows","SESSION_ID_KEY","GEO_LOCATION_KEY","LEGACY_USER_LOCATION_KEY","OPENAI_USER_LOCATION_KEY","LOCATION_META_KEYS","stripLocationFieldsFromMeta","meta","fields","next","key","value","isRecord","stripped","field","extractMeta","extra","extractErrorText","result","content","c","resolveToolType","toolName","toolTypeOption","buildTrackInput","options","timing","clientInfo","io","kbSearch","toolType","stripFields","shouldStrip","rawMeta","input","output","flowMeta","FLOW_META_KEY","baseMeta","argSessionId","mergedMeta","extractSessionId","extractSource","safeTrack","tracker","onError","error","toError","safeFlush","injectWidgetConfig","cache","apiUrl","existingWaniwaniConfig","WIDGET_CONFIG_META_KEY","waniwaniConfig","token","sessionId","geoLocation","extractGeoLocation","source","WIDGET_META_KEYS","injectWidgetDefinitionMeta","definitionMeta","hasAnyKey","resultMeta","injectRequestMetadata","requestMeta","isRecord","v","extractTransportSessionId","extra","rawHeaders","headers","key","sid","waniSid","WRAPPED_HANDLER","log","createLogger","DEFAULT_BASE_URL","REDACTED_VALUE","buildStateUpdateRedactor","definitionMeta","fields","REDACTED_STATE_UPDATE_FIELDS_META_KEY","fieldSet","f","input","isRecord","stateUpdates","changed","next","field","createWrappedHandler","toolName","originalHandler","ctx","server","tracker","opts","tokenCache","injectToken","stateUpdateRedactor","wrappedHandler","extra","effectiveOpts","meta","extractMeta","clientInfo","extractSessionId","transportSid","extractTransportSessionId","extractSource","headers","resolvedSource","extractSourceFromHeaders","scopedClient","createScopedClient","SCOPED_CLIENT_KEY","retrievalCollector","startTime","result","retrievalCollectorStore","durationMs","isErrorResult","errorText","extractErrorText","safeTrack","buildTrackInput","safeFlush","injectRequestMetadata","injectWidgetDefinitionMeta","injectWidgetConfig","error","classifyCause","withWaniwani","options","wrappedServer","waniwani","WidgetTokenCache","originalRegisterTool","args","toolNameRaw","config","handlerRaw","wrapped","registeredTools","entry","existing","registeredToolsMap","flowGraphs","fg","prepareFunnelSyncPayload"]}