import * as Context from "effect/Context";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Schema from "effect/Schema";import * as Scope from "effect/Scope";import * as Stdio from "effect/Stdio";import * as Stream from "effect/Stream";import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"; import * as CodexRpc from "./_generated/meta.gen.ts";import * as CodexError from "./errors.ts";import * as CodexProtocol from "./protocol.ts";import { decodeNotificationPayload, decodeOptionalPayload, encodeOptionalPayload, runHandler,} from "./_internal/shared.ts";import { makeChildStdio, makeTerminationError } from "./_internal/stdio.ts"; export interface CodexAppServerClientOptions { readonly logIncoming?: boolean; readonly logOutgoing?: boolean; readonly logger?: ( event: CodexProtocol.CodexAppServerProtocolLogEvent, ) => Effect.Effect<void, never>;} interface CodexAppServerClientRaw { readonly notifications: CodexProtocol.CodexAppServerPatchedProtocol["incomingNotifications"]; readonly requests: CodexProtocol.CodexAppServerPatchedProtocol["incomingRequests"]; readonly request: CodexProtocol.CodexAppServerPatchedProtocol["request"]; readonly notify: CodexProtocol.CodexAppServerPatchedProtocol["notify"]; readonly respond: CodexProtocol.CodexAppServerPatchedProtocol["respond"]; readonly respondError: CodexProtocol.CodexAppServerPatchedProtocol["respondError"];} export class CodexAppServerClient extends Context.Service< CodexAppServerClient, { readonly raw: CodexAppServerClientRaw; readonly request: <M extends CodexRpc.ClientRequestMethod>( method: M, payload: CodexRpc.ClientRequestParamsByMethod[M], ) => Effect.Effect<CodexRpc.ClientRequestResponsesByMethod[M], CodexError.CodexAppServerError>; readonly notify: <M extends CodexRpc.ClientNotificationMethod>( method: M, payload: CodexRpc.ClientNotificationParamsByMethod[M], ) => Effect.Effect<void, CodexError.CodexAppServerError>; readonly handleServerRequest: <M extends CodexRpc.ServerRequestMethod>( method: M, handler: ( payload: CodexRpc.ServerRequestParamsByMethod[M], ) => Effect.Effect< CodexRpc.ServerRequestResponsesByMethod[M], CodexError.CodexAppServerError >, ) => Effect.Effect<void>; readonly handleServerNotification: <M extends CodexRpc.ServerNotificationMethod>( method: M, handler: ( payload: CodexRpc.ServerNotificationParamsByMethod[M], ) => Effect.Effect<void, CodexError.CodexAppServerError>, ) => Effect.Effect<void>; readonly handleUnknownServerRequest: ( handler: ( method: string, params: unknown, ) => Effect.Effect<unknown, CodexError.CodexAppServerError>, ) => Effect.Effect<void>; readonly handleUnknownServerNotification: ( handler: ( method: string, params: unknown, ) => Effect.Effect<void, CodexError.CodexAppServerError>, ) => Effect.Effect<void>; }>()("effect-codex-app-server/client/CodexAppServerClient") {} type ServerRequestHandler = ( payload: unknown,) => Effect.Effect<unknown, CodexError.CodexAppServerError>;type ServerNotificationHandler = ( payload: unknown,) => Effect.Effect<void, CodexError.CodexAppServerError>; export const make = Effect.fn("effect-codex-app-server/CodexAppServerClient.make")(function* ( stdio: Stdio.Stdio, options: CodexAppServerClientOptions = {}, terminationError?: Effect.Effect<CodexError.CodexAppServerError>,): Effect.fn.Return<CodexAppServerClient["Service"], never, Scope.Scope> { const requestHandlers = new Map<string, ServerRequestHandler>(); const notificationHandlers = new Map<string, Array<ServerNotificationHandler>>(); let unknownRequestHandler: | ((method: string, params: unknown) => Effect.Effect<unknown, CodexError.CodexAppServerError>) | undefined; let unknownNotificationHandler: | ((method: string, params: unknown) => Effect.Effect<void, CodexError.CodexAppServerError>) | undefined; const getServerRequestParamSchema = <M extends CodexRpc.ServerRequestMethod>( method: M, ): | Schema.Codec<CodexRpc.ServerRequestParamsByMethod[M], CodexRpc.ServerRequestParamsByMethod[M]> | undefined => CodexRpc.SERVER_REQUEST_PARAMS[method] as never; const getServerRequestResponseSchema = <M extends CodexRpc.ServerRequestMethod>( method: M, ): | Schema.Codec< CodexRpc.ServerRequestResponsesByMethod[M], CodexRpc.ServerRequestResponsesByMethod[M] > | undefined => CodexRpc.SERVER_REQUEST_RESPONSES[method] as never; const getClientRequestParamSchema = <M extends CodexRpc.ClientRequestMethod>( method: M, ): | Schema.Codec<CodexRpc.ClientRequestParamsByMethod[M], CodexRpc.ClientRequestParamsByMethod[M]> | undefined => CodexRpc.CLIENT_REQUEST_PARAMS[method] as never; const getClientRequestResponseSchema = <M extends CodexRpc.ClientRequestMethod>( method: M, ): | Schema.Codec< CodexRpc.ClientRequestResponsesByMethod[M], CodexRpc.ClientRequestResponsesByMethod[M] > | undefined => CodexRpc.CLIENT_REQUEST_RESPONSES[method] as never; const getClientNotificationParamSchema = <M extends CodexRpc.ClientNotificationMethod>( method: M, ): | Schema.Codec< CodexRpc.ClientNotificationParamsByMethod[M], CodexRpc.ClientNotificationParamsByMethod[M] > | undefined => CodexRpc.CLIENT_NOTIFICATION_PARAMS[method] as never; const dispatchNotification = ( notification: CodexProtocol.CodexAppServerIncomingNotification, ): Effect.Effect<void, never> => { const schema = notification.method in CodexRpc.SERVER_NOTIFICATION_PARAMS ? CodexRpc.SERVER_NOTIFICATION_PARAMS[ notification.method as CodexRpc.ServerNotificationMethod ] : undefined; const handlers = notificationHandlers.get(notification.method) ?? []; if (schema) { return decodeNotificationPayload(notification.method, schema, notification.params).pipe( Effect.flatMap((decoded) => Effect.forEach(handlers, (handler) => handler(decoded), { discard: true }), ), Effect.catch(() => Effect.void), ); } return unknownNotificationHandler ? unknownNotificationHandler(notification.method, notification.params).pipe( Effect.catch(() => Effect.void), ) : Effect.void; }; const dispatchRequest = ( request: CodexProtocol.CodexAppServerIncomingRequest, ): Effect.Effect<unknown, CodexError.CodexAppServerError> => { if (request.method in CodexRpc.SERVER_REQUEST_PARAMS) { const method = request.method as CodexRpc.ServerRequestMethod; const payloadSchema = getServerRequestParamSchema(method); const responseSchema = getServerRequestResponseSchema(method); const handler = requestHandlers.get(method); return decodeOptionalPayload(method, payloadSchema, request.params).pipe( Effect.flatMap((decoded) => runHandler(handler, decoded, method)), Effect.flatMap((result) => encodeOptionalPayload(method, responseSchema, result)), ); } return unknownRequestHandler ? unknownRequestHandler(request.method, request.params) : Effect.fail(CodexError.CodexAppServerRequestError.methodNotFound(request.method)); }; const transport = yield* CodexProtocol.makeCodexAppServerPatchedProtocol({ stdio, ...(terminationError ? { terminationError } : {}), ...(options.logIncoming !== undefined ? { logIncoming: options.logIncoming } : {}), ...(options.logOutgoing !== undefined ? { logOutgoing: options.logOutgoing } : {}), ...(options.logger ? { logger: options.logger } : {}), onNotification: dispatchNotification, onRequest: dispatchRequest, }); const request = <M extends CodexRpc.ClientRequestMethod>( method: M, payload: CodexRpc.ClientRequestParamsByMethod[M], ): Effect.Effect<CodexRpc.ClientRequestResponsesByMethod[M], CodexError.CodexAppServerError> => encodeOptionalPayload(method, getClientRequestParamSchema(method), payload).pipe( Effect.flatMap((encoded) => transport.request(method, encoded)), Effect.flatMap( ( raw, ): Effect.Effect< CodexRpc.ClientRequestResponsesByMethod[M], CodexError.CodexAppServerError > => decodeOptionalPayload(method, getClientRequestResponseSchema(method), raw), ), ); const notify = <M extends CodexRpc.ClientNotificationMethod>( method: M, payload: CodexRpc.ClientNotificationParamsByMethod[M], ) => encodeOptionalPayload(method, getClientNotificationParamSchema(method), payload).pipe( Effect.flatMap((encoded) => transport.notify(method, encoded)), ); return CodexAppServerClient.of({ raw: { notifications: transport.incomingNotifications, requests: transport.incomingRequests, request: transport.request, notify: transport.notify, respond: transport.respond, respondError: transport.respondError, }, request, notify, handleServerRequest: (method, handler) => Effect.sync(() => { requestHandlers.set(method, handler as ServerRequestHandler); }), handleServerNotification: (method, handler) => Effect.sync(() => { const current = notificationHandlers.get(method) ?? []; current.push(handler as ServerNotificationHandler); notificationHandlers.set(method, current); }), handleUnknownServerRequest: (handler) => Effect.sync(() => { unknownRequestHandler = handler; }), handleUnknownServerNotification: (handler) => Effect.sync(() => { unknownNotificationHandler = handler; }), });}); export const layer = ( stdio: Stdio.Stdio, options: CodexAppServerClientOptions = {},): Layer.Layer<CodexAppServerClient> => Layer.effect(CodexAppServerClient, make(stdio, options)); export const layerChildProcess = ( handle: ChildProcessSpawner.ChildProcessHandle, options: CodexAppServerClientOptions = {},): Layer.Layer<CodexAppServerClient> => Layer.effect(CodexAppServerClient, makeChildProcessClient(handle, options)); const makeChildProcessClient = Effect.fn( "effect-codex-app-server/CodexAppServerClient.makeChildProcessClient",)(function* (handle: ChildProcessSpawner.ChildProcessHandle, options: CodexAppServerClientOptions) { yield* Stream.runDrain(handle.stderr).pipe(Effect.ignore, Effect.forkScoped); return yield* make(makeChildStdio(handle), options, makeTerminationError(handle));});
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Schema from "effect/Schema";
import * as Scope from "effect/Scope";
import * as Stdio from "effect/Stdio";
import * as Stream from "effect/Stream";
import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";
import * as CodexRpc from "./_generated/meta.gen.ts";
import * as CodexError from "./errors.ts";
import * as CodexProtocol from "./protocol.ts";
import {
decodeNotificationPayload,
decodeOptionalPayload,
encodeOptionalPayload,
runHandler,
} from "./_internal/shared.ts";
import { makeChildStdio, makeTerminationError } from "./_internal/stdio.ts";
export interface CodexAppServerClientOptions {
readonly logIncoming?: boolean;
readonly logOutgoing?: boolean;
readonly logger?: (
event: CodexProtocol.CodexAppServerProtocolLogEvent,
) => Effect.Effect<void, never>;
}
interface CodexAppServerClientRaw {
readonly notifications: CodexProtocol.CodexAppServerPatchedProtocol["incomingNotifications"];
readonly requests: CodexProtocol.CodexAppServerPatchedProtocol["incomingRequests"];
readonly request: CodexProtocol.CodexAppServerPatchedProtocol["request"];
readonly notify: CodexProtocol.CodexAppServerPatchedProtocol["notify"];
readonly respond: CodexProtocol.CodexAppServerPatchedProtocol["respond"];
readonly respondError: CodexProtocol.CodexAppServerPatchedProtocol["respondError"];
}
export class CodexAppServerClient extends Context.Service<
CodexAppServerClient,
{
readonly raw: CodexAppServerClientRaw;
readonly request: <M extends CodexRpc.ClientRequestMethod>(
method: M,
payload: CodexRpc.ClientRequestParamsByMethod[M],
) => Effect.Effect<CodexRpc.ClientRequestResponsesByMethod[M], CodexError.CodexAppServerError>;
readonly notify: <M extends CodexRpc.ClientNotificationMethod>(
method: M,
payload: CodexRpc.ClientNotificationParamsByMethod[M],
) => Effect.Effect<void, CodexError.CodexAppServerError>;
readonly handleServerRequest: <M extends CodexRpc.ServerRequestMethod>(
method: M,
handler: (
payload: CodexRpc.ServerRequestParamsByMethod[M],
) => Effect.Effect<
CodexRpc.ServerRequestResponsesByMethod[M],
CodexError.CodexAppServerError
>,
) => Effect.Effect<void>;
readonly handleServerNotification: <M extends CodexRpc.ServerNotificationMethod>(
method: M,
handler: (
payload: CodexRpc.ServerNotificationParamsByMethod[M],
) => Effect.Effect<void, CodexError.CodexAppServerError>,
) => Effect.Effect<void>;
readonly handleUnknownServerRequest: (
handler: (
method: string,
params: unknown,
) => Effect.Effect<unknown, CodexError.CodexAppServerError>,
) => Effect.Effect<void>;
readonly handleUnknownServerNotification: (
handler: (
method: string,
params: unknown,
) => Effect.Effect<void, CodexError.CodexAppServerError>,
) => Effect.Effect<void>;
}
>()("effect-codex-app-server/client/CodexAppServerClient") {}
type ServerRequestHandler = (
payload: unknown,
) => Effect.Effect<unknown, CodexError.CodexAppServerError>;
type ServerNotificationHandler = (
payload: unknown,
) => Effect.Effect<void, CodexError.CodexAppServerError>;
export const make = Effect.fn("effect-codex-app-server/CodexAppServerClient.make")(function* (
stdio: Stdio.Stdio,
options: CodexAppServerClientOptions = {},
terminationError?: Effect.Effect<CodexError.CodexAppServerError>,
): Effect.fn.Return<CodexAppServerClient["Service"], never, Scope.Scope> {
const requestHandlers = new Map<string, ServerRequestHandler>();
const notificationHandlers = new Map<string, Array<ServerNotificationHandler>>();
let unknownRequestHandler:
| ((method: string, params: unknown) => Effect.Effect<unknown, CodexError.CodexAppServerError>)
| undefined;
let unknownNotificationHandler:
| ((method: string, params: unknown) => Effect.Effect<void, CodexError.CodexAppServerError>)
| undefined;
const getServerRequestParamSchema = <M extends CodexRpc.ServerRequestMethod>(
method: M,
):
| Schema.Codec<CodexRpc.ServerRequestParamsByMethod[M], CodexRpc.ServerRequestParamsByMethod[M]>
| undefined => CodexRpc.SERVER_REQUEST_PARAMS[method] as never;
const getServerRequestResponseSchema = <M extends CodexRpc.ServerRequestMethod>(
method: M,
):
| Schema.Codec<
CodexRpc.ServerRequestResponsesByMethod[M],
CodexRpc.ServerRequestResponsesByMethod[M]
>
| undefined => CodexRpc.SERVER_REQUEST_RESPONSES[method] as never;
const getClientRequestParamSchema = <M extends CodexRpc.ClientRequestMethod>(
method: M,
):
| Schema.Codec<CodexRpc.ClientRequestParamsByMethod[M], CodexRpc.ClientRequestParamsByMethod[M]>
| undefined => CodexRpc.CLIENT_REQUEST_PARAMS[method] as never;
const getClientRequestResponseSchema = <M extends CodexRpc.ClientRequestMethod>(
method: M,
):
| Schema.Codec<
CodexRpc.ClientRequestResponsesByMethod[M],
CodexRpc.ClientRequestResponsesByMethod[M]
>
| undefined => CodexRpc.CLIENT_REQUEST_RESPONSES[method] as never;
const getClientNotificationParamSchema = <M extends CodexRpc.ClientNotificationMethod>(
method: M,
):
| Schema.Codec<
CodexRpc.ClientNotificationParamsByMethod[M],
CodexRpc.ClientNotificationParamsByMethod[M]
>
| undefined => CodexRpc.CLIENT_NOTIFICATION_PARAMS[method] as never;
const dispatchNotification = (
notification: CodexProtocol.CodexAppServerIncomingNotification,
): Effect.Effect<void, never> => {
const schema =
notification.method in CodexRpc.SERVER_NOTIFICATION_PARAMS
? CodexRpc.SERVER_NOTIFICATION_PARAMS[
notification.method as CodexRpc.ServerNotificationMethod
]
: undefined;
const handlers = notificationHandlers.get(notification.method) ?? [];
if (schema) {
return decodeNotificationPayload(notification.method, schema, notification.params).pipe(
Effect.flatMap((decoded) =>
Effect.forEach(handlers, (handler) => handler(decoded), { discard: true }),
),
Effect.catch(() => Effect.void),
);
}
return unknownNotificationHandler
? unknownNotificationHandler(notification.method, notification.params).pipe(
Effect.catch(() => Effect.void),
)
: Effect.void;
};
const dispatchRequest = (
request: CodexProtocol.CodexAppServerIncomingRequest,
): Effect.Effect<unknown, CodexError.CodexAppServerError> => {
if (request.method in CodexRpc.SERVER_REQUEST_PARAMS) {
const method = request.method as CodexRpc.ServerRequestMethod;
const payloadSchema = getServerRequestParamSchema(method);
const responseSchema = getServerRequestResponseSchema(method);
const handler = requestHandlers.get(method);
return decodeOptionalPayload(method, payloadSchema, request.params).pipe(
Effect.flatMap((decoded) => runHandler(handler, decoded, method)),
Effect.flatMap((result) => encodeOptionalPayload(method, responseSchema, result)),
);
}
return unknownRequestHandler
? unknownRequestHandler(request.method, request.params)
: Effect.fail(CodexError.CodexAppServerRequestError.methodNotFound(request.method));
};
const transport = yield* CodexProtocol.makeCodexAppServerPatchedProtocol({
stdio,
...(terminationError ? { terminationError } : {}),
...(options.logIncoming !== undefined ? { logIncoming: options.logIncoming } : {}),
...(options.logOutgoing !== undefined ? { logOutgoing: options.logOutgoing } : {}),
...(options.logger ? { logger: options.logger } : {}),
onNotification: dispatchNotification,
onRequest: dispatchRequest,
});
const request = <M extends CodexRpc.ClientRequestMethod>(
method: M,
payload: CodexRpc.ClientRequestParamsByMethod[M],
): Effect.Effect<CodexRpc.ClientRequestResponsesByMethod[M], CodexError.CodexAppServerError> =>
encodeOptionalPayload(method, getClientRequestParamSchema(method), payload).pipe(
Effect.flatMap((encoded) => transport.request(method, encoded)),
Effect.flatMap(
(
raw,
): Effect.Effect<
CodexRpc.ClientRequestResponsesByMethod[M],
CodexError.CodexAppServerError
> => decodeOptionalPayload(method, getClientRequestResponseSchema(method), raw),
),
);
const notify = <M extends CodexRpc.ClientNotificationMethod>(
method: M,
payload: CodexRpc.ClientNotificationParamsByMethod[M],
) =>
encodeOptionalPayload(method, getClientNotificationParamSchema(method), payload).pipe(
Effect.flatMap((encoded) => transport.notify(method, encoded)),
);
return CodexAppServerClient.of({
raw: {
notifications: transport.incomingNotifications,
requests: transport.incomingRequests,
request: transport.request,
notify: transport.notify,
respond: transport.respond,
respondError: transport.respondError,
},
request,
notify,
handleServerRequest: (method, handler) =>
Effect.sync(() => {
requestHandlers.set(method, handler as ServerRequestHandler);
}),
handleServerNotification: (method, handler) =>
Effect.sync(() => {
const current = notificationHandlers.get(method) ?? [];
current.push(handler as ServerNotificationHandler);
notificationHandlers.set(method, current);
}),
handleUnknownServerRequest: (handler) =>
Effect.sync(() => {
unknownRequestHandler = handler;
}),
handleUnknownServerNotification: (handler) =>
Effect.sync(() => {
unknownNotificationHandler = handler;
}),
});
});
export const layer = (
stdio: Stdio.Stdio,
options: CodexAppServerClientOptions = {},
): Layer.Layer<CodexAppServerClient> => Layer.effect(CodexAppServerClient, make(stdio, options));
export const layerChildProcess = (
handle: ChildProcessSpawner.ChildProcessHandle,
options: CodexAppServerClientOptions = {},
): Layer.Layer<CodexAppServerClient> =>
Layer.effect(CodexAppServerClient, makeChildProcessClient(handle, options));
const makeChildProcessClient = Effect.fn(
"effect-codex-app-server/CodexAppServerClient.makeChildProcessClient",
)(function* (handle: ChildProcessSpawner.ChildProcessHandle, options: CodexAppServerClientOptions) {
yield* Stream.runDrain(handle.stderr).pipe(Effect.ignore, Effect.forkScoped);
return yield* make(makeChildStdio(handle), options, makeTerminationError(handle));
});