import { type Static, type TSchema, Type } from "@sinclair/typebox"; const NonEmptyString = Type.String({ minLength: 1 }); export const PresenceEntrySchema = Type.Object( { host: Type.Optional(NonEmptyString), ip: Type.Optional(NonEmptyString), version: Type.Optional(NonEmptyString), mode: Type.Optional(NonEmptyString), lastInputSeconds: Type.Optional(Type.Integer({ minimum: 0 })), reason: Type.Optional(NonEmptyString), tags: Type.Optional(Type.Array(NonEmptyString)), text: Type.Optional(Type.String()), ts: Type.Integer({ minimum: 0 }), instanceId: Type.Optional(NonEmptyString), }, { additionalProperties: false }, ); export const HealthSnapshotSchema = Type.Any(); export const StateVersionSchema = Type.Object( { presence: Type.Integer({ minimum: 0 }), health: Type.Integer({ minimum: 0 }), }, { additionalProperties: false }, ); export const SnapshotSchema = Type.Object( { presence: Type.Array(PresenceEntrySchema), health: HealthSnapshotSchema, stateVersion: StateVersionSchema, uptimeMs: Type.Integer({ minimum: 0 }), }, { additionalProperties: false }, ); export const TickEventSchema = Type.Object( { ts: Type.Integer({ minimum: 0 }), }, { additionalProperties: false }, ); export const ShutdownEventSchema = Type.Object( { reason: NonEmptyString, restartExpectedMs: Type.Optional(Type.Integer({ minimum: 0 })), }, { additionalProperties: false }, ); export const HelloSchema = Type.Object( { type: Type.Literal("hello"), minProtocol: Type.Integer({ minimum: 1 }), maxProtocol: Type.Integer({ minimum: 1 }), client: Type.Object( { name: NonEmptyString, version: NonEmptyString, platform: NonEmptyString, mode: NonEmptyString, instanceId: Type.Optional(NonEmptyString), }, { additionalProperties: false }, ), caps: Type.Optional(Type.Array(NonEmptyString, { default: [] })), auth: Type.Optional( Type.Object( { token: Type.Optional(Type.String()), }, { additionalProperties: false }, ), ), locale: Type.Optional(Type.String()), userAgent: Type.Optional(Type.String()), }, { additionalProperties: false }, ); export const HelloOkSchema = Type.Object( { type: Type.Literal("hello-ok"), protocol: Type.Integer({ minimum: 1 }), server: Type.Object( { version: NonEmptyString, commit: Type.Optional(NonEmptyString), host: Type.Optional(NonEmptyString), connId: NonEmptyString, }, { additionalProperties: false }, ), features: Type.Object( { methods: Type.Array(NonEmptyString), events: Type.Array(NonEmptyString), }, { additionalProperties: false }, ), snapshot: SnapshotSchema, policy: Type.Object( { maxPayload: Type.Integer({ minimum: 1 }), maxBufferedBytes: Type.Integer({ minimum: 1 }), tickIntervalMs: Type.Integer({ minimum: 1 }), }, { additionalProperties: false }, ), }, { additionalProperties: false }, ); export const HelloErrorSchema = Type.Object( { type: Type.Literal("hello-error"), reason: NonEmptyString, expectedProtocol: Type.Optional(Type.Integer({ minimum: 1 })), minClient: Type.Optional(NonEmptyString), }, { additionalProperties: false }, ); export const ErrorShapeSchema = Type.Object( { code: NonEmptyString, message: NonEmptyString, details: Type.Optional(Type.Unknown()), retryable: Type.Optional(Type.Boolean()), retryAfterMs: Type.Optional(Type.Integer({ minimum: 0 })), }, { additionalProperties: false }, ); export const RequestFrameSchema = Type.Object( { type: Type.Literal("req"), id: NonEmptyString, method: NonEmptyString, params: Type.Optional(Type.Unknown()), }, { additionalProperties: false }, ); export const ResponseFrameSchema = Type.Object( { type: Type.Literal("res"), id: NonEmptyString, ok: Type.Boolean(), payload: Type.Optional(Type.Unknown()), error: Type.Optional(ErrorShapeSchema), }, { additionalProperties: false }, ); export const EventFrameSchema = Type.Object( { type: Type.Literal("event"), event: NonEmptyString, payload: Type.Optional(Type.Unknown()), seq: Type.Optional(Type.Integer({ minimum: 0 })), stateVersion: Type.Optional(StateVersionSchema), }, { additionalProperties: false }, ); // Discriminated union of all top-level frames. Using a discriminator makes // downstream codegen (quicktype) produce tighter types instead of all-optional // blobs. export const GatewayFrameSchema = Type.Union( [ HelloSchema, HelloOkSchema, HelloErrorSchema, RequestFrameSchema, ResponseFrameSchema, EventFrameSchema, ], { discriminator: "type" }, ); export const AgentEventSchema = Type.Object( { runId: NonEmptyString, seq: Type.Integer({ minimum: 0 }), stream: NonEmptyString, ts: Type.Integer({ minimum: 0 }), data: Type.Record(Type.String(), Type.Unknown()), }, { additionalProperties: false }, ); export const SendParamsSchema = Type.Object( { to: NonEmptyString, message: NonEmptyString, mediaUrl: Type.Optional(Type.String()), provider: Type.Optional(Type.String()), idempotencyKey: NonEmptyString, }, { additionalProperties: false }, ); export const AgentParamsSchema = Type.Object( { message: NonEmptyString, to: Type.Optional(Type.String()), sessionId: Type.Optional(Type.String()), thinking: Type.Optional(Type.String()), deliver: Type.Optional(Type.Boolean()), timeout: Type.Optional(Type.Integer({ minimum: 0 })), idempotencyKey: NonEmptyString, }, { additionalProperties: false }, ); // WebChat/WebSocket-native chat methods export const ChatHistoryParamsSchema = Type.Object( { sessionKey: NonEmptyString, }, { additionalProperties: false }, ); export const ChatSendParamsSchema = Type.Object( { sessionKey: NonEmptyString, message: NonEmptyString, thinking: Type.Optional(Type.String()), deliver: Type.Optional(Type.Boolean()), attachments: Type.Optional(Type.Array(Type.Unknown())), timeoutMs: Type.Optional(Type.Integer({ minimum: 0 })), idempotencyKey: NonEmptyString, }, { additionalProperties: false }, ); export const ChatEventSchema = Type.Object( { runId: NonEmptyString, sessionKey: NonEmptyString, seq: Type.Integer({ minimum: 0 }), state: Type.Union([ Type.Literal("delta"), Type.Literal("final"), Type.Literal("error"), ]), message: Type.Optional(Type.Unknown()), errorMessage: Type.Optional(Type.String()), usage: Type.Optional(Type.Unknown()), stopReason: Type.Optional(Type.String()), }, { additionalProperties: false }, ); export const ProtocolSchemas: Record = { Hello: HelloSchema, HelloOk: HelloOkSchema, HelloError: HelloErrorSchema, RequestFrame: RequestFrameSchema, ResponseFrame: ResponseFrameSchema, EventFrame: EventFrameSchema, GatewayFrame: GatewayFrameSchema, PresenceEntry: PresenceEntrySchema, StateVersion: StateVersionSchema, Snapshot: SnapshotSchema, ErrorShape: ErrorShapeSchema, AgentEvent: AgentEventSchema, SendParams: SendParamsSchema, AgentParams: AgentParamsSchema, ChatHistoryParams: ChatHistoryParamsSchema, ChatSendParams: ChatSendParamsSchema, ChatEvent: ChatEventSchema, TickEvent: TickEventSchema, ShutdownEvent: ShutdownEventSchema, }; export const PROTOCOL_VERSION = 1 as const; export type Hello = Static; export type HelloOk = Static; export type HelloError = Static; export type RequestFrame = Static; export type ResponseFrame = Static; export type EventFrame = Static; export type GatewayFrame = Static; export type Snapshot = Static; export type PresenceEntry = Static; export type ErrorShape = Static; export type StateVersion = Static; export type AgentEvent = Static; export type ChatEvent = Static; export type TickEvent = Static; export type ShutdownEvent = Static; export const ErrorCodes = { NOT_LINKED: "NOT_LINKED", AGENT_TIMEOUT: "AGENT_TIMEOUT", INVALID_REQUEST: "INVALID_REQUEST", UNAVAILABLE: "UNAVAILABLE", } as const; export type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes]; export function errorShape( code: ErrorCode, message: string, opts?: { details?: unknown; retryable?: boolean; retryAfterMs?: number }, ): ErrorShape { return { code, message, ...opts, }; }