Skip to content

Commit bd9db33

Browse files
committed
Merge remote-tracking branch 'origin/main' into feature/container-support
2 parents 1a48d78 + 66f3f91 commit bd9db33

File tree

111 files changed

+10776
-692
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

111 files changed

+10776
-692
lines changed

.github/pull_request_template.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Closes #
2626

2727
How have you tested the change?
2828

29-
- [ ] I ran `npm run test:all`
29+
- [ ] I ran `npm run test:unit` and `npm run test:integ`
3030
- [ ] I ran `npm run typecheck`
3131
- [ ] I ran `npm run lint`
3232
- [ ] If I modified `src/assets/`, I ran `npm run test:update-snapshots` and committed the updated snapshots

.github/workflows/agent-restricted.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ jobs:
6767
- uses: actions/checkout@v6
6868

6969
- name: Run Strands Agent
70-
uses: ./
70+
uses: ./.github/actions/strands-action
7171
with:
7272
prompt: ${{ inputs.prompt }}
7373
provider: ${{ inputs.provider }}

.github/workflows/build-and-test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ jobs:
4040
- run: npm ci
4141
- run: npm run build --if-present
4242
- run: npm run test:unit
43+
- run: npm run test:integ
4344
- name: Upload coverage artifact
4445
if: matrix.node-version == '20.x'
4546
uses: actions/upload-artifact@v4

.github/workflows/codeql.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: CodeQL
2+
3+
on:
4+
push:
5+
branches: ['main']
6+
pull_request:
7+
branches: ['main']
8+
9+
# Cancel in-progress runs when a new commit is pushed
10+
concurrency:
11+
group: ${{ github.workflow }}-${{ github.ref }}
12+
cancel-in-progress: true
13+
14+
jobs:
15+
analyze:
16+
name: Analyze
17+
runs-on: ubuntu-latest
18+
timeout-minutes: 15
19+
permissions:
20+
security-events: write
21+
contents: read
22+
23+
steps:
24+
- uses: actions/checkout@v6
25+
26+
- name: Initialize CodeQL
27+
uses: github/codeql-action/init@v3
28+
with:
29+
languages: javascript-typescript
30+
31+
- name: Perform CodeQL Analysis
32+
uses: github/codeql-action/analyze@v3

.github/workflows/e2e-tests.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: E2E Tests
2+
on:
3+
workflow_dispatch:
4+
inputs:
5+
aws_region:
6+
description: 'AWS region for deployment'
7+
default: 'us-east-1'
8+
pull_request:
9+
branches: [main]
10+
11+
permissions:
12+
id-token: write # OIDC — lets GitHub assume an AWS IAM role via short-lived token (no stored keys)
13+
contents: read
14+
15+
jobs:
16+
e2e:
17+
runs-on: ubuntu-latest
18+
environment: e2e-testing
19+
timeout-minutes: 30
20+
steps:
21+
- uses: actions/checkout@v6
22+
- uses: actions/setup-node@v6
23+
with:
24+
node-version: '20.x'
25+
cache: 'npm'
26+
- name: Configure git
27+
run: |
28+
git config --global user.email "ci@amazon.com"
29+
git config --global user.name "CI"
30+
- uses: astral-sh/setup-uv@v5
31+
- name: Configure AWS credentials
32+
uses: aws-actions/configure-aws-credentials@v4
33+
with:
34+
role-to-assume: ${{ secrets.E2E_AWS_ROLE_ARN }}
35+
aws-region: ${{ inputs.aws_region || 'us-east-1' }}
36+
- name: Get AWS Account ID
37+
id: aws
38+
run: echo "account_id=$(aws sts get-caller-identity --query Account --output text)" >> "$GITHUB_OUTPUT"
39+
- run: npm ci
40+
- run: npm run build
41+
- name: Run E2E tests
42+
env:
43+
AWS_ACCOUNT_ID: ${{ steps.aws.outputs.account_id }}
44+
AWS_REGION: ${{ inputs.aws_region || 'us-east-1' }}
45+
run: npm run test:e2e

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ agentcore invoke
5656

5757
| Framework | Notes |
5858
| ------------------- | ----------------------------- |
59-
| Strands | AWS-native, streaming support |
59+
| Strands Agents | AWS-native, streaming support |
6060
| LangChain/LangGraph | Graph-based workflows |
6161
| Google ADK | Gemini models only |
6262
| OpenAI Agents | OpenAI models only |

