@waniwani/sdk 0.20.1 → 0.20.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,6 +6,325 @@ import { z } from 'zod';
6
6
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
7
  export { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
8
 
9
+ /**
10
+ * Reading a widget's view binding off MCP `_meta`.
11
+ *
12
+ * Kept in `shared/` because three surfaces ask the same question and none of
13
+ * them should own the answer: the chat resolves a tool part's view before
14
+ * rendering it, the WebMCP endpoint resolves a display tool's view before
15
+ * handing it to the page, and both have to agree or a widget renders in one
16
+ * place and not the other.
17
+ *
18
+ * The three spellings below are not alternatives to choose between. They are
19
+ * what three host generations actually emit, and a server built on any of them
20
+ * is a server we have to render.
21
+ */
22
+ /** Anything carrying MCP `_meta`: a tool definition, a tool result, a resource. */
23
+ type MetaCarrier = {
24
+ _meta?: Record<string, unknown>;
25
+ };
26
+ /**
27
+ * The view a `_meta` binds to, checking all three shapes in priority order:
28
+ *
29
+ * 1. `_meta.ui.resourceUri` — MCP Apps, nested (skybridge, ext-apps)
30
+ * 2. `_meta["ui/resourceUri"]` — MCP Apps, flat (earlier drafts, still in the wild)
31
+ * 3. `_meta["openai/outputTemplate"]` — OpenAI Apps SDK
32
+ *
33
+ * Order is priority, not preference: a server emitting more than one emits them
34
+ * as aliases of the same view, and the nested form is the one the current spec
35
+ * defines.
36
+ */
37
+ declare function resourceUriFromMeta(meta: Record<string, unknown> | undefined): string | undefined;
38
+ /** Whether the host should size the frame from the view's content. */
39
+ declare function autoHeightFromMeta(meta: Record<string, unknown> | undefined): boolean;
40
+ /**
41
+ * The view a tool renders, read from its definition.
42
+ *
43
+ * Definition rather than result on purpose. A spec-compliant server binds the
44
+ * view on the tool, so a result carries the binding only by accident, and
45
+ * reading the result first means a tool whose view is declared correctly looks
46
+ * like a tool with no view at all.
47
+ */
48
+ declare function viewUriFor(tool: MetaCarrier | undefined): string | undefined;
49
+ /**
50
+ * Whether a tool exists to render something rather than to be reasoned about.
51
+ *
52
+ * The distinction matters wherever a tool list crosses a boundary. A display
53
+ * tool advertised to a browsing agent is a tool it will try to call to make a
54
+ * widget appear, on a surface with no widget host in it, which burns a turn and
55
+ * strands the conversation on a render nobody performs.
56
+ */
57
+ declare function isDisplayTool(tool: MetaCarrier | undefined): boolean;
58
+
59
+ /**
60
+ * The shapes crossing the WebMCP boundary.
61
+ *
62
+ * Structural rather than imported from `@modelcontextprotocol/sdk`, because
63
+ * everything here is also read by the browser bridge, and the MCP SDK is an
64
+ * optional peer dependency that has no business in a page bundle. What the
65
+ * server sends and what the page reads is JSON either way.
66
+ */
67
+ /**
68
+ * One MCP content block. `type` is the discriminator every block carries; the
69
+ * index signature keeps the rest of whichever block it is, since nothing on
70
+ * this path reads them and re-deriving the union would only restate a schema
71
+ * the server already validated on the way out.
72
+ */
73
+ type WebMcpContentBlock = {
74
+ type: string;
75
+ text?: string;
76
+ [key: string]: unknown;
77
+ };
78
+ /** A tool as `tools/list` describes it. */
79
+ type WebMcpTool = {
80
+ name: string;
81
+ description?: string;
82
+ inputSchema?: Record<string, unknown>;
83
+ annotations?: Record<string, unknown>;
84
+ _meta?: Record<string, unknown>;
85
+ };
86
+ /** A tool result as `tools/call` returns it. */
87
+ type WebMcpCallResult = {
88
+ content?: WebMcpContentBlock[];
89
+ structuredContent?: Record<string, unknown>;
90
+ isError?: boolean;
91
+ _meta?: Record<string, unknown>;
92
+ };
93
+ /**
94
+ * Everything the page needs to mount a widget itself.
95
+ *
96
+ * Assembled server-side because none of it is derivable on the page: the view
97
+ * URI carries a content hash that moves every deploy, and the display tool's
98
+ * result is what the view reads its own configuration from.
99
+ */
100
+ type WebMcpWidgetPayload = {
101
+ /**
102
+ * The `ui://` the view was resolved from.
103
+ *
104
+ * Carries a content hash that moves on every deploy, which is why this is
105
+ * resolved server-side per call and why nothing on the page can cache it.
106
+ *
107
+ * The only half of the view the page is told. Where to fetch it from is the
108
+ * resource endpoint the page already derived from its token, the same one
109
+ * the chat's own widget iframes use.
110
+ */
111
+ viewUri: string;
112
+ /** Display tool the view belongs to. */
113
+ tool: string;
114
+ /** Props, delivered to the view as `ui/notifications/tool-input`. */
115
+ data: Record<string, unknown>;
116
+ /** The display tool's own result, delivered as `ui/notifications/tool-result`. */
117
+ result: {
118
+ content: WebMcpContentBlock[];
119
+ structuredContent?: Record<string, unknown>;
120
+ _meta?: Record<string, unknown>;
121
+ };
122
+ /** Whether the flow is waiting on the visitor before it can advance. */
123
+ interactive: boolean;
124
+ /**
125
+ * Set by the preview endpoint. A real widget step is a flow waiting on the
126
+ * visitor and has no way out; a preview is something someone opened to look
127
+ * at, so the host can give it one.
128
+ */
129
+ preview?: boolean;
130
+ };
131
+ /** What every page-facing request carries, whatever it is asking for. */
132
+ type WebMcpRequestBase = {
133
+ /**
134
+ * Tab-scoped, and the key the flow engine stores its state under.
135
+ *
136
+ * Must be stable for the tab and must not outlive it. A value that changes
137
+ * between calls silently restarts every flow; one that persists across days
138
+ * resumes a flow abandoned last week mid-question.
139
+ */
140
+ sessionId: string;
141
+ /**
142
+ * The visitor's persistent id. Identity rather than state, and what lets a
143
+ * flow started here and a conversation continued in the chat bubble belong
144
+ * to one person.
145
+ */
146
+ visitorId?: string;
147
+ /**
148
+ * The channel this page's embed belongs to.
149
+ *
150
+ * Sent because ingest rejects events it cannot attribute to a channel, and a
151
+ * token-only embed learns its channel from `/config` rather than from its
152
+ * own markup. Without it a tool call is attributed to nothing and the events
153
+ * behind it are dropped.
154
+ */
155
+ channelId?: string;
156
+ /** Where the visitor was standing when the agent called. */
157
+ page?: WebMcpPageContext;
158
+ };
159
+ /**
160
+ * `POST` body for the page-facing tools endpoint.
161
+ *
162
+ * The bridge builds exactly this, so a server implementing the other end is
163
+ * type-checked against what actually arrives rather than against a description
164
+ * of it.
165
+ */
166
+ type WebMcpRequest = (WebMcpRequestBase & {
167
+ action: "list";
168
+ }) | (WebMcpRequestBase & {
169
+ action: "call";
170
+ name: string;
171
+ arguments?: Record<string, unknown>;
172
+ });
173
+ /** Where the visitor was standing when the agent called. */
174
+ type WebMcpPageContext = {
175
+ url?: string;
176
+ title?: string;
177
+ };
178
+ type WebMcpListResponse = {
179
+ tools: WebMcpTool[];
180
+ };
181
+ type WebMcpCallResponse = {
182
+ content: WebMcpContentBlock[];
183
+ structuredContent?: Record<string, unknown>;
184
+ isError?: boolean;
185
+ /** Present and non-null only when the call produced a resolvable widget step. */
186
+ widget: WebMcpWidgetPayload | null;
187
+ };
188
+
189
+ /**
190
+ * The WebMCP bridge.
191
+ *
192
+ * A browsing agent standing on a page never speaks to the MCP server. It speaks
193
+ * to the page, through `document.modelContext`, and this is what puts the
194
+ * server's tools there. Every `execute()` the agent calls lands on the endpoint
195
+ * below, which stamps attribution server-side and answers.
196
+ *
197
+ * Nothing here discovers its own configuration. The endpoint, both ids, and the
198
+ * widget callback are arguments, because there are two callers with two
199
+ * different ways of knowing them: the chat embed reads them from its remote
200
+ * config, and the standalone loader derives them from its own script tag.
201
+ * Sniffing `document.currentScript` inside a bundle that may be loaded twenty
202
+ * modules deep finds the wrong script or none.
203
+ */
204
+
205
+ type WebMcpBridgeOptions = {
206
+ /** Absolute URL of the server's page-facing tools endpoint. */
207
+ endpoint: string;
208
+ /**
209
+ * Extra request headers, merged over `content-type`.
210
+ *
211
+ * The hosted API authenticates with `Authorization: Bearer <public token>`
212
+ * here, the same as every other call the embed makes. A self-hosted server
213
+ * reached directly needs none.
214
+ */
215
+ headers?: Record<string, string>;
216
+ /**
217
+ * Tab-scoped id, and the key the flow engine stores its state under.
218
+ *
219
+ * Must be stable for the tab and must not outlive it. A value that changes
220
+ * between calls silently restarts every flow; a value that persists across
221
+ * days resumes a flow abandoned last week mid-question.
222
+ */
223
+ sessionId: string;
224
+ /**
225
+ * The visitor's persistent id, from `shared/visitor-id`.
226
+ *
227
+ * Identity rather than state. Sending it is what lets a flow started here
228
+ * and a conversation continued in the chat bubble belong to one person.
229
+ */
230
+ visitorId?: string;
231
+ /**
232
+ * The channel this embed belongs to, from the script tag or `/config`.
233
+ *
234
+ * Ingest drops events it cannot attribute to a channel, so a tool call sent
235
+ * without this produces conversions nobody can see.
236
+ */
237
+ channelId?: string;
238
+ /**
239
+ * Called when a tool call resolves to a widget the page should mount.
240
+ *
241
+ * Optional, and the surface works without it: everything the visitor needs
242
+ * is also in the text the agent receives. A page with no host gets a flow
243
+ * carried in words.
244
+ */
245
+ onWidget?: (widget: WebMcpWidgetPayload) => void;
246
+ /** Defaults to `console`. */
247
+ logger?: Pick<Console, "error" | "info">;
248
+ };
249
+ /** Live for as long as the bridge is registered. */
250
+ type WebMcpBridge = {
251
+ /** Tools successfully registered with the browser. */
252
+ readonly tools: WebMcpTool[];
253
+ /** Unregister everything. Idempotent. */
254
+ dispose: () => void;
255
+ };
256
+ /** Whether this browser can host site tools at all. Almost every visit: no. */
257
+ declare function supportsWebMcp(): boolean;
258
+ /**
259
+ * Register the server's tools with the browsing agent.
260
+ *
261
+ * Resolves once registration has been attempted for every advertised tool. A
262
+ * tool the browser refuses is logged and skipped rather than failing the rest,
263
+ * because a partial tool list is a working page and an exception here is a
264
+ * blank one.
265
+ *
266
+ * Returns `null` when the browser has no `modelContext`, which is the common
267
+ * case and not an error.
268
+ */
269
+ declare function createWebMcpBridge(options: WebMcpBridgeOptions): Promise<WebMcpBridge | null>;
270
+
271
+ /**
272
+ * Structural stand-in for a Zod schema, so the package root's type graph does
273
+ * not reference `zod`, an optional peer. Pass a real Zod schema: `extract()`
274
+ * converts it with `z.toJSONSchema()`, which no other `parse`-shaped object
275
+ * survives.
276
+ */
277
+ interface DocumentSchema<T> {
278
+ parse(value: unknown): T;
279
+ }
280
+ interface DocumentExtractResult<T> {
281
+ /** The document's contents, parsed with the schema you passed. A null field is one the document did not legibly answer. */
282
+ fields: T;
283
+ /** Pages the vendor processed and billed */
284
+ pageCount: number;
285
+ /** Mean per-page OCR confidence, null when none was reported */
286
+ pageConfidence: number | null;
287
+ /** Handle for this extraction, valid for the 7-day retention window */
288
+ documentId: string;
289
+ }
290
+ /** A document the visitor uploaded through the chat widget, already stored by the platform. */
291
+ interface AttachedDocument {
292
+ /** Pass to `documents.extract({ documentId, schema })` to read it. */
293
+ documentId: string;
294
+ /** Name the visitor's file had */
295
+ filename: string;
296
+ /** MIME type the browser declared on upload */
297
+ mediaType: string;
298
+ }
299
+ interface DocumentExtractCommon<T> {
300
+ /** The shape to return. Extraction runs in strict mode, so mark anything a document may not answer `.nullable()`. */
301
+ schema: DocumentSchema<T>;
302
+ /** Zero-based page indexes to read: `[0]` is the first page. Omit to read every page. Ignored for images. Billing is per page processed, so a narrower selection costs less. */
303
+ pages?: number[];
304
+ /** Conversation this document arrived in */
305
+ sessionId?: string;
306
+ /** Ties the extraction to one tool call */
307
+ correlationId?: string;
308
+ }
309
+ /** Read a document the platform has never seen, by fetching it. */
310
+ interface DocumentExtractUrlInput<T> extends DocumentExtractCommon<T> {
311
+ /** A publicly fetchable URL; private and loopback addresses are refused */
312
+ url: string;
313
+ /** Name of the file, used to refuse an unsupported type before fetching it */
314
+ filename: string;
315
+ documentId?: never;
316
+ }
317
+ /** Read a document the platform already holds, uploaded by a visitor through the chat widget. */
318
+ interface DocumentExtractStoredInput<T> extends DocumentExtractCommon<T> {
319
+ /** From `context.waniwani.attachedDocuments`, or `readAttachedDocuments()` on your own chat route. The platform kept the filename, so there is none to pass. */
320
+ documentId: string;
321
+ }
322
+ type DocumentExtractInput<T> = DocumentExtractUrlInput<T> | DocumentExtractStoredInput<T>;
323
+ interface DocumentsClient {
324
+ /** Read one document and get its contents shaped by `schema`. Accepts PDF, PNG, JPEG, TIFF, BMP, GIF and WEBP up to 50 MB. */
325
+ extract<T>(input: DocumentExtractInput<T>): Promise<DocumentExtractResult<T>>;
326
+ }
327
+
9
328
  interface SearchResult {
10
329
  source: string;
11
330
  heading: string;
@@ -351,6 +670,12 @@ interface TrackingClient {
351
670
  shutdown: (options?: TrackingShutdownOptions) => Promise<TrackingShutdownResult>;
352
671
  }
353
672
 
673
+ /** A file the host bound to this tool call, resolved to something fetchable. */
674
+ interface AttachedFile {
675
+ url: string;
676
+ filename: string;
677
+ }
678
+
354
679
  /**
355
680
  * Well-known key used to attach the scoped client to the MCP `extra` object.
356
681
  * Read by `createTool` and flow compilation to surface it in handler contexts.
@@ -397,6 +722,21 @@ interface ScopedWaniWaniClient {
397
722
  }>;
398
723
  /** Knowledge base client (no meta needed). */
399
724
  readonly kb: KbClient;
725
+ /**
726
+ * Files the host attached to this tool call, each with a fetchable `url` and
727
+ * a `filename` — spread one straight into `documents.extract()`. Empty on
728
+ * hosts that attach nothing.
729
+ */
730
+ readonly attachedFiles: AttachedFile[];
731
+ /**
732
+ * Documents the visitor uploaded through the chat widget on this turn. The
733
+ * platform holds the bytes, so each entry is a handle: spread one straight
734
+ * into `documents.extract({ documentId, schema })`. Empty on hosts that
735
+ * upload nothing.
736
+ */
737
+ readonly attachedDocuments: AttachedDocument[];
738
+ /** Documents client; `sessionId` and `correlationId` are carried from the request. */
739
+ readonly documents: DocumentsClient;
400
740
  /** @internal Resolved API config from withWaniwani(). */
401
741
  readonly _config?: {
402
742
  apiUrl?: string;
@@ -996,6 +1336,64 @@ type FlowErrorContent = {
996
1336
  error: string;
997
1337
  };
998
1338
 
1339
+ /**
1340
+ * Widget steps on the WebMCP surface.
1341
+ *
1342
+ * In a chat host a flow's widget step is a two-call dance: the flow answers
1343
+ * `status: "widget"` naming a display tool, the model calls that tool, and the
1344
+ * host renders the `ui://` template it points at. None of that survives the
1345
+ * trip to a browsing agent. There is no widget host in the agent's window, the
1346
+ * display tool is not advertised on this surface, and the round trip would cost
1347
+ * a turn for a render nobody performs.
1348
+ *
1349
+ * So the step is resolved before the agent ever sees it. The server calls the
1350
+ * display tool, finds its view, and hands the page everything it needs to mount
1351
+ * the widget itself. The agent gets prose that never names a display tool. The
1352
+ * visitor gets the calendar on the page they are already looking at.
1353
+ */
1354
+
1355
+ type WidgetStep = {
1356
+ /**
1357
+ * The flow's own response, carried whole.
1358
+ *
1359
+ * Typed as the engine's own `FlowWidgetContent` rather than a local shape, so
1360
+ * a field added to a widget step reaches this file as a type change instead
1361
+ * of being silently dropped. That has already happened once: `intro` was
1362
+ * added to the payload and the surface that hand-rolled this parse kept
1363
+ * working only because it spread the rest through.
1364
+ */
1365
+ payload: FlowWidgetContent;
1366
+ tool: string;
1367
+ data: Record<string, unknown>;
1368
+ interactive: boolean;
1369
+ };
1370
+ /**
1371
+ * A flow's widget step, or `null` for every other tool result.
1372
+ *
1373
+ * Flow tools answer with a single JSON text block, so anything that does not
1374
+ * parse as JSON with `status: "widget"` and a `tool` to call belongs to someone
1375
+ * else and passes through untouched. That includes ordinary tools, which is
1376
+ * most of them.
1377
+ */
1378
+ declare function readWidgetStep(result: WebMcpCallResult): WidgetStep | null;
1379
+ /** The status a rewritten widget step reports in place of `"widget"`. */
1380
+ type WidgetStepStatus = "widget_shown" | "widget_unavailable";
1381
+ /**
1382
+ * The step as the agent should read it.
1383
+ *
1384
+ * Built by editing the flow's own payload rather than composing a new object,
1385
+ * so the fields the protocol requires the agent to echo back survive. Dropping
1386
+ * `sessionId` here breaks the flow several turns later, which is the worst kind
1387
+ * of bug to go looking for.
1388
+ *
1389
+ * `tool` and `description` are the two that must not survive. Both name a
1390
+ * display tool the agent cannot call, and leaving either in place is an
1391
+ * instruction to attempt it.
1392
+ */
1393
+ declare function rewriteForAgent(step: WidgetStep, rendered: boolean): Record<string, unknown> & {
1394
+ status: WidgetStepStatus;
1395
+ };
1396
+
999
1397
  /**
1000
1398
  * A LangGraph-inspired state graph builder for MCP tools.
1001
1399
  *
@@ -1221,6 +1619,8 @@ interface WaniWaniClient extends TrackingClient {
1221
1619
  readonly _config: InternalConfig;
1222
1620
  /** Knowledge base client for ingestion, search, and source listing */
1223
1621
  readonly kb: KbClient;
1622
+ /** Documents client for reading a file into typed fields */
1623
+ readonly documents: DocumentsClient;
1224
1624
  }
1225
1625
  interface InternalConfig {
1226
1626
  apiUrl: string;
@@ -1319,4 +1719,4 @@ type WithWaniwaniOptions = {
1319
1719
  */
1320
1720
  declare function withWaniwani(server: McpServer, options?: WithWaniwaniOptions): Promise<McpServer>;
1321
1721
 
1322
- export { type AddNodeConfig, type ConditionFn, END, type FlowConfig, type FlowIntro, type FlowIntroPayload, type FlowTestResult, type InferFlowState, type InterruptSignal, type KvStore, type KvStoreSetOptions, MemoryKvStore, type NodeContext, type NodeHandler, type RegisteredFlow, type RegisteredTool, SCOPED_CLIENT_KEY, START, type ScopedWaniWaniClient, StateGraph, type TrackingRouteOptions, type TypedInterrupt, type TypedShowWidget, WaniwaniKvStore, type WidgetSignal, type WithWaniwaniOptions, createFlow, createFlowTestHarness, createTrackingRoute, extractScopedClient, redacted, withWaniwani };
1722
+ export { type AddNodeConfig, type AttachedDocument, type AttachedFile, type ConditionFn, END, type FlowConfig, type FlowIntro, type FlowIntroPayload, type FlowTestResult, type InferFlowState, type InterruptSignal, type KvStore, type KvStoreSetOptions, MemoryKvStore, type MetaCarrier, type NodeContext, type NodeHandler, type RegisteredFlow, type RegisteredTool, SCOPED_CLIENT_KEY, START, type ScopedWaniWaniClient, StateGraph, type TrackingRouteOptions, type TypedInterrupt, type TypedShowWidget, WaniwaniKvStore, type WebMcpBridge, type WebMcpBridgeOptions, type WebMcpCallResponse, type WebMcpCallResult, type WebMcpContentBlock, type WebMcpListResponse, type WebMcpPageContext, type WebMcpRequest, type WebMcpTool, type WebMcpWidgetPayload, type WidgetSignal, type WidgetStep, type WidgetStepStatus, type WithWaniwaniOptions, autoHeightFromMeta, createFlow, createFlowTestHarness, createTrackingRoute, createWebMcpBridge, extractScopedClient, isDisplayTool, readWidgetStep, redacted, resourceUriFromMeta, rewriteForAgent, supportsWebMcp, viewUriFor, withWaniwani };