Advertisement

← vibesdk

worker/agents/index.ts

8.8 KB Download file
import { getAgentByName } from 'agents';
import { generateId } from '../utils/idGenerator';
import { StructuredLogger } from '../logger';
import { InferenceContext } from './inferutils/config.types';
import { SandboxSdkClient } from '../services/sandbox/sandboxSdkClient';
import { selectTemplate } from './planning/templateSelector';
import { TemplateDetails } from '../services/sandbox/sandboxTypes';
import { createScratchTemplateDetails } from './utils/templates';
import { TemplateSelection } from './schemas';
import type { ImageAttachment } from '../types/image-attachment';
import { BaseSandboxService } from 'worker/services/sandbox/BaseSandboxService';
import { AgentState, CurrentDevState } from './core/state';
import { CodeGeneratorAgent } from './core/codingAgent';
import { BehaviorType, ProjectType } from './core/types';

type AgentStubProps = {
    behaviorType?: BehaviorType;
    projectType?: ProjectType;
};

export async function getAgentStub(
    env: Env, 
    agentId: string,
    props?: AgentStubProps
) : Promise<DurableObjectStub<CodeGeneratorAgent>> {
    const options = props ? { props } : undefined;
    return getAgentByName<Env, CodeGeneratorAgent>(env.CodeGenObject, agentId, options);
}

export async function getAgentStubLightweight(env: Env, agentId: string) : Promise<DurableObjectStub<CodeGeneratorAgent>> {
    return getAgentByName<Env, CodeGeneratorAgent>(env.CodeGenObject, agentId, {
        // props: { readOnlyMode: true }
    });
}

export async function getAgentState(env: Env, agentId: string) : Promise<AgentState> {
    const agentInstance = await getAgentStub(env, agentId);
    return await agentInstance.getFullState() as AgentState;
}

/** Raw `.git` object store export from a SpaceDO (think apps). */
export interface SpaceGitExportStub extends DurableObjectStub {
    exportGitObjects(): Promise<Array<{ path: string; data: Uint8Array }>>;
}

/**
 * Resolve the SpaceDO git-export stub for an app. A think app's real
 * repository (files + full commit history) lives in SpaceDO/Artifacts, not in
 * the CodeGeneratorAgent's own git — so clone and GitHub export source objects
 * from here. The SpaceDO instance is always keyed by the agent id.
 */
export function getSpaceGitStub(env: Env, agentId: string): SpaceGitExportStub | null {
    const ns = (env as unknown as { SPACE_DO?: DurableObjectNamespace }).SPACE_DO;
    if (!ns) return null;
    return ns.get(ns.idFromName(agentId)) as unknown as SpaceGitExportStub;
}

/** Whether an app uses the think behavior (repo lives in SpaceDO/Artifacts). */
export async function isThinkApp(env: Env, agentId: string): Promise<boolean> {
    try {
        const state = await getAgentState(env, agentId);
        return state?.behaviorType === 'think';
    } catch {
        return false;
    }
}

export async function cloneAgent(env: Env, agentId: string) : Promise<{newAgentId: string, newAgent: DurableObjectStub<CodeGeneratorAgent>}> {
    const agentInstance = await getAgentStub(env, agentId);
    if (!agentInstance || !await agentInstance.isInitialized()) {
        throw new Error(`Agent ${agentId} not found`);
    }
    const newAgentId = generateId();

    const originalState = await agentInstance.getFullState();

    const newState: AgentState = {
        ...originalState,
        sessionId: newAgentId,
        sandboxInstanceId: undefined,
        pendingUserInputs: [],
        shouldBeGenerating: false,
        projectUpdatesAccumulator: [],
        reviewingInitiated: false,
        mvpGenerated: false,
        ...(originalState.behaviorType === 'phasic' ? {
            generatedPhases: [],
            currentDevState: CurrentDevState.IDLE,
        } : {}),
    } as AgentState;

    const newAgent = await getAgentStub(env, newAgentId, {
        behaviorType: originalState.behaviorType,
        projectType: originalState.projectType,
    });

    await newAgent.setState(newState);
    return {newAgentId, newAgent};
}

type TemplateQueryResult = { templateDetails: TemplateDetails; selection: TemplateSelection; projectType: ProjectType };

