foldkit 0.99.0 → 0.100.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -89,6 +89,10 @@ export declare const RequestGetRuntimeState: import("../schema/index.js").Callab
89
89
  export declare const RequestDispatchMessage: import("../schema/index.js").CallableTaggedStruct<"RequestDispatchMessage", {
90
90
  message: S.Unknown;
91
91
  }>;
92
+ /** Request a description of the app's Message Schema. The runtime derives a JSON Schema document once at bridge boot from the configured `DevToolsConfig.Message`; the response is `None` when no Message Schema was configured. With `maybeVariantTag: None`, the response carries a small variant index (tag names plus payload field names and a tagged-union indicator) so MCP clients can enumerate the top-level variants without paying for the full schema. With `maybeVariantTag: Some(path)`, the value is interpreted as a dot-separated path of variant `_tag` values walked through each variant's single tagged-union payload field; the response carries the JSON Schema document narrowed along that chain, with any deeper unions collapsed to summary placeholders. Use the index to discover variants, then fetch one variant before calling `RequestDispatchMessage`. */
93
+ export declare const RequestGetMessageSchema: import("../schema/index.js").CallableTaggedStruct<"RequestGetMessageSchema", {
94
+ maybeVariantTag: S.OptionFromNullOr<S.String>;
95
+ }>;
92
96
  /** Request the list of currently connected browser runtimes. Handled by the Vite plugin, not forwarded to a runtime. */
93
97
  export declare const RequestListRuntimes: import("../schema/index.js").CallableTaggedStruct<"RequestListRuntimes", {}>;
94
98
  /** A request from the MCP server. RequestListRuntimes is handled at the Vite plugin layer; all other requests are routed to a browser runtime. */
@@ -108,7 +112,9 @@ export declare const Request: S.Union<readonly [import("../schema/index.js").Cal
108
112
  keyframeIndex: S.Number;
109
113
  }>, import("../schema/index.js").CallableTaggedStruct<"RequestResume", {}>, import("../schema/index.js").CallableTaggedStruct<"RequestDispatchMessage", {
110
114
  message: S.Unknown;
111
- }>, import("../schema/index.js").CallableTaggedStruct<"RequestListRuntimes", {}>, import("../schema/index.js").CallableTaggedStruct<"RequestGetInit", {}>, import("../schema/index.js").CallableTaggedStruct<"RequestGetRuntimeState", {}>]>;
115
+ }>, import("../schema/index.js").CallableTaggedStruct<"RequestListRuntimes", {}>, import("../schema/index.js").CallableTaggedStruct<"RequestGetInit", {}>, import("../schema/index.js").CallableTaggedStruct<"RequestGetRuntimeState", {}>, import("../schema/index.js").CallableTaggedStruct<"RequestGetMessageSchema", {
116
+ maybeVariantTag: S.OptionFromNullOr<S.String>;
117
+ }>]>;
112
118
  /** A request from the MCP server. */
113
119
  export type Request = typeof Request.Type;
114
120
  /** Response carrying a Model snapshot. The `value` is the resolved subtree at `atPath` (or the whole Model when no path was supplied). When `summarized` is true, large arrays/records/strings have been collapsed to `_summary` placeholders to keep payloads small for AI agents; pass `expand: true` on the Request to receive the literal value. */
@@ -186,6 +192,65 @@ export declare const ResponseResumed: import("../schema/index.js").CallableTagge
186
192
  export declare const ResponseDispatched: import("../schema/index.js").CallableTaggedStruct<"ResponseDispatched", {
187
193
  acceptedAtIndex: S.Number;
188
194
  }>;
195
+ /** One variant entry in a `MessageSchemaIndex`. `payloadFields` lists the variant's payload property names (excluding `_tag`); `unionFields` lists the subset of those properties whose schemas are themselves `_tag`-discriminated unions. A Submodel-wrapper variant always shows up with `unionFields: ['message']`, but the same flag also catches plain tagged-union value types like `UrlRequest = Internal | External`. Either way, the agent will need to pick a variant when filling these fields. */
196
+ export declare const MessageSchemaIndexEntry: S.Struct<{
197
+ readonly tag: S.String;
198
+ readonly payloadFields: S.$Array<S.String>;
199
+ readonly unionFields: S.$Array<S.String>;
200
+ }>;
201
+ /** One variant entry in a `MessageSchemaIndex`. */
202
+ export type MessageSchemaIndexEntry = typeof MessageSchemaIndexEntry.Type;
203
+ /** A flat directory of every top-level Message variant the runtime accepts, designed to fit in an agent context regardless of Message-union size. Use the tag names to make a follow-up `RequestGetMessageSchema` with `maybeVariantTag` set to fetch the full JSON Schema for one variant. */
204
+ export declare const MessageSchemaIndex: S.Struct<{
205
+ readonly variants: S.$Array<S.Struct<{
206
+ readonly tag: S.String;
207
+ readonly payloadFields: S.$Array<S.String>;
208
+ readonly unionFields: S.$Array<S.String>;
209
+ }>>;
210
+ }>;
211
+ /** A flat directory of every top-level Message variant. */
212
+ export type MessageSchemaIndex = typeof MessageSchemaIndex.Type;
213
+ /** The result payload carried by `ResponseMessageSchema`. `MessageSchemaIndexResult` is returned when the request omitted `maybeVariantTag`; `MessageSchemaDocumentResult` carries a JSON Schema document narrowed to one variant when a tag was supplied. */
214
+ export declare const MessageSchemaIndexResult: import("../schema/index.js").CallableTaggedStruct<"MessageSchemaIndexResult", {
215
+ index: S.Struct<{
216
+ readonly variants: S.$Array<S.Struct<{
217
+ readonly tag: S.String;
218
+ readonly payloadFields: S.$Array<S.String>;
219
+ readonly unionFields: S.$Array<S.String>;
220
+ }>>;
221
+ }>;
222
+ }>;
223
+ /** A JSON Schema document carrying a single variant of the runtime's Message union, plus the original `definitions` block so any `$ref`s the variant carries still resolve. */
224
+ export declare const MessageSchemaDocumentResult: import("../schema/index.js").CallableTaggedStruct<"MessageSchemaDocumentResult", {
225
+ document: S.Unknown;
226
+ }>;
227
+ declare const MessageSchemaResult: S.Union<readonly [import("../schema/index.js").CallableTaggedStruct<"MessageSchemaIndexResult", {
228
+ index: S.Struct<{
229
+ readonly variants: S.$Array<S.Struct<{
230
+ readonly tag: S.String;
231
+ readonly payloadFields: S.$Array<S.String>;
232
+ readonly unionFields: S.$Array<S.String>;
233
+ }>>;
234
+ }>;
235
+ }>, import("../schema/index.js").CallableTaggedStruct<"MessageSchemaDocumentResult", {
236
+ document: S.Unknown;
237
+ }>]>;
238
+ /** The result payload carried by `ResponseMessageSchema`. */
239
+ export type MessageSchemaResult = typeof MessageSchemaResult.Type;
240
+ /** Response describing the app's Message Schema. `maybeResult` is `Some(MessageSchemaIndexResult)` for index requests, `Some(MessageSchemaDocumentResult)` for variant-narrowed requests, and `None` when the runtime has not configured `DevToolsConfig.Message` or when JSON Schema derivation failed. Variant tags appear as `_tag` enums in the document, nested Submodel Messages recurse correctly, and `S.Option` fields render as `anyOf: [{_tag: 'Some', value}, {_tag: 'None'}]`. Apps using `S.OptionFromNullishOr(T)` (the Foldkit-canonical option codec for shapes that cross a JSON boundary) instead see the field as nullable `anyOf: [T, null]`; agents dispatching against that shape send either the bare value or `null`, not a tagged `Some`/`None` envelope. Fields with no JSON representation, such as `S.instanceOf(File)`, render as `{ type: 'null' }` rather than throwing; those variants cannot be dispatched via the bridge because their values live in browser memory. A few AST nodes (symbol-keyed structs, symbol-indexed records, tuples with post-rest elements) still cause the derivation to throw; the bridge guards the call and returns `None` in that case while logging a warning to the dev console. */
241
+ export declare const ResponseMessageSchema: import("../schema/index.js").CallableTaggedStruct<"ResponseMessageSchema", {
242
+ maybeResult: S.OptionFromNullOr<S.Union<readonly [import("../schema/index.js").CallableTaggedStruct<"MessageSchemaIndexResult", {
243
+ index: S.Struct<{
244
+ readonly variants: S.$Array<S.Struct<{
245
+ readonly tag: S.String;
246
+ readonly payloadFields: S.$Array<S.String>;
247
+ readonly unionFields: S.$Array<S.String>;
248
+ }>>;
249
+ }>;
250
+ }>, import("../schema/index.js").CallableTaggedStruct<"MessageSchemaDocumentResult", {
251
+ document: S.Unknown;
252
+ }>]>>;
253
+ }>;
189
254
  /** Response carrying the list of connected runtimes. */
