Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 60 additions & 37 deletions agents-cli/src/codegen/plan-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,52 +171,65 @@ ${fileNames}

CRITICAL RULES:

1. TOOL TYPES - VERY IMPORTANT:
1. AGENT AND SUBAGENT STRUCTURE - MOST IMPORTANT:
- **Top-level Agents** (entityType: "agent"): Create ONE file per top-level agent in agents/ directory
- **SubAgents** (entityType: "subAgent"): NEVER create separate files - ALWAYS include as entities in their parent agent's file
- Each agent file contains:
* All subAgents that belong to that agent (as entities in the same file)
* The top-level agent itself (as an entity)
- EXAMPLE: If "weather-agent" has subAgents "forecast-agent" and "geocode-agent", create ONE file "agents/weather-agent.ts" with THREE entities:
* Entity 1: forecast-agent (entityType: "subAgent")
* Entity 2: geocode-agent (entityType: "subAgent")
* Entity 3: weather-agent (entityType: "agent")
- WRONG: Creating separate files "agents/forecast-agent.ts" and "agents/geocode-agent.ts"
- The filename is based on the top-level agent's ID, NOT the subAgent IDs

2. TOOL TYPES - VERY IMPORTANT:
- **Function Tools** (type: "function"): ALWAYS define INLINE within agent files using "inlineContent" array
- **MCP Tools** (type: "mcp"): Create separate files in tools/ directory
- VALID FILE TYPES: Only use these exact types: "agent", "tool", "dataComponent", "artifactComponent", "statusComponent", "environment", "index"
- NEVER create file type "functionTool" - function tools go in "inlineContent" of agent files

2. STATUS COMPONENTS - VERY IMPORTANT:
3. STATUS COMPONENTS - VERY IMPORTANT:
- **Status Components**: ALWAYS create separate files in status-components/ directory
- Status components are found in agent.statusUpdates.statusComponents array
- Each status component should get its own file
- Agents must import status components from status-components/ directory
- Status components are NEVER inlined in agent files

3. ENVIRONMENT FILES - VERY IMPORTANT:
4. ENVIRONMENT FILES - VERY IMPORTANT:
- **When credential references exist**: Create environment files in environments/ directory
- **Environment Structure**: Create ONLY "${targetEnvironment}.env.ts" file for target environment (credentials are embedded in this file)
- **Environment Index**: Create "environments/index.ts" that imports environment files and exports envSettings using createEnvironmentSettings
- **NO separate credential files**: Credentials are defined INSIDE the environment files, not as separate files
- **Environment entities**: Use type "environment" for both environment files and index file

4. File Structure:
5. File Structure:
- If patterns show "toolsLocation": "inline", ALL tools should be in "inlineContent" of agent files
- If patterns show "toolsLocation": "separate", MCP tools get separate files, function tools still inline
- Follow the detected file naming convention (kebab-case, camelCase, or snake_case)

4. Variable Names:
6. Variable Names:
- MUST use the exact variable names from the mappings above
- If ID "weather" is used by both agent and subAgent, they will have different variable names
- Do NOT generate new variable names - use what's provided

5. File Placement:
- agents/ directory: Agent files (with function tools in "inlineContent")
7. File Placement:
- agents/ directory: Agent files (with subAgents and function tools inline)
- tools/ directory: MCP tool files only
- data-components/ directory: Data component files
- artifact-components/ directory: Artifact component files
- status-components/ directory: Status component files
- environments/ directory: Environment/credential files
- index.ts: Main project file

6. File Paths (CRITICAL):
8. File Paths (CRITICAL):
- Paths MUST be relative to the project root directory
- DO NOT include the project name in the path
- CORRECT: "agents/weather-agent.ts", "tools/inkeep-facts.ts", "status-components/tool-summary.ts"
- WRONG: "my-project/agents/weather-agent.ts", "project-name/tools/inkeep-facts.ts"

