Buckets:
| import { onCleanup, onMount } from "solid-js" | |
| import { createStore } from "solid-js/store" | |
| import { Option, Schema, SchemaGetter } from "effect" | |
| import { isRecord } from "../util/record" | |
| import { useTuiPaths } from "./runtime" | |
| import { createSimpleContext } from "./helper" | |
| import { editorIntegration } from "../editor" | |
| const MCP_PROTOCOL_VERSION = "2025-11-25" | |
| const JsonRpcMessageSchema = Schema.Struct({ | |
| id: Schema.optional(Schema.Union([Schema.Number, Schema.String, Schema.Null])), | |
| method: Schema.optional(Schema.String), | |
| params: Schema.optional(Schema.Unknown), | |
| result: Schema.optional(Schema.Unknown), | |
| error: Schema.optional( | |
| Schema.Struct({ | |
| code: Schema.optional(Schema.Number), | |
| message: Schema.optional(Schema.String), | |
| }), | |
| ), | |
| }) | |
| const PositionSchema = Schema.Struct({ | |
| line: Schema.Number, | |
| character: Schema.Number, | |
| }) | |
| const EditorSelectionRangeSchema = Schema.Struct({ | |
| text: Schema.String, | |
| selection: Schema.Struct({ | |
| start: PositionSchema, | |
| end: PositionSchema, | |
| }), | |
| }) | |
| const EditorSelectionRangesSchema = Schema.Struct({ | |
| filePath: Schema.String, | |
| source: Schema.optional(Schema.Literals(["websocket", "zed"])), | |
| ranges: Schema.mutable(Schema.Array(EditorSelectionRangeSchema).check(Schema.isMinLength(1))), | |
| }) | |
| const EditorSelectionSchema = Schema.Union([ | |
| EditorSelectionRangesSchema, | |
| Schema.Struct({ | |
| text: Schema.String, | |
| filePath: Schema.String, | |
| source: Schema.optional(Schema.Literals(["websocket", "zed"])), | |
| selection: Schema.Struct({ | |
| start: PositionSchema, | |
| end: PositionSchema, | |
| }), | |
| }), | |
| ]).pipe( | |
| Schema.decodeTo(EditorSelectionRangesSchema, { | |
| decode: SchemaGetter.transform((value) => | |
| "ranges" in value | |
| ? value | |
| : { | |
| filePath: value.filePath, | |
| source: value.source, | |
| ranges: [ | |
| { | |
| text: value.text, | |
| selection: value.selection, | |
| }, | |
| ], | |
| }, | |
| ), | |
| encode: SchemaGetter.passthrough({ strict: false }), | |
| }), | |
| ) | |
| const EditorMentionSchema = Schema.Struct({ | |
| filePath: Schema.String, | |
| lineStart: Schema.Number, | |
| lineEnd: Schema.Number, | |
| }) | |
| const EditorServerInfoSchema = Schema.Struct({ | |
| protocolVersion: Schema.optional(Schema.String), | |
| serverInfo: Schema.optional( | |
| Schema.Struct({ | |
| name: Schema.optional(Schema.String), | |
| version: Schema.optional(Schema.String), | |
| }), | |
| ), | |
| }) | |
| const decodeJsonRpcMessage = Schema.decodeUnknownOption(JsonRpcMessageSchema) | |
| const decodeEditorSelection = Schema.decodeUnknownOption(EditorSelectionSchema) | |
| const decodeEditorMention = Schema.decodeUnknownOption(EditorMentionSchema) | |
| const decodeEditorServerInfo = Schema.decodeUnknownOption(EditorServerInfoSchema) | |
| type JsonRpcMessage = Schema.Schema.Type<typeof JsonRpcMessageSchema> | |
| export type EditorSelection = Schema.Schema.Type<typeof EditorSelectionSchema> | |
| export type EditorMention = Schema.Schema.Type<typeof EditorMentionSchema> | |
| export type EditorLabelState = "pending" | "sent" | "none" | |
| type EditorServerInfo = Schema.Schema.Type<typeof EditorServerInfoSchema> | |
| type EditorConnection = { | |
| url: string | |
| authToken?: string | |
| source: string | |
| } | |
| export type EditorIntegration = Readonly<{ | |
| connection?(directory: string): EditorConnection | undefined | |
| selection?(directory: string): Promise<unknown> | |
| }> | |
| export const { use: useEditorContext, provider: EditorContextProvider } = createSimpleContext({ | |
| name: "EditorContext", | |
| init: (props: { integration?: EditorIntegration; WebSocketImpl?: typeof WebSocket }) => { | |
| const paths = useTuiPaths() | |
| const editor = props.integration ?? editorIntegration | |
| const value = process.env.CLAUDE_CODE_SSE_PORT || process.env.OPENCODE_EDITOR_SSE_PORT | |
| const parsedPort = value ? Number.parseInt(value, 10) : undefined | |
| const port = | |
| parsedPort && Number.isInteger(parsedPort) && parsedPort > 0 && parsedPort <= 65535 ? parsedPort : undefined | |
| const zedTerminal = process.env.ZED_TERM === "true" || process.env.TERM_PROGRAM?.toLowerCase() === "zed" | |
| const mentionListeners = new Set<(mention: EditorMention) => void>() | |
| const WebSocketImpl = props.WebSocketImpl ?? WebSocket | |
| const [store, setStore] = createStore<{ | |
| status: "disabled" | "connecting" | "connected" | |
| selection: EditorSelection | undefined | |
| selectionSent: boolean | |
| server: EditorServerInfo | undefined | |
| }>({ | |
| status: "disabled", | |
| selection: undefined, | |
| selectionSent: false, | |
| server: undefined, | |
| }) | |
| let socket: WebSocket | undefined | |
| let closed = false | |
| let reconnect: ReturnType<typeof setTimeout> | undefined | |
| let attempt = 0 | |
| let requestID = 0 | |
| let zedSelection: Promise<void> | undefined | |
| let lastZedSelectionKey: string | undefined | |
| let directory = paths.cwd | |
| let preserveSelectionOnReconnect = false | |
| const pending = new Map<number, string>() | |
| const setSelection = (selection: EditorSelection | undefined) => { | |
| const changed = editorSelectionKey(selection) !== editorSelectionKey(store.selection) | |
| setStore("selection", selection) | |
| if (changed) setStore("selectionSent", false) | |
| } | |
| const clearSelectionForReconnect = (options?: { resetZedSelectionKey?: boolean }) => { | |
| if (preserveSelectionOnReconnect) { | |
| preserveSelectionOnReconnect = false | |
| return | |
| } | |
| if (options?.resetZedSelectionKey) lastZedSelectionKey = undefined | |
| setSelection(undefined) | |
| } | |
| const send = (payload: JsonRpcMessage) => { | |
| if (!socket || socket.readyState !== 1) return | |
| socket.send(JSON.stringify({ jsonrpc: "2.0", ...payload })) | |
| } | |
| const request = (method: string, params?: unknown) => { | |
| requestID += 1 | |
| pending.set(requestID, method) | |
| send({ id: requestID, method, params }) | |
| } | |
| const connect = () => { | |
| if (closed) return | |
| const connection = resolveEditorConnection(directory, port, editor.connection) | |
| if (!connection) { | |
| if (!zedTerminal) { | |
| setStore("status", "disabled") | |
| scheduleReconnect() | |
| return | |
| } | |
| if (!editor.selection) { | |
| setStore("status", "disabled") | |
| scheduleReconnect() | |
| return | |
| } | |
| zedSelection ??= editor | |
| .selection(directory) | |
| .then((result) => { | |
| if (closed || socket) return | |
| if (!isRecord(result) || result.type === "unavailable") return | |
| const decoded = result.type === "selection" ? decodeEditorSelection(result.selection) : Option.none() | |
| const selection = Option.getOrUndefined(decoded) | |
| const key = editorSelectionKey(selection) | |
| if (key !== lastZedSelectionKey) { | |
| lastZedSelectionKey = key | |
| setSelection(selection) | |
| setStore("status", selection ? "connected" : "disabled") | |
| } | |
| }) | |
| .catch(() => { | |
| // Keep the last known Zed selection for transient polling failures. | |
| }) | |
| .finally(() => { | |
| zedSelection = undefined | |
| }) | |
| scheduleZedPoll() | |
| return | |
| } | |
| setStore("status", "connecting") | |
| const current = openEditorSocket(connection, WebSocketImpl) | |
| socket = current | |
| current.addEventListener("open", () => { | |
| if (socket !== current) { | |
| current.close() | |
| return | |
| } | |
| attempt = 0 | |
| setStore("status", "connected") | |
| request("initialize", { | |
| protocolVersion: MCP_PROTOCOL_VERSION, | |
| capabilities: {}, | |
| clientInfo: { name: "opencode", version: "0.0.0" }, | |
| }) | |
| }) | |
| current.addEventListener("message", (event) => { | |
| const message = parseMessage(event.data) | |
| if (!message) return | |
| const selection = message.method === "selection_changed" ? decodeEditorSelection(message.params) : Option.none() | |
| if (Option.isSome(selection)) { | |
| setSelection({ ...selection.value, source: "websocket" }) | |
| return | |
| } | |
| const mention = message.method === "at_mentioned" ? decodeEditorMention(message.params) : Option.none() | |
| if (Option.isSome(mention)) { | |
| mentionListeners.forEach((listener) => listener(mention.value)) | |
| return | |
| } | |
| if (typeof message.id !== "number") return | |
| const method = pending.get(message.id) | |
| if (!method) return | |
| pending.delete(message.id) | |
| if (message.error) return | |
| const initialize = method === "initialize" ? decodeEditorServerInfo(message.result) : Option.none() | |
| if (Option.isSome(initialize)) { | |
| setStore("server", initialize.value) | |
| send({ method: "notifications/initialized" }) | |
| return | |
| } | |
| }) | |
| current.addEventListener("close", () => { | |
| if (socket !== current) return | |
| socket = undefined | |
| pending.clear() | |
| if (closed) return | |
| setStore("status", "connecting") | |
| scheduleReconnect() | |
| }) | |
| } | |
| const scheduleReconnect = () => { | |
| if (closed) return | |
| if (reconnect) clearTimeout(reconnect) | |
| attempt += 1 | |
| const delay = Math.min(1000 * 2 ** (attempt - 1), 10_000) | |
| reconnect = setTimeout(connect, delay) | |
| } | |
| const scheduleZedPoll = () => { | |
| if (closed) return | |
| if (reconnect) clearTimeout(reconnect) | |
| reconnect = setTimeout(connect, 1000) | |
| } | |
| const reconnectWithDirectory = (nextDirectory?: string) => { | |
| const resolved = nextDirectory || paths.cwd | |
| const sameDirectory = directory === resolved | |
| clearSelectionForReconnect({ resetZedSelectionKey: !sameDirectory }) | |
| if (sameDirectory) return | |
| directory = resolved | |
| attempt = 0 | |
| pending.clear() | |
| if (reconnect) clearTimeout(reconnect) | |
| reconnect = undefined | |
| if (socket) { | |
| const current = socket | |
| socket = undefined | |
| current.close() | |
| } | |
| setStore("status", "disabled") | |
| setStore("server", undefined) | |
| connect() | |
| } | |
| onMount(() => { | |
| connect() | |
| onCleanup(() => { | |
| closed = true | |
| if (reconnect) clearTimeout(reconnect) | |
| socket?.close() | |
| }) | |
| }) | |
| return { | |
| enabled() { | |
| return Boolean(resolveEditorConnection(directory, port, editor.connection) || (zedTerminal && editor.selection)) | |
| }, | |
| connected() { | |
| return store.status === "connected" | |
| }, | |
| selection() { | |
| return store.selection | |
| }, | |
| clearSelection() { | |
| lastZedSelectionKey = undefined | |
| zedSelection = undefined | |
| setSelection(undefined) | |
| }, | |
| preserveSelectionFromNewSession() { | |
| preserveSelectionOnReconnect = true | |
| }, | |
| markSelectionSent() { | |
| if (!store.selection) return | |
| setStore("selectionSent", true) | |
| }, | |
| labelState(): EditorLabelState { | |
| if (!store.selection) return "none" | |
| return store.selectionSent ? "sent" : "pending" | |
| }, | |
| onMention(listener: (mention: EditorMention) => void) { | |
| mentionListeners.add(listener) | |
| return () => mentionListeners.delete(listener) | |
| }, | |
| server() { | |
| return store.server | |
| }, | |
| reconnect(directory?: string) { | |
| reconnectWithDirectory(directory) | |
| }, | |
| } | |
| }, | |
| }) | |
| function resolveEditorConnection( | |
| directory: string, | |
| port: number | undefined, | |
| discover: ((directory: string) => EditorConnection | undefined) | undefined, | |
| ): EditorConnection | undefined { | |
| if (port) { | |
| return { | |
| url: `ws://127.0.0.1:${port}`, | |
| source: `env:${port}`, | |
| } | |
| } | |
| return discover?.(directory) | |
| } | |
| export function editorSelectionKey(selection: EditorSelection | undefined) { | |
| if (!selection) return "" | |
| return [ | |
| selection.filePath, | |
| ...selection.ranges.flatMap((range) => [ | |
| range.selection.start.line, | |
| range.selection.start.character, | |
| range.selection.end.line, | |
| range.selection.end.character, | |
| range.text, | |
| ]), | |
| ].join("\0") | |
| } | |
| function openEditorSocket(connection: EditorConnection, WebSocketImpl: typeof WebSocket) { | |
| if (!connection.authToken) return new WebSocketImpl(connection.url) | |
| return new WebSocketImpl(connection.url, { | |
| headers: { | |
| "x-claude-code-ide-authorization": connection.authToken, | |
| }, | |
| } as any) | |
| } | |
| function parseMessage(value: unknown) { | |
| if (typeof value !== "string") return | |
| try { | |
| return Option.getOrUndefined(decodeJsonRpcMessage(JSON.parse(value))) | |
| } catch { | |
| return | |
| } | |
| } | |
Xet Storage Details
- Size:
- 12.6 kB
- Xet hash:
- e275cfa3ca83eb3a62b2d64360bf33f9a20a0c3d0a831423fc63a194fa303b18
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.