@aws-blocks/bb-agent 0.3.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DESIGN.md +65 -17
- package/README.md +73 -6
- package/dist/agent.aws.d.ts +15 -1
- package/dist/agent.aws.d.ts.map +1 -1
- package/dist/agent.aws.js +49 -0
- package/dist/agent.d.ts +82 -14
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +238 -57
- package/dist/agentcore-bundle.d.ts +12 -0
- package/dist/agentcore-bundle.d.ts.map +1 -0
- package/dist/agentcore-bundle.js +150 -0
- package/dist/agentcore-bundle.test.d.ts +2 -0
- package/dist/agentcore-bundle.test.d.ts.map +1 -0
- package/dist/agentcore-bundle.test.js +46 -0
- package/dist/agentcore-entry.d.ts +21 -0
- package/dist/agentcore-entry.d.ts.map +1 -0
- package/dist/agentcore-entry.js +120 -0
- package/dist/agentcore-runtime.cdk.d.ts +27 -0
- package/dist/agentcore-runtime.cdk.d.ts.map +1 -0
- package/dist/agentcore-runtime.cdk.js +168 -0
- package/dist/index.aws.d.ts +1 -0
- package/dist/index.aws.d.ts.map +1 -1
- package/dist/index.cdk.d.ts +10 -4
- package/dist/index.cdk.d.ts.map +1 -1
- package/dist/index.cdk.js +27 -28
- package/dist/index.cdk.test.js +128 -51
- package/dist/index.mock.d.ts +1 -0
- package/dist/index.mock.d.ts.map +1 -1
- package/dist/index.test.js +404 -1
- package/dist/model-factory.d.ts +2 -2
- package/dist/model-factory.d.ts.map +1 -1
- package/dist/model-factory.js +2 -2
- package/dist/providers/canned.d.ts +8 -1
- package/dist/providers/canned.d.ts.map +1 -1
- package/dist/providers/canned.js +127 -42
- package/dist/types.d.ts +63 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +16 -9
- package/src/agent.aws.ts +58 -1
- package/src/agent.ts +269 -56
- package/src/agentcore-bundle.test.ts +52 -0
- package/src/agentcore-bundle.ts +162 -0
- package/src/agentcore-entry.ts +134 -0
- package/src/agentcore-runtime.cdk.ts +203 -0
- package/src/index.aws.ts +3 -0
- package/src/index.cdk.test.ts +145 -53
- package/src/index.cdk.ts +29 -31
- package/src/index.mock.ts +3 -0
- package/src/index.test.ts +449 -1
- package/src/model-factory.ts +3 -3
- package/src/providers/canned.ts +131 -36
- package/src/types.ts +64 -1
- package/src/version.ts +1 -1
- package/dist/job-event-source.d.ts +0 -19
- package/dist/job-event-source.d.ts.map +0 -1
- package/dist/job-event-source.js +0 -20
- package/src/job-event-source.ts +0 -21
package/dist/index.cdk.test.js
CHANGED
|
@@ -1,66 +1,143 @@
|
|
|
1
1
|
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
/**
|
|
4
|
-
* CDK-side
|
|
4
|
+
* CDK-side tests for the Agent BB.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* Must run under `--conditions=cdk`; otherwise the internal BBs resolve to
|
|
15
|
-
* their mock implementations and no CloudFormation resources are produced.
|
|
6
|
+
* Pin that the Agent provisions an AgentCore Runtime for the streaming loop that runs AS the shared
|
|
7
|
+
* Blocks execution role (the same principal as the handler) — not a bespoke per-runtime role. That
|
|
8
|
+
* shared role already carries everything the loop touches (Realtime publish via the handler wiring,
|
|
9
|
+
* Bedrock, the conversation/message tables, the session bucket); the Agent adds only the
|
|
10
|
+
* AgentCore-specific bits — the scoped `bedrock-agentcore` assume-role trust, Bedrock model access,
|
|
11
|
+
* and the shared handler's permission to INVOKE the runtime — and injects the config location so the
|
|
12
|
+
* container loads the same app config as the handler.
|
|
16
13
|
*/
|
|
17
|
-
import { test,
|
|
14
|
+
import { test, before, after } from 'node:test';
|
|
18
15
|
import assert from 'node:assert';
|
|
16
|
+
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
17
|
+
import { dirname, join } from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
19
|
import * as cdk from 'aws-cdk-lib';
|
|
20
20
|
import { Template } from 'aws-cdk-lib/assertions';
|
|
21
|
-
import {
|
|
21
|
+
import { BlocksStack, BlocksPresets } from '@aws-blocks/core/cdk';
|
|
22
|
+
import { LambdaCompute } from '@aws-blocks/bb-lambda-compute/cdk';
|
|
22
23
|
import { Agent } from './index.cdk.js';
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Inject LambdaCompute as the stack's default compute, the same way
|
|
26
|
+
* `@aws-blocks/blocks` does for real apps. `main` (PR #459) made the Agent
|
|
27
|
+
* resolve the stack's `defaultCompute` at construction (the Realtime/AgentCore
|
|
28
|
+
* wiring reads it), so a bare handler-only stub stack (no default compute) can
|
|
29
|
+
* no longer synthesize it — `BlocksStack.create` initializes the default
|
|
30
|
+
* compute the getter resolves to. (`root as never` is core's existing plumbing
|
|
31
|
+
* pattern for the factory signature.)
|
|
32
|
+
*/
|
|
33
|
+
const lambdaFactory = (root) => new LambdaCompute(root, 'DefaultCompute');
|
|
34
|
+
/** A real directory to hand fromCodeAsset (any existing dir works for a synth-only test). */
|
|
35
|
+
const ASSET_DIR = dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
let tmpDir;
|
|
37
|
+
let handlerPath;
|
|
38
|
+
let backendPath;
|
|
39
|
+
before(() => {
|
|
40
|
+
// Building Blocks resolve to their mock entry points unless `--conditions=cdk`
|
|
41
|
+
// is active; keep it set so `BlocksStack.create` produces real CloudFormation.
|
|
42
|
+
process.env.NODE_OPTIONS = `${process.env.NODE_OPTIONS ?? ''} --conditions=cdk`;
|
|
43
|
+
// `BlocksStack.create` needs a real backend handler + backend module on disk
|
|
44
|
+
// (it imports the backend module and points the handler compute at the file).
|
|
45
|
+
tmpDir = mkdtempSync(join(ASSET_DIR, 'tmp-agent-cdk-'));
|
|
46
|
+
handlerPath = join(tmpDir, 'handler.mjs');
|
|
47
|
+
writeFileSync(handlerPath, "export const handler = async () => ({ statusCode: 200, body: '{}' });\n");
|
|
48
|
+
backendPath = join(tmpDir, 'backend.mjs');
|
|
49
|
+
writeFileSync(backendPath, 'export default () => {};\n');
|
|
47
50
|
});
|
|
48
|
-
|
|
51
|
+
after(() => {
|
|
52
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
53
|
+
});
|
|
54
|
+
async function synth() {
|
|
49
55
|
const app = new cdk.App();
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
// Real BlocksStack (with the shared executionRole/BlocksRole + a LambdaCompute default compute)
|
|
57
|
+
// so the Agent can resolve its default compute. `id` === 'teststack' so BLOCKS_STACK_NAME (derived
|
|
58
|
+
// from the owning root id) resolves to 'teststack', which the injection assertion below matches.
|
|
59
|
+
const stack = await BlocksStack.create(app, 'teststack', {
|
|
60
|
+
backendHandlerPath: handlerPath,
|
|
61
|
+
backendCDKPath: backendPath,
|
|
62
|
+
defaults: BlocksPresets.production,
|
|
63
|
+
defaultComputeFactory: lambdaFactory,
|
|
64
|
+
});
|
|
65
|
+
// agentcoreAssetPath bypasses the synth-time co-bundle (which needs a BlocksStack backend path).
|
|
66
|
+
new Agent(stack, 'agent', { systemPrompt: 'You are a test agent.', agentcoreAssetPath: ASSET_DIR });
|
|
67
|
+
return Template.fromStack(stack);
|
|
57
68
|
}
|
|
58
|
-
test('CDK:
|
|
59
|
-
|
|
69
|
+
test('CDK: Agent provisions an AgentCore Runtime for the loop', async () => {
|
|
70
|
+
const template = await synth();
|
|
71
|
+
assert.ok(Object.keys(template.findResources('AWS::BedrockAgentCore::Runtime')).length >= 1, 'expected an AWS::BedrockAgentCore::Runtime resource');
|
|
72
|
+
});
|
|
73
|
+
test('CDK: the loop runs as the shared execution role, which carries everything it touches', async () => {
|
|
74
|
+
const template = await synth();
|
|
75
|
+
const json = JSON.stringify(template.toJSON());
|
|
76
|
+
// Realtime publish (both halves) — load-bearing for streaming from the container. Granted to the
|
|
77
|
+
// shared role by the Realtime BB's handler wiring (not by the Agent), and inherited because the
|
|
78
|
+
// loop runs AS that role.
|
|
79
|
+
assert.ok(json.includes('execute-api:ManageConnections'), 'Realtime postToConnection');
|
|
80
|
+
assert.ok(json.includes('dynamodb:Query'), 'connections-table + history query');
|
|
81
|
+
// Model + storage the loop uses (Bedrock granted here; S3/DynamoDB via the child BBs).
|
|
82
|
+
assert.ok(json.includes('bedrock:InvokeModel'), 'Bedrock invoke');
|
|
83
|
+
assert.ok(json.includes('s3:GetObject'), 'session-bucket access (via FileBucket)');
|
|
84
|
+
// The RPC handler (also the shared role) can start the loop.
|
|
85
|
+
assert.ok(json.includes('bedrock-agentcore:InvokeAgentRuntime'), 'InvokeAgentRuntime');
|
|
86
|
+
// The runtime runs AS the shared BlocksRole — not a bespoke per-runtime role. Its RoleArn
|
|
87
|
+
// should reference BlocksRole, and there should be no `RuntimeRole` construct anywhere.
|
|
88
|
+
const runtime = Object.values(template.findResources('AWS::BedrockAgentCore::Runtime'))[0];
|
|
89
|
+
assert.ok(JSON.stringify(runtime.Properties ?? {}).includes('BlocksRole'), 'runtime executionRole should be the shared BlocksRole');
|
|
90
|
+
assert.ok(!json.includes('RuntimeRole'), 'no bespoke per-runtime role should be created');
|
|
91
|
+
// The container is given the config location so loadConfigToProcessEnv() loads the same full app
|
|
92
|
+
// config as the handler (delivers BLOCKS_RT_CALLBACK_URL + any config-backed BB values a tool needs).
|
|
93
|
+
const runtimeJson = JSON.stringify(runtime.Properties ?? {});
|
|
94
|
+
assert.ok(runtimeJson.includes('BLOCKS_CONFIG_BUCKET'), 'runtime must be injected BLOCKS_CONFIG_BUCKET');
|
|
95
|
+
assert.ok(runtimeJson.includes('BLOCKS_CONFIG_KEY'), 'runtime must be injected BLOCKS_CONFIG_KEY');
|
|
96
|
+
// BLOCKS_STACK_NAME must be the owning root id (`backendStackName`) — the SAME value the handler
|
|
97
|
+
// uses to derive resource names — so the container's namespace matches what CDK provisioned. Here
|
|
98
|
+
// the owning BlocksStack's id is 'teststack', so backendStackName resolves to 'teststack'.
|
|
99
|
+
assert.ok(runtimeJson.includes('BLOCKS_STACK_NAME'), 'runtime must be injected BLOCKS_STACK_NAME');
|
|
100
|
+
assert.ok(runtimeJson.includes('teststack'), 'BLOCKS_STACK_NAME resolves to the owning root id');
|
|
60
101
|
});
|
|
61
|
-
test('CDK: the Agent
|
|
62
|
-
|
|
102
|
+
test('CDK: the Agent adds the bedrock-agentcore assume-role trust to the shared role (scoped)', async () => {
|
|
103
|
+
// Core is BB-agnostic — it does not trust bedrock-agentcore. The Agent adds that trust here, so
|
|
104
|
+
// the AgentCore Runtime can assume the shared role it runs AS. It must be scoped by
|
|
105
|
+
// aws:SourceAccount (AWS's recommended AgentCore trust policy), and lambda trust must remain.
|
|
106
|
+
const template = await synth();
|
|
107
|
+
const roles = template.findResources('AWS::IAM::Role');
|
|
108
|
+
const blocksRoleId = Object.keys(roles).find(k => k.includes('BlocksRole'));
|
|
109
|
+
assert.ok(blocksRoleId, 'expected the shared BlocksRole');
|
|
110
|
+
const statements = roles[blocksRoleId].Properties.AssumeRolePolicyDocument.Statement;
|
|
111
|
+
const servicesOf = (s) => {
|
|
112
|
+
const svc = s.Principal?.Service;
|
|
113
|
+
return Array.isArray(svc) ? svc : svc ? [svc] : [];
|
|
114
|
+
};
|
|
115
|
+
const services = statements.flatMap(servicesOf);
|
|
116
|
+
assert.ok(services.includes('lambda.amazonaws.com'), 'shared role must stay Lambda-assumable');
|
|
117
|
+
assert.ok(services.includes('bedrock-agentcore.amazonaws.com'), 'shared role must be assumable by the AgentCore Runtime');
|
|
118
|
+
const agentCoreStmt = statements.find(s => servicesOf(s).includes('bedrock-agentcore.amazonaws.com'));
|
|
119
|
+
assert.ok(agentCoreStmt?.Condition, 'AgentCore trust statement must carry a scoping Condition');
|
|
120
|
+
assert.ok(JSON.stringify(agentCoreStmt.Condition).includes('aws:SourceAccount'), 'AgentCore trust must be scoped by aws:SourceAccount');
|
|
63
121
|
});
|
|
64
|
-
test('CDK: the
|
|
65
|
-
|
|
122
|
+
test('CDK: multiple agents add the identical shared-role grants ONCE, not per-agent', async () => {
|
|
123
|
+
// Every agent would otherwise add the SAME bedrock-agentcore trust / Bedrock / InvokeAgentRuntime
|
|
124
|
+
// statements to the one shared role. They're identical (stack-scoped), so they must be added
|
|
125
|
+
// exactly once regardless of agent count — keeping the shared role's policy from bloating with
|
|
126
|
+
// duplicates. (Overflow into managed policies is a normal CDK mechanism and not asserted against.)
|
|
127
|
+
const app = new cdk.App();
|
|
128
|
+
const stack = await BlocksStack.create(app, 'teststack', {
|
|
129
|
+
backendHandlerPath: handlerPath,
|
|
130
|
+
backendCDKPath: backendPath,
|
|
131
|
+
defaults: BlocksPresets.production,
|
|
132
|
+
defaultComputeFactory: lambdaFactory,
|
|
133
|
+
});
|
|
134
|
+
for (const id of ['agent1', 'agent2', 'agent3']) {
|
|
135
|
+
new Agent(stack, id, { systemPrompt: 'You are a test agent.', agentcoreAssetPath: ASSET_DIR });
|
|
136
|
+
}
|
|
137
|
+
const template = Template.fromStack(stack);
|
|
138
|
+
const json = JSON.stringify(template.toJSON());
|
|
139
|
+
// Three runtimes (per-agent), but the shared-role grant statements appear once (deduped).
|
|
140
|
+
assert.strictEqual(Object.keys(template.findResources('AWS::BedrockAgentCore::Runtime')).length, 3, 'three runtimes');
|
|
141
|
+
const invokeGrants = json.split('bedrock-agentcore:InvokeAgentRuntime').length - 1;
|
|
142
|
+
assert.strictEqual(invokeGrants, 1, `InvokeAgentRuntime should be granted once, found ${invokeGrants}`);
|
|
66
143
|
});
|
package/dist/index.mock.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { Agent } from './agent.mock.js';
|
|
2
|
+
export type { AgentTurnPayload } from './agent.js';
|
|
2
3
|
export { AgentErrors, InterruptError } from './errors.js';
|
|
3
4
|
export { BedrockModels, OllamaModels } from './models.js';
|
|
4
5
|
export type { AgentConfig, AgentResult, AgentStreamChunk, AgentStreamResult, ToolDefinition, AgentTool, ToolFactory, ToolsConfig, ToolHandlerArgs, DefaultToolContext, InterruptResponse, ToolCallRecord, ModelConfig, StreamOptions, Message, Conversation, JSONValue, TokenUsage } from './types.js';
|
package/dist/index.mock.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mock.d.ts","sourceRoot":"","sources":["../src/index.mock.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.mock.d.ts","sourceRoot":"","sources":["../src/index.mock.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAGxC,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,cAAc,EAAE,WAAW,EAAE,aAAa,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
|