@@ -3,92 +3,102 @@ import os from "node:os"
33import path from "node:path"
44import { onCleanup , onMount } from "solid-js"
55import { createStore } from "solid-js/store"
6- import z from "zod "
6+ import { Option , Schema , SchemaGetter } from "effect "
77import { isRecord } from "@/util/record"
88import { createSimpleContext } from "./helper"
99import { resolveZedDbPath , resolveZedSelection } from "./editor-zed"
1010
1111const MCP_PROTOCOL_VERSION = "2025-11-25"
1212
13- const JsonRpcMessageSchema = z . object ( {
14- id : z . union ( [ z . number ( ) , z . string ( ) , z . null ( ) ] ) . optional ( ) ,
15- method : z . string ( ) . optional ( ) ,
16- params : z . unknown ( ) . optional ( ) ,
17- result : z . unknown ( ) . optional ( ) ,
18- error : z
19- . object ( {
20- code : z . number ( ) . optional ( ) ,
21- message : z . string ( ) . optional ( ) ,
22- } )
23- . optional ( ) ,
13+ const JsonRpcMessageSchema = Schema . Struct ( {
14+ id : Schema . optional ( Schema . Union ( [ Schema . Number , Schema . String , Schema . Null ] ) ) ,
15+ method : Schema . optional ( Schema . String ) ,
16+ params : Schema . optional ( Schema . Unknown ) ,
17+ result : Schema . optional ( Schema . Unknown ) ,
18+ error : Schema . optional (
19+ Schema . Struct ( {
20+ code : Schema . optional ( Schema . Number ) ,
21+ message : Schema . optional ( Schema . String ) ,
22+ } ) ,
23+ ) ,
2424} )
2525
26- const PositionSchema = z . object ( {
27- line : z . number ( ) ,
28- character : z . number ( ) ,
26+ const PositionSchema = Schema . Struct ( {
27+ line : Schema . Number ,
28+ character : Schema . Number ,
2929} )
3030
31- const EditorSelectionRangeSchema = z . object ( {
32- text : z . string ( ) ,
33- selection : z . object ( {
31+ const EditorSelectionRangeSchema = Schema . Struct ( {
32+ text : Schema . String ,
33+ selection : Schema . Struct ( {
3434 start : PositionSchema ,
3535 end : PositionSchema ,
3636 } ) ,
3737} )
3838
39- const EditorSelectionSchema = z
40- . union ( [
41- z . object ( {
42- filePath : z . string ( ) ,
43- source : z . enum ( [ "websocket" , "zed" ] ) . optional ( ) ,
44- ranges : z . array ( EditorSelectionRangeSchema ) . min ( 1 ) ,
45- } ) ,
46- z . object ( {
47- text : z . string ( ) ,
48- filePath : z . string ( ) ,
49- source : z . enum ( [ "websocket" , "zed" ] ) . optional ( ) ,
50- selection : z . object ( {
51- start : PositionSchema ,
52- end : PositionSchema ,
53- } ) ,
39+ const EditorSelectionRangesSchema = Schema . Struct ( {
40+ filePath : Schema . String ,
41+ source : Schema . optional ( Schema . Literals ( [ "websocket" , "zed" ] ) ) ,
42+ ranges : Schema . mutable ( Schema . Array ( EditorSelectionRangeSchema ) . check ( Schema . isMinLength ( 1 ) ) ) ,
43+ } )
44+
45+ const EditorSelectionSchema = Schema . Union ( [
46+ EditorSelectionRangesSchema ,
47+ Schema . Struct ( {
48+ text : Schema . String ,
49+ filePath : Schema . String ,
50+ source : Schema . optional ( Schema . Literals ( [ "websocket" , "zed" ] ) ) ,
51+ selection : Schema . Struct ( {
52+ start : PositionSchema ,
53+ end : PositionSchema ,
5454 } ) ,
55- ] )
56- . transform ( ( value ) =>
57- "ranges" in value
58- ? value
59- : {
60- filePath : value . filePath ,
61- source : value . source ,
62- ranges : [
63- {
64- text : value . text ,
65- selection : value . selection ,
66- } ,
67- ] ,
68- } ,
69- )
70-
71- const EditorMentionSchema = z . object ( {
72- filePath : z . string ( ) ,
73- lineStart : z . number ( ) ,
74- lineEnd : z . number ( ) ,
55+ } ) ,
56+ ] ) . pipe (
57+ Schema . decodeTo ( EditorSelectionRangesSchema , {
58+ decode : SchemaGetter . transform ( ( value ) =>
59+ "ranges" in value
60+ ? value
61+ : {
62+ filePath : value . filePath ,
63+ source : value . source ,
64+ ranges : [
65+ {
66+ text : value . text ,
67+ selection : value . selection ,
68+ } ,
69+ ] ,
70+ } ,
71+ ) ,
72+ encode : SchemaGetter . passthrough ( { strict : false } ) ,
73+ } ) ,
74+ )
75+
76+ const EditorMentionSchema = Schema . Struct ( {
77+ filePath : Schema . String ,
78+ lineStart : Schema . Number ,
79+ lineEnd : Schema . Number ,
7580} )
7681
77- const EditorServerInfoSchema = z . object ( {
78- protocolVersion : z . string ( ) . optional ( ) ,
79- serverInfo : z
80- . object ( {
81- name : z . string ( ) . optional ( ) ,
82- version : z . string ( ) . optional ( ) ,
83- } )
84- . optional ( ) ,
82+ const EditorServerInfoSchema = Schema . Struct ( {
83+ protocolVersion : Schema . optional ( Schema . String ) ,
84+ serverInfo : Schema . optional (
85+ Schema . Struct ( {
86+ name : Schema . optional ( Schema . String ) ,
87+ version : Schema . optional ( Schema . String ) ,
88+ } ) ,
89+ ) ,
8590} )
8691
87- type JsonRpcMessage = z . infer < typeof JsonRpcMessageSchema >
88- export type EditorSelection = z . infer < typeof EditorSelectionSchema >
89- export type EditorMention = z . infer < typeof EditorMentionSchema >
92+ const decodeJsonRpcMessage = Schema . decodeUnknownOption ( JsonRpcMessageSchema )
93+ const decodeEditorSelection = Schema . decodeUnknownOption ( EditorSelectionSchema )
94+ const decodeEditorMention = Schema . decodeUnknownOption ( EditorMentionSchema )
95+ const decodeEditorServerInfo = Schema . decodeUnknownOption ( EditorServerInfoSchema )
96+
97+ type JsonRpcMessage = Schema . Schema . Type < typeof JsonRpcMessageSchema >
98+ export type EditorSelection = Schema . Schema . Type < typeof EditorSelectionSchema >
99+ export type EditorMention = Schema . Schema . Type < typeof EditorMentionSchema >
90100export type EditorLabelState = "pending" | "sent" | "none"
91- type EditorServerInfo = z . infer < typeof EditorServerInfoSchema >
101+ type EditorServerInfo = Schema . Schema . Type < typeof EditorServerInfoSchema >
92102
93103type EditorConnection = {
94104 url : string
@@ -214,16 +224,15 @@ export const { use: useEditorContext, provider: EditorContextProvider } = create
214224 const message = parseMessage ( event . data )
215225 if ( ! message ) return
216226
217- const selection =
218- message . method === "selection_changed" ? EditorSelectionSchema . safeParse ( message . params ) : undefined
219- if ( selection ?. success ) {
220- setSelection ( { ...selection . data , source : "websocket" } )
227+ const selection = message . method === "selection_changed" ? decodeEditorSelection ( message . params ) : Option . none ( )
228+ if ( Option . isSome ( selection ) ) {
229+ setSelection ( { ...selection . value , source : "websocket" } )
221230 return
222231 }
223232
224- const mention = message . method === "at_mentioned" ? EditorMentionSchema . safeParse ( message . params ) : undefined
225- if ( mention ?. success ) {
226- mentionListeners . forEach ( ( listener ) => listener ( mention . data ) )
233+ const mention = message . method === "at_mentioned" ? decodeEditorMention ( message . params ) : Option . none ( )
234+ if ( Option . isSome ( mention ) ) {
235+ mentionListeners . forEach ( ( listener ) => listener ( mention . value ) )
227236 return
228237 }
229238
@@ -235,9 +244,9 @@ export const { use: useEditorContext, provider: EditorContextProvider } = create
235244 pending . delete ( message . id )
236245 if ( message . error ) return
237246
238- const initialize = method === "initialize" ? EditorServerInfoSchema . safeParse ( message . result ) : undefined
239- if ( initialize ?. success ) {
240- setStore ( "server" , initialize . data )
247+ const initialize = method === "initialize" ? decodeEditorServerInfo ( message . result ) : Option . none ( )
248+ if ( Option . isSome ( initialize ) ) {
249+ setStore ( "server" , initialize . value )
241250 send ( { method : "notifications/initialized" } )
242251 return
243252 }
@@ -447,7 +456,7 @@ function parseMessage(value: unknown) {
447456 if ( typeof value !== "string" ) return
448457
449458 try {
450- return JsonRpcMessageSchema . parse ( JSON . parse ( value ) )
459+ return Option . getOrUndefined ( decodeJsonRpcMessage ( JSON . parse ( value ) ) )
451460 } catch {
452461 return
453462 }
0 commit comments