190
255
  export declare const ResponseRuntimes: import("../schema/index.js").CallableTaggedStruct<"ResponseRuntimes", {
191
256
  runtimes: S.$Array<S.Struct<{
@@ -304,6 +369,18 @@ export declare const Response: S.Union<readonly [import("../schema/index.js").Ca
304
369
  isPaused: S.Boolean;
305
370
  maybePausedAtIndex: S.OptionFromNullOr<S.Number>;
306
371
  hasInitModel: S.Boolean;
372
+ }>, import("../schema/index.js").CallableTaggedStruct<"ResponseMessageSchema", {
373
+ maybeResult: S.OptionFromNullOr<S.Union<readonly [import("../schema/index.js").CallableTaggedStruct<"MessageSchemaIndexResult", {
374
+ index: S.Struct<{
375
+ readonly variants: S.$Array<S.Struct<{
376
+ readonly tag: S.String;
377
+ readonly payloadFields: S.$Array<S.String>;
378
+ readonly unionFields: S.$Array<S.String>;
379
+ }>>;
380
+ }>;
381
+ }>, import("../schema/index.js").CallableTaggedStruct<"MessageSchemaDocumentResult", {
382
+ document: S.Unknown;
383
+ }>]>>;
307
384
  }>, import("../schema/index.js").CallableTaggedStruct<"ResponseError", {
308
385
  reason: S.String;
309
386
  }>]>;
@@ -353,7 +430,9 @@ export declare const RequestFrame: S.Struct<{
353
430
  keyframeIndex: S.Number;
354
431
  }>, import("../schema/index.js").CallableTaggedStruct<"RequestResume", {}>, import("../schema/index.js").CallableTaggedStruct<"RequestDispatchMessage", {
355
432
  message: S.Unknown;
356
- }>, import("../schema/index.js").CallableTaggedStruct<"RequestListRuntimes", {}>, import("../schema/index.js").CallableTaggedStruct<"RequestGetInit", {}>, import("../schema/index.js").CallableTaggedStruct<"RequestGetRuntimeState", {}>]>;
433
+ }>, import("../schema/index.js").CallableTaggedStruct<"RequestListRuntimes", {}>, import("../schema/index.js").CallableTaggedStruct<"RequestGetInit", {}>, import("../schema/index.js").CallableTaggedStruct<"RequestGetRuntimeState", {}>, import("../schema/index.js").CallableTaggedStruct<"RequestGetMessageSchema", {
434
+ maybeVariantTag: S.OptionFromNullOr<S.String>;
435
+ }>]>;
357
436
  }>;
358
437
  /** A wire frame carrying a Request from the MCP server. */
359
438
  export type RequestFrame = typeof RequestFrame.Type;
@@ -444,6 +523,18 @@ export declare const ResponseFrame: S.Struct<{
444
523
  isPaused: S.Boolean;
445
524
  maybePausedAtIndex: S.OptionFromNullOr<S.Number>;
446
525
  hasInitModel: S.Boolean;
526
+ }>, import("../schema/index.js").CallableTaggedStruct<"ResponseMessageSchema", {
527
+ maybeResult: S.OptionFromNullOr<S.Union<readonly [import("../schema/index.js").CallableTaggedStruct<"MessageSchemaIndexResult", {
528
+ index: S.Struct<{
529
+ readonly variants: S.$Array<S.Struct<{
530
+ readonly tag: S.String;
531
+ readonly payloadFields: S.$Array<S.String>;
532
+ readonly unionFields: S.$Array<S.String>;
533
+ }>>;
534
+ }>;
535
+ }>, import("../schema/index.js").CallableTaggedStruct<"MessageSchemaDocumentResult", {
536
+ document: S.Unknown;
537
+ }>]>>;
447
538
  }>, import("../schema/index.js").CallableTaggedStruct<"ResponseError", {
448
539
  reason: S.String;
449
540
  }>]>;
@@ -465,4 +556,5 @@ export declare const EventFrame: S.Struct<{
465
556
  }>;
466
557
  /** A wire frame carrying a runtime lifecycle event. */
467
558
  export type EventFrame = typeof EventFrame.Type;
559
+ export {};
468
560
  //# sourceMappingURL=protocol.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/devTools/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,QAAQ,CAAA;AAMpC,0NAA0N;AAC1N,eAAO,MAAM,iBAAiB;;;EAG5B,CAAA;AACF,2EAA2E;AAC3E,MAAM,MAAM,iBAAiB,GAAG,OAAO,iBAAiB,CAAC,IAAI,CAAA;AAE7D,0MAA0M;AAC1M,eAAO,MAAM,eAAe;;;EAG1B,CAAA;AACF,yFAAyF;AACzF,MAAM,MAAM,eAAe,GAAG,OAAO,eAAe,CAAC,IAAI,CAAA;AAEzD,wfAAwf;AACxf,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;EAa1B,CAAA;AACF,iFAAiF;AACjF,MAAM,MAAM,eAAe,GAAG,OAAO,eAAe,CAAC,IAAI,CAAA;AAEzD,wHAAwH;AACxH,eAAO,MAAM,YAAY;;EAEvB,CAAA;AACF,wCAAwC;AACxC,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAA;AAEnD,kDAAkD;AAClD,eAAO,MAAM,WAAW;;;;EAItB,CAAA;AACF,kDAAkD;AAClD,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC,IAAI,CAAA;AAIjD,yFAAyF;AACzF,eAAO,MAAM,eAAe;;;EAG1B,CAAA;AAEF,8JAA8J;AAC9J,eAAO,MAAM,iBAAiB;;;;EAI5B,CAAA;AAEF,8EAA8E;AAC9E,eAAO,MAAM,mBAAmB;;;EAG9B,CAAA;AAEF,8JAA8J;AAC9J,eAAO,MAAM,iBAAiB;;EAE5B,CAAA;AAEF,+CAA+C;AAC/C,eAAO,MAAM,oBAAoB,+EAA6B,CAAA;AAE9D,mHAAmH;AACnH,eAAO,MAAM,uBAAuB;;EAElC,CAAA;AAEF,uEAAuE;AACvE,eAAO,MAAM,aAAa,wEAAsB,CAAA;AAEhD,wGAAwG;AACxG,eAAO,MAAM,cAAc,yEAAuB,CAAA;AAElD,oIAAoI;AACpI,eAAO,MAAM,sBAAsB,iFAA+B,CAAA;AAElE,kKAAkK;AAClK,eAAO,MAAM,sBAAsB;;EAEjC,CAAA;AAEF,wHAAwH;AACxH,eAAO,MAAM,mBAAmB,8EAA4B,CAAA;AAE5D,kJAAkJ;AAClJ,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;4OAYlB,CAAA;AACF,qCAAqC;AACrC,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAA;AAIzC,wVAAwV;AACxV,eAAO,MAAM,aAAa;;;;EAIxB,CAAA;AAEF,uQAAuQ;AACvQ,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;EAG3B,CAAA;AAEF,gLAAgL;AAChL,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;EAE1B,CAAA;AAEF,yDAAyD;AACzD,eAAO,MAAM,iBAAiB;;;;EAE5B,CAAA;AAEF,oFAAoF;AACpF,eAAO,MAAM,gBAAgB;;EAE3B,CAAA;AAEF,gEAAgE;AAChE,eAAO,MAAM,eAAe,0EAAwB,CAAA;AAEpD,ofAAof;AACpf,eAAO,MAAM,kBAAkB;;EAE7B,CAAA;AAEF,wDAAwD;AACxD,eAAO,MAAM,gBAAgB;;;;;;EAE3B,CAAA;AAEF,sbAAsb;AACtb,eAAO,MAAM,YAAY;;;;;;;;;;EAIvB,CAAA;AAEF,4jBAA4jB;AAC5jB,eAAO,MAAM,oBAAoB;;;;;;;EAO/B,CAAA;AAEF,8DAA8D;AAC9D,eAAO,MAAM,aAAa;;EAExB,CAAA;AAEF,wCAAwC;AACxC,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAYnB,CAAA;AACF,wCAAwC;AACxC,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAA;AAI3C,uCAAuC;AACvC,eAAO,MAAM,cAAc;;;;;;EAEzB,CAAA;AAEF,mDAAmD;AACnD,eAAO,MAAM,iBAAiB;;EAE5B,CAAA;AAEF,iIAAiI;AACjI,eAAO,MAAM,KAAK;;;;;;;;IAA+C,CAAA;AACjE,iCAAiC;AACjC,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,CAAA;AAIrC,0NAA0N;AAC1N,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;EAIvB,CAAA;AACF,2DAA2D;AAC3D,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAA;AAEnD,uEAAuE;AACvE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGxB,CAAA;AACF,uEAAuE;AACvE,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,IAAI,CAAA;AAErD,0FAA0F;AAC1F,eAAO,MAAM,UAAU;;;;;;;;;;;EAGrB,CAAA;AACF,uDAAuD;AACvD,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,IAAI,CAAA"}
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/devTools/protocol.ts"],"names":[],"mappings":"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"}
@@ -73,6 +73,10 @@ export const RequestGetRuntimeState = ts('RequestGetRuntimeState');
73
73
  export const RequestDispatchMessage = ts('RequestDispatchMessage', {
74
74
  message: S.Unknown,
75
75
  });
