import { z } from 'zod' export const assistantIdSchema = z.string().uuid() export const workModeSchema = z.enum(['ask', 'plan', 'execute']) export const interactiveWorkModes = ['ask', 'execute'] as const export type WorkMode = z.infer export type InteractiveWorkMode = (typeof interactiveWorkModes)[number] export function normalizeInteractiveWorkMode( workMode: WorkMode | undefined ): InteractiveWorkMode { return workMode === 'execute' ? 'execute' : 'ask' } export const projectCreateSchema = z .object({ name: z.string().trim().min(1).max(120), description: z.string().trim().max(2_000), rootPath: z.string().trim().max(4_096), defaultWorkMode: workModeSchema }) .strict() export const projectUpdateSchema = projectCreateSchema export type ProjectCreateInput = z.infer export const conversationAttachmentSchema = z .object({ id: assistantIdSchema, name: z.string().trim().min(1).max(500), size: z.number().int().nonnegative().max(12 * 1024 * 1024), preview: z.string().max(500), kind: z.enum(['text', 'image']), thumbnailUrl: z .string() .max(2_000_000) .refine( (value) => value.startsWith('data:image/png;base64,') || value.startsWith('data:image/jpeg;base64,'), '会话附件缩略图格式无效' ) .optional(), contentUrl: z .string() .max(400_000) .refine( (value) => value.startsWith('data:image/png;base64,') || value.startsWith('data:image/jpeg;base64,'), '会话附件图片格式无效' ) .optional() }) .strict() export type ConversationAttachment = z.infer< typeof conversationAttachmentSchema > export const conversationSnapshotSchema = z .object({ id: assistantIdSchema, projectId: assistantIdSchema.optional(), title: z.string().trim().min(1).max(200), updatedAt: z.number().int().nonnegative(), messages: z .array( z .object({ id: assistantIdSchema, role: z.enum(['user', 'assistant']), content: z.string().max(1_000_000), createdAt: z.number().int().nonnegative(), state: z.enum(['streaming', 'complete', 'error']), status: z.string().max(4_000).optional(), tools: z .array( z .object({ callId: z.string().max(256).optional(), name: z.string().max(200), state: z.enum([ 'pending', 'running', 'completed', 'failed', 'recoverable', 'cancelled', 'interrupted' ]), summary: z.string().max(2_000), error: z.string().max(2_000).optional() }) .strict() ) .max(100) .optional(), sources: z.array(z.string().max(8_192)).max(100).optional(), sourceReferences: z .array( z .object({ libraryId: assistantIdSchema, libraryName: z.string().max(200), documentId: assistantIdSchema, documentName: z.string().max(500), sourceName: z.string().max(500), sourceLocation: z.string().max(4_096).optional(), locator: z.string().max(1_000).optional(), snippet: z.string().max(16_000), rank: z.number().finite(), retrievalChannels: z .array(z.enum(['fts', 'vector', 'graph'])) .max(3) .optional(), evidenceIds: z .array(assistantIdSchema) .max(100) .optional() }) .strict() ) .max(20) .optional(), artifactIds: z.array(assistantIdSchema).max(8).optional(), attachments: z .array(conversationAttachmentSchema) .max(8) .optional() }) .strict() ) .max(500) }) .strict() export type ConversationSnapshot = z.infer< typeof conversationSnapshotSchema > export const conversationSnapshotsSchema = z .array(conversationSnapshotSchema) .max(100) export type AssistantProject = ProjectCreateInput & { id: string status: 'active' | 'archived' createdAt: string updatedAt: string } export type WorkspaceChanges = { rootPath: string available: boolean status: string patch: string files: WorkspaceChangedFile[] truncated: boolean error?: string } export type WorkspaceChangedFile = { path: string status: string previousPath?: string } export type WorkspaceDirectoryEntry = { name: string path: string type: 'file' | 'directory' } export type WorkspaceDirectoryListing = { path: string entries: WorkspaceDirectoryEntry[] truncated: boolean } export type WorkspaceFilePreview = { path: string name: string content: string mimeType: 'text/markdown' | 'text/plain' | 'application/json' size: number } export type AssistantTaskStatus = | 'queued' | 'running' | 'waiting_approval' | 'paused' | 'completed' | 'failed' | 'cancelled' | 'interrupted' export type AssistantTask = { id: string projectId?: string conversationId?: string parentTaskId?: string expertId?: string routingMode?: 'manual' | 'smart' title: string instructions: string origin: 'user' | 'assistant' | 'schedule' | 'delegation' | 'subagent' status: AssistantTaskStatus progress?: number createdAt: string startedAt?: string completedAt?: string error?: string } export type ModelUsageCallInput = { requestId: string callId: string runtime: string provider: string model: string input: number output: number cacheRead: number cacheWrite: number } export type TokenUsageRecord = { requestId: string projectId?: string projectName?: string conversationId?: string conversationTitle?: string runtime: string provider: string model: string callCount: number input: number output: number cacheRead: number cacheWrite: number totalTokens: number } export type TokenUsageSummary = { totals: { callCount: number input: number output: number cacheRead: number cacheWrite: number totalTokens: number } records: TokenUsageRecord[] } export type AssistantArtifact = { id: string projectId?: string taskId?: string kind: 'markdown' | 'text' | 'json' | 'image' | 'file' title: string mimeType: string content?: string byteSize: number createdAt: string updatedAt: string } export const memoryCreateSchema = z .object({ scope: z.enum(['global', 'project', 'conversation']), scopeId: z.string().max(256).optional(), type: z.enum(['preference', 'fact', 'summary', 'procedure']), content: z.string().trim().min(1).max(8_000) }) .strict() export type MemoryCreateInput = z.infer export type AssistantMemory = MemoryCreateInput & { id: string confidence: number salience: number status: 'proposed' | 'confirmed' | 'rejected' createdAt: string updatedAt: string } export const scheduleCreateSchema = z .object({ projectId: z.string().uuid().optional(), title: z.string().trim().min(1).max(120), prompt: z.string().trim().min(1).max(100_000), workMode: z.enum(['ask', 'plan']), recurrence: z.enum(['once', 'daily', 'weekly']), nextRunAt: z.string().datetime({ offset: true }) }) .strict() export type ScheduleCreateInput = z.infer export type AssistantSchedule = ScheduleCreateInput & { id: string enabled: boolean lastRunAt?: string createdAt: string updatedAt: string } export const heartbeatRecurrenceSchema = z.discriminatedUnion('type', [ z .object({ type: z.literal('daily'), localTime: z .string() .regex(/^(?:[01]\d|2[0-3]):[0-5]\d$/) }) .strict(), z .object({ type: z.literal('weekly'), localTime: z .string() .regex(/^(?:[01]\d|2[0-3]):[0-5]\d$/), weekday: z.number().int().min(0).max(6) }) .strict() ]) export const heartbeatCreateSchema = z .object({ projectId: assistantIdSchema.optional(), name: z.string().trim().min(1).max(120), timezone: z.string().trim().min(1).max(100), recurrence: heartbeatRecurrenceSchema, enabled: z.boolean(), lookbackHours: z.number().int().min(1).max(24 * 30), retentionDays: z.number().int().min(1).max(365) }) .strict() export const heartbeatUpdateSchema = heartbeatCreateSchema export const heartbeatListSchema = z .object({ projectId: assistantIdSchema.optional() }) .strict() export const heartbeatHistorySchema = z .object({ configId: assistantIdSchema.optional(), limit: z.number().int().min(1).max(200).default(50) }) .strict() export const heartbeatIdSchema = z .object({ id: assistantIdSchema }) .strict() export const heartbeatUpdateRequestSchema = z .object({ id: assistantIdSchema, config: heartbeatUpdateSchema }) .strict() export const heartbeatPauseSchema = z .object({ id: assistantIdSchema, paused: z.boolean() }) .strict() export const heartbeatRunNowSchema = z .object({ id: assistantIdSchema, idempotencyKey: z.string().trim().min(1).max(200) }) .strict() export const heartbeatSummaryOutputSchema = z .object({ summary: z.string().trim().min(1).max(12_000), highlights: z.array(z.string().trim().min(1).max(1_000)).max(20), proposedMemories: z .array( z .object({ scope: z.enum(['global', 'project']), type: z.enum([ 'preference', 'fact', 'summary', 'procedure' ]), content: z.string().trim().min(1).max(8_000), confidence: z.number().min(0).max(1), salience: z.number().min(0).max(1) }) .strict() ) .max(10), followUpTasks: z .array( z .object({ title: z.string().trim().min(1).max(200), instructions: z.string().trim().min(1).max(8_000) }) .strict() ) .max(10) }) .strict() export type HeartbeatRecurrence = z.infer< typeof heartbeatRecurrenceSchema > export type HeartbeatCreateInput = z.infer< typeof heartbeatCreateSchema > export type HeartbeatUpdateInput = z.infer< typeof heartbeatUpdateSchema > export type HeartbeatSummaryOutput = z.infer< typeof heartbeatSummaryOutputSchema > export type HeartbeatRunStatus = | 'claimed' | 'completed' | 'failed' | 'skipped' export type AssistantHeartbeatConfig = HeartbeatCreateInput & { id: string nextRunAt: string lastRunAt?: string lastStatus?: HeartbeatRunStatus createdAt: string updatedAt: string } export type AssistantHeartbeatRun = { id: string configId: string trigger: 'scheduled' | 'manual' scheduledFor: string status: HeartbeatRunStatus attemptCount: number nextAttemptAt?: string startedAt?: string completedAt?: string error?: string entryId?: string createdAt: string updatedAt: string } export type AssistantHeartbeatEntry = { id: string configId: string runId: string scheduledFor: string summary: string highlights: string[] artifactId?: string proposedMemoryIds: string[] followUpTaskIds: string[] createdAt: string } const routingKeywordSchema = z .string() .transform((value) => value.normalize('NFKC').trim().replace(/\s+/gu, ' ').toLowerCase() ) .pipe(z.string().min(2).max(48)) export const expertCreateSchema = z .object({ name: z.string().trim().min(1).max(80), description: z.string().trim().max(500), systemInstructions: z.string().trim().min(1).max(20_000), routingKeywords: z .array(routingKeywordSchema) .max(32) .default([]) .transform((keywords) => [...new Set(keywords)]) }) .strict() export type ExpertCreateInput = z.input export type ExpertUpdateInput = ExpertCreateInput export type AssistantExpert = z.output & { id: string enabled: boolean createdAt: string updatedAt: string }