Advertisement

← vibesdk

worker/services/sandbox/sandboxTypes.ts

16.0 KB Download file
import * as z from 'zod'

// --- Core File/Template Types ---

// Define the interface explicitly to break circular reference
export interface FileTreeNode {
    path: string;
    type: 'file' | 'directory';
    children?: FileTreeNode[];
}

export const FileTreeNodeSchema: z.ZodType<FileTreeNode> = z.lazy(() => z.object({
    path: z.string(),
    type: z.enum(['file', 'directory']),
    children: z.array(FileTreeNodeSchema).optional(),
}));

export const TemplateFileSchema = z.object({
    filePath: z.string(),
    fileContents: z.string(),
})
export type TemplateFile = z.infer<typeof TemplateFileSchema>

// ==========================================
// RUNTIME ERROR SCHEMAS
// ==========================================

export const SimpleErrorSchema = z.object({
  timestamp: z.string(),     // ISO timestamp
  level: z.number(),          // Pino log level (50=error, 60=fatal)
  message: z.string(),        // The 'msg' field from JSON log
  rawOutput: z.string()       // The complete raw JSON log line
});
export type SimpleError = z.infer<typeof SimpleErrorSchema>;

// StoredError extends SimpleError with storage-specific fields
export const StoredErrorSchema = SimpleErrorSchema.extend({
  id: z.number(),
  instanceId: z.string(),
  processId: z.string(),
  errorHash: z.string(),
  occurrenceCount: z.number(),
  createdAt: z.string()
});
export type StoredError = z.infer<typeof StoredErrorSchema>;

export const RuntimeErrorSchema = SimpleErrorSchema
export type RuntimeError = z.infer<typeof RuntimeErrorSchema>

// -- Instance creation options --

export const InstanceCreationRequestSchema = z.object({
    files: z.array(TemplateFileSchema),
    projectName: z.string(),
    webhookUrl: z.string().url().optional(),
    envVars: z.record(z.string(), z.string()).optional(),
    initCommand: z.string().default('bun run dev'),
})
export type InstanceCreationRequest = z.infer<typeof InstanceCreationRequestSchema>

// --- Instance Details ---

export const InstanceDetailsSchema = z.object({
    runId: z.string(),
    startTime: z.union([z.string(), z.date()]),
    uptime: z.number(),
    previewURL: z.string().optional(),
    tunnelURL: z.string().optional(),
    directory: z.string(),
    serviceDirectory: z.string(),
    fileTree: FileTreeNodeSchema.optional(),
    runtimeErrors: z.array(RuntimeErrorSchema).optional(),
    processId: z.string().optional(),   
})
export type InstanceDetails = z.infer<typeof InstanceDetailsSchema>

// --- Command Execution ---

export const CommandExecutionResultSchema = z.object({
    command: z.string(),
    success: z.boolean(),
    output: z.string(),
    error: z.string().optional(),
    exitCode: z.number().optional(),
})
export type CommandExecutionResult = z.infer<typeof CommandExecutionResultSchema>

// --- API Request/Response Schemas ---

// /templates (GET)

export const TemplateInfoSchema = z.object({
    name: z.string(),
    language: z.string().optional(),
    frameworks: z.array(z.string()).optional(),
    projectType: z.enum(['app', 'workflow', 'presentation', 'general']).default('app'),
    description: z.object({
        selection: z.string(),
        usage: z.string(),
    }),
    renderMode: z.enum(['sandbox', 'browser']).optional(),
    slideDirectory: z.string().optional(),
    disabled: z.boolean(),
})
export type TemplateInfo = z.infer<typeof TemplateInfoSchema>

// --- Template Details ---

export const TemplateDetailsSchema = TemplateInfoSchema.extend({
    fileTree: FileTreeNodeSchema,
    allFiles: z.record(z.string(), z.string()),
    deps: z.record(z.string(), z.string()),
    importantFiles: z.array(z.string()),
    dontTouchFiles: z.array(z.string()),
    redactedFiles: z.array(z.string()),
})
export type TemplateDetails = z.infer<typeof TemplateDetailsSchema>

