mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
AskUserQuestionItem.detail is part of the user-interaction seam contract but the web frame schema dropped it and the composer never rendered it (review r3635427108). askUserQuestionItemSchema now forwards detail, the composer renders it under the title in the description text style, and the fixture's multi-select question carries one.
47 lines
3.2 KiB
TypeScript
47 lines
3.2 KiB
TypeScript
/**
|
|
* 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 { sessionEventSchema, sessionIdSchema, toolEventViewSchema } from './sessions.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<Wire<AskUserQuestionItem>>
|
|
|
|
/** 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('stream/error'), error: rpcErrorSchema }),
|
|
]) as unknown as z.ZodType<MuxFrame>
|
|
|
|
/** 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, parentSessionId: sessionIdSchema.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('stream/error'), error: rpcErrorSchema }),
|
|
]) as unknown as z.ZodType<HostFrame>
|