import type { AgentKnowledgeDatasetConfig, AgentSoulConfig, AgentSoulDifyToolConfig, } from '@dify/contracts/api/console/agent/types.gen' import type { ConsoleSegmentListResponse, DatasetListItemResponse, DocumentStatusListResponse, DocumentWithSegmentsListResponse, KnowledgeConfig, } from '@dify/contracts/api/console/datasets/types.gen' import type { AvailableModelListResponse, DefaultModelDataResponse, ModelProviderListResponse, } from '@dify/contracts/api/console/workspaces/types.gen' import type { SeedContext, SeedResource, SeedTask } from '../../../support/seed' import type { UploadedConsoleFile } from './agent-drive' import { readFile } from 'node:fs/promises' import { createApiContext, createTestApp, expectApiResponseOK, publishWorkflowApp, syncAgentV2WorkflowDraft, } from '../../../support/api' import { bootstrapMarketplacePlugins } from '../../../support/marketplace-plugins' import { sleep } from '../../../support/process' import { blocked, created, skipped, updated, verified } from '../../../support/seed' import { createTestAgent, publishAgent, saveAgentComposerDraft } from './agent' import { agentBuilderExpectedTokens, agentBuilderFixedInputs, agentBuilderPreseededResources, } from './agent-builder-resources' import { getAgentDriveSkills, uploadAgentConfigFileToDraft, uploadAgentConfigSkillToDraft, uploadAgentDriveSkill, } from './agent-drive' import { createAgentSoulConfigWithKnowledgeDataset, createAgentSoulConfigWithModel, normalAgentSoulConfig, } from './agent-soul' import { buildQuery, findConsoleResourceByName, isRecord, matchesNameOrLabel, } from './fixtures/common' import { splitToolDisplayName } from './fixtures/tools' import { agentBuilderTestMaterials, getAgentBuilderTestMaterialPath } from './test-materials' type StableModel = { name: string provider: string type: string } type ToolResource = SeedResource & { providerName: string toolName: string } const modelCredentialEnv = 'E2E_MODEL_PROVIDER_CREDENTIALS_JSON' const speechToTextModelProviderEnv = 'E2E_SPEECH_TO_TEXT_MODEL_PROVIDER' const speechToTextModelNameEnv = 'E2E_SPEECH_TO_TEXT_MODEL_NAME' const marketplacePluginIdsEnv = 'E2E_MARKETPLACE_PLUGIN_IDS' const activeModelStatus = 'active' const stableModelCredentialName = 'E2E Stable Model' const agentV2MarketplacePluginIds = [ 'langgenius/openai', 'langgenius/json_process', 'langgenius/tavily', ] const getProviderAlias = (provider: string) => provider.split('/').filter(Boolean).at(-1) ?? provider const matchesProvider = (actual: string, expected: string) => actual === expected || getProviderAlias(actual) === getProviderAlias(expected) const matchesProviderLabel = ( provider: { label?: { en_US?: string | null; zh_Hans?: string | null } | null; provider: string }, expected: string, ) => matchesProvider(provider.provider, expected) || provider.label?.en_US === expected || provider.label?.zh_Hans === expected const stableModelConfig = (): StableModel => ({ name: process.env.E2E_STABLE_MODEL_NAME?.trim() || 'gpt-5-nano', provider: process.env.E2E_STABLE_MODEL_PROVIDER?.trim() || 'openai', type: process.env.E2E_STABLE_MODEL_TYPE?.trim() || 'llm', }) const agentDecisionModelConfig = (): StableModel => ({ name: process.env.E2E_AGENT_DECISION_MODEL_NAME?.trim() || 'gpt-5.5', provider: process.env.E2E_AGENT_DECISION_MODEL_PROVIDER?.trim() || 'openai', type: process.env.E2E_AGENT_DECISION_MODEL_TYPE?.trim() || 'llm', }) const speechToTextModelConfig = (): StableModel => ({ name: process.env[speechToTextModelNameEnv]?.trim() || 'gpt-4o-mini-transcribe', provider: process.env[speechToTextModelProviderEnv]?.trim() || 'openai', type: 'speech2text', }) const parseJsonEnv = (envName: string) => { const raw = process.env[envName]?.trim() if (!raw) return { ok: false as const, reason: `${envName} is required.` } try { const value = JSON.parse(raw) as unknown if (!isRecord(value)) return { ok: false as const, reason: `${envName} must be a JSON object.` } return { ok: true as const, value } } catch (error) { return { ok: false as const, reason: `${envName} must be valid JSON: ${error instanceof Error ? error.message : String(error)}`, } } } const findModel = async (config: StableModel, title: string) => { const ctx = await createApiContext() try { const response = await ctx.get( `/console/api/workspaces/current/models/model-types/${config.type}`, ) await expectApiResponseOK(response, `Check ${title}`) const body = (await response.json()) as AvailableModelListResponse const provider = body.data.find((item) => matchesProvider(item.provider, config.provider)) const model = provider?.models.find( (item) => item.model === config.name || item.label?.en_US === config.name || item.label?.zh_Hans === config.name, ) if (!provider || !model) return undefined return { name: model.model, provider: provider.provider, status: model.status, type: config.type, } } finally { await ctx.dispose() } } const resolveProvider = async (config: StableModel) => { const ctx = await createApiContext() try { const response = await ctx.get( `/console/api/workspaces/current/model-providers?${buildQuery({ model_type: config.type })}`, ) await expectApiResponseOK(response, `Resolve model provider ${config.provider}`) const body = (await response.json()) as ModelProviderListResponse const provider = body.data.find((item) => matchesProviderLabel(item, config.provider)) return { availableProviders: body.data.map((provider) => provider.provider), credential: provider?.custom_configuration.available_credentials?.find( (credential) => credential.credential_name === stableModelCredentialName, ), provider: provider?.provider, } } finally { await ctx.dispose() } } const selectCustomProviderCredential = async (provider: string, credentialId?: string) => { const ctx = await createApiContext() try { if (credentialId) { const switchResponse = await ctx.post( `/console/api/workspaces/current/model-providers/${provider}/credentials/switch`, { data: { credential_id: credentialId }, }, ) await expectApiResponseOK(switchResponse, `Switch model provider credential for ${provider}`) } const preferredResponse = await ctx.post( `/console/api/workspaces/current/model-providers/${provider}/preferred-provider-type`, { data: { preferred_provider_type: 'custom' }, }, ) await expectApiResponseOK( preferredResponse, `Select custom provider credential for ${provider}`, ) } finally { await ctx.dispose() } } const upsertStableProviderCredential = async ( provider: string, credentials: Record, credentialId?: string, ) => { const ctx = await createApiContext() try { if (credentialId) { const updateResponse = await ctx.put( `/console/api/workspaces/current/model-providers/${provider}/credentials`, { data: { credential_id: credentialId, credentials, name: stableModelCredentialName, }, }, ) await expectApiResponseOK(updateResponse, `Update model provider credential for ${provider}`) return } const createResponse = await ctx.post( `/console/api/workspaces/current/model-providers/${provider}/credentials`, { data: { credentials, name: stableModelCredentialName, }, }, ) await expectApiResponseOK(createResponse, `Create model provider credential for ${provider}`) } finally { await ctx.dispose() } } const seedModel = async ( context: SeedContext, { config, title, }: { config: StableModel title: string }, ) => { const existing = await findModel(config, title) const resource = { id: `${existing?.provider ?? config.provider}/${existing?.name ?? config.name}`, kind: 'model', name: title, } if (existing?.status === activeModelStatus) return verified(title, resource) if (context.dryRun) return skipped( title, `Would configure ${config.provider}/${config.name} using ${modelCredentialEnv}.`, ) const credentials = parseJsonEnv(modelCredentialEnv) if (!credentials.ok) return blocked(title, `${config.provider}/${config.name} is not active; ${credentials.reason}`) const { availableProviders, credential, provider } = await resolveProvider(config) if (!provider) { const available = availableProviders.length > 0 ? availableProviders.join(', ') : 'none' return blocked( title, `Provider ${config.provider} was not found in available model providers for ${config.type}. Available providers: ${available}.`, ) } try { await upsertStableProviderCredential(provider, credentials.value, credential?.credential_id) await selectCustomProviderCredential(provider, credential?.credential_id) } catch (error) { const message = error instanceof Error ? error.message : String(error) if (!message.includes(`Credential with name '${stableModelCredentialName}' already exists.`)) return blocked(title, message) const refreshed = await resolveProvider(config) if (!refreshed.provider || !refreshed.credential) { return blocked( title, `Credential ${stableModelCredentialName} already exists for ${provider}, but the seed could not resolve its credential id.`, ) } try { await upsertStableProviderCredential( refreshed.provider, credentials.value, refreshed.credential.credential_id, ) await selectCustomProviderCredential(refreshed.provider, refreshed.credential.credential_id) } catch (retryError) { return blocked(title, retryError instanceof Error ? retryError.message : String(retryError)) } } const seeded = await findModel(config, title) if (seeded?.status !== activeModelStatus) { return blocked( title, `${config.provider}/${config.name} is ${seeded?.status ?? 'missing'} after credential setup.`, ) } return updated(title, { id: `${seeded.provider}/${seeded.name}`, kind: 'model', name: title, }) } const seedStableModel = async (context: SeedContext) => seedModel(context, { config: stableModelConfig(), title: agentBuilderPreseededResources.stableChatModel, }) const seedAgentDecisionModel = async (context: SeedContext) => seedModel(context, { config: agentDecisionModelConfig(), title: agentBuilderPreseededResources.agentDecisionChatModel, }) const getDefaultModel = async (modelType: string) => { const ctx = await createApiContext() try { const response = await ctx.get( `/console/api/workspaces/current/default-model?${buildQuery({ model_type: modelType })}`, ) await expectApiResponseOK(response, `Get default ${modelType} model`) const body = (await response.json()) as DefaultModelDataResponse return body.data } finally { await ctx.dispose() } } const setDefaultModel = async (model: StableModel) => { const ctx = await createApiContext() try { const response = await ctx.post('/console/api/workspaces/current/default-model', { data: { model_settings: [ { model: model.name, model_type: model.type, provider: model.provider, }, ], }, }) await expectApiResponseOK(response, `Set default ${model.type} model`) } finally { await ctx.dispose() } } const seedSpeechToTextModel = async (context: SeedContext) => { const config = speechToTextModelConfig() const title = agentBuilderPreseededResources.speechToTextModel const modelResult = await seedModel(context, { config, title }) if (modelResult.status === 'blocked' || modelResult.status === 'skipped') return modelResult const model = await findModel(config, title) if (!model || model.status !== activeModelStatus) return blocked(title, `${config.provider}/${config.name} is not active after model setup.`) const resource = { id: `${model.provider}/${model.name}`, kind: 'model', name: title, } const defaultModel = await getDefaultModel(config.type) const isExpectedDefault = defaultModel?.model === model.name && matchesProvider(defaultModel.provider.provider, model.provider) if (isExpectedDefault) return modelResult.status === 'updated' ? modelResult : verified(title, resource) if (context.dryRun) return skipped( title, `Would set ${model.provider}/${model.name} as the workspace default Speech-to-Text model.`, ) await setDefaultModel({ name: model.name, provider: model.provider, type: config.type, }) const updatedDefaultModel = await getDefaultModel(config.type) if ( updatedDefaultModel?.model !== model.name || !matchesProvider(updatedDefaultModel.provider.provider, model.provider) ) { return blocked( title, `${model.provider}/${model.name} was not selected as the workspace default Speech-to-Text model.`, ) } return updated(title, resource) } type BuiltinToolProvider = { label?: { en_US?: string; zh_Hans?: string } name: string tools: Array<{ label?: { en_US?: string; zh_Hans?: string } name: string }> } const findBuiltinTool = async (displayName: string) => { const parsed = splitToolDisplayName(displayName) if (!parsed.ok) return { ok: false as const, reason: parsed.reason } const ctx = await createApiContext() try { const response = await ctx.get('/console/api/workspaces/current/tools/builtin') await expectApiResponseOK(response, `Check built-in tool ${displayName}`) const providers = (await response.json()) as BuiltinToolProvider[] const provider = providers.find((item) => matchesNameOrLabel(parsed.providerName, item.name, item.label), ) const tool = provider?.tools.find((item) => matchesNameOrLabel(parsed.toolName, item.name, item.label), ) if (!provider || !tool) return { ok: false as const, reason: `Built-in tool "${displayName}" was not found.` } return { ok: true as const, resource: { id: `${provider.name}/${tool.name}`, kind: 'tool', name: displayName, providerName: provider.name, toolName: tool.name, } satisfies ToolResource, } } finally { await ctx.dispose() } } const seedTool = (displayName: string): SeedTask => ({ id: `tool:${displayName}`, title: displayName, async run() { const result = await findBuiltinTool(displayName) if (!result.ok) return blocked(displayName, result.reason) return verified(displayName, result.resource) }, }) const uploadConsoleFile = async ( fileName: string, filePath: string, ): Promise => { const ctx = await createApiContext() try { const response = await ctx.post('/console/api/files/upload', { multipart: { file: { buffer: await readFile(filePath), mimeType: 'text/plain', name: fileName, }, }, }) await expectApiResponseOK(response, `Upload seed file ${fileName}`) return (await response.json()) as UploadedConsoleFile } finally { await ctx.dispose() } } const findDataset = (name: string) => { const query = buildQuery({ keyword: name, limit: '20', page: '1' }) return findConsoleResourceByName({ action: `Find seed dataset ${name}`, path: `/console/api/datasets?${query}`, resourceName: name, }) } const getDatasetDocuments = async (datasetId: string) => { const ctx = await createApiContext() try { const response = await ctx.get( `/console/api/datasets/${datasetId}/documents?${buildQuery({ limit: '100', page: '1' })}`, ) await expectApiResponseOK(response, `List dataset documents ${datasetId}`) const body = (await response.json()) as DocumentWithSegmentsListResponse return body.data } finally { await ctx.dispose() } } const requiredKnowledgeSegmentTokens = [ agentBuilderFixedInputs.customKnowledgeQuery, agentBuilderFixedInputs.knowledgeRuntimeQuery, agentBuilderExpectedTokens.knowledgeReply, ] const datasetHasKnowledgeSegment = async (datasetId: string) => { const documents = await getDatasetDocuments(datasetId) const ctx = await createApiContext() try { for (const document of documents) { const response = await ctx.get( `/console/api/datasets/${datasetId}/documents/${document.id}/segments?${buildQuery({ enabled: 'true', keyword: agentBuilderExpectedTokens.knowledgeReply, limit: '20', page: '1', })}`, ) await expectApiResponseOK( response, `Check dataset knowledge segment ${agentBuilderExpectedTokens.knowledgeReply}`, ) const body = (await response.json()) as ConsoleSegmentListResponse if ( body.data.some( (segment) => segment.enabled && requiredKnowledgeSegmentTokens.every((token) => segment.content.includes(token)), ) ) { return true } } return false } finally { await ctx.dispose() } } const waitForDatasetCompleted = async (datasetId: string) => { const deadline = Date.now() + 180_000 let status = 'missing' while (Date.now() < deadline) { const ctx = await createApiContext() try { const response = await ctx.get(`/console/api/datasets/${datasetId}/indexing-status`) await expectApiResponseOK(response, `Check dataset indexing ${datasetId}`) const body = (await response.json()) as DocumentStatusListResponse status = body.data.length < 1 ? 'missing' : body.data.every((item) => item.indexing_status === 'completed') ? 'completed' : body.data.map((item) => item.indexing_status ?? 'missing').join(',') if (status === 'completed') return { ok: true as const } } finally { await ctx.dispose() } await sleep(1_000) } return { ok: false as const, status } } const addKnowledgeDocument = async (datasetId: string) => { const uploadedFile = await uploadConsoleFile( agentBuilderTestMaterials.knowledgeSource, getAgentBuilderTestMaterialPath('knowledgeSource'), ) const body = { data_source: { info_list: { data_source_type: 'upload_file', file_info_list: { file_ids: [uploadedFile.id], }, }, }, doc_form: 'text_model', doc_language: 'English', indexing_technique: 'economy', process_rule: { mode: 'automatic', }, retrieval_model: { reranking_enable: false, score_threshold_enabled: false, search_method: 'keyword_search', top_k: 4, }, } satisfies KnowledgeConfig const ctx = await createApiContext() try { const response = await ctx.post(`/console/api/datasets/${datasetId}/documents`, { data: body }) await expectApiResponseOK(response, `Seed knowledge document ${datasetId}`) } finally { await ctx.dispose() } } const createDataset = async (name: string) => { const ctx = await createApiContext() try { const response = await ctx.post('/console/api/datasets', { data: { indexing_technique: 'economy', name, permission: 'only_me', provider: 'vendor', }, }) await expectApiResponseOK(response, `Create dataset ${name}`) return (await response.json()) as DatasetListItemResponse } finally { await ctx.dispose() } } const seedReadyKnowledge = async (context: SeedContext) => { const title = agentBuilderPreseededResources.agentKnowledgeBase let dataset = await findDataset(title) if (context.dryRun) { return dataset ? verified(title, { id: dataset.id, kind: 'dataset', name: title }) : skipped(title, `Would create dataset "${title}".`) } const wasCreated = !dataset dataset ??= await createDataset(title) const hasKnowledgeSegment = await datasetHasKnowledgeSegment(dataset.id) if (!hasKnowledgeSegment) await addKnowledgeDocument(dataset.id) const indexing = await waitForDatasetCompleted(dataset.id) if (!indexing.ok) { return blocked( title, `Dataset "${title}" indexing did not complete in seed timeout; last status: ${indexing.status}.`, ) } return datasetHasKnowledgeSegment(dataset.id).then((ready) => { if (!ready) { return blocked( title, `Dataset "${title}" does not expose an indexed segment containing ${requiredKnowledgeSegmentTokens.join(' and ')} after indexing.`, ) } const resource = { id: dataset.id, kind: 'dataset', name: title } return wasCreated ? created(title, resource) : verified(title, resource) }) } const ensureAgent = async (name: string) => { const query = buildQuery({ limit: '20', name, page: '1' }) const existing = await findConsoleResourceByName({ action: `Find seed Agent ${name}`, path: `/console/api/agent?${query}`, resourceName: name, }) if (existing) return { agent: existing, created: false } const agent = await createTestAgent({ description: 'Created by Dify E2E seed.', name, role: 'E2E seeded assistant', }) return { agent, created: true } } const getStableModelResource = (context: SeedContext): StableModel | undefined => { const resource = context.resources.get('stable-model') if (!resource?.id) return undefined const separatorIndex = resource.id.lastIndexOf('/') if (separatorIndex === -1) return undefined return { name: resource.id.slice(separatorIndex + 1), provider: resource.id.slice(0, separatorIndex), type: stableModelConfig().type, } } const getToolResource = (context: SeedContext, displayName: string) => context.resources.get(`tool:${displayName}`) as ToolResource | undefined const toolConfig = (tool: ToolResource) => ({ credential_type: 'unauthorized', enabled: true, provider_id: tool.providerName, provider_type: 'builtin', runtime_parameters: {}, tool_name: tool.toolName, }) satisfies AgentSoulDifyToolConfig const saveSeededAgentComposer = async ({ agentId, config, shouldPublish = false, }: { agentId: string config: AgentSoulConfig shouldPublish?: boolean }) => { await saveAgentComposerDraft(agentId, config) if (shouldPublish) await publishAgent(agentId, 'E2E seed') } const ensureDriveSkill = async (agentId: string) => { const skills = await getAgentDriveSkills(agentId) if (skills.some((skill) => skill.name === agentBuilderPreseededResources.summarySkill)) return await uploadAgentDriveSkill({ agentId, fileName: agentBuilderTestMaterials.summarySkill, filePath: getAgentBuilderTestMaterialPath('summarySkill'), }) } const seedFullConfigAgent = async (context: SeedContext) => { const title = agentBuilderPreseededResources.fullConfigAgent const model = getStableModelResource(context) const jsonTool = getToolResource(context, agentBuilderPreseededResources.jsonReplaceTool) const dataset = context.resources.get('ready-knowledge') if (!model) return blocked(title, `${agentBuilderPreseededResources.stableChatModel} is not ready.`) if (!jsonTool) return blocked(title, `${agentBuilderPreseededResources.jsonReplaceTool} is not ready.`) if (!dataset?.id) return blocked(title, `${agentBuilderPreseededResources.agentKnowledgeBase} is not ready.`) if (context.dryRun) return skipped(title, `Would create or update Agent "${title}".`) const { agent, created: wasCreated } = await ensureAgent(title) const agentId = agent.id const smallFile = await uploadAgentConfigFileToDraft({ agentId, fileName: agentBuilderTestMaterials.smallFile, filePath: getAgentBuilderTestMaterialPath('smallFile'), }) const specialFile = await uploadAgentConfigFileToDraft({ agentId, fileName: agentBuilderTestMaterials.specialFilename, filePath: getAgentBuilderTestMaterialPath('specialFilename'), }) const summarySkill = await uploadAgentConfigSkillToDraft({ agentId, fileName: agentBuilderTestMaterials.summarySkill, filePath: getAgentBuilderTestMaterialPath('summarySkill'), }) await ensureDriveSkill(agentId) await saveSeededAgentComposer({ agentId, config: createAgentSoulConfigWithKnowledgeDataset( createAgentSoulConfigWithModel( { ...normalAgentSoulConfig, config_files: [smallFile, specialFile], config_skills: [summarySkill], tools: { dify_tools: [toolConfig(jsonTool)], }, }, model, ), { id: dataset.id, name: dataset.name, } satisfies AgentKnowledgeDatasetConfig, ), }) const resource = { id: agentId, kind: 'agent', name: title } return wasCreated ? created(title, resource) : updated(title, resource) } const seedToolStatesAgent = async (context: SeedContext) => { const title = agentBuilderPreseededResources.toolStatesAgent const jsonTool = getToolResource(context, agentBuilderPreseededResources.jsonReplaceTool) const tavilyTool = getToolResource(context, agentBuilderPreseededResources.tavilySearchTool) if (!jsonTool) return blocked(title, `${agentBuilderPreseededResources.jsonReplaceTool} is not ready.`) if (!tavilyTool) return blocked(title, `${agentBuilderPreseededResources.tavilySearchTool} is not ready.`) if (context.dryRun) return skipped(title, `Would create or update Agent "${title}".`) const { agent, created: wasCreated } = await ensureAgent(title) const summarySkill = await uploadAgentConfigSkillToDraft({ agentId: agent.id, fileName: agentBuilderTestMaterials.summarySkill, filePath: getAgentBuilderTestMaterialPath('summarySkill'), }) await ensureDriveSkill(agent.id) await saveSeededAgentComposer({ agentId: agent.id, config: { ...normalAgentSoulConfig, config_skills: [summarySkill], tools: { dify_tools: [toolConfig(jsonTool), toolConfig(tavilyTool)], }, }, }) const resource = { id: agent.id, kind: 'agent', name: title } return wasCreated ? created(title, resource) : updated(title, resource) } const seedDualRetrievalAgent = async (context: SeedContext) => { const title = agentBuilderPreseededResources.dualRetrievalAgent const dataset = context.resources.get('ready-knowledge') if (!dataset?.id) return blocked(title, `${agentBuilderPreseededResources.agentKnowledgeBase} is not ready.`) if (context.dryRun) return skipped(title, `Would create or update Agent "${title}".`) const { agent, created: wasCreated } = await ensureAgent(title) const datasetConfig = { id: dataset.id, name: dataset.name } satisfies AgentKnowledgeDatasetConfig await saveSeededAgentComposer({ agentId: agent.id, config: { ...normalAgentSoulConfig, knowledge: { sets: [ { datasets: [datasetConfig], id: 'e2e-dual-retrieval-agent-decide', name: 'Retrieval 1', query: { mode: 'generated_query' }, retrieval: { mode: 'multiple', top_k: 4 }, }, { datasets: [datasetConfig], id: 'e2e-dual-retrieval-custom-query', name: 'Retrieval 2', query: { mode: 'user_query', value: agentBuilderFixedInputs.customKnowledgeQuery, }, retrieval: { mode: 'multiple', top_k: 4 }, }, ], }, }, }) const resource = { id: agent.id, kind: 'agent', name: title } return wasCreated ? created(title, resource) : updated(title, resource) } const findWorkflow = (name: string) => { const query = buildQuery({ limit: '20', mode: 'workflow', name, page: '1' }) return findConsoleResourceByName({ action: `Find seed workflow ${name}`, path: `/console/api/apps?${query}`, resourceName: name, }) } const seedWorkflowReference = async (context: SeedContext) => { const title = agentBuilderPreseededResources.workflowReferenceAgent const workflowName = agentBuilderPreseededResources.referenceWorkflow const model = getStableModelResource(context) if (!model) return blocked(title, `${agentBuilderPreseededResources.stableChatModel} is not ready.`) if (context.dryRun) return skipped(title, `Would create or update Agent "${title}" and workflow "${workflowName}".`) const { agent, created: wasAgentCreated } = await ensureAgent(title) await saveSeededAgentComposer({ agentId: agent.id, config: createAgentSoulConfigWithModel(normalAgentSoulConfig, model), shouldPublish: true, }) let workflow = await findWorkflow(workflowName) let wasWorkflowCreated = false if (!workflow) { workflow = await createTestApp(workflowName, 'workflow') wasWorkflowCreated = true } await syncAgentV2WorkflowDraft(workflow.id, agent.id) await publishWorkflowApp(workflow.id) const resource = { id: workflow.id, kind: 'workflow', name: workflowName } return wasAgentCreated || wasWorkflowCreated ? created(`${title} / ${workflowName}`, resource) : updated(`${title} / ${workflowName}`, resource) } const agentV2BaseSeedTasks = (): SeedTask[] => [ { id: 'marketplace-plugins', title: 'Agent V2 marketplace plugins', run: (context) => bootstrapMarketplacePlugins(context, { defaultPluginIds: agentV2MarketplacePluginIds, pluginIdsEnv: marketplacePluginIdsEnv, title: 'Agent V2 marketplace plugins', }), }, { id: 'stable-model', title: agentBuilderPreseededResources.stableChatModel, run: seedStableModel, }, { id: 'agent-decision-model', title: agentBuilderPreseededResources.agentDecisionChatModel, run: seedAgentDecisionModel, }, seedTool(agentBuilderPreseededResources.jsonReplaceTool), seedTool(agentBuilderPreseededResources.tavilySearchTool), { id: 'ready-knowledge', title: agentBuilderPreseededResources.agentKnowledgeBase, run: seedReadyKnowledge, }, ] const agentV2PreparedFixtureSeedTasks = (): SeedTask[] => [ { id: 'full-config-agent', title: agentBuilderPreseededResources.fullConfigAgent, run: seedFullConfigAgent, }, { id: 'tool-states-agent', title: agentBuilderPreseededResources.toolStatesAgent, run: seedToolStatesAgent, }, { id: 'dual-retrieval-agent', title: agentBuilderPreseededResources.dualRetrievalAgent, run: seedDualRetrievalAgent, }, { id: 'workflow-reference', title: `${agentBuilderPreseededResources.workflowReferenceAgent} / ${agentBuilderPreseededResources.referenceWorkflow}`, run: seedWorkflowReference, }, ] const agentV2PreparedSeedTasks = (): SeedTask[] => [ ...agentV2BaseSeedTasks(), ...agentV2PreparedFixtureSeedTasks(), ] const agentV2ExternalRuntimeSeedTasks = (): SeedTask[] => [ ...agentV2BaseSeedTasks(), { id: 'speech-to-text-model', title: agentBuilderPreseededResources.speechToTextModel, run: seedSpeechToTextModel, }, ] const agentV2PostMergeSeedTasks = (): SeedTask[] => [ ...agentV2ExternalRuntimeSeedTasks(), ...agentV2PreparedFixtureSeedTasks(), ] export const createAgentV2SeedTasks = (profile: string = 'post-merge'): SeedTask[] => { if (profile === 'post-merge') return agentV2PostMergeSeedTasks() if (profile === 'prepared') return agentV2PreparedSeedTasks() if (profile === 'external-runtime') return agentV2ExternalRuntimeSeedTasks() throw new Error(`Unknown Agent V2 seed profile "${profile}".`) }