export const TemplateListResponseSchema = z.object({
    success: z.boolean(),
    templates: z.array(TemplateInfoSchema),
    count: z.number(),
    error: z.string().optional(),
})
export type TemplateListResponse = z.infer<typeof TemplateListResponseSchema>

// /template/:name (GET)
export const TemplateDetailsResponseSchema = z.object({
    success: z.boolean(),
    templateDetails: TemplateDetailsSchema.optional(),
    error: z.string().optional(),
})
export type TemplateDetailsResponse = z.infer<typeof TemplateDetailsResponseSchema>

// /template/:name/files (POST)
export const GetTemplateFilesRequestSchema = z.object({
    filePaths: z.array(z.string()),
})
export type GetTemplateFilesRequest = z.infer<typeof GetTemplateFilesRequestSchema>

export const GetTemplateFilesResponseSchema = z.object({
    success: z.boolean(),
    files: z.array(TemplateFileSchema),
    errors: z.array(z.object({ file: z.string(), error: z.string() })).optional(),
    error: z.string().optional(),
})
export type GetTemplateFilesResponse = z.infer<typeof GetTemplateFilesResponseSchema>

export const BootstrapRequestSchema = InstanceCreationRequestSchema
export type BootstrapRequest = z.infer<typeof BootstrapRequestSchema>

export const PreviewSchema = z.object({
    runId: z.string().optional(),
    previewURL: z.string().optional(),
    tunnelURL: z.string().optional(),
})
export type PreviewType = z.infer<typeof PreviewSchema>

export const BootstrapResponseSchema = PreviewSchema.extend({
    success: z.boolean(),
    processId: z.string().optional(),
    message: z.string().optional(),
    error: z.string().optional(),
})
export type BootstrapResponse = z.infer<typeof BootstrapResponseSchema>

// /instances/:id/status (GET)
export const BootstrapStatusResponseSchema = z.object({
    success: z.boolean(),
    pending: z.boolean(),
    message: z.string().optional(),
    previewURL: z.string().optional(),
    tunnelURL: z.string().optional(),
    processId: z.string().optional(),
    isHealthy: z.boolean(),
    error: z.string().optional(),
})
export type BootstrapStatusResponse = z.infer<typeof BootstrapStatusResponseSchema>

// /instances (GET)
export const ListInstancesResponseSchema = z.object({
    success: z.boolean(),
    instances: z.array(InstanceDetailsSchema),
    count: z.number(),
    error: z.string().optional(),
})
export type ListInstancesResponse = z.infer<typeof ListInstancesResponseSchema>

// /instances/:id (GET)
export const GetInstanceResponseSchema = z.object({
    success: z.boolean(),
    instance: InstanceDetailsSchema.optional(),
    error: z.string().optional(),
})
export type GetInstanceResponse = z.infer<typeof GetInstanceResponseSchema>

// /instances/:id/files (POST)
export const WriteFilesRequestSchema = z.object({
    files: z.array(z.object({
        filePath: z.string(),
        fileContents: z.string(),
    })), 
    commitMessage: z.string().optional(),
})
export type WriteFilesRequest = z.infer<typeof WriteFilesRequestSchema>

// /instances/:id/files (GET) - Define schema for getting files from an instance
export const GetFilesResponseSchema = z.object({
    success: z.boolean(),
    files: z.array(TemplateFileSchema), // Re-use TemplateFileSchema { filePath, fileContents }
    errors: z.array(z.object({ file: z.string(), error: z.string() })).optional(),
    error: z.string().optional(),
})
export type GetFilesResponse = z.infer<typeof GetFilesResponseSchema>

export const WriteFilesResponseSchema = z.object({
    success: z.boolean(),
    message: z.string().optional(),
    results: z.array(z.object({
        file: z.string(),
        success: z.boolean(),
        error: z.string().optional(),
    })),
    error: z.string().optional(),
})
export type WriteFilesResponse = z.infer<typeof WriteFilesResponseSchema>

export const GetLogsResponseSchema = z.object({
    success: z.boolean(),
    logs: z.object({
        stdout: z.string(),
        stderr: z.string(),
    }),
    error: z.string().optional(),
})
export type GetLogsResponse = z.infer<typeof GetLogsResponseSchema>

