import * as z from "zod/mini"; import { createGeneration, type Generation, generationSchema, } from "./v3-generation.js"; const canonicalIdStringSchema = z.string().check(z.trim(), z.minLength(1)); const rpcEnvelopeSchema = z.object({ t: z.string().check(z.startsWith("getuserfeedback:")), gen: generationSchema, viewId: z.optional(canonicalIdStringSchema), }); /** * RPC Request - supports idempotency keys for retries/replays */ export const rpcRequestSchema = z.extend(rpcEnvelopeSchema, { t: z.literal("getuserfeedback:rpc:request"), rpcId: z.string().check(z.uuid()), method: z.string(), params: z.unknown(), idemKey: canonicalIdStringSchema, }); /** * RPC Response - success or failure with retry hint */ const rpcResponseSuccessSchema = z.extend(rpcEnvelopeSchema, { t: z.literal("getuserfeedback:rpc:response"), rpcId: z.string().check(z.uuid()), ok: z.literal(true), result: z.unknown(), }); const rpcResponseErrorSchema = z.extend(rpcEnvelopeSchema, { t: z.literal("getuserfeedback:rpc:response"), rpcId: z.string().check(z.uuid()), ok: z.literal(false), error: z.string(), retriable: z._default(z.boolean(), false), }); export const rpcResponseSchema = z.discriminatedUnion("ok", [ rpcResponseSuccessSchema, rpcResponseErrorSchema, ]); export const rpcRequestWithViewIdSchema = z.extend(rpcRequestSchema, { viewId: canonicalIdStringSchema, }); export const rpcResponseWithViewIdSchema = z.discriminatedUnion("ok", [ z.extend(rpcResponseSuccessSchema, { viewId: canonicalIdStringSchema, }), z.extend(rpcResponseErrorSchema, { viewId: canonicalIdStringSchema, }), ]); export type RpcRequest = z.output; export type RpcResponse = z.output; const toGeneration = (generation: number | Generation): Generation => typeof generation === "number" ? createGeneration(generation) : generation; export function buildRpcRequest(params: { gen: number | Generation; rpcId: string; method: string; params: unknown; idemKey: string; viewId?: string; }): RpcRequest { return { t: "getuserfeedback:rpc:request", gen: toGeneration(params.gen), rpcId: params.rpcId, method: params.method, params: params.params, idemKey: params.idemKey, viewId: params.viewId, }; } export function buildRpcResponse( params: | { gen: number | Generation; rpcId: string; ok: true; result: unknown; viewId?: string; } | { gen: number | Generation; rpcId: string; ok: false; error: string; retriable?: boolean; viewId?: string; }, ): RpcResponse { if (params.ok) { return { t: "getuserfeedback:rpc:response", gen: toGeneration(params.gen), rpcId: params.rpcId, ok: true, result: params.result, viewId: params.viewId, }; } return { t: "getuserfeedback:rpc:response", gen: toGeneration(params.gen), rpcId: params.rpcId, ok: false, error: params.error, retriable: params.retriable ?? false, viewId: params.viewId, }; }