Spaces:
Runtime error
Runtime error
Download apps/webapp/app/trigger/utils/stdio.ts from BTankut/Context-Store-LLM: direct link, hf CLI and curl.
- Browser
- Download file 6.61 kB
-
https://huggingface.co/spaces/BTankut/Context-Store-LLM/resolve/main/apps/webapp/app/trigger/utils/stdio.ts
- Command line
-
hf download hf://spaces/BTankut/Context-Store-LLM/apps/webapp/app/trigger/utils/stdio.ts
-
curl -L -o stdio.ts https://huggingface.co/spaces/BTankut/Context-Store-LLM/resolve/main/apps/webapp/app/trigger/utils/stdio.ts
6.61 kB
| import { type ChildProcess, type IOType } from "node:child_process"; | |
| import process from "node:process"; | |
| import { type Stream } from "node:stream"; | |
| import { type Transport } from "@modelcontextprotocol/sdk/shared/transport"; | |
| import { | |
| type JSONRPCMessage, | |
| JSONRPCMessageSchema, | |
| } from "@modelcontextprotocol/sdk/types.js"; | |
| import { execa } from "execa"; | |
| /** | |
| * Buffers a continuous stdio stream into discrete JSON-RPC messages. | |
| */ | |
| export class ReadBuffer { | |
| private _buffer?: Buffer; | |
| append(chunk: Buffer): void { | |
| this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk]) : chunk; | |
| } | |
| readMessage(): JSONRPCMessage | null { | |
| if (!this._buffer) { | |
| return null; | |
| } | |
| const index = this._buffer.indexOf("\n"); | |
| if (index === -1) { | |
| return null; | |
| } | |
| const line = this._buffer.toString("utf8", 0, index).replace(/\r$/, ""); | |
| this._buffer = this._buffer.subarray(index + 1); | |
| return deserializeMessage(line); | |
| } | |
| clear(): void { | |
| this._buffer = undefined; | |
| } | |
| } | |
| export function deserializeMessage(line: string): JSONRPCMessage { | |
| return JSONRPCMessageSchema.parse(JSON.parse(line)); | |
| } | |
| export function serializeMessage(message: JSONRPCMessage): string { | |
| return `${JSON.stringify(message)}\n`; | |
| } | |
| export interface StdioServerParameters { | |
| /** | |
| * The executable to run to start the server. | |
| */ | |
| command: string; | |
| /** | |
| * Command line arguments to pass to the executable. | |
| */ | |
| args?: string[]; | |
| /** | |
| * The environment to use when spawning the process. | |
| * | |
| * If not specified, the result of getDefaultEnvironment() will be used. | |
| */ | |
| env?: Record<string, string>; | |
| /** | |
| * How to handle stderr of the child process. This matches the semantics of Node's `child_process.spawn`. | |
| * | |
| * The default is "inherit", meaning messages to stderr will be printed to the parent process's stderr. | |
| */ | |
| stderr?: IOType | Stream | number; | |
| /** | |
| * The working directory to use when spawning the process. | |
| * | |
| * If not specified, the current working directory will be inherited. | |
| */ | |
| cwd?: string; | |
| } | |
| /** | |
| * Environment variables to inherit by default, if an environment is not explicitly given. | |
| */ | |
| export const DEFAULT_INHERITED_ENV_VARS = | |
| process.platform === "win32" | |
| ? [ | |
| "APPDATA", | |
| "HOMEDRIVE", | |
| "HOMEPATH", | |
| "LOCALAPPDATA", | |
| "PATH", | |
| "PROCESSOR_ARCHITECTURE", | |
| "SYSTEMDRIVE", | |
| "SYSTEMROOT", | |
| "TEMP", | |
| "USERNAME", | |
| "USERPROFILE", | |
| ] | |
| : /* list inspired by the default env inheritance of sudo */ | |
| ["HOME", "LOGNAME", "PATH", "SHELL", "TERM", "USER"]; | |
| /** | |
| * Returns a default environment object including only environment variables deemed safe to inherit. | |
| */ | |
| export function getDefaultEnvironment(): Record<string, string> { | |
| const env: Record<string, string> = {}; | |
| for (const key of DEFAULT_INHERITED_ENV_VARS) { | |
| const value = process.env[key]; | |
| if (value === undefined) { | |
| continue; | |
| } | |
| if (value.startsWith("()")) { | |
| // Skip functions, which are a security risk. | |
| continue; | |
| } | |
| env[key] = value; | |
| } | |
| return env; | |
| } | |
| /** | |
| * Client transport for stdio: this will connect to a server by spawning a process and communicating with it over stdin/stdout. | |
| * | |
| * This transport is only available in Node.js environments. | |
| */ | |
| export class StdioClientTransport implements Transport { | |
| private _process?: ChildProcess; | |
| private _abortController: AbortController = new AbortController(); | |
| private _readBuffer: ReadBuffer = new ReadBuffer(); | |
| private _serverParams: StdioServerParameters; | |
| onclose?: () => void; | |
| onerror?: (error: Error) => void; | |
| onmessage?: (message: JSONRPCMessage) => void; | |
| constructor(server: StdioServerParameters) { | |
| this._serverParams = server; | |
| } | |
| /** | |
| * Starts the server process and prepares to communicate with it. | |
| */ | |
| async start(): Promise<void> { | |
| if (this._process) { | |
| throw new Error( | |
| "StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.", | |
| ); | |
| } | |
| return new Promise((resolve, reject) => { | |
| this._process = execa( | |
| this._serverParams.command, | |
| this._serverParams.args ?? [], | |
| { | |
| env: this._serverParams.env ?? getDefaultEnvironment(), | |
| stderr: "inherit", | |
| shell: "/bin/sh", | |
| windowsHide: process.platform === "win32" && isElectron(), | |
| cwd: this._serverParams.cwd, | |
| cancelSignal: this._abortController.signal, | |
| stdin: "pipe", | |
| stdout: "pipe", | |
| }, | |
| ); | |
| this._process.on("error", (error) => { | |
| if (error.name === "AbortError") { | |
| // Expected when close() is called. | |
| this.onclose?.(); | |
| return; | |
| } | |
| reject(error); | |
| this.onerror?.(error); | |
| }); | |
| this._process.on("spawn", () => { | |
| resolve(); | |
| }); | |
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | |
| this._process.on("close", (_code) => { | |
| this._process = undefined; | |
| this.onclose?.(); | |
| }); | |
| this._process.stdin?.on("error", (error) => { | |
| this.onerror?.(error); | |
| }); | |
| this._process.stdout?.on("data", (chunk) => { | |
| this._readBuffer.append(chunk); | |
| this.processReadBuffer(); | |
| }); | |
| this._process.stdout?.on("error", (error) => { | |
| this.onerror?.(error); | |
| }); | |
| }); | |
| } | |
| /** | |
| * The stderr stream of the child process, if `StdioServerParameters.stderr` was set to "pipe" or "overlapped". | |
| * | |
| * This is only available after the process has been started. | |
| */ | |
| get stderr(): Stream | null { | |
| return this._process?.stderr ?? null; | |
| } | |
| private processReadBuffer() { | |
| while (true) { | |
| try { | |
| const message = this._readBuffer.readMessage(); | |
| if (message === null) { | |
| break; | |
| } | |
| this.onmessage?.(message); | |
| } catch (error) { | |
| this.onerror?.(error as Error); | |
| } | |
| } | |
| } | |
| async close(): Promise<void> { | |
| this._abortController.abort(); | |
| this._process = undefined; | |
| this._readBuffer.clear(); | |
| } | |
| send(message: JSONRPCMessage): Promise<void> { | |
| return new Promise((resolve) => { | |
| if (!this._process?.stdin) { | |
| throw new Error("Not connected"); | |
| } | |
| const json = serializeMessage(message); | |
| if (this._process.stdin.write(json)) { | |
| resolve(); | |
| } else { | |
| this._process.stdin.once("drain", resolve); | |
| } | |
| }); | |
| } | |
| } | |
| function isElectron() { | |
| return "type" in process; | |
| } | |