docs/frameworks.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ for existing code.
77

88
| Framework | Supported Model Providers |
99
| ----------------------- | ---------------------------------- |
10-
| **Strands** | Bedrock, Anthropic, OpenAI, Gemini |
10+
| **Strands Agents** | Bedrock, Anthropic, OpenAI, Gemini |
1111
| **LangChain_LangGraph** | Bedrock, Anthropic, OpenAI, Gemini |
1212
| **GoogleADK** | Gemini only |
1313
| **OpenAIAgents** | OpenAI only |
1414

1515
## Framework Selection Guide
1616

17-
### Strands
17+
### Strands Agents
1818

1919
AWS's native agent framework designed for Amazon Bedrock.
2020

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { hasAwsCredentials, parseJsonOutput, prereqs, runCLI } from '../src/test-utils/index.js';
2+
import { execSync } from 'node:child_process';
3+
import { randomUUID } from 'node:crypto';
4+
import { mkdir, rm, writeFile } from 'node:fs/promises';
5+
import { tmpdir } from 'node:os';
6+
import { join } from 'node:path';
7+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
8+
9+
const hasAws = hasAwsCredentials();
10+
const canRun = prereqs.npm && prereqs.git && prereqs.uv && hasAws;
11+
12+
describe.sequential('e2e: create → deploy → invoke', () => {
13+
let testDir: string;
14+
let projectPath: string;
15+
let agentName: string;
16+
17+
beforeAll(async () => {
18+
if (!canRun) return;
19+
20+
testDir = join(tmpdir(), `agentcore-e2e-${randomUUID()}`);
21+
await mkdir(testDir, { recursive: true });
22+
23+
agentName = `E2e${Date.now()}`;
24+
const result = await runCLI(
25+
[
26+
'create',
27+
'--name',
28+
agentName,
29+
'--language',
30+
'Python',
31+
'--framework',
32+
'Strands',
33+
'--model-provider',
34+
'Bedrock',
35+
'--memory',
36+
'none',
37+
'--json',
38+
],
39+
testDir,
40+
false
41+
);
42+
43+
expect(result.exitCode, `Create failed: ${result.stderr}`).toBe(0);
44+
const json = parseJsonOutput(result.stdout) as { projectPath: string };
45+
projectPath = json.projectPath;
46+
47+
// TODO: Replace with `agentcore add target` once the CLI command is re-introduced
48+
const account =
49+
process.env.AWS_ACCOUNT_ID ||
50+
execSync('aws sts get-caller-identity --query Account --output text').toString().trim();
51+
const region = process.env.AWS_REGION || 'us-east-1';
52+
const awsTargetsPath = join(projectPath, 'agentcore', 'aws-targets.json');
53+
await writeFile(awsTargetsPath, JSON.stringify([{ name: 'default', account, region }]));
54+
}, 120000);
55+
56+
afterAll(async () => {
57+
if (projectPath && hasAws) {
58+
await runCLI(['remove', 'all', '--json'], projectPath, false);
59+
const result = await runCLI(['deploy', '--yes', '--json'], projectPath, false);
60+
61+
if (result.exitCode !== 0) {
62+
console.log('Teardown stdout:', result.stdout);
63+
console.log('Teardown stderr:', result.stderr);
64+
}
65+
}
66+
if (testDir) await rm(testDir, { recursive: true, force: true });
67+
}, 600000);
68+
69+
it.skipIf(!canRun)(
70+
'deploys to AWS successfully',
71+
async () => {
72+
expect(projectPath, 'Project should have been created').toBeTruthy();
73+
74+
const result = await runCLI(['deploy', '--yes', '--json'], projectPath, false);
75+
76+
if (result.exitCode !== 0) {
77+
console.log('Deploy stdout:', result.stdout);
78+
console.log('Deploy stderr:', result.stderr);
79+
}
80+
81+
expect(result.exitCode, `Deploy failed: ${result.stderr}`).toBe(0);
82+
83+
const json = parseJsonOutput(result.stdout) as { success: boolean };
84+
expect(json.success, 'Deploy should report success').toBe(true);
85+
},
86+
300000
87+
);
88+
89+
it.skipIf(!canRun)(
90+
'invokes the deployed agent',
91+
async () => {
92+
expect(projectPath, 'Project should have been created').toBeTruthy();
93+
94+
const result = await runCLI(
95+
['invoke', '--prompt', 'Say hello', '--agent', agentName, '--json'],
96+
projectPath,
97+
false
98+
);
99+
100+
if (result.exitCode !== 0) {
101+
console.log('Invoke stdout:', result.stdout);
102+
console.log('Invoke stderr:', result.stderr);
103+
}
104+
105+
expect(result.exitCode, `Invoke failed: ${result.stderr}`).toBe(0);
106+
107+
const json = parseJsonOutput(result.stdout) as { success: boolean };
108+
expect(json.success, 'Invoke should report success').toBe(true);
109+
},
110+
120000
111+
);
112+
});
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { createTestProject, readProjectConfig, runCLI } from '../src/test-utils/index.js';
2+
import type { TestProject } from '../src/test-utils/index.js';
3+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
4+
5+
describe('integration: add and remove resources', () => {
6+
let project: TestProject;
7+
8+
beforeAll(async () => {
9+
project = await createTestProject({
10+
language: 'Python',
11+
framework: 'Strands',
12+
modelProvider: 'Bedrock',
13+
memory: 'none',
14+
});
15+
});
16+
17+
afterAll(async () => {
18+
await project.cleanup();
19+
});
20+
21+
describe('memory lifecycle', () => {
22+
const memoryName = `IntegMem${Date.now().toString().slice(-6)}`;
23+
24+
it('adds a memory resource', async () => {
25+
const result = await runCLI(['add', 'memory', '--name', memoryName, '--json'], project.projectPath);
26+
27+
expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0);
28+
const json = JSON.parse(result.stdout);
29+
expect(json.success).toBe(true);
30+
31+
// Verify config updated
32+
const config = await readProjectConfig(project.projectPath);
33+
const memories = config.memories as Record<string, unknown>[] | undefined;
34+
expect(memories, 'memories should exist').toBeDefined();
35+
const found = memories!.some((m: Record<string, unknown>) => m.name === memoryName);
36+
expect(found, `Memory "${memoryName}" should be in config`).toBe(true);
37+
});
38+
39+
it('removes the memory resource', async () => {
40+
const result = await runCLI(['remove', 'memory', '--name', memoryName, '--json'], project.projectPath);
41+
42+
expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0);
43+
const json = JSON.parse(result.stdout);
44+
expect(json.success).toBe(true);
45+
46+
// Verify config updated
47+
const config = await readProjectConfig(project.projectPath);
48+
const memories = (config.memories as Record<string, unknown>[] | undefined) ?? [];
49+
const found = memories.some((m: Record<string, unknown>) => m.name === memoryName);
50+
expect(found, `Memory "${memoryName}" should be removed from config`).toBe(false);
51+
});
52+
});
53+
54+
describe('identity lifecycle', () => {
55+
const identityName = `IntegId${Date.now().toString().slice(-6)}`;
56+
57+
it('adds an identity resource', async () => {
58+
const result = await runCLI(
59+
['add', 'identity', '--name', identityName, '--api-key', 'test-key-integ-123', '--json'],
60+
project.projectPath
61+
);
62+
63+
expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0);
64+
const json = JSON.parse(result.stdout);
65+
expect(json.success).toBe(true);
66+
67+
// Verify config updated
68+
const config = await readProjectConfig(project.projectPath);
69+
const credentials = config.credentials as Record<string, unknown>[] | undefined;
70+
expect(credentials, 'credentials should exist').toBeDefined();
71+
const found = credentials!.some((c: Record<string, unknown>) => c.name === identityName);
72+
expect(found, `Identity "${identityName}" should be in config`).toBe(true);
73+
});
74+
75+
it('removes the identity resource', async () => {
76+
const result = await runCLI(['remove', 'identity', '--name', identityName, '--json'], project.projectPath);
77+
78+
expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0);
79+
const json = JSON.parse(result.stdout);
80+
expect(json.success).toBe(true);
81+
82+
// Verify config updated
83+
const config = await readProjectConfig(project.projectPath);
84+
const credentials = (config.credentials as Record<string, unknown>[] | undefined) ?? [];
85+
const found = credentials.some((c: Record<string, unknown>) => c.name === identityName);
86+
expect(found, `Identity "${identityName}" should be removed from config`).toBe(false);
87+
});
88+
});
89+
});

0 commit comments

Comments
 (0)