// /instances/:id/commands (POST)
export const ExecuteCommandsRequestSchema = z.object({
    commands: z.array(z.string()),
    timeout: z.number().optional(),
})
export type ExecuteCommandsRequest = z.infer<typeof ExecuteCommandsRequestSchema>

export const ExecuteCommandsResponseSchema = z.object({
    success: z.boolean(),
    results: z.array(CommandExecutionResultSchema),
    message: z.string().optional(),
    error: z.string().optional(),
})
export type ExecuteCommandsResponse = z.infer<typeof ExecuteCommandsResponseSchema>

// /instances/:id/errors (GET)
export const RuntimeErrorResponseSchema = z.object({
    success: z.boolean(),
    errors: z.array(RuntimeErrorSchema),
    hasErrors: z.boolean(),
    error: z.string().optional(),
})
export type RuntimeErrorResponse = z.infer<typeof RuntimeErrorResponseSchema>

// /instances/:id/errors (DELETE)
export const ClearErrorsResponseSchema = z.object({
    success: z.boolean(),
    message: z.string().optional(),
    error: z.string().optional(),
})
export type ClearErrorsResponse = z.infer<typeof ClearErrorsResponseSchema>

// /instances/:id/fix-code (POST)
export const FixCodeResponseSchema = z.object({
    success: z.boolean(),
    message: z.string().optional(),
    fixes: z.array(z.object({
        filePath: z.string(),
        originalCode: z.string(),
        fixedCode: z.string(),
        explanation: z.string(),
    })),
    applied: z.array(z.string()).optional(),
    failed: z.array(z.string()).optional(),
    commands: z.array(z.string()).optional(),
    error: z.string().optional(),
})
export type FixCodeResponse = z.infer<typeof FixCodeResponseSchema>

// /instances/:id (DELETE)
export const ShutdownResponseSchema = z.object({
    success: z.boolean(),
    message: z.string().optional(),
    error: z.string().optional(),
})
export type ShutdownResponse = z.infer<typeof ShutdownResponseSchema>

// /instances/:id/lint (GET)
export const LintSeveritySchema = z.enum(['error', 'warning', 'info'])
export type LintSeverity = z.infer<typeof LintSeveritySchema>

export const CodeIssueSchema = z.object({
    message: z.string(),
    filePath: z.string(),
    line: z.number(),
    column: z.number().optional(),
    severity: LintSeveritySchema,
    ruleId: z.string().optional(),
    source: z.string().optional()
})
export type CodeIssue = z.infer<typeof CodeIssueSchema>

export const CodeIssueResponseSchema =  z.object({
    issues: z.array(CodeIssueSchema),
    summary: z.object({
        errorCount: z.number(),
        warningCount: z.number(),
        infoCount: z.number()
    }).optional(),
    rawOutput: z.string().optional(),
})
export type CodeIssueResponse = z.infer<typeof CodeIssueResponseSchema>

export const StaticAnalysisResponseSchema = z.object({
    success: z.boolean(),
    lint: CodeIssueResponseSchema,
    typecheck: CodeIssueResponseSchema,
    error: z.string().optional()
})
export type StaticAnalysisResponse = z.infer<typeof StaticAnalysisResponseSchema>

// --- Cloudflare Deployment ---

// /instances/:id/deploy (POST) - Request body
export const DeploymentCredentialsSchema = z.object({
    apiToken: z.string().optional(),
    accountId: z.string().optional(),
})
export type DeploymentCredentials = z.infer<typeof DeploymentCredentialsSchema>

// /instances/:id/deploy (POST) - Response
export const DeploymentResultSchema = z.object({
    success: z.boolean(),
    message: z.string(),
    deployedUrl: z.string().optional(),
    deploymentId: z.string().optional(),
    output: z.string().optional(),
    error: z.string().optional(),
})
export type DeploymentResult = z.infer<typeof DeploymentResultSchema>

// --- Webhook Event Types ---

// Base webhook event schema
export const WebhookEventBaseSchema = z.object({
    eventType: z.string(),
    instanceId: z.string(),
    timestamp: z.union([z.string(), z.date()]).transform(val => typeof val === 'string' ? val : val.toISOString()),
    agentId: z.string().optional(),
})

