import { z } from 'zod' import type { CapabilityAssignments, CapabilitySnapshot, McpServerInput, McpServerTestResult } from './capability-contracts' import { workModeSchema, type AssistantProject, type AssistantArtifact, type AssistantMemory, type AssistantSchedule, type AssistantExpert, type AssistantTask, type ConversationSnapshot, type WorkspaceChanges, type ProjectCreateInput, type MemoryCreateInput, type ScheduleCreateInput, type ExpertCreateInput } from './assistant-contracts' export const agentRequestSchema = z .object({ requestId: z.string().uuid(), conversationId: z.string().min(1).max(128), projectId: z.string().uuid().optional(), expertId: z.string().uuid().optional(), teamMode: z.boolean().optional(), workMode: workModeSchema.optional(), prompt: z.string().trim().min(1).max(100_000), contextIds: z.array(z.string().uuid()).max(8).optional(), history: z .array( z .object({ role: z.enum(['user', 'assistant']), content: z.string().max(100_000) }) .strict() ) .max(40) .optional() }) .strict() .superRefine((request, context) => { const historyLength = request.history?.reduce( (total, message) => total + message.content.length, 0 ) ?? 0 if (historyLength > 500_000) { context.addIssue({ code: 'custom', path: ['history'], message: '会话历史总长度不能超过 500,000 个字符' }) } }) export type AgentRequest = z.infer export const runtimeProviderSchema = z.enum([ 'auto', 'model', 'opencode', 'continue' ]) export const toolApprovalPolicySchema = z.enum([ 'always', 'session', 'workspace', 'policy' ]) export const continueModeSchema = z.enum(['chat', 'agent']) export const defaultModelProfileId = '00000000-0000-4000-8000-000000000001' export const defaultRuntimeSettings = { provider: 'auto', modelBaseUrl: 'https://bigtoken.ai', modelName: 'sonnet-5', opencodeBaseUrl: '', opencodeEmbedded: false, opencodeBinaryPath: '', opencodeConfigPath: '', continueBinaryPath: '', continueConfigPath: '', continueMode: 'chat', workspacePath: '', toolApproval: 'always' } as const export const runtimePathSchema = z .string() .max(4_096) .refine( (value) => [...value].every((character) => { const code = character.charCodeAt(0) return code > 31 && code !== 127 }), 'Runtime 路径包含控制字符' ) export const runtimeFileSelectionKindSchema = z.enum([ 'opencodeBinary', 'opencodeConfig', 'continueBinary', 'continueConfig' ]) export type RuntimeFileSelectionKind = z.infer< typeof runtimeFileSelectionKindSchema > const modelApiKeyUpdateSchema = z.discriminatedUnion('action', [ z.object({ action: z.literal('keep') }).strict(), z .object({ action: z.literal('replace'), value: z .string() .trim() .min(1) .max(8_192) .refine( (value) => [...value].every((character) => { const code = character.charCodeAt(0) return code > 31 && code !== 127 }), { message: 'API Key 包含控制字符' } ) }) .strict(), z.object({ action: z.literal('clear') }).strict() ]) const modelProfileInputSchema = z .object({ id: z.string().uuid(), name: z.string().trim().min(1).max(64), baseUrl: z.string().url().max(2_048), modelName: z .string() .trim() .min(1) .max(128) .regex(/^[\w./:-]+$/, '模型名称包含不支持的字符'), apiKey: modelApiKeyUpdateSchema }) .strict() export const runtimeModelSourceSchema = z.discriminatedUnion('kind', [ z.object({ kind: z.literal('platform') }).strict(), z .object({ kind: z.literal('profile'), profileId: z.string().uuid() }) .strict() ]) export const runtimeSettingsInputSchema = z .object({ provider: runtimeProviderSchema, modelBaseUrl: z.string().url().max(2_048), modelName: z .string() .trim() .min(1) .max(128) .regex(/^[\w./:-]+$/, '模型名称包含不支持的字符'), opencodeBaseUrl: z.union([ z.literal(''), z.string().url().max(2_048) ]), opencodeEmbedded: z.boolean(), opencodeBinaryPath: runtimePathSchema, opencodeConfigPath: runtimePathSchema, continueBinaryPath: runtimePathSchema, continueConfigPath: runtimePathSchema, continueMode: continueModeSchema, workspacePath: z.string().trim().min(1).max(4_096), apiKey: modelApiKeyUpdateSchema, modelProfiles: z.array(modelProfileInputSchema).min(1).max(20).optional(), defaultModelProfileId: z.string().uuid().optional(), opencodeModelSource: runtimeModelSourceSchema.optional(), continueModelSource: runtimeModelSourceSchema.optional(), toolApproval: toolApprovalPolicySchema }).strict() .superRefine((settings, context) => { const endpoints = settings.modelProfiles?.map((profile, index) => ({ path: ['modelProfiles', index, 'baseUrl'] as (string | number)[], value: profile.baseUrl })) ?? [{ path: ['modelBaseUrl'], value: settings.modelBaseUrl }] for (const endpoint of endpoints) { const url = new URL(endpoint.value) if ( url.protocol !== 'https:' || url.username || url.password || url.search || url.hash || (url.pathname !== '/' && url.pathname !== '') ) { context.addIssue({ code: 'custom', path: endpoint.path, message: '模型服务地址必须是无凭据和路径的 HTTPS origin' }) } } if (settings.modelProfiles) { const ids = new Set(settings.modelProfiles.map((profile) => profile.id)) const names = new Set( settings.modelProfiles.map((profile) => profile.name.toLowerCase()) ) if ( ids.size !== settings.modelProfiles.length || names.size !== settings.modelProfiles.length ) { context.addIssue({ code: 'custom', path: ['modelProfiles'], message: '模型连接的 ID 和名称必须唯一' }) } const defaultId = settings.defaultModelProfileId ?? settings.modelProfiles[0]?.id if (!defaultId || !ids.has(defaultId)) { context.addIssue({ code: 'custom', path: ['defaultModelProfileId'], message: '默认模型连接不存在' }) } for (const [key, source] of [ ['opencodeModelSource', settings.opencodeModelSource], ['continueModelSource', settings.continueModelSource] ] as const) { if (source?.kind === 'profile' && !ids.has(source.profileId)) { context.addIssue({ code: 'custom', path: [key], message: 'Runtime 引用的模型连接不存在' }) } } } if (settings.opencodeBaseUrl) { const opencodeUrl = new URL(settings.opencodeBaseUrl) if ( !['http:', 'https:'].includes(opencodeUrl.protocol) || opencodeUrl.username || opencodeUrl.password || opencodeUrl.search || opencodeUrl.hash || (opencodeUrl.pathname !== '/' && opencodeUrl.pathname !== '') ) { context.addIssue({ code: 'custom', path: ['opencodeBaseUrl'], message: 'OpenCode 地址必须是无凭据和路径的 HTTP(S) origin' }) } } }) export type RuntimeSettingsInput = z.infer export type RuntimeModelSource = z.infer export type ModelConnectionSettings = { id: string name: string baseUrl: string modelName: string apiKeyConfigured: boolean credentialSource: 'none' | 'encrypted' | 'environment' } export type RuntimeSettings = { provider: RuntimeSettingsInput['provider'] modelBaseUrl: string modelName: string opencodeBaseUrl: string opencodeEmbedded: boolean opencodeBinaryPath: string opencodeConfigPath: string continueBinaryPath: string continueConfigPath: string continueMode: RuntimeSettingsInput['continueMode'] workspacePath: string apiKeyConfigured: boolean credentialSource: 'none' | 'encrypted' | 'environment' modelProfiles: ModelConnectionSettings[] defaultModelProfileId: string opencodeModelSource: RuntimeModelSource continueModelSource: RuntimeModelSource secureStorageAvailable: boolean toolApproval: RuntimeSettingsInput['toolApproval'] warning?: string } export type ContextAttachment = { id: string name: string size: number preview: string kind: 'text' | 'image' thumbnailUrl?: string } export type AgentRuntimeStatus = { id: 'setup' | 'model' | 'opencode' | 'continue' label: string available: boolean detail: string } export type RuntimeBinaryDetection = | { available: true path: string version?: string detail: string } | { available: false path?: never version?: never detail: string } export type AgentRuntimeDetection = { opencode: RuntimeBinaryDetection continue: RuntimeBinaryDetection } export const approvalDecisionSchema = z.enum([ 'deny', 'once', 'session', 'permanent' ]) export type ApprovalDecision = z.infer export type AgentEvent = | { requestId: string type: 'status' message: string } | { requestId: string type: 'text' delta: string } | { requestId: string type: 'tool' name: string state: 'pending' | 'running' | 'completed' | 'failed' summary: string } | { requestId: string type: 'approval' approvalId: string title: string description: string toolName?: string argumentSummary?: string allowPermanent?: boolean } | { requestId: string type: 'done' sessionId?: string } | { requestId: string type: 'error' message: string } export type AppInfo = { name: string version: string platform: string arch: string shortcut: string } export const knowledgeIdSchema = z.string().uuid() export const knowledgeCreateSchema = z .object({ name: z.string().trim().min(1).max(120), description: z.string().trim().max(1_000), storageMode: z.enum(['reference', 'managed']), graphEnabled: z.boolean(), graphStrategy: z.enum(['rules', 'model', 'hybrid', 'ask']) }) .strict() export const knowledgeImportPathsSchema = z .object({ libraryId: knowledgeIdSchema, paths: z.array(z.string().trim().min(1).max(4_096)).min(1).max(20), graphStrategy: z.enum(['rules', 'model', 'hybrid']).optional() }) .strict() export const knowledgeUrlImportSchema = z .object({ libraryId: knowledgeIdSchema, url: z.string().url().max(2_048), graphStrategy: z.enum(['rules', 'model', 'hybrid']).optional() }) .strict() export const knowledgeUpdateLibrarySchema = z .object({ libraryId: knowledgeIdSchema, graphEnabled: z.boolean(), graphStrategy: z.enum(['rules', 'model', 'hybrid', 'ask']) }) .strict() export const knowledgeEntityUpdateSchema = z .object({ label: z.string().trim().min(1).max(120), type: z.string().trim().min(1).max(120), description: z.string().trim().max(2_000), aliases: z.array(z.string().trim().min(1).max(120)).max(50) }) .strict() export const knowledgeRelationInputSchema = z .object({ sourceId: knowledgeIdSchema, targetId: knowledgeIdSchema, type: z.string().trim().min(1).max(120), description: z.string().trim().max(2_000) }) .strict() export type KnowledgeLibrary = z.infer & { id: string sourceCount: number documentCount: number indexedDocumentCount: number updatedAt?: string } export type KnowledgeSourceItem = { id: string libraryId: string name: string kind: 'file' | 'directory' | 'url' location?: string status: 'queued' | 'syncing' | 'paused' | 'ready' | 'failed' progress?: number documentCount: number lastSyncedAt?: string error?: string } export type KnowledgeDocumentItem = { id: string libraryId: string sourceId?: string name: string path?: string status: 'queued' | 'parsing' | 'indexing' | 'ready' | 'failed' indexProgress?: number chunkCount?: number size?: number updatedAt?: string error?: string } export type KnowledgeGraphNode = { id: string label: string type: string description?: string aliases?: string[] x: number y: number evidenceIds?: string[] } export type KnowledgeGraphRelation = { id: string sourceId: string targetId: string type: string description?: string evidenceIds?: string[] } export type KnowledgeEvidence = { id: string documentId: string documentName: string excerpt: string location?: string } export type KnowledgeSnapshot = { libraries: KnowledgeLibrary[] selectedLibraryId?: string sources: KnowledgeSourceItem[] documents: KnowledgeDocumentItem[] graphNodes: KnowledgeGraphNode[] graphRelations: KnowledgeGraphRelation[] evidence: KnowledgeEvidence[] } export type KnowledgeSearchReference = { libraryId: string libraryName: string documentId: string documentName: string sourceName: string sourceLocation?: string locator?: string snippet: string rank: number } export type DesktopApi = { app: { getInfo: () => Promise show: () => Promise hide: () => Promise onNewConversation: (listener: () => void) => () => void onOpenSettings: (listener: () => void) => () => void } agent: { getStatus: () => Promise run: (request: AgentRequest) => Promise cancel: (requestId: string) => Promise respondApproval: ( approvalId: string, decision: ApprovalDecision ) => Promise onEvent: (listener: (event: AgentEvent) => void) => () => void } settings: { getRuntime: () => Promise updateRuntime: (input: RuntimeSettingsInput) => Promise selectWorkspace: () => Promise detectAgentRuntimes: () => Promise selectRuntimeFile: ( kind: RuntimeFileSelectionKind ) => Promise testRuntime: () => Promise } projects: { list: (includeArchived?: boolean) => Promise create: (input: ProjectCreateInput) => Promise update: ( projectId: string, input: ProjectCreateInput ) => Promise setArchived: (projectId: string, archived: boolean) => Promise } conversations: { list: () => Promise replace: (conversations: ConversationSnapshot[]) => Promise } workspace: { getChanges: (projectId: string) => Promise } tasks: { list: () => Promise } artifacts: { list: (projectId?: string) => Promise importFiles: (projectId?: string) => Promise } memory: { list: (scopeId?: string) => Promise create: (input: MemoryCreateInput) => Promise setStatus: ( memoryId: string, status: AssistantMemory['status'] ) => Promise remove: (memoryId: string) => Promise } schedules: { list: (projectId?: string) => Promise create: (input: ScheduleCreateInput) => Promise setEnabled: (scheduleId: string, enabled: boolean) => Promise remove: (scheduleId: string) => Promise runNow: (scheduleId: string) => Promise } experts: { list: () => Promise create: (input: ExpertCreateInput) => Promise } capabilities: { getSnapshot: () => Promise importSkill: () => Promise removeSkill: (skillId: string) => Promise setSkillEnabled: ( skillId: string, enabled: boolean ) => Promise setSkillAssignments: ( skillId: string, assignments: CapabilityAssignments ) => Promise saveMcpServer: ( serverId: string | undefined, input: McpServerInput ) => Promise removeMcpServer: (serverId: string) => Promise testMcpServer: (serverId: string) => Promise } context: { selectFiles: () => Promise captureScreen: () => Promise captureWindow: () => Promise readClipboard: () => Promise remove: (contextId: string) => Promise } knowledge: { getSnapshot: (libraryId?: string) => Promise createLibrary: ( input: z.infer ) => Promise updateLibrary: ( libraryId: string, update: { graphEnabled: boolean graphStrategy: 'rules' | 'model' | 'hybrid' | 'ask' } ) => Promise deleteLibrary: (libraryId: string) => Promise selectFiles: ( libraryId: string, graphStrategy?: 'rules' | 'model' | 'hybrid' ) => Promise selectDirectory: ( libraryId: string, graphStrategy?: 'rules' | 'model' | 'hybrid' ) => Promise importDroppedFiles: ( libraryId: string, files: File[], graphStrategy?: 'rules' | 'model' | 'hybrid' ) => Promise importUrl: ( libraryId: string, url: string, graphStrategy?: 'rules' | 'model' | 'hybrid' ) => Promise syncSource: (sourceId: string) => Promise pauseSource: (sourceId: string) => Promise retrySource: (sourceId: string) => Promise removeSource: (sourceId: string) => Promise search: ( libraryIds: string[], query: string ) => Promise createEntity: ( libraryId: string, input: z.infer ) => Promise updateEntity: ( entityId: string, update: z.infer ) => Promise moveEntity: ( entityId: string, position: { x: number; y: number } ) => Promise deleteEntity: (entityId: string) => Promise mergeEntities: ( sourceEntityId: string, targetEntityId: string ) => Promise createRelation: ( libraryId: string, input: z.infer ) => Promise updateRelation: ( relationId: string, input: z.infer ) => Promise deleteRelation: (relationId: string) => Promise } }