import { z } from 'zod'; export const HttpProviderConfigSchema = z.object({ type: z.literal('http').default('http'), api_key: z.string().optional(), base_url: z.string().url(), models: z.array(z.string()), default_model: z.string(), nickname: z.string(), temperature: z.number().min(0).max(2).optional(), system_prompt: z.string().optional(), timeout: z.number().positive().optional(), max_retries: z.number().min(0).max(5).optional(), }); export const CLIProviderConfigSchema = z.object({ type: z.literal('cli'), cli_type: z.enum(['claude', 'codex', 'gemini', 'grok', 'aider', 'custom']), cli_command: z.string().optional(), prompt_delivery: z.enum(['flag', 'positional', 'stdin']).optional(), prompt_flag: z.string().optional(), output_format: z.enum(['text', 'json', 'jsonl']).optional(), response_json_path: z.string().optional(), usage_json_path: z.string().optional(), cli_args: z.array(z.string()).optional(), process_timeout: z.number().positive().optional(), working_directory: z.string().optional(), env_vars: z.record(z.string(), z.string()).optional(), nickname: z.string(), default_model: z.string().optional(), models: z.array(z.string()).optional(), system_prompt: z.string().optional(), }); // Unified provider config: backward-compatible discriminated union // Configs without `type` default to 'http' export const ProviderConfigSchema = z.preprocess( (val) => { if (val && typeof val === 'object' && !('type' in val)) { return { ...val, type: 'http' }; } return val; }, z.discriminatedUnion('type', [HttpProviderConfigSchema, CLIProviderConfigSchema]) ); export const MCPServerConfigSchema = z.object({ name: z.string(), type: z.enum(['stdio', 'http']), command: z.string().optional(), args: z.array(z.string()).optional(), url: z.string().url().optional(), apiKey: z.string().optional(), enabled: z.boolean().default(true), retryAttempts: z.number().min(0).max(10).default(3), retryDelay: z.number().min(100).max(30000).default(1000), }); export const MCPBridgeConfigSchema = z.object({ enabled: z.boolean().default(false), approval_mode: z.enum(['always', 'trusted', 'never']).default('always'), approval_timeout: z.number().min(30).max(3600).default(300), // 5 minutes trusted_tools: z.array(z.string()).default([]), // Global fallback trusted tools trusted_tools_by_server: z.record(z.string(), z.array(z.string())).optional(), // Per-server trusted tools mcp_servers: z.array(MCPServerConfigSchema).default([]), max_tool_rounds: z.number().min(1).max(50).default(10), // Max rounds of tool calling per request }); export const ModelPricingSchema = z.object({ inputPricePerMillion: z.number().min(0), outputPricePerMillion: z.number().min(0), }); export const PricingConfigSchema = z.record( z.string(), // provider name z.record(z.string(), ModelPricingSchema) // model name -> pricing ); // Guardrails Plugin Configs export const RateLimiterConfigSchema = z.object({ enabled: z.boolean().default(false), priority: z.number().min(0).max(1000).default(10), requests_per_minute: z.number().min(1).default(60), requests_per_hour: z.number().min(1).default(1000), per_provider: z.boolean().default(false), burst_allowance: z.number().min(0).default(5), }); export const TokenLimiterConfigSchema = z.object({ enabled: z.boolean().default(false), priority: z.number().min(0).max(1000).default(20), max_input_tokens: z.number().min(1).default(8192), max_output_tokens: z.number().min(1).optional(), warn_at_percentage: z.number().min(0).max(100).default(80), }); export const PatternBlockerConfigSchema = z.object({ enabled: z.boolean().default(false), priority: z.number().min(0).max(1000).default(30), blocked_patterns: z.array(z.string()).default([]), blocked_patterns_regex: z.array(z.string()).default([]), case_sensitive: z.boolean().default(false), action_on_match: z.enum(['block', 'warn', 'redact']).default('block'), }); export const PIIRedactorConfigSchema = z.object({ enabled: z.boolean().default(false), priority: z.number().min(0).max(1000).default(25), detect_emails: z.boolean().default(true), detect_phones: z.boolean().default(true), detect_ssn: z.boolean().default(true), detect_api_keys: z.boolean().default(true), detect_credit_cards: z.boolean().default(true), detect_ip_addresses: z.boolean().default(false), custom_patterns: z .array( z.object({ name: z.string(), pattern: z.string(), placeholder: z.string(), }) ) .default([]), allowlist: z.array(z.string()).default([]), allowlist_domains: z.array(z.string()).default([]), restore_on_response: z.boolean().default(false), log_detections: z.boolean().default(true), }); export const GuardrailsPluginsConfigSchema = z.object({ rate_limiter: RateLimiterConfigSchema.optional(), token_limiter: TokenLimiterConfigSchema.optional(), pattern_blocker: PatternBlockerConfigSchema.optional(), pii_redactor: PIIRedactorConfigSchema.optional(), }); export const GuardrailsConfigSchema = z.object({ enabled: z.boolean().default(false), log_violations: z.boolean().default(true), log_modifications: z.boolean().default(false), fail_open: z.boolean().default(false), // If true, allow on plugin errors plugins: GuardrailsPluginsConfigSchema.optional(), }); export const ConfigSchema = z.object({ providers: z.record(z.string(), ProviderConfigSchema), default_provider: z.string().optional(), default_temperature: z.number().min(0).max(2).default(0.7), enable_failover: z.boolean().default(true), log_level: z.enum(['debug', 'info', 'warn', 'error']).default('info'), mcp_bridge: MCPBridgeConfigSchema.optional(), pricing: PricingConfigSchema.optional(), guardrails: GuardrailsConfigSchema.optional(), }); export type HttpProviderConfig = z.infer; export type CLIProviderConfig = z.infer; export type ProviderConfig = HttpProviderConfig | CLIProviderConfig; export type MCPServerConfig = z.infer; export type MCPBridgeConfig = z.infer; export type ModelPricing = z.infer; export type PricingConfig = z.infer; export type RateLimiterConfig = z.infer; export type TokenLimiterConfig = z.infer; export type PatternBlockerConfig = z.infer; export type PIIRedactorConfig = z.infer; export type GuardrailsPluginsConfig = z.infer; export type GuardrailsConfig = z.infer; export type Config = z.infer; // Multimodal content types export interface ImageInput { data?: string; // base64-encoded image data url?: string; // image URL — passed directly to provider mimeType?: string; // required for data, optional for url } export type TextContentPart = { type: 'text'; text: string }; export type ImageContentPartBase64 = { type: 'image'; data: string; mimeType: string }; export type ImageContentPartUrl = { type: 'image'; url: string; mimeType?: string }; export type ImageContentPart = ImageContentPartBase64 | ImageContentPartUrl; export type ContentPart = TextContentPart | ImageContentPart; export type MessageContent = string | ContentPart[]; /** Extract plain text from MessageContent, regardless of format */ export function getTextContent(content: MessageContent): string { if (typeof content === 'string') return content; return content .filter((p): p is TextContentPart => p.type === 'text') .map((p) => p.text) .join('\n'); } /** Check if content contains images */ export function hasImages(content: MessageContent): boolean { if (typeof content === 'string') return false; return content.some((p) => p.type === 'image'); } /** Build a MessageContent from text + optional images */ export function buildContent(text: string, images?: ImageInput[]): MessageContent { if (!images || images.length === 0) return text; const parts: ContentPart[] = [{ type: 'text', text }]; for (const img of images) { if (img.url) { const part: ImageContentPartUrl = { type: 'image', url: img.url }; if (img.mimeType) part.mimeType = img.mimeType; parts.push(part); } else if (img.data !== undefined && img.mimeType !== undefined) { parts.push({ type: 'image', data: img.data, mimeType: img.mimeType }); } } return parts; } export interface ConversationMessage { role: 'system' | 'user' | 'assistant'; content: MessageContent; timestamp: Date; provider?: string; } export interface Conversation { id: string; messages: ConversationMessage[]; provider: string; createdAt: Date; updatedAt: Date; } export interface ProviderHealth { provider: string; healthy: boolean; latency?: number; lastCheck: Date; error?: string; } export interface DuckResponse { provider: string; nickname: string; model: string; content: string; usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number; promptTokens?: number; completionTokens?: number; totalTokens?: number; }; latency: number; } // Consensus & Voting Types export interface VoteResult { voter: string; nickname: string; choice: string; confidence: number; reasoning: string; rawResponse: string; } export interface AggregatedVote { question: string; options: string[]; winner: string | null; isTie: boolean; tally: Record; confidenceByOption: Record; votes: VoteResult[]; totalVoters: number; validVotes: number; consensusLevel: 'unanimous' | 'majority' | 'plurality' | 'split' | 'none'; } // Judge Evaluation Types export interface JudgeRanking { provider: string; nickname: string; rank: number; score: number; justification: string; } export interface JudgeEvaluation { judge: string; judgeNickname: string; prompt: string; criteria: string[]; rankings: JudgeRanking[]; criteriaScores: Record>; summary: string; rawResponse: string; } // Iteration Types export interface IterationRound { round: number; provider: string; nickname: string; role: 'generator' | 'critic' | 'refiner'; content: string; timestamp: Date; } export interface IterationResult { prompt: string; mode: 'refine' | 'critique-improve'; providers: [string, string]; rounds: IterationRound[]; finalResponse: string; totalIterations: number; converged: boolean; } // Debate Types export type DebateFormat = 'oxford' | 'socratic' | 'adversarial'; export type DebatePosition = 'pro' | 'con' | 'neutral'; export interface DebateParticipant { provider: string; nickname: string; position: DebatePosition; } export interface DebateArgument { round: number; provider: string; nickname: string; position: DebatePosition; content: string; timestamp: Date; } export interface DebateResult { topic: string; format: DebateFormat; participants: DebateParticipant[]; rounds: DebateArgument[][]; synthesis: string; synthesizer: string; totalRounds: number; } // Usage Statistics Types export interface ModelUsageStats { requests: number; promptTokens: number; completionTokens: number; errors: number; } export interface DailyUsage { [provider: string]: { [model: string]: ModelUsageStats; }; } export interface UsageData { version: number; daily: Record; } export type UsageTimePeriod = 'today' | '7d' | '30d' | 'all'; export interface UsageStatsResult { period: UsageTimePeriod; startDate: string; endDate: string; usage: DailyUsage; totals: { requests: number; promptTokens: number; completionTokens: number; errors: number; estimatedCostUSD?: number; }; costByProvider?: Record; }