// Runtime error webhook event - compatible with current runner service
export const WebhookRuntimeErrorEventSchema = WebhookEventBaseSchema.extend({
    eventType: z.literal('runtime_error'),
    payload: z.object({
        runId: z.string(),
        error: RuntimeErrorSchema,
        instanceInfo: z.object({
            instanceId: z.string(),
            serviceDirectory: z.string().optional(),
        }),
    }),
})
export type WebhookRuntimeErrorEvent = z.infer<typeof WebhookRuntimeErrorEventSchema>

// Build status webhook event (for future use)
export const WebhookBuildStatusEventSchema = WebhookEventBaseSchema.extend({
    eventType: z.literal('build_status'),
    payload: z.object({
        status: z.enum(['started', 'completed', 'failed']),
        buildOutput: z.string().optional(),
        buildErrors: z.array(z.string()).optional(),
        duration: z.number().optional(),
    }),
})
export type WebhookBuildStatusEvent = z.infer<typeof WebhookBuildStatusEventSchema>

// Deployment status webhook event (for future use)
export const WebhookDeploymentStatusEventSchema = WebhookEventBaseSchema.extend({
    eventType: z.literal('deployment_status'),
    payload: z.object({
        status: z.enum(['started', 'completed', 'failed']),
        deploymentType: z.enum(['preview', 'cloudflare_workers']).optional(),
        deployedUrl: z.string().optional(),
        deploymentId: z.string().optional(),
        error: z.string().optional(),
    }),
})
export type WebhookDeploymentStatusEvent = z.infer<typeof WebhookDeploymentStatusEventSchema>

// Instance health webhook event (for future use)
export const WebhookInstanceHealthEventSchema = WebhookEventBaseSchema.extend({
    eventType: z.literal('instance_health'),
    payload: z.object({
        status: z.enum(['healthy', 'unhealthy', 'shutting_down']),
        uptime: z.number().optional(),
        memoryUsage: z.number().optional(),
        cpuUsage: z.number().optional(),
        lastActivity: z.union([z.string(), z.date()]).optional(),
        message: z.string().optional(),
    }),
})
export type WebhookInstanceHealthEvent = z.infer<typeof WebhookInstanceHealthEventSchema>

// Command execution webhook event (for future use)
export const WebhookCommandExecutionEventSchema = WebhookEventBaseSchema.extend({
    eventType: z.literal('command_execution'),
    payload: z.object({
        status: z.enum(['started', 'completed', 'failed']),
        command: z.string(),
        output: z.string().optional(),
        error: z.string().optional(),
        exitCode: z.number().optional(),
        duration: z.number().optional(),
    }),
})
export type WebhookCommandExecutionEvent = z.infer<typeof WebhookCommandExecutionEventSchema>

// Union type for all webhook events
export const WebhookEventSchema = z.discriminatedUnion('eventType', [
    WebhookRuntimeErrorEventSchema,
    WebhookBuildStatusEventSchema,
    WebhookDeploymentStatusEventSchema,
    WebhookInstanceHealthEventSchema,
    WebhookCommandExecutionEventSchema,
])
export type WebhookEvent = z.infer<typeof WebhookEventSchema>

// Webhook payload with authentication
export const WebhookPayloadSchema = z.object({
    signature: z.string().optional(),
    timestamp: z.union([z.string(), z.date()]),
    event: WebhookEventSchema,
})
export type WebhookPayload = z.infer<typeof WebhookPayloadSchema>
/**
 * GitHub integration types for exporting generated applications
 */

// Common fields for GitHub operations
interface GitHubUserInfo {
    token: string;
    email: string;
    username: string;
    isPrivate: boolean;
}

// Request for pushing to existing repository (low-level push)
export interface GitHubPushRequest extends GitHubUserInfo {
    cloneUrl: string;
    repositoryHtmlUrl: string;
}

export const GitHubPushResponseSchema = z.object({
    success: z.boolean(),
    commitSha: z.string().optional(),
    error: z.string().optional(),
    details: z.object({
        operation: z.string().optional(),
        exitCode: z.number().optional(),
        stderr: z.string().optional(),
    }).optional(),
})
export type GitHubPushResponse = z.infer<typeof GitHubPushResponseSchema>