76
+ /** Request a description of the app's Message Schema. The runtime derives a JSON Schema document once at bridge boot from the configured `DevToolsConfig.Message`; the response is `None` when no Message Schema was configured. With `maybeVariantTag: None`, the response carries a small variant index (tag names plus payload field names and a tagged-union indicator) so MCP clients can enumerate the top-level variants without paying for the full schema. With `maybeVariantTag: Some(path)`, the value is interpreted as a dot-separated path of variant `_tag` values walked through each variant's single tagged-union payload field; the response carries the JSON Schema document narrowed along that chain, with any deeper unions collapsed to summary placeholders. Use the index to discover variants, then fetch one variant before calling `RequestDispatchMessage`. */
77
+ export const RequestGetMessageSchema = ts('RequestGetMessageSchema', {
78
+ maybeVariantTag: S.OptionFromNullOr(S.String),
79
+ });
76
80
  /** Request the list of currently connected browser runtimes. Handled by the Vite plugin, not forwarded to a runtime. */
77
81
  export const RequestListRuntimes = ts('RequestListRuntimes');
78
82
  /** A request from the MCP server. RequestListRuntimes is handled at the Vite plugin layer; all other requests are routed to a browser runtime. */
@@ -88,6 +92,7 @@ export const Request = S.Union([
88
92
  RequestListRuntimes,
89
93
  RequestGetInit,
90
94
  RequestGetRuntimeState,
95
+ RequestGetMessageSchema,
91
96
  ]);
92
97
  // RESPONSE
93
98
  /** Response carrying a Model snapshot. The `value` is the resolved subtree at `atPath` (or the whole Model when no path was supplied). When `summarized` is true, large arrays/records/strings have been collapsed to `_summary` placeholders to keep payloads small for AI agents; pass `expand: true` on the Request to receive the literal value. */
@@ -119,6 +124,32 @@ export const ResponseResumed = ts('ResponseResumed');
119
124
  export const ResponseDispatched = ts('ResponseDispatched', {
120
125
  acceptedAtIndex: S.Number,
121
126
  });
127
+ /** One variant entry in a `MessageSchemaIndex`. `payloadFields` lists the variant's payload property names (excluding `_tag`); `unionFields` lists the subset of those properties whose schemas are themselves `_tag`-discriminated unions. A Submodel-wrapper variant always shows up with `unionFields: ['message']`, but the same flag also catches plain tagged-union value types like `UrlRequest = Internal | External`. Either way, the agent will need to pick a variant when filling these fields. */
128
+ export const MessageSchemaIndexEntry = S.Struct({
129
+ tag: S.String,
130
+ payloadFields: S.Array(S.String),
131
+ unionFields: S.Array(S.String),
132
+ });
133
+ /** A flat directory of every top-level Message variant the runtime accepts, designed to fit in an agent context regardless of Message-union size. Use the tag names to make a follow-up `RequestGetMessageSchema` with `maybeVariantTag` set to fetch the full JSON Schema for one variant. */
134
+ export const MessageSchemaIndex = S.Struct({
135
+ variants: S.Array(MessageSchemaIndexEntry),
136
+ });
137
+ /** The result payload carried by `ResponseMessageSchema`. `MessageSchemaIndexResult` is returned when the request omitted `maybeVariantTag`; `MessageSchemaDocumentResult` carries a JSON Schema document narrowed to one variant when a tag was supplied. */
138
+ export const MessageSchemaIndexResult = ts('MessageSchemaIndexResult', {
139
+ index: MessageSchemaIndex,
140
+ });
141
+ /** A JSON Schema document carrying a single variant of the runtime's Message union, plus the original `definitions` block so any `$ref`s the variant carries still resolve. */
142
+ export const MessageSchemaDocumentResult = ts('MessageSchemaDocumentResult', {
143
+ document: S.Unknown,
144
+ });
145
+ const MessageSchemaResult = S.Union([
146
+ MessageSchemaIndexResult,
147
+ MessageSchemaDocumentResult,
148
+ ]);
149
+ /** Response describing the app's Message Schema. `maybeResult` is `Some(MessageSchemaIndexResult)` for index requests, `Some(MessageSchemaDocumentResult)` for variant-narrowed requests, and `None` when the runtime has not configured `DevToolsConfig.Message` or when JSON Schema derivation failed. Variant tags appear as `_tag` enums in the document, nested Submodel Messages recurse correctly, and `S.Option` fields render as `anyOf: [{_tag: 'Some', value}, {_tag: 'None'}]`. Apps using `S.OptionFromNullishOr(T)` (the Foldkit-canonical option codec for shapes that cross a JSON boundary) instead see the field as nullable `anyOf: [T, null]`; agents dispatching against that shape send either the bare value or `null`, not a tagged `Some`/`None` envelope. Fields with no JSON representation, such as `S.instanceOf(File)`, render as `{ type: 'null' }` rather than throwing; those variants cannot be dispatched via the bridge because their values live in browser memory. A few AST nodes (symbol-keyed structs, symbol-indexed records, tuples with post-rest elements) still cause the derivation to throw; the bridge guards the call and returns `None` in that case while logging a warning to the dev console. */
150
+ export const ResponseMessageSchema = ts('ResponseMessageSchema', {
151
+ maybeResult: S.OptionFromNullOr(MessageSchemaResult),
152
+ });
122
153
  /** Response carrying the list of connected runtimes. */
