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
43 changes: 43 additions & 0 deletions src/cli/commands/add/__tests__/add-agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,49 @@ describe('add agent command', () => {
const agent = projectSpec.runtimes.find((a: { name: string }) => a.name === agentName);
expect(agent, 'Agent should be in agentcore.json').toBeTruthy();
expect(agent.codeLocation.includes(codeDir), `codeLocation should reference ${codeDir}`).toBeTruthy();

// Existing user code must not be overwritten by the starter stub
const entrypoint = await readFile(join(projectDir, codeDir, 'main.py'), 'utf-8');
expect(entrypoint).toBe('# existing code\n');
});

it('vends a runtime-contract starter at the resolved --entrypoint for a fresh Python BYO agent', async () => {
const agentName = `ByoStub${Date.now()}`;
const codeDir = `stub-agent-${Date.now()}`;

const result = await runCLI(
[
'add',
'agent',
'--name',
agentName,
'--type',
'byo',
'--code-location',
codeDir,
'--entrypoint',
'agent.py',
'--language',
'Python',
'--framework',
'Strands',
'--model-provider',
'Bedrock',
'--json',
],
projectDir
);

expect(result.exitCode, `stdout: ${result.stdout}`).toBe(0);

// Honors --entrypoint: written to agent.py, not a hardcoded main.py
expect(await exists(join(projectDir, codeDir, 'main.py'))).toBe(false);
const entrypoint = await readFile(join(projectDir, codeDir, 'agent.py'), 'utf-8');
expect(entrypoint).toContain('from bedrock_agentcore.runtime import BedrockAgentCoreApp');
expect(entrypoint).toContain('app = BedrockAgentCoreApp()');
expect(entrypoint).toContain('@app.entrypoint');
expect(entrypoint).toContain('app.run()');
expect(entrypoint).not.toContain('def handler(event, context)');
});