7. Dependencies:
9. Dependencies:
- Each file should list which variables it needs to import from other files
- Imports should use relative paths
- Respect detected import style (named vs default)
Expand All @@ -225,47 +238,52 @@ OUTPUT FORMAT (JSON):
{
"files": [
{
"path": "agents/weather-agent.ts",
"path": "agents/weather-basic.ts",
"type": "agent",
"entities": [
{
"id": "weather",
"variableName": "weatherSubAgent",
"id": "get-coordinates-agent",
"variableName": "getCoordinatesAgent",
"entityType": "subAgent",
"exportName": "weatherSubAgent"
"exportName": "getCoordinatesAgent"
},
{
"id": "weather",
"variableName": "weatherAgent",
"id": "weather-forecaster",
"variableName": "weatherForecaster",
"entityType": "subAgent",
"exportName": "weatherForecaster"
},
{
"id": "weather-assistant",
"variableName": "weatherAssistant",
"entityType": "subAgent",
"exportName": "weatherAssistant"
},
{
"id": "weather-basic",
"variableName": "weatherBasic",
"entityType": "agent",
"exportName": "weatherAgent"
"exportName": "weatherBasic"
}
],
"dependencies": [
{
"variableName": "weatherApi",
"fromPath": "../tools/weather-api",
"variableName": "weatherMcp",
"fromPath": "../tools/weather-mcp",
"entityType": "tool"
}
],
"inlineContent": [
{
"id": "get-forecast",
"variableName": "getForecast",
"entityType": "tool",
"exportName": "getForecast"
}
]
"inlineContent": []
},
{
"path": "tools/weather-api.ts",
"path": "tools/weather-mcp.ts",
"type": "tool",
"entities": [
{
"id": "weather-api",
"variableName": "weatherApi",
"id": "weather-mcp",
"variableName": "weatherMcp",
"entityType": "tool",
"exportName": "weatherApi"
"exportName": "weatherMcp"
}
],
"dependencies": [],
Expand Down Expand Up @@ -324,22 +342,27 @@ OUTPUT FORMAT (JSON):
"type": "index",
"entities": [
{
"id": "my-weather-project",
"variableName": "myWeatherProject",
"id": "weather-project",
"variableName": "weatherProject",
"entityType": "project",
"exportName": "myWeatherProject"
"exportName": "weatherProject"
}
],
"dependencies": [
{
"variableName": "weatherAgent",
"fromPath": "./agents/weather-agent",
"variableName": "weatherBasic",
"fromPath": "./agents/weather-basic",
"entityType": "agent"
},
{
"variableName": "weatherApi",
"fromPath": "./tools/weather-api",
"variableName": "weatherMcp",
"fromPath": "./tools/weather-mcp",
"entityType": "tool"
},
{
"variableName": "temperatureData",
"fromPath": "./data-components/temperature-data",
"entityType": "dataComponent"
}
]
}
Expand Down
163 changes: 163 additions & 0 deletions agents-cli/src/codegen/plan-to-specs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { join } from 'node:path';
import type { FullProjectDefinition } from '@inkeep/agents-core';
import type { FileSpec } from '../commands/pull.batch-generator-with-tools';
import type { GenerationPlan } from './types';

/**
* Build file specs from a generation plan
* Converts the plan-based structure to the file spec structure needed by batch generator
*/
export async function buildFileSpecsFromPlan(
plan: GenerationPlan,
projectData: FullProjectDefinition,
dirs: {
projectRoot: string;
agentsDir: string;
toolsDir: string;
dataComponentsDir: string;
artifactComponentsDir: string;
statusComponentsDir: string;
environmentsDir: string;
}
): Promise<FileSpec[]> {
const fileSpecs: FileSpec[] = [];

// Build filename and variable name mappings from the plan's flat file array
const toolFilenames = new Map<string, string>();
const componentFilenames = new Map<string, string>();
const toolVariableNames = new Map<string, string>();
const componentVariableNames = new Map<string, string>();

// Collect filenames and variable names from plan files
for (const fileInfo of plan.files) {
for (const entity of fileInfo.entities) {
const fileName = fileInfo.path.split('/').pop()?.replace('.ts', '') || '';
const variableName = entity.variableName || entity.exportName || entity.id;

if (entity.entityType === 'tool') {
toolFilenames.set(entity.id, fileName);
toolVariableNames.set(entity.id, variableName);
} else if (
entity.entityType === 'dataComponent' ||
entity.entityType === 'artifactComponent' ||
entity.entityType === 'statusComponent'
) {
componentFilenames.set(entity.id, fileName);
componentVariableNames.set(entity.id, variableName);
}
}
}

// Process each file in the plan
for (const fileInfo of plan.files) {
const fullPath = join(dirs.projectRoot, fileInfo.path);

// Handle different file types
if (fileInfo.type === 'index') {
fileSpecs.push({
type: 'index',
id: projectData.id,
data: projectData,
outputPath: fullPath,
toolFilenames,
componentFilenames,
toolVariableNames,
componentVariableNames,
});
} else if (fileInfo.type === 'agent') {
// Find the agent entity in this file
const agentEntity = fileInfo.entities.find((e) => e.entityType === 'agent');
if (agentEntity) {
const agentData = projectData.agents?.[agentEntity.id];
if (agentData) {
fileSpecs.push({
type: 'agent',
id: agentEntity.id,
data: agentData,
outputPath: fullPath,
toolFilenames,
componentFilenames,
toolVariableNames,
componentVariableNames,
});
}
}
} else if (fileInfo.type === 'tool') {
// Find the tool entity in this file
const toolEntity = fileInfo.entities.find((e) => e.entityType === 'tool');
if (toolEntity) {
const toolData = projectData.tools?.[toolEntity.id];
if (toolData) {
const variableName = toolEntity.variableName || toolEntity.exportName || toolEntity.id;
fileSpecs.push({
type: 'tool',
id: toolEntity.id,
data: toolData,
outputPath: fullPath,
variableName,
});
}
}
} else if (fileInfo.type === 'dataComponent') {
// Find the data component entity in this file
const componentEntity = fileInfo.entities.find((e) => e.entityType === 'dataComponent');
if (componentEntity) {
const componentData = projectData.dataComponents?.[componentEntity.id];
if (componentData) {
const variableName = componentEntity.variableName || componentEntity.exportName || componentEntity.id;
fileSpecs.push({
type: 'data_component',
id: componentEntity.id,
data: componentData,
outputPath: fullPath,
variableName,
});
}
}
} else if (fileInfo.type === 'artifactComponent') {
// Find the artifact component entity in this file
const componentEntity = fileInfo.entities.find((e) => e.entityType === 'artifactComponent');
if (componentEntity) {
const componentData = projectData.artifactComponents?.[componentEntity.id];
if (componentData) {
const variableName = componentEntity.variableName || componentEntity.exportName || componentEntity.id;
fileSpecs.push({
type: 'artifact_component',
id: componentEntity.id,
data: componentData,
outputPath: fullPath,
variableName,
});
}
}
} else if (fileInfo.type === 'statusComponent') {
// Find the status component entity in this file
const componentEntity = fileInfo.entities.find((e) => e.entityType === 'statusComponent');
if (componentEntity) {
// Status components come from agent definitions
// Find the component data from the agent's statusUpdates
for (const agent of Object.values(projectData.agents || {})) {
const agentObj = agent as any;
const statusComponents = agentObj.statusUpdates?.statusComponents || [];

for (const statusComponent of statusComponents) {
if (statusComponent.type === componentEntity.id) {
const variableName = componentEntity.variableName || componentEntity.exportName || componentEntity.id;
fileSpecs.push({
type: 'status_component',
id: componentEntity.id,
data: statusComponent,
outputPath: fullPath,
variableName,
});
break;
}
}
}
}
}
// Skip environment files - they're handled by generateEnvironmentFiles
}

return fileSpecs;
}
Loading
Loading