type TemplateQueryArgs = {
    env: Env;
    inferenceContext: InferenceContext;
    query: string;
    projectType: ProjectType | 'auto';
    images: ImageAttachment[] | undefined;
    logger: StructuredLogger;
    selectedTemplate?: string;
};

async function handleGeneralType(): Promise<TemplateQueryResult> {
    const scratch = createScratchTemplateDetails();
    const selection: TemplateSelection = {
        selectedTemplateName: null,
        reasoning: 'General (from-scratch) mode: no template selected',
        useCase: 'General',
        complexity: 'moderate',
        styleSelection: 'Custom',
        projectType: 'general',
    } as TemplateSelection;
    return { templateDetails: scratch, selection, projectType: 'general' };
}

async function handleUserSelectedTemplate(
    templateName: string,
    logger: StructuredLogger
): Promise<TemplateQueryResult> {
    logger.info('Using user-specified template, bypassing AI selection', { selectedTemplate: templateName });
    
    const templatesResponse = await SandboxSdkClient.listTemplates();
    if (!templatesResponse?.success) {
        throw new Error(`Failed to fetch templates from sandbox service, ${templatesResponse.error}`);
    }

    const matchedTemplate = templatesResponse.templates.find(t => t.name === templateName);
    if (!matchedTemplate) {
        throw new Error(`Specified template '${templateName}' not found in available templates`);
    }

    const templateDetailsResponse = await BaseSandboxService.getTemplateDetails(matchedTemplate.name);
    if (!templateDetailsResponse.success || !templateDetailsResponse.templateDetails) {
        throw new Error(`Failed to fetch template details for '${templateName}'`);
    }

    const selection: TemplateSelection = {
        selectedTemplateName: matchedTemplate.name,
        reasoning: 'User-specified template (AI selection bypassed)',
        useCase: 'General',
        complexity: 'moderate',
        styleSelection: 'Custom',
        projectType: matchedTemplate.projectType || 'app',
    };

    return {
        templateDetails: templateDetailsResponse.templateDetails,
        selection,
        projectType: matchedTemplate.projectType || 'app',
    };
}

async function handleAITemplateSelection(args: Omit<TemplateQueryArgs, 'selectedTemplate'>): Promise<TemplateQueryResult> {
    const { env, inferenceContext, query, projectType, images, logger } = args;

    const templatesResponse = await SandboxSdkClient.listTemplates();
    if (!templatesResponse?.success) {
        throw new Error(`Failed to fetch templates from sandbox service, ${templatesResponse.error}`);
    }

    const aiSelection = await selectTemplate({
        env,
        inferenceContext,
        query,
        projectType,
        availableTemplates: templatesResponse.templates,
        images,
    });

    logger.info('AI selected template', { selection: aiSelection });

    if (!aiSelection.selectedTemplateName) {
        logger.warn('No suitable template found; falling back to scratch');
        const scratch = createScratchTemplateDetails();
        return { templateDetails: scratch, selection: aiSelection, projectType: aiSelection.projectType };
    }

    const matchedTemplate = templatesResponse.templates.find(t => t.name === aiSelection.selectedTemplateName);
    if (!matchedTemplate) {
        logger.error('Selected template not found');
        throw new Error('Selected template not found');
    }

    const templateDetailsResponse = await BaseSandboxService.getTemplateDetails(matchedTemplate.name);
    if (!templateDetailsResponse.success || !templateDetailsResponse.templateDetails) {
        logger.error('Failed to fetch files', { templateDetailsResponse });
        throw new Error('Failed to fetch files');
    }

    return {
        templateDetails: templateDetailsResponse.templateDetails,
        selection: aiSelection,
        projectType: aiSelection.projectType,
    };
}

export async function getTemplateForQuery(
    env: Env,
    inferenceContext: InferenceContext,
    query: string,
    projectType: ProjectType | 'auto',
    images: ImageAttachment[] | undefined,
    logger: StructuredLogger,
    selectedTemplate?: string,
): Promise<TemplateQueryResult> {
    // Flow 1: General type - start from scratch
    if (projectType === 'general') {
        return handleGeneralType();
    }

    // Flow 2: User-specified template - bypass AI selection
    if (selectedTemplate && selectedTemplate !== 'auto') {
        return handleUserSelectedTemplate(selectedTemplate, logger);
    }

    // Flow 3: AI template selection
    return handleAITemplateSelection({ env, inferenceContext, query, projectType, images, logger });
}