/** * events domain zod schemas: MuxFrame / HostFrame unions (discriminatedUnion('type')). * A frame is the payload slot of the ServerRequest full form; the SessionEvent inside * a session/event frame reuses sessions.schema's strict-envelope + wide-data passthrough branch. */ import { z } from 'zod' import type { AskUserQuestionItem } from '@deepseek-ai/dsh-user-interaction/types' import type { HostFrame, MuxFrame } from './events.ts' import type { Wire } from './rpc.schema.ts' import { rpcErrorSchema, rpcIdSchema } from './rpc.schema.ts' import { approvalRequestIdSchema } from './approvals.schema.ts' import { contentBlockSchema, sessionEventSchema, sessionIdSchema, toolEventViewSchema } from './sessions.schema.ts' import { workspaceIdSchema, workspaceViewSchema } from './workspace.schema.ts' /** Question shape validated strictly against core dsh-user-interaction. */ export const askUserQuestionItemSchema = z.object({ id: z.string(), question: z.string(), header: z.string().optional(), detail: z.string().optional(), options: z.array(z.object({ label: z.string(), description: z.string().optional() })).optional(), multiSelect: z.boolean().optional(), }) satisfies z.ZodType> /** Unified message envelope carried by transient queue frames. */ const messageSchema = z.object({ id: z.string().min(1), role: z.union([z.literal('system'), z.literal('user'), z.literal('assistant')]), content: z.array(contentBlockSchema), source: z.looseObject({ kind: z.string() }), }) /** MuxFrame union (payload slot of a mux-stream ServerRequest). */ export const muxFrameSchema = z.discriminatedUnion('type', [ z.object({ type: z.literal('session/event'), sessionId: sessionIdSchema, event: sessionEventSchema, view: toolEventViewSchema.optional() }), z.object({ type: z.literal('session/subscribed'), sessionId: sessionIdSchema, lastSeq: z.number().int() }), z.object({ type: z.literal('approval/requested'), sessionId: sessionIdSchema, approvalId: approvalRequestIdSchema, toolName: z.string(), callId: z.string().optional(), reason: z.string().optional() }), z.object({ type: z.literal('approval/resolved'), sessionId: sessionIdSchema, approvalId: approvalRequestIdSchema, outcome: z.union([z.literal('allowed-once'), z.literal('rejected'), z.literal('cancelled'), z.literal('unavailable')]) }), // Non-empty by wire contract: the user-interaction service rejects empty // batches at ask() (EMPTY_QUESTIONS), so an empty frame is host breakage // and must fail loud here, not reach the composer. z.object({ type: z.literal('question/requested'), sessionId: sessionIdSchema, questions: z.array(askUserQuestionItemSchema).min(1) }), z.object({ type: z.literal('question/resolved'), sessionId: sessionIdSchema, questionRpcId: rpcIdSchema, outcome: z.union([z.literal('answered'), z.literal('cancelled')]) }), z.object({ type: z.literal('session/queued'), sessionId: sessionIdSchema, message: messageSchema, steering: z.boolean() }), // value stays wide: it already passed its unit's own schema on the host, // and deep-validating here would import every domain's schema into the carrier. z.object({ type: z.literal('session/projection'), sessionId: sessionIdSchema, key: z.string().min(1), value: z.unknown(), seq: z.number().int().nonnegative() }), z.object({ type: z.literal('stream/error'), error: rpcErrorSchema }), ]) as unknown as z.ZodType /** HostFrame union (payload slot of a host-stream ServerRequest). */ export const hostFrameSchema = z.discriminatedUnion('type', [ z.object({ type: z.literal('host/session-added'), sessionId: sessionIdSchema, blank: z.boolean(), parentSessionId: sessionIdSchema.optional(), cwd: z.string().optional() }), z.object({ type: z.literal('host/session-removed'), sessionId: sessionIdSchema }), z.object({ type: z.literal('host/session-status'), sessionId: sessionIdSchema, running: z.boolean() }), z.object({ type: z.literal('host/agent-error'), sessionId: sessionIdSchema, message: z.string() }), z.object({ type: z.literal('host/workspace-changed'), workspace: workspaceViewSchema }), z.object({ type: z.literal('host/workspace-removed'), workspaceId: workspaceIdSchema }), z.object({ type: z.literal('host/commands-changed') }), z.object({ type: z.literal('stream/error'), error: rpcErrorSchema }), ]) as unknown as z.ZodType