it('requires code-location for BYO path', async () => {
Expand Down
22 changes: 20 additions & 2 deletions src/cli/primitives/AgentPrimitive.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,13 @@ import { requireTTY } from '../tui/guards/tty';
import type { GenerateConfig, MemoryOption } from '../tui/screens/generate/types';
import { BasePrimitive } from './BasePrimitive';
import { CredentialPrimitive } from './CredentialPrimitive';
import { BYO_PYTHON_ENTRYPOINT_STUB, BYO_TYPESCRIPT_ENTRYPOINT_STUB } from './constants';
import { buildAuthorizerConfigFromJwtConfig, createManagedOAuthCredential } from './auth-utils';
import { computeDefaultCredentialEnvVarName } from './credential-utils';
import type { AddResult, AddScreenComponent, RemovableResource } from './types';
import { DescribeSubnetsCommand, EC2Client } from '@aws-sdk/client-ec2';
import type { Command } from '@commander-js/extra-typings';
import { mkdirSync } from 'fs';
import { existsSync, mkdirSync, writeFileSync } from 'fs';
import { dirname, join } from 'path';

/**
Expand Down Expand Up @@ -671,6 +672,23 @@ export class AgentPrimitive extends BasePrimitive<AddAgentOptions, RemovableReso
const codeDir = join(projectRoot, codeLocation.replace(/\/$/, ''));
mkdirSync(codeDir, { recursive: true });

// Vend a starter entrypoint following the AgentCore runtime contract so `agentcore dev`
// works out of the box. Never overwrite existing user code; skip for non-Python/TS.
const entrypoint = options.entrypoint ?? 'main.py';
const stub =
options.language === 'Python'
? BYO_PYTHON_ENTRYPOINT_STUB
: options.language === 'TypeScript'
? BYO_TYPESCRIPT_ENTRYPOINT_STUB
: undefined;
// Entrypoint may carry a "file.py:handler" suffix and a subdirectory path.
const entrypointFile = entrypoint.split(':')[0]!;
const entrypointPath = join(codeDir, entrypointFile);
if (stub && !existsSync(entrypointPath)) {
mkdirSync(dirname(entrypointPath), { recursive: true });
writeFileSync(entrypointPath, stub);
}

const project = await configIO.readProjectSpec();

const protocol = options.protocol ?? 'HTTP';
Expand Down Expand Up @@ -711,7 +729,7 @@ export class AgentPrimitive extends BasePrimitive<AddAgentOptions, RemovableReso
const agent: AgentEnvSpec = {
name: options.name,
build: options.buildType,
entrypoint: (options.entrypoint ?? 'main.py') as FilePath,
entrypoint: entrypoint as FilePath,
codeLocation: codeLocation as DirectoryPath,
runtimeVersion: DEFAULT_PYTHON_VERSION,
protocol,
Expand Down
40 changes: 40 additions & 0 deletions src/cli/primitives/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,43 @@ export const PASSTHROUGH_PROTOCOL_TYPES = ['MCP', 'A2A', 'INFERENCE', 'CUSTOM']

/** Error shown when `--additional-params` is not parseable as a JSON object (lite_llm harness). */
export const ADDITIONAL_PARAMS_JSON_ERROR = '--additional-params must be a valid JSON object';

/**
* Starter entrypoint vended into a BYO Python agent's code directory so `agentcore dev`
* works out of the box. Follows the AgentCore runtime contract (BedrockAgentCoreApp /
* @app.entrypoint / app.run), not a Lambda-style handler.
*/
export const BYO_PYTHON_ENTRYPOINT_STUB = `from bedrock_agentcore.runtime import BedrockAgentCoreApp

app = BedrockAgentCoreApp()


@app.entrypoint
def invoke(payload, context):
"""Replace this with your agent logic."""
prompt = payload.get("prompt", "")
return {"result": f"Echo: {prompt}"}


if __name__ == "__main__":
app.run()
`;

/**
* Starter entrypoint vended into a BYO TypeScript agent's code directory. Follows the
* AgentCore runtime contract via the TypeScript SDK's BedrockAgentCoreApp.
*/
export const BYO_TYPESCRIPT_ENTRYPOINT_STUB = `import { BedrockAgentCoreApp } from 'bedrock-agentcore/runtime';

const app = new BedrockAgentCoreApp({
invocationHandler: {
// Replace this with your agent logic.
async process(payload: any, context: any) {
const prompt = payload?.prompt ?? '';
return { result: \`Echo: \${prompt}\` };
},
},
});

app.run({ port: parseInt(process.env.PORT ?? '8080') });
`;
29 changes: 19 additions & 10 deletions src/cli/tui/screens/add/AddFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,25 @@ function AgentAddedSummary({
</Text>
</Box>
)}
{!isCreate && (
<Box flexDirection="column" marginTop={1}>
<Text color="yellow">
Copy your agent code to <Text color="cyan">{config.codeLocation}</Text> before deploying.
</Text>
<Text dimColor>
Ensure <Text color="cyan">{config.entrypoint}</Text> is the entrypoint file in that folder.
</Text>
</Box>
)}
{!isCreate &&
(config.language === 'Python' || config.language === 'TypeScript' ? (
<Box flexDirection="column" marginTop={1}>
<Text dimColor>
Your agent entrypoint is <Text color="cyan">{config.entrypoint}</Text> in{' '}
<Text color="cyan">{config.codeLocation}</Text>. A starter was added if it did not already exist — edit it
with your agent logic.
</Text>
</Box>
) : (
<Box flexDirection="column" marginTop={1}>
<Text color="yellow">
Copy your agent code to <Text color="cyan">{config.codeLocation}</Text> before deploying.
</Text>
<Text dimColor>
Ensure <Text color="cyan">{config.entrypoint}</Text> is the entrypoint file in that folder.
</Text>
</Box>
))}
{isImport && config.bedrockAgentId && (
<Box marginTop={1}>
<Text dimColor>
Expand Down
11 changes: 9 additions & 2 deletions src/cli/tui/screens/create/CreateScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,15 @@ function buildExitMessage(

// BYO code location reminder
if (agentConfig?.agentType === 'byo') {
lines.push(`\x1b[33mCopy your agent code to \x1b[36m${agentConfig.codeLocation}\x1b[33m before deploying.\x1b[0m`);
lines.push(`\x1b[2mEnsure \x1b[36m${agentConfig.entrypoint}\x1b[2m is the entrypoint file in that folder.\x1b[0m`);
const vendedStub = agentConfig.language === 'Python' || agentConfig.language === 'TypeScript';
if (vendedStub) {
lines.push(
`\x1b[2mYour agent entrypoint is \x1b[36m${agentConfig.entrypoint}\x1b[2m in \x1b[36m${agentConfig.codeLocation}\x1b[2m. A starter was added if it didn't already exist — edit it with your agent logic.\x1b[0m`
);
} else {
lines.push(`\x1b[33mCopy your agent code to \x1b[36m${agentConfig.codeLocation}\x1b[33m before deploying.\x1b[0m`);
lines.push(`\x1b[2mEnsure \x1b[36m${agentConfig.entrypoint}\x1b[2m is the entrypoint file in that folder.\x1b[0m`);
}
lines.push('');
}

Expand Down
Loading