import * as Effect from "effect/Effect"; import * as Schema from "effect/Schema"; import * as SchemaIssue from "effect/SchemaIssue"; import { RpcClientError } from "effect/unstable/rpc"; import * as AcpSchema from "../_generated/schema.gen.ts"; import * as AcpError from "../errors.ts"; const formatSchemaIssue = SchemaIssue.makeFormatterDefault(); export const callRpc = ( effect: Effect.Effect, ): Effect.Effect => effect.pipe( Effect.catchTag("RpcClientError", (error) => Effect.fail( new AcpError.AcpTransportError({ detail: error.message, cause: error, }), ), ), Effect.catchIf(Schema.is(AcpSchema.Error), (error) => Effect.fail(AcpError.AcpRequestError.fromProtocolError(error)), ), ); export const runHandler = Effect.fnUntraced(function* ( handler: ((payload: A) => Effect.Effect) | undefined, payload: A, method: string, ) { if (!handler) { return yield* Effect.fail(AcpError.AcpRequestError.methodNotFound(method).toProtocolError()); } return yield* handler(payload).pipe( Effect.mapError((error) => Schema.is(AcpError.AcpRequestError)(error) ? error.toProtocolError() : AcpError.AcpRequestError.internalError(error.message).toProtocolError(), ), ); }); export function decodeExtRequestRegistration( method: string, payload: Schema.Codec, handler: (payload: A) => Effect.Effect, ) { return (params: unknown): Effect.Effect => Schema.decodeUnknownEffect(payload)(params).pipe( Effect.mapError((error) => AcpError.AcpRequestError.invalidParams( `Invalid ${method} payload: ${formatSchemaIssue(error.issue)}`, { issue: error.issue }, ), ), Effect.flatMap((decoded) => handler(decoded)), ); } export function decodeExtNotificationRegistration( method: string, payload: Schema.Codec, handler: (payload: A) => Effect.Effect, ) { return (params: unknown): Effect.Effect => Schema.decodeUnknownEffect(payload)(params).pipe( Effect.mapError( (error) => new AcpError.AcpProtocolParseError({ detail: `Invalid ${method} notification payload: ${formatSchemaIssue(error.issue)}`, cause: error, }), ), Effect.flatMap((decoded) => handler(decoded)), ); } const encoder = new TextEncoder(); const JsonRpcId = Schema.Union([Schema.Number, Schema.String]); const JsonRpcHeaders = Schema.Array(Schema.Unknown); export const jsonRpcRequest = (method: string, params: Schema.Codec) => Schema.Struct({ jsonrpc: Schema.Literal("2.0"), id: JsonRpcId, method: Schema.Literal(method), params, headers: JsonRpcHeaders, }); export const jsonRpcNotification = (method: string, params: Schema.Codec) => Schema.Struct({ jsonrpc: Schema.Literal("2.0"), method: Schema.Literal(method), params, }); export const jsonRpcResponse = (result: Schema.Codec) => Schema.Struct({ jsonrpc: Schema.Literal("2.0"), id: JsonRpcId, result, }); export const encodeJsonl = (schema: Schema.Codec, value: A) => Effect.map(Schema.encodeEffect(Schema.fromJsonString(schema))(value), (encoded) => encoder.encode(`${encoded}\n`), );