123
154
  export const ResponseRuntimes = ts('ResponseRuntimes', {
124
155
  runtimes: S.Array(RuntimeInfo),
@@ -154,6 +185,7 @@ export const Response = S.Union([
154
185
  ResponseRuntimes,
155
186
  ResponseInit,
156
187
  ResponseRuntimeState,
188
+ ResponseMessageSchema,
157
189
  ResponseError,
158
190
  ]);
159
191
  // EVENT
@@ -1,2 +1,2 @@
1
- export { EventConnected, EventDisconnected, EventFrame, Event, KeyframeInfo, RequestDispatchMessage, RequestFrame, RequestGetInit, RequestGetMessage, RequestGetModel, RequestGetModelAt, RequestGetRuntimeState, RequestListKeyframes, RequestListMessages, RequestListRuntimes, RequestReplayToKeyframe, RequestResume, Request, ResponseDispatched, ResponseError, ResponseFrame, ResponseInit, ResponseKeyframes, ResponseMessage, ResponseMessages, ResponseModel, ResponseReplayed, ResponseResumed, ResponseRuntimes, ResponseRuntimeState, Response, RuntimeInfo, SerializedEntry, } from './protocol.js';
1
+ export { EventConnected, EventDisconnected, EventFrame, Event, KeyframeInfo, RequestDispatchMessage, RequestFrame, RequestGetInit, RequestGetMessage, RequestGetMessageSchema, RequestGetModel, RequestGetModelAt, RequestGetRuntimeState, RequestListKeyframes, RequestListMessages, RequestListRuntimes, RequestReplayToKeyframe, RequestResume, Request, ResponseDispatched, ResponseError, ResponseFrame, ResponseInit, ResponseKeyframes, ResponseMessage, ResponseMessages, ResponseMessageSchema, ResponseModel, ResponseReplayed, ResponseResumed, ResponseRuntimes, ResponseRuntimeState, Response, RuntimeInfo, SerializedEntry, } from './protocol.js';
2
2
  //# sourceMappingURL=public.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/devTools/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,UAAU,EACV,KAAK,EACL,YAAY,EACZ,sBAAsB,EACtB,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,uBAAuB,EACvB,aAAa,EACb,OAAO,EACP,kBAAkB,EAClB,aAAa,EACb,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,QAAQ,EACR,WAAW,EACX,eAAe,GAChB,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/devTools/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,UAAU,EACV,KAAK,EACL,YAAY,EACZ,sBAAsB,EACtB,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,uBAAuB,EACvB,eAAe,EACf,iBAAiB,EACjB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,uBAAuB,EACvB,aAAa,EACb,OAAO,EACP,kBAAkB,EAClB,aAAa,EACb,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,qBAAqB,EACrB,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,QAAQ,EACR,WAAW,EACX,eAAe,GAChB,MAAM,eAAe,CAAA"}
@@ -1 +1 @@
1
- export { EventConnected, EventDisconnected, EventFrame, Event, KeyframeInfo, RequestDispatchMessage, RequestFrame, RequestGetInit, RequestGetMessage, RequestGetModel, RequestGetModelAt, RequestGetRuntimeState, RequestListKeyframes, RequestListMessages, RequestListRuntimes, RequestReplayToKeyframe, RequestResume, Request, ResponseDispatched, ResponseError, ResponseFrame, ResponseInit, ResponseKeyframes, ResponseMessage, ResponseMessages, ResponseModel, ResponseReplayed, ResponseResumed, ResponseRuntimes, ResponseRuntimeState, Response, RuntimeInfo, SerializedEntry, } from './protocol.js';
1
+ export { EventConnected, EventDisconnected, EventFrame, Event, KeyframeInfo, RequestDispatchMessage, RequestFrame, RequestGetInit, RequestGetMessage, RequestGetMessageSchema, RequestGetModel, RequestGetModelAt, RequestGetRuntimeState, RequestListKeyframes, RequestListMessages, RequestListRuntimes, RequestReplayToKeyframe, RequestResume, Request, ResponseDispatched, ResponseError, ResponseFrame, ResponseInit, ResponseKeyframes, ResponseMessage, ResponseMessages, ResponseMessageSchema, ResponseModel, ResponseReplayed, ResponseResumed, ResponseRuntimes, ResponseRuntimeState, Response, RuntimeInfo, SerializedEntry, } from './protocol.js';
@@ -0,0 +1,82 @@
1
+ import { Option } from 'effect';
2
+ import type { MessageSchemaIndexEntry } from './protocol.js';
3
+ /**
4
+ * Build a flat directory of every top-level Message variant from a JSON Schema
5
+ * document produced by `Schema.toJsonSchemaDocument`. The directory is small
6
+ * even for hundreds of variants, so an MCP client can paginate by tag without
7
+ * paying for the full schema.
8
+ *
9
+ * Returns `None` when the document's top-level `schema` is not a discriminated
10
+ * union of `_tag`-keyed structs (e.g. a single-variant Message Schema, or a
11
+ * shape produced by a future Effect Schema release that the summarizer does
12
+ * not yet understand). The caller should fall back to fetching the full
13
+ * document in that case.
14
+ */
15
+ export declare const indexMessageSchemaDocument: (document: unknown) => Option.Option<ReadonlyArray<MessageSchemaIndexEntry>>;
16
+ /**
17
+ * Split a dot-separated variant path into its segments. Empty segments are
18
+ * dropped so callers can pass user-supplied strings without first trimming
19
+ * leading/trailing dots.
20
+ */
21
+ export declare const splitVariantPath: (variantPath: string) => ReadonlyArray<string>;
22
+ /**
23
+ * Enumerate the variant tags available as the next segment of a variant path.
24
+ * Given a partial path that resolves to a tagged-union field, returns the tags
25
+ * of every variant in that union. Useful for crafting `not-found` error
26
+ * messages: if `narrowToVariant` fails on `"a.b.c"`, calling this with `["a", "b"]`
27
+ * yields the valid choices for the third segment.
28
+ *
29
+ * Returns `None` when the prefix cannot be resolved at all (e.g. the first
30
+ * segment names no top-level variant, or an intermediate variant lacks a
31
+ * tagged-union payload field).
32
+ */
33
+ export declare const variantTagsAtPathPrefix: (document: unknown, pathPrefix: ReadonlyArray<string>) => Option.Option<ReadonlyArray<string>>;
34
+ /**
35
+ * Diagnose where a variant-path walk would fail. Walks back from one segment
36
+ * before the supplied path length, returning the deepest prefix whose
37
+ * tagged-union level resolves cleanly, the tags available at that level, and
38
+ * the next segment from the original path (the one that broke the walk). When
39
+ * the offending segment is a known tag at that level, the failure means the
40
+ * variant exists but has no tagged-union field to step into further. When it
41
+ * is unknown, the failure is a simple typo. Returns `None` when not even the
42
+ * empty prefix resolves (i.e. the document is not a discriminated union at
43
+ * the top level).
44
+ */
45
+ export declare const diagnoseVariantPath: (document: unknown, segments: ReadonlyArray<string>) => Option.Option<{
46
+ prefix: ReadonlyArray<string>;
47
+ failingSegment: Option.Option<string>;
48
+ available: ReadonlyArray<string>;
49
+ }>;
50
+ /**
51
+ * Replace the top-level `anyOf` in a JSON Schema document with a single-element
52
+ * `anyOf` containing the variant(s) selected by a dot-separated variant path.
53
+ *
54
+ * `variantPath` is a dot-string of variant `_tag` values, walked through the
55
+ * one tagged-union payload field at each step. For example, `"GotChildMessage"`
56
+ * narrows the top-level union to that wrapper variant, and
57
+ * `"GotChildMessage.Opened"` walks into the wrapper's nested union and narrows
58
+ * to the inner variant. Any deeper discriminated unions inside the deepest
59
+ * variant's payload are collapsed to `{ _summary: 'union', variants: [...] }`
60
+ * placeholders so the response stays small even for deeply-nested Submodel
61
+ * trees; agents drill further by extending the path.
62
+ *
63
+ * The `definitions` block is kept (any `$ref` targets the narrowed variant
64
+ * relies on still resolve, and dead refs left over from trimmed variants are
65
+ * harmless), but any discriminated unions inside it are collapsed to the same
66
+ * `_summary` placeholder shape so a shared union annotated with an
67
+ * `identifier` does not balloon the response. The path walker does not
68
+ * resolve `$ref` indirection through `definitions`; agents that need to step
69
+ * through a `$ref`-shared union look up the definition by name and use the
70
+ * placeholder's variant list directly.
71
+ *
72
+ * Returns `None` when the document is not a top-level discriminated union, the
73
+ * path is empty, a segment names no variant in the current union, or an
74
+ * intermediate variant lacks exactly one tagged-union payload field to step
75
+ * into (zero or multiple union fields are both ambiguous). The "exactly one"
76
+ * rule encodes the Foldkit idiom (`Got<Child>Message { message }` Submodel
77
+ * wrappers, single-union value-type fields); apps whose Message variants need
78
+ * additional surrounding state should pass it as an argument to the child's
79
+ * `update`/`view` rather than as a sibling field on the parent Message.
80
+ */
81
+ export declare const narrowToVariant: (document: unknown, variantPath: string) => Option.Option<unknown>;
82
+ //# sourceMappingURL=schemaSummarize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemaSummarize.d.ts","sourceRoot":"","sources":["../../src/devTools/schemaSummarize.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,MAAM,EAMP,MAAM,QAAQ,CAAA;AAEf,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAA;AAgH5D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GACrC,UAAU,OAAO,KAChB,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,uBAAuB,CAAC,CACG,CAAA;AA0H1D;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,aAAa,MAAM,KAAG,aAAa,CAAC,MAAM,CAKxE,CAAA;AA8BH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,uBAAuB,GAClC,UAAU,OAAO,EACjB,YAAY,aAAa,CAAC,MAAM,CAAC,KAChC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAGnC,CAAA;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,GAC9B,UAAU,OAAO,EACjB,UAAU,aAAa,CAAC,MAAM,CAAC,KAC9B,MAAM,CAAC,MAAM,CAAC;IACf,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IAC7B,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACrC,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CACjC,CAuBA,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,eAAe,GAC1B,UAAU,OAAO,EACjB,aAAa,MAAM,KAClB,MAAM,CAAC,MAAM,CAAC,OAAO,CAiBvB,CAAA"}
@@ -0,0 +1,264 @@
1
+ import { Array as Array_, Option, Predicate, Record, String as String_, flow, pipe, } from 'effect';
2
+ const PATH_SEPARATOR = '.';
3
+ const isRecord = (value) => Predicate.isObject(value) && !Array_.isArray(value);
4
+ const isReadonlyArray = (value) => Array_.isArray(value);
5
+ const isStringArray = (values) => Array_.every(values, Predicate.isString);
6
+ const stringEnumOf = (schema) => {
7
+ if (!isRecord(schema)) {
8
+ return Option.none();
9
+ }
10
+ const candidate = schema['enum'];
11
+ if (!Array_.isArray(candidate)) {
12
+ return Option.none();
13
+ }
14
+ return Option.liftPredicate(candidate, isStringArray);
15
+ };
16
+ const variantTagOf = (schema) => {
17
+ if (!isRecord(schema) || schema['type'] !== 'object') {
18
+ return Option.none();
19
+ }
20
+ const properties = schema['properties'];
21
+ if (!isRecord(properties)) {
22
+ return Option.none();
23
+ }
24
+ return stringEnumOf(properties['_tag']).pipe(Option.flatMap(tags => tags.length === 1 ? Array_.head(tags) : Option.none()));
25
+ };
26
+ const anyOfOf = (schema) => {
27
+ if (!isRecord(schema)) {
28
+ return Option.none();
29
+ }
30
+ return Option.liftPredicate(schema['anyOf'], isReadonlyArray);
31
+ };
32
+ const isDiscriminatedUnion = (schema) => Option.exists(anyOfOf(schema), Predicate.and(Array_.isReadonlyArrayNonEmpty, Array_.every(flow(variantTagOf, Option.isSome))));
33
+ const payloadFieldsOf = (variant) => {
34
+ if (!isRecord(variant)) {
35
+ return [];
36
+ }
37
+ const properties = variant['properties'];
38
+ if (!isRecord(properties)) {
39
+ return [];
40
+ }
41
+ return pipe(Record.keys(properties), Array_.filter(name => name !== '_tag'));
42
+ };
43
+ const unionFieldsOf = (variant) => {
44
+ if (!isRecord(variant)) {
45
+ return [];
46
+ }
47
+ const properties = variant['properties'];
48
+ if (!isRecord(properties)) {
49
+ return [];
50
+ }
51
+ return pipe(Record.toEntries(properties), Array_.filter(([name, schema]) => name !== '_tag' && isDiscriminatedUnion(schema)), Array_.map(([name]) => name));
52
+ };
53
+ const entryOf = (variant) => variantTagOf(variant).pipe(Option.map(tag => ({
54
+ tag,
55
+ payloadFields: payloadFieldsOf(variant),
56
+ unionFields: unionFieldsOf(variant),
57
+ })));
58
+ const topLevelVariantsOf = (document) => {
59
+ if (!isRecord(document)) {
60
+ return Option.none();
61
+ }
62
+ return anyOfOf(document['schema']);
63
+ };
64
+ const indexEntriesOf = (members) => pipe(members, Array_.map(entryOf), Array_.getSomes);
65
+ /**
66
+ * Build a flat directory of every top-level Message variant from a JSON Schema
67
+ * document produced by `Schema.toJsonSchemaDocument`. The directory is small
68
+ * even for hundreds of variants, so an MCP client can paginate by tag without
69
+ * paying for the full schema.
70
+ *
71
+ * Returns `None` when the document's top-level `schema` is not a discriminated
72
+ * union of `_tag`-keyed structs (e.g. a single-variant Message Schema, or a
73
+ * shape produced by a future Effect Schema release that the summarizer does
74
+ * not yet understand). The caller should fall back to fetching the full
75
+ * document in that case.
76
+ */
77
+ export const indexMessageSchemaDocument = (document) => Option.map(topLevelVariantsOf(document), indexEntriesOf);
78
+ const collapseUnionsInValue = (value) => {
79
+ if (Array_.isArray(value)) {
80
+ return Array_.map(value, collapseUnionsInValue);
81
+ }
82
+ if (!isRecord(value)) {
83
+ return value;
84
+ }
85
+ if (isDiscriminatedUnion(value)) {
86
+ return Option.match(anyOfOf(value), {
87
+ onNone: () => value,
88
+ onSome: members => ({
89
+ _summary: 'union',
90
+ variants: indexEntriesOf(members),
91
+ }),
92
+ });
93
+ }
94
+ return Record.map(value, child => collapseUnionsInValue(child));
95
+ };
96
+ const collapseUnionsInVariantPayload = (variant) => {
97
+ if (!isRecord(variant)) {
98
+ return variant;
99
+ }
100
+ const properties = variant['properties'];
101
+ if (!isRecord(properties)) {
102
+ return variant;
103
+ }
104
+ const collapsed = Record.map(properties, (schema, name) => name === '_tag' ? schema : collapseUnionsInValue(schema));
105
+ return { ...variant, properties: collapsed };
106
+ };
107
+ const findVariantByTag = (members, tag) => Array_.findFirst(members, variant => Option.exists(variantTagOf(variant), candidate => candidate === tag));
108
+ /**
109
+ * Idiomatic Foldkit Messages carry at most one tagged-union payload field per
110
+ * variant: either a `Got<Child>Message { message }` Submodel wrapper or a
111
+ * regular Message with one tagged-union value-type payload (e.g. `ClickedLink {
112
+ * request: UrlRequest }`). Multi-union-field variants are non-idiomatic;
113
+ * surrounding state that a child Submodel needs belongs as an argument to
114
+ * the child's `update`/`view`, not as a sibling field on the parent Message.
115
+ * Returns `None` when zero or multiple union fields exist so the path walker
116
+ * can produce an actionable error rather than silently picking one.
117
+ */
118
+ const singleUnionFieldOf = (variant) => {
119
+ if (!isRecord(variant)) {
120
+ return Option.none();
121
+ }
122
+ const properties = variant['properties'];
123
+ if (!isRecord(properties)) {
124
+ return Option.none();
125
+ }
126
+ const unionEntries = pipe(Record.toEntries(properties), Array_.filter(([name, schema]) => name !== '_tag' && isDiscriminatedUnion(schema)));
127
+ if (unionEntries.length !== 1) {
128
+ return Option.none();
129
+ }
130
+ return Option.gen(function* () {
131
+ const [name, schema] = yield* Array_.head(unionEntries);
132
+ const members = yield* anyOfOf(schema);
133
+ return { name, members };
134
+ });
135
+ };
136
+ const replaceUnionField = (variant, fieldName, narrowedChild) => {
137
+ if (!isRecord(variant)) {
138
+ return variant;
139
+ }
140
+ const properties = variant['properties'];
141
+ if (!isRecord(properties)) {
142
+ return variant;
143
+ }
144
+ const updated = {
145
+ ...properties,
146
+ [fieldName]: { anyOf: [narrowedChild] },
147
+ };
148
+ return { ...variant, properties: updated };
149
+ };
150
+ const stepIntoVariant = (variant, rest) => Option.gen(function* () {
151
+ const field = yield* singleUnionFieldOf(variant);
152
+ const narrowedChild = yield* narrowAtPath(field.members, rest);
153
+ return replaceUnionField(variant, field.name, narrowedChild);
154
+ });
155
+ const narrowAtPath = (members, segments) => Option.gen(function* () {
156
+ const tag = yield* Array_.head(segments);
157
+ const variant = yield* findVariantByTag(members, tag);
158
+ const rest = Array_.drop(segments, 1);
159
+ if (Array_.isReadonlyArrayEmpty(rest)) {
160
+ return collapseUnionsInVariantPayload(variant);
161
+ }
162
+ return yield* stepIntoVariant(variant, rest);
163
+ });
164
+ /**
165
+ * Split a dot-separated variant path into its segments. Empty segments are
166
+ * dropped so callers can pass user-supplied strings without first trimming
167
+ * leading/trailing dots.
168
+ */
169
+ export const splitVariantPath = (variantPath) => pipe(variantPath, String_.split(PATH_SEPARATOR), Array_.filter(String_.isNonEmpty));
170
+ const stepToNextUnionMembers = (members, segment) => Option.gen(function* () {
171
+ const variant = yield* findVariantByTag(members, segment);
172
+ const field = yield* singleUnionFieldOf(variant);
173
+ return field.members;
174
+ });
175
+ const variantsAtPathPrefix = (document, segments) => Option.flatMap(topLevelVariantsOf(document), topMembers => Array_.reduce(segments, Option.some(topMembers), (currentMembers, segment) => Option.flatMap(currentMembers, members => stepToNextUnionMembers(members, segment))));
176
+ /**
177
+ * Enumerate the variant tags available as the next segment of a variant path.
178
+ * Given a partial path that resolves to a tagged-union field, returns the tags
179
+ * of every variant in that union. Useful for crafting `not-found` error
180
+ * messages: if `narrowToVariant` fails on `"a.b.c"`, calling this with `["a", "b"]`
181
+ * yields the valid choices for the third segment.
182
+ *
183
+ * Returns `None` when the prefix cannot be resolved at all (e.g. the first
184
+ * segment names no top-level variant, or an intermediate variant lacks a
185
+ * tagged-union payload field).
186
+ */
187
+ export const variantTagsAtPathPrefix = (document, pathPrefix) => Option.map(variantsAtPathPrefix(document, pathPrefix), members => Array_.map(indexEntriesOf(members), entry => entry.tag));
188
+ /**
189
+ * Diagnose where a variant-path walk would fail. Walks back from one segment
190
+ * before the supplied path length, returning the deepest prefix whose
191
+ * tagged-union level resolves cleanly, the tags available at that level, and
192
+ * the next segment from the original path (the one that broke the walk). When
193
+ * the offending segment is a known tag at that level, the failure means the
194
+ * variant exists but has no tagged-union field to step into further. When it
195
+ * is unknown, the failure is a simple typo. Returns `None` when not even the
196
+ * empty prefix resolves (i.e. the document is not a discriminated union at
197
+ * the top level).
198
+ */
199
+ export const diagnoseVariantPath = (document, segments) => {
200
+ const tryLength = (length) => {
201
+ if (length < 0) {
202
+ return Option.none();
203
+ }
204
+ const prefix = Array_.take(segments, length);
205
+ return Option.match(variantTagsAtPathPrefix(document, prefix), {
206
+ onNone: () => tryLength(length - 1),
207
+ onSome: available => Option.some({
208
+ prefix,
209
+ failingSegment: Array_.get(segments, length),
210
+ available,
211
+ }),
212
+ });
213
+ };
214
+ return tryLength(segments.length - 1);
215
+ };
216
+ /**
217
+ * Replace the top-level `anyOf` in a JSON Schema document with a single-element
218
+ * `anyOf` containing the variant(s) selected by a dot-separated variant path.
219
+ *
220
+ * `variantPath` is a dot-string of variant `_tag` values, walked through the
221
+ * one tagged-union payload field at each step. For example, `"GotChildMessage"`
222
+ * narrows the top-level union to that wrapper variant, and
223
+ * `"GotChildMessage.Opened"` walks into the wrapper's nested union and narrows
224
+ * to the inner variant. Any deeper discriminated unions inside the deepest
225
+ * variant's payload are collapsed to `{ _summary: 'union', variants: [...] }`
226
+ * placeholders so the response stays small even for deeply-nested Submodel
227
+ * trees; agents drill further by extending the path.
228
+ *
229
+ * The `definitions` block is kept (any `$ref` targets the narrowed variant
230
+ * relies on still resolve, and dead refs left over from trimmed variants are
231
+ * harmless), but any discriminated unions inside it are collapsed to the same
232
+ * `_summary` placeholder shape so a shared union annotated with an
233
+ * `identifier` does not balloon the response. The path walker does not
234
+ * resolve `$ref` indirection through `definitions`; agents that need to step
235
+ * through a `$ref`-shared union look up the definition by name and use the
236
+ * placeholder's variant list directly.
237
+ *
238
+ * Returns `None` when the document is not a top-level discriminated union, the
239
+ * path is empty, a segment names no variant in the current union, or an
240
+ * intermediate variant lacks exactly one tagged-union payload field to step
241
+ * into (zero or multiple union fields are both ambiguous). The "exactly one"
242
+ * rule encodes the Foldkit idiom (`Got<Child>Message { message }` Submodel
243
+ * wrappers, single-union value-type fields); apps whose Message variants need
244
+ * additional surrounding state should pass it as an argument to the child's
245
+ * `update`/`view` rather than as a sibling field on the parent Message.
246
+ */
247
+ export const narrowToVariant = (document, variantPath) => {
248
+ if (!isRecord(document)) {
249
+ return Option.none();
250
+ }
251
+ const segments = splitVariantPath(variantPath);
252
+ if (Array_.isReadonlyArrayEmpty(segments)) {
253
+ return Option.none();
254
+ }
255
+ return Option.gen(function* () {
256
+ const members = yield* topLevelVariantsOf(document);
257
+ const narrowed = yield* narrowAtPath(members, segments);
258
+ return {
259
+ ...document,
260
+ schema: { anyOf: [narrowed] },
261
+ definitions: collapseUnionsInValue(document['definitions']),
262
+ };
263
+ });
264
+ };
@@ -20,6 +20,15 @@ type Hot = NonNullable<ImportMeta['hot']>;
20
20
  * without author-side changes. When `maybeMessageSchema` is `None`, dispatch
21
21
  * requests are rejected with an informative error.
22
22
  *
23
+ * The bridge also derives a JSON Schema document from `maybeMessageSchema`
24
+ * once at boot (via `Schema.toJsonSchemaDocument`) to fulfill
25
+ * `RequestGetMessageSchema`, so MCP clients can discover the exact Message
26
+ * shapes the runtime accepts without reading the application source. A few
27
+ * AST nodes (symbol-keyed structs, symbol-indexed records, tuples with
28
+ * post-rest elements) cause `Schema.toJsonSchemaDocument` to throw; the
29
+ * derivation is guarded so a failure logs a warning and the schema-discovery
30
+ * tool returns `None` rather than crashing the bridge.
31
+ *
23
32
  * Production-safe: callers must check `import.meta.hot` is defined before
24
33
  * invoking this. The function assumes a live HMR connection.
25
34
  */
@@ -1 +1 @@
1
- {"version":3,"file":"webSocketBridge.d.ts","sourceRoot":"","sources":["../../src/devTools/webSocketBridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,MAAM,EAIN,MAAM,EAEN,MAAM,IAAI,CAAC,EAGZ,MAAM,QAAQ,CAAA;AA+Bf,OAAO,EAAE,KAAK,aAAa,EAAc,MAAM,YAAY,CAAA;AAQ3D,KAAK,GAAG,GAAG,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAA;AAczC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,oBAAoB,GAC/B,OAAO,aAAa,EACpB,KAAK,GAAG,EACR,UAAU,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EACnD,oBAAoB,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KACnD,MAAM,CAAC,MAAM,CAAC,IAAI,CAiFjB,CAAA"}
1
+ {"version":3,"file":"webSocketBridge.d.ts","sourceRoot":"","sources":["../../src/devTools/webSocketBridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,MAAM,EAIN,MAAM,EAEN,MAAM,IAAI,CAAC,EAGZ,MAAM,QAAQ,CAAA;AAwCf,OAAO,EAAE,KAAK,aAAa,EAAc,MAAM,YAAY,CAAA;AAQ3D,KAAK,GAAG,GAAG,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAA;AA4BzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,oBAAoB,GAC/B,OAAO,aAAa,EACpB,KAAK,GAAG,EACR,UAAU,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EACnD,oBAAoB,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KACnD,MAAM,CAAC,MAAM,CAAC,IAAI,CAsFjB,CAAA"}
@@ -1,6 +1,7 @@
1
1
  import { Array, Cause, Effect, Exit, HashMap, Match, Option, Order, Schema as S, SubscriptionRef, pipe, } from 'effect';
2
2
  import { OptionExt } from '../effectExtensions/index.js';
3
- import { EventConnected, EventDisconnected, EventFrame, KeyframeInfo, RequestFrame, ResponseDispatched, ResponseError, ResponseFrame, ResponseInit, ResponseKeyframes, ResponseMessage, ResponseMessages, ResponseModel, ResponseReplayed, ResponseResumed, ResponseRuntimeState, RuntimeInfo, } from './protocol.js';
3
+ import { EventConnected, EventDisconnected, EventFrame, KeyframeInfo, MessageSchemaDocumentResult, MessageSchemaIndexResult, RequestFrame, ResponseDispatched, ResponseError, ResponseFrame, ResponseInit, ResponseKeyframes, ResponseMessage, ResponseMessageSchema, ResponseMessages, ResponseModel, ResponseReplayed, ResponseResumed, ResponseRuntimeState, RuntimeInfo, } from './protocol.js';
4
+ import { diagnoseVariantPath, indexMessageSchemaDocument, narrowToVariant, splitVariantPath, } from './schemaSummarize.js';
4
5
  import { toInspectableValue, toSerializedCommand, toSerializedEntry, toSerializedMount, } from './serialize.js';
5
6
  import { INIT_INDEX } from './store.js';
6
7
  import { formatPathNotFound, resolvePath, summarizeValue, } from './summarize.js';
@@ -9,6 +10,15 @@ const RESPONSE_CHANNEL = 'foldkit:devTools:response';
9
10
  const EVENT_CHANNEL = 'foldkit:devTools:event';
10
11
  const generateConnectionId = () => `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
11
12
  const currentAbsoluteIndex = (entriesLength, startIndex) => (entriesLength === 0 ? INIT_INDEX : startIndex + entriesLength - 1);
13
+ const tryDeriveJsonSchemaDocument = (schema) => {
14
+ try {
15
+ return Option.some(S.toJsonSchemaDocument(schema));
16
+ }
17
+ catch (error) {
18
+ console.warn('[foldkit:devTools] Failed to derive JSON Schema from Message Schema; foldkit_get_message_schema will return None.', error);
19
+ return Option.none();
20
+ }
21
+ };
12
22
  /**
13
23
  * Start the browser-side WebSocket bridge that exposes a Foldkit runtime's
14
24
  * DevToolsStore to an external MCP server (via the Vite plugin relay).
@@ -28,6 +38,15 @@ const currentAbsoluteIndex = (entriesLength, startIndex) => (entriesLength === 0
28
38
  * without author-side changes. When `maybeMessageSchema` is `None`, dispatch
29
39
  * requests are rejected with an informative error.
30
40
  *
41
+ * The bridge also derives a JSON Schema document from `maybeMessageSchema`
42
+ * once at boot (via `Schema.toJsonSchemaDocument`) to fulfill
43
+ * `RequestGetMessageSchema`, so MCP clients can discover the exact Message
44
+ * shapes the runtime accepts without reading the application source. A few
45
+ * AST nodes (symbol-keyed structs, symbol-indexed records, tuples with
46
+ * post-rest elements) cause `Schema.toJsonSchemaDocument` to throw; the
47
+ * derivation is guarded so a failure logs a warning and the schema-discovery
48
+ * tool returns `None` rather than crashing the bridge.
49
+ *
31
50
  * Production-safe: callers must check `import.meta.hot` is defined before
32
51
  * invoking this. The function assumes a live HMR connection.
33
52
  */
@@ -35,6 +54,7 @@ export const startWebSocketBridge = (store, hot, dispatch, maybeMessageSchema) =
35
54
  const connectionId = generateConnectionId();
36
55
  const capturedContext = yield* Effect.context();
37
56
  const maybeDispatchSchema = Option.map(maybeMessageSchema, S.toCodecJson);
57
+ const maybeJsonSchemaDocument = Option.flatMap(maybeMessageSchema, tryDeriveJsonSchemaDocument);
38
58
  const encodeEventFrame = S.encodeUnknownSync(EventFrame);
39
59
  const encodeResponseFrame = S.encodeUnknownSync(ResponseFrame);
40
60
  const sendEvent = (event) => {
@@ -54,7 +74,7 @@ export const startWebSocketBridge = (store, hot, dispatch, maybeMessageSchema) =
54
74
  }),
55
75
  }));
56
76
  const handleRequest = (id, request) => Effect.gen(function* () {
57
- const response = yield* dispatchRequest(store, dispatch, maybeDispatchSchema, request);
77
+ const response = yield* dispatchRequest(store, dispatch, maybeDispatchSchema, maybeJsonSchemaDocument, request);
58
78
  sendResponse(id, response);
59
79
  });
60
80
  const handleRequestFrame = (frame) => {
@@ -99,7 +119,47 @@ const readModelResponse = (store, index, maybePath, expand) => Effect.gen(functi
99
119
  }).pipe(Effect.catchCause(cause => Effect.succeed(ResponseError({
100
120
  reason: `Failed to read Model at index ${index}: ${Cause.pretty(cause)}`,
101
121
  }))));
102
- const dispatchRequest = (store, dispatch, maybeDispatchSchema, request) => Match.value(request).pipe(Match.tagsExhaustive({
122
+ const indexResponse = (document) => Option.match(indexMessageSchemaDocument(document), {
123
+ onNone: () => ResponseError({
124
+ reason: "Could not index Message Schema: the top-level shape is not a discriminated union of '_tag'-keyed structs. Open an issue if you see this against an Effect Schema released after foldkit's last sync.",
125
+ }),
126
+ onSome: variants => ResponseMessageSchema({
127
+ maybeResult: Option.some(MessageSchemaIndexResult({ index: { variants } })),
128
+ }),
129
+ });
130
+ const narrowResponse = (document, variantPath) => Option.match(narrowToVariant(document, variantPath), {
131
+ onNone: () => formatUnknownVariantError(document, variantPath),
132
+ onSome: narrowed => ResponseMessageSchema({
133
+ maybeResult: Option.some(MessageSchemaDocumentResult({ document: narrowed })),
134
+ }),
135
+ });
136
+ const buildMessageSchemaResponse = (maybeJsonSchemaDocument, maybeVariantTag) => Option.match(maybeJsonSchemaDocument, {
137
+ onNone: () => ResponseMessageSchema({ maybeResult: Option.none() }),
138
+ onSome: document => Option.match(maybeVariantTag, {
139
+ onNone: () => indexResponse(document),
140
+ onSome: variantTag => narrowResponse(document, variantTag),
141
+ }),
142
+ });
143
+ const formatUnknownVariantError = (document, variantPath) => {
144
+ const segments = splitVariantPath(variantPath);
145
+ return Option.match(diagnoseVariantPath(document, segments), {
146
+ onNone: () => ResponseError({
147
+ reason: `No Message variant at path '${variantPath}'. The runtime's Message Schema is not a discriminated union of '_tag'-keyed structs.`,
148
+ }),
149
+ onSome: ({ prefix, failingSegment, available }) => {
150
+ const prefixLabel = Array.isReadonlyArrayNonEmpty(prefix)
151
+ ? prefix.join('.')
152
+ : '<top level>';
153
+ const failingIsKnownTag = Option.exists(failingSegment, tag => available.includes(tag));
154
+ const failingTag = Option.getOrElse(failingSegment, () => '');
155
+ const reason = failingIsKnownTag
156
+ ? `No further structure to drill into at path '${variantPath}'. The variant '${failingTag}' at ${prefixLabel} does not carry exactly one tagged-union payload field, which is what the walker steps through. Idiomatic Foldkit Messages have at most one tagged-union field per variant (the 'message' field on Submodel wrappers, or a single value-type union); state surrounding a Submodel call belongs as an argument to the child's update/view, not as a sibling field on the parent Message.`
157
+ : `No Message variant at path '${variantPath}'. Available variants at ${prefixLabel}: ${available.join(', ')}.`;
158
+ return ResponseError({ reason });
159
+ },
160
+ });
161
+ };
162
+ const dispatchRequest = (store, dispatch, maybeDispatchSchema, maybeJsonSchemaDocument, request) => Match.value(request).pipe(Match.tagsExhaustive({
103
163
  RequestGetModel: ({ maybePath, expand }) => Effect.gen(function* () {
104
164
  const state = yield* SubscriptionRef.get(store.stateRef);
105
165
  const index = currentAbsoluteIndex(state.entries.length, state.startIndex);
@@ -166,6 +226,7 @@ const dispatchRequest = (store, dispatch, maybeDispatchSchema, request) => Match
166
226
  reason: `Invalid Message: ${error instanceof Error ? error.message : String(error)}\n\nReceived (typeof ${typeof message}): ${JSON.stringify(message)}`,
167
227
  })))),
168
228
  }),
229
+ RequestGetMessageSchema: ({ maybeVariantTag }) => Effect.succeed(buildMessageSchemaResponse(maybeJsonSchemaDocument, maybeVariantTag)),
169
230
  RequestListRuntimes: () => Effect.succeed(ResponseError({
170
231
  reason: 'RequestListRuntimes is plugin-handled and should not reach the runtime bridge',
171
232
  })),
@@ -1,4 +1,5 @@
1
1
  import { RoutingConfig } from './runtime.js';
2
2
  export declare const addNavigationEventListeners: <Message>(dispatch: (message: Message) => void, routingConfig: RoutingConfig<Message>) => void;
3
+ export declare const addLinkClickListener: <Message>(dispatch: (message: Message) => void, routingConfig: RoutingConfig<Message>) => void;
3
4
  export declare const addBfcacheRestoreListener: () => void;
4
5
  //# sourceMappingURL=browserListeners.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"browserListeners.d.ts","sourceRoot":"","sources":["../../src/runtime/browserListeners.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAE5C,eAAO,MAAM,2BAA2B,GAAI,OAAO,EACjD,UAAU,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,EACpC,eAAe,aAAa,CAAC,OAAO,CAAC,SAKtC,CAAA;AA6ED,eAAO,MAAM,yBAAyB,YASrC,CAAA"}
1
+ {"version":3,"file":"browserListeners.d.ts","sourceRoot":"","sources":["../../src/runtime/browserListeners.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAE5C,eAAO,MAAM,2BAA2B,GAAI,OAAO,EACjD,UAAU,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,EACpC,eAAe,aAAa,CAAC,OAAO,CAAC,SAKtC,CAAA;AAaD,eAAO,MAAM,oBAAoB,GAAI,OAAO,EAC1C,UAAU,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,EACpC,eAAe,aAAa,CAAC,OAAO,CAAC,SAoDtC,CAAA;AA4BD,eAAO,MAAM,yBAAyB,YASrC,CAAA"}
@@ -12,20 +12,32 @@ const addPopStateListener = (dispatch, routingConfig) => {
12
12
  };
13
13
  window.addEventListener('popstate', onPopState);
14
14
  };
15
- const addLinkClickListener = (dispatch, routingConfig) => {
15
+ export const addLinkClickListener = (dispatch, routingConfig) => {
16
16
  const onLinkClick = (event) => {
17
- const target = event.target;
18
- if (!(target instanceof Element)) {
17
+ const isNonPrimaryButton = event.button !== 0;
18
+ const isModifierKeyPressed = event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
19
+ const isDefaultPrevented = event.defaultPrevented;
20
+ if (isNonPrimaryButton || isModifierKeyPressed || isDefaultPrevented) {
19
21
  return;
20
22
  }
21
- const maybeLink = Option.fromNullishOr(target.closest('a'));
23
+ const eventTarget = event.target;
24
+ if (!(eventTarget instanceof Element)) {
25
+ return;
26
+ }
27
+ const maybeLink = Option.fromNullishOr(eventTarget.closest('a'));
22
28
  if (Option.isNone(maybeLink)) {
23
29
  return;
24
30
  }
25
- const { href } = maybeLink.value;
31
+ const link = maybeLink.value;
32
+ const { href } = link;
26
33
  if (String.isEmpty(href)) {
27
34
  return;
28
35
  }
36
+ const isNonSelfTarget = !String.isEmpty(link.target) && link.target !== '_self';
37
+ const isDownloadLink = link.hasAttribute('download');
38
+ if (isNonSelfTarget || isDownloadLink) {
39
+ return;
40
+ }
29
41
  event.preventDefault();
30
42
  const linkUrl = new URL(href);
31
43
  const currentUrl = new URL(window.location.href);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foldkit",
3
- "version": "0.99.0",
3
+ "version": "0.100.0",
4
4
  "description": "A TypeScript frontend framework, built on Effect and architected like Elm",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",