@aws/agentcore 1.0.0-preview.11 → 1.0.0-preview.13
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/dist/agent-inspector/index.css +1 -1
- package/dist/agent-inspector/index.js +83 -78
- package/dist/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +860 -158
- package/dist/assets/cdk/bin/cdk.ts +53 -5
- package/dist/assets/cdk/lib/cdk-stack.ts +164 -1
- package/dist/assets/cdk/package.json +1 -1
- package/dist/assets/cdk/test/cdk.test.ts +2 -0
- package/dist/assets/python/a2a/googleadk/base/main.py +24 -16
- package/dist/assets/python/a2a/langchain_langgraph/base/main.py +24 -16
- package/dist/assets/python/a2a/langchain_langgraph/base/pyproject.toml +1 -1
- package/dist/assets/python/a2a/strands/base/main.py +23 -17
- package/dist/assets/python/agui/googleadk/base/main.py +62 -2
- package/dist/assets/python/agui/googleadk/base/pyproject.toml +1 -1
- package/dist/assets/python/agui/langchain_langgraph/base/main.py +56 -0
- package/dist/assets/python/agui/langchain_langgraph/base/pyproject.toml +1 -1
- package/dist/assets/python/agui/strands/base/main.py +63 -1
- package/dist/assets/python/http/autogen/base/main.py +27 -19
- package/dist/assets/python/http/autogen/base/pyproject.toml +1 -1
- package/dist/assets/python/http/googleadk/base/main.py +24 -16
- package/dist/assets/python/http/langchain_langgraph/base/main.py +24 -16
- package/dist/assets/python/http/langchain_langgraph/base/pyproject.toml +1 -1
- package/dist/assets/python/http/openaiagents/base/main.py +24 -16
- package/dist/assets/python/http/openaiagents/base/pyproject.toml +1 -1
- package/dist/assets/python/http/strands/base/main.py +62 -19
- package/dist/assets/python/http/strands/capabilities/payments/__init__.py +4 -0
- package/dist/assets/python/http/strands/capabilities/payments/payments.py +142 -0
- package/dist/assets/python/mcp/standalone/base/main.py +58 -0
- package/dist/cli/index.mjs +772 -660
- package/dist/lib/errors/types.d.ts +3 -0
- package/dist/lib/errors/types.d.ts.map +1 -1
- package/dist/lib/errors/types.js +10 -1
- package/dist/lib/errors/types.js.map +1 -1
- package/dist/lib/errors/zod.js +1 -1
- package/dist/lib/errors/zod.js.map +1 -1
- package/dist/lib/packaging/helpers.d.ts.map +1 -1
- package/dist/lib/packaging/helpers.js +50 -10
- package/dist/lib/packaging/helpers.js.map +1 -1
- package/dist/lib/result.d.ts +6 -4
- package/dist/lib/result.d.ts.map +1 -1
- package/dist/lib/result.js +7 -0
- package/dist/lib/result.js.map +1 -1
- package/dist/lib/utils/env.d.ts +4 -0
- package/dist/lib/utils/env.d.ts.map +1 -1
- package/dist/lib/utils/env.js +18 -0
- package/dist/lib/utils/env.js.map +1 -1
- package/dist/lib/utils/index.d.ts +1 -1
- package/dist/lib/utils/index.d.ts.map +1 -1
- package/dist/lib/utils/index.js +2 -1
- package/dist/lib/utils/index.js.map +1 -1
- package/dist/schema/schemas/agent-env.d.ts +50 -4
- package/dist/schema/schemas/agent-env.d.ts.map +1 -1
- package/dist/schema/schemas/agent-env.js +102 -4
- package/dist/schema/schemas/agent-env.js.map +1 -1
- package/dist/schema/schemas/agentcore-project.d.ts +69 -4
- package/dist/schema/schemas/agentcore-project.d.ts.map +1 -1
- package/dist/schema/schemas/agentcore-project.js +61 -5
- package/dist/schema/schemas/agentcore-project.js.map +1 -1
- package/dist/schema/schemas/deployed-state.d.ts +97 -0
- package/dist/schema/schemas/deployed-state.d.ts.map +1 -1
- package/dist/schema/schemas/deployed-state.js +24 -4
- package/dist/schema/schemas/deployed-state.js.map +1 -1
- package/dist/schema/schemas/primitives/harness.d.ts +41 -0
- package/dist/schema/schemas/primitives/harness.d.ts.map +1 -1
- package/dist/schema/schemas/primitives/harness.js +65 -1
- package/dist/schema/schemas/primitives/harness.js.map +1 -1
- package/dist/schema/schemas/primitives/index.d.ts +4 -2
- package/dist/schema/schemas/primitives/index.d.ts.map +1 -1
- package/dist/schema/schemas/primitives/index.js +14 -2
- package/dist/schema/schemas/primitives/index.js.map +1 -1
- package/dist/schema/schemas/primitives/payment.d.ts +54 -0
- package/dist/schema/schemas/primitives/payment.d.ts.map +1 -0
- package/dist/schema/schemas/primitives/payment.js +79 -0
- package/dist/schema/schemas/primitives/payment.js.map +1 -0
- package/npm-shrinkwrap.json +1027 -706
- package/package.json +13 -7
- package/scripts/run-e2e-local.sh +9 -3
|
@@ -73,12 +73,14 @@ async function main() {
|
|
|
73
73
|
const spec = await configIO.readProjectSpec();
|
|
74
74
|
const targets = await configIO.readAWSDeploymentTargets();
|
|
75
75
|
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
// dynamically and cast the resulting object.
|
|
76
|
+
// The vended CDK project compiles against the published @aws/agentcore-cdk
|
|
77
|
+
// schema type, which may lag the CLI's own AgentCoreProjectSpec (e.g. payments,
|
|
78
|
+
// harnesses, gateway fields). Cast once so those fields are reachable.
|
|
80
79
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
81
80
|
const specAny = spec as any;
|
|
81
|
+
|
|
82
|
+
// Extract MCP configuration from project spec.
|
|
83
|
+
// Gateway fields are stored in agentcore.json but may not yet be on the
|
|
82
84
|
const mcpSpec = specAny.agentCoreGateways?.length
|
|
83
85
|
? {
|
|
84
86
|
agentCoreGateways: specAny.agentCoreGateways,
|
|
@@ -111,6 +113,9 @@ async function main() {
|
|
|
111
113
|
codeLocation?: string;
|
|
112
114
|
tools?: { type: string; name: string }[];
|
|
113
115
|
apiKeyArn?: string;
|
|
116
|
+
efsAccessPoints?: { accessPointArn: string; mountPath: string }[];
|
|
117
|
+
s3AccessPoints?: { accessPointArn: string; mountPath: string }[];
|
|
118
|
+
apiFormat?: 'converse_stream' | 'responses' | 'chat_completions';
|
|
114
119
|
}[] = [];
|
|
115
120
|
for (const entry of specAny.harnesses ?? []) {
|
|
116
121
|
const harnessDir = path.resolve(projectRoot, entry.path);
|
|
@@ -127,6 +132,9 @@ async function main() {
|
|
|
127
132
|
codeLocation: harnessSpec.dockerfile ? harnessDir : undefined,
|
|
128
133
|
tools: harnessSpec.tools,
|
|
129
134
|
apiKeyArn: harnessSpec.model?.apiKeyArn,
|
|
135
|
+
efsAccessPoints: harnessSpec.efsAccessPoints,
|
|
136
|
+
s3AccessPoints: harnessSpec.s3AccessPoints,
|
|
137
|
+
apiFormat: harnessSpec.model?.apiFormat,
|
|
130
138
|
});
|
|
131
139
|
} catch (err) {
|
|
132
140
|
throw new Error(
|
|
@@ -150,11 +158,51 @@ async function main() {
|
|
|
150
158
|
| Record<string, { credentialProviderArn: string; clientSecretArn?: string }>
|
|
151
159
|
| undefined;
|
|
152
160
|
|
|
161
|
+
// Payment credential provider ARNs live in the same credentials map as identity credentials
|
|
162
|
+
const paymentCredentials = credentials;
|
|
163
|
+
|
|
164
|
+
const paymentSpec = specAny.payments?.length
|
|
165
|
+
? specAny.payments.map(
|
|
166
|
+
(p: {
|
|
167
|
+
name: string;
|
|
168
|
+
description?: string;
|
|
169
|
+
authorizerType: 'AWS_IAM' | 'CUSTOM_JWT';
|
|
170
|
+
authorizerConfiguration?: unknown;
|
|
171
|
+
autoPayment?: boolean;
|
|
172
|
+
paymentToolAllowlist?: string[];
|
|
173
|
+
networkPreferences?: string[];
|
|
174
|
+
connectors: { name: string; provider?: string; credentialName: string }[];
|
|
175
|
+
}) => ({
|
|
176
|
+
name: p.name,
|
|
177
|
+
description: p.description,
|
|
178
|
+
authorizerType: p.authorizerType,
|
|
179
|
+
authorizerConfiguration: p.authorizerConfiguration,
|
|
180
|
+
autoPayment: p.autoPayment,
|
|
181
|
+
paymentToolAllowlist: p.paymentToolAllowlist,
|
|
182
|
+
networkPreferences: p.networkPreferences,
|
|
183
|
+
connectors: p.connectors.map(c => {
|
|
184
|
+
const credentialProviderArn = paymentCredentials?.[c.credentialName]?.credentialProviderArn;
|
|
185
|
+
if (!credentialProviderArn) {
|
|
186
|
+
// Fail fast with an actionable message rather than passing an empty
|
|
187
|
+
// ARN that fails opaquely server-side at CreatePaymentConnector.
|
|
188
|
+
throw new Error(
|
|
189
|
+
\`Payment connector "\${c.name}" on manager "\${p.name}" references credential \` +
|
|
190
|
+
\`"\${c.credentialName}", but no deployed credential provider was found for it. \` +
|
|
191
|
+
\`Run \\\`agentcore deploy\\\` so the credential provider is created first.\`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
return { name: c.name, provider: c.provider, credentialProviderArn };
|
|
195
|
+
}),
|
|
196
|
+
})
|
|
197
|
+
)
|
|
198
|
+
: undefined;
|
|
199
|
+
|
|
153
200
|
new AgentCoreStack(app, stackName, {
|
|
154
201
|
spec,
|
|
155
202
|
mcpSpec,
|
|
156
203
|
credentials,
|
|
157
204
|
harnesses: harnessConfigs.length > 0 ? harnessConfigs : undefined,
|
|
205
|
+
paymentSpec,
|
|
158
206
|
env,
|
|
159
207
|
description: \`AgentCore stack for \${spec.name} deployed to \${target.name} (\${target.region})\`,
|
|
160
208
|
tags: {
|
|
@@ -169,7 +217,7 @@ async function main() {
|
|
|
169
217
|
|
|
170
218
|
main().catch((error: unknown) => {
|
|
171
219
|
console.error('AgentCore CDK synthesis failed:', error instanceof Error ? error.message : error);
|
|
172
|
-
process.
|
|
220
|
+
process.exit(1);
|
|
173
221
|
});
|
|
174
222
|
"
|
|
175
223
|
`;
|
|
@@ -296,10 +344,14 @@ exports[`Assets Directory Snapshots > CDK assets > cdk/cdk/lib/cdk-stack.ts shou
|
|
|
296
344
|
"import {
|
|
297
345
|
AgentCoreApplication,
|
|
298
346
|
AgentCoreMcp,
|
|
347
|
+
AgentCorePaymentManager,
|
|
348
|
+
AgentCorePaymentConnector,
|
|
299
349
|
type AgentCoreProjectSpec,
|
|
300
350
|
type AgentCoreMcpSpec,
|
|
351
|
+
type CustomJWTAuthorizerConfig,
|
|
301
352
|
} from '@aws/agentcore-cdk';
|
|
302
353
|
import { CfnOutput, Stack, type StackProps } from 'aws-cdk-lib';
|
|
354
|
+
import * as iam from 'aws-cdk-lib/aws-iam';
|
|
303
355
|
import { Construct } from 'constructs';
|
|
304
356
|
|
|
305
357
|
export interface HarnessConfig {
|
|
@@ -312,6 +364,25 @@ export interface HarnessConfig {
|
|
|
312
364
|
codeLocation?: string;
|
|
313
365
|
tools?: { type: string; name: string }[];
|
|
314
366
|
apiKeyArn?: string;
|
|
367
|
+
efsAccessPoints?: { accessPointArn: string; mountPath: string }[];
|
|
368
|
+
s3AccessPoints?: { accessPointArn: string; mountPath: string }[];
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export interface PaymentConnectorSpec {
|
|
372
|
+
name: string;
|
|
373
|
+
provider: 'CoinbaseCDP' | 'StripePrivy';
|
|
374
|
+
credentialProviderArn: string;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export interface PaymentSpec {
|
|
378
|
+
name: string;
|
|
379
|
+
description?: string;
|
|
380
|
+
authorizerType: 'AWS_IAM' | 'CUSTOM_JWT';
|
|
381
|
+
authorizerConfiguration?: { customJWTAuthorizer: CustomJWTAuthorizerConfig };
|
|
382
|
+
autoPayment?: boolean;
|
|
383
|
+
paymentToolAllowlist?: string[];
|
|
384
|
+
networkPreferences?: string[];
|
|
385
|
+
connectors: PaymentConnectorSpec[];
|
|
315
386
|
}
|
|
316
387
|
|
|
317
388
|
export interface AgentCoreStackProps extends StackProps {
|
|
@@ -331,6 +402,30 @@ export interface AgentCoreStackProps extends StackProps {
|
|
|
331
402
|
* Harness role configurations.
|
|
332
403
|
*/
|
|
333
404
|
harnesses?: HarnessConfig[];
|
|
405
|
+
/**
|
|
406
|
+
* Payment specifications with resolved credential provider ARNs.
|
|
407
|
+
*/
|
|
408
|
+
paymentSpec?: PaymentSpec[];
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function toCdkId(name: string): string {
|
|
412
|
+
return name.replace(/_/g, '');
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Decide whether a deployed runtime should receive payment env vars + IAM grants.
|
|
417
|
+
* Payments today only ships a runtime shim for Python HTTP runtimes; injecting
|
|
418
|
+
* AGENTCORE_PAYMENT_* env vars into TypeScript / MCP / A2A / AGUI runtimes
|
|
419
|
+
* would surface env vars they cannot consume and would dilute least-privilege
|
|
420
|
+
* IAM grants for runtimes that never call ProcessPayment.
|
|
421
|
+
*/
|
|
422
|
+
function isPaymentEligibleAgent(agent: { entrypoint?: string; protocol?: string }): boolean {
|
|
423
|
+
if (agent.protocol && agent.protocol !== 'HTTP') {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
const entrypoint = typeof agent.entrypoint === 'string' ? agent.entrypoint : '';
|
|
427
|
+
const entrypointFile = entrypoint.split(':')[0] ?? '';
|
|
428
|
+
return entrypointFile.endsWith('.py');
|
|
334
429
|
}
|
|
335
430
|
|
|
336
431
|
/**
|
|
@@ -346,7 +441,7 @@ export class AgentCoreStack extends Stack {
|
|
|
346
441
|
constructor(scope: Construct, id: string, props: AgentCoreStackProps) {
|
|
347
442
|
super(scope, id, props);
|
|
348
443
|
|
|
349
|
-
const { spec, mcpSpec, credentials, harnesses } = props;
|
|
444
|
+
const { spec, mcpSpec, credentials, harnesses, paymentSpec } = props;
|
|
350
445
|
|
|
351
446
|
// Create AgentCoreApplication with all agents and harness roles
|
|
352
447
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -367,6 +462,122 @@ export class AgentCoreStack extends Stack {
|
|
|
367
462
|
});
|
|
368
463
|
}
|
|
369
464
|
|
|
465
|
+
// Create payment infrastructure via CFN constructs
|
|
466
|
+
if (paymentSpec && paymentSpec.length > 0) {
|
|
467
|
+
for (const payment of paymentSpec) {
|
|
468
|
+
const mgrId = toCdkId(payment.name);
|
|
469
|
+
const manager = new AgentCorePaymentManager(this, \`Payment\${mgrId}\`, {
|
|
470
|
+
projectName: spec.name,
|
|
471
|
+
name: payment.name,
|
|
472
|
+
authorizerType: payment.authorizerType,
|
|
473
|
+
description: payment.description,
|
|
474
|
+
authorizerConfiguration: payment.authorizerConfiguration,
|
|
475
|
+
tags: spec.tags,
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
const prefix = \`AGENTCORE_PAYMENT_\${payment.name.toUpperCase().replace(/-/g, '_')}\`;
|
|
479
|
+
|
|
480
|
+
// Wire env vars from construct output tokens into eligible agent environments only.
|
|
481
|
+
// See isPaymentEligibleAgent — non-Python or non-HTTP runtimes have no shim that
|
|
482
|
+
// can consume these env vars, and giving them sts:AssumeRole on the
|
|
483
|
+
// ProcessPaymentRole would broaden the privilege surface unnecessarily.
|
|
484
|
+
for (const env of this.application.environments.values()) {
|
|
485
|
+
if (!isPaymentEligibleAgent(env.agent)) {
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
env.runtime.addEnvironmentVariable(\`\${prefix}_MANAGER_ARN\`, manager.paymentManagerArn);
|
|
489
|
+
env.runtime.addEnvironmentVariable(\`\${prefix}_PROCESS_PAYMENT_ROLE_ARN\`, manager.processPaymentRoleArn);
|
|
490
|
+
|
|
491
|
+
// Grant runtime execution role permission to assume the ProcessPaymentRole.
|
|
492
|
+
// The ProcessPaymentRole's trust policy allows AccountRootPrincipal, but the
|
|
493
|
+
// caller still needs sts:AssumeRole on its own role to perform the assumption.
|
|
494
|
+
env.runtime.role.addToPrincipalPolicy(
|
|
495
|
+
new iam.PolicyStatement({
|
|
496
|
+
actions: ['sts:AssumeRole'],
|
|
497
|
+
resources: [manager.processPaymentRoleArn],
|
|
498
|
+
})
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
// Grant payment data-plane actions directly to the runtime role.
|
|
502
|
+
//
|
|
503
|
+
// NOTE: This deviates from the canonical role model in the AgentCore Payments
|
|
504
|
+
// beta guide, which assigns Get/List/Create instrument+session actions to a
|
|
505
|
+
// separate ManagementRole and limits the agent's role to ProcessPayment only.
|
|
506
|
+
// The current SDK plugin (AgentCorePaymentsPlugin.generate_payment_header)
|
|
507
|
+
// calls GetPaymentInstrument internally during the 402 auto-pay path, so the
|
|
508
|
+
// runtime role needs read access. CreatePaymentSession is included so
|
|
509
|
+
// \`agentcore invoke --auto-session\` works without a separate ManagementRole
|
|
510
|
+
// call. Tighten this if the SDK is updated to accept pre-fetched instrument
|
|
511
|
+
// details and split create-session into a backend-only flow.
|
|
512
|
+
env.runtime.role.addToPrincipalPolicy(
|
|
513
|
+
new iam.PolicyStatement({
|
|
514
|
+
actions: [
|
|
515
|
+
'bedrock-agentcore:GetPaymentInstrument',
|
|
516
|
+
'bedrock-agentcore:ListPaymentInstruments',
|
|
517
|
+
'bedrock-agentcore:GetPaymentInstrumentBalance',
|
|
518
|
+
'bedrock-agentcore:GetPaymentSession',
|
|
519
|
+
'bedrock-agentcore:ListPaymentSessions',
|
|
520
|
+
'bedrock-agentcore:CreatePaymentSession',
|
|
521
|
+
'bedrock-agentcore:ProcessPayment',
|
|
522
|
+
],
|
|
523
|
+
resources: [manager.paymentManagerArn, \`\${manager.paymentManagerArn}/*\`],
|
|
524
|
+
})
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
if (payment.autoPayment !== undefined) {
|
|
528
|
+
env.runtime.addEnvironmentVariable(\`\${prefix}_AUTO_PAYMENT\`, String(payment.autoPayment));
|
|
529
|
+
}
|
|
530
|
+
if (payment.paymentToolAllowlist) {
|
|
531
|
+
env.runtime.addEnvironmentVariable(\`\${prefix}_TOOL_ALLOWLIST\`, payment.paymentToolAllowlist.join(','));
|
|
532
|
+
}
|
|
533
|
+
if (payment.networkPreferences) {
|
|
534
|
+
env.runtime.addEnvironmentVariable(\`\${prefix}_NETWORK_PREFERENCES\`, payment.networkPreferences.join(','));
|
|
535
|
+
}
|
|
536
|
+
if (payment.authorizerType === 'CUSTOM_JWT') {
|
|
537
|
+
env.runtime.addEnvironmentVariable(\`\${prefix}_AUTH_MODE\`, 'bearer');
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Create connectors for this manager
|
|
542
|
+
for (const connector of payment.connectors) {
|
|
543
|
+
const connId = toCdkId(connector.name);
|
|
544
|
+
const conn = new AgentCorePaymentConnector(this, \`Payment\${mgrId}\${connId}\`, {
|
|
545
|
+
projectName: spec.name,
|
|
546
|
+
paymentManager: manager,
|
|
547
|
+
connectorName: connector.name,
|
|
548
|
+
connectorType: connector.provider,
|
|
549
|
+
credentialProviderArn: connector.credentialProviderArn,
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
// Wire first connector's ID as env var (eligible agents only)
|
|
553
|
+
if (connector === payment.connectors[0]) {
|
|
554
|
+
for (const env of this.application.environments.values()) {
|
|
555
|
+
if (!isPaymentEligibleAgent(env.agent)) continue;
|
|
556
|
+
env.runtime.addEnvironmentVariable(\`\${prefix}_CONNECTOR_ID\`, conn.paymentConnectorId);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
new CfnOutput(this, \`Payment\${mgrId}\${connId}ConnectorId\`, {
|
|
561
|
+
value: conn.paymentConnectorId,
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// CFN Outputs for post-deploy state parsing
|
|
566
|
+
new CfnOutput(this, \`Payment\${mgrId}ManagerArn\`, {
|
|
567
|
+
value: manager.paymentManagerArn,
|
|
568
|
+
});
|
|
569
|
+
new CfnOutput(this, \`Payment\${mgrId}ManagerId\`, {
|
|
570
|
+
value: manager.paymentManagerId,
|
|
571
|
+
});
|
|
572
|
+
new CfnOutput(this, \`Payment\${mgrId}ProcessPaymentRoleArn\`, {
|
|
573
|
+
value: manager.processPaymentRoleArn,
|
|
574
|
+
});
|
|
575
|
+
new CfnOutput(this, \`Payment\${mgrId}ResourceRetrievalRoleArn\`, {
|
|
576
|
+
value: manager.resourceRetrievalRoleArn,
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
370
581
|
// Stack-level output
|
|
371
582
|
new CfnOutput(this, 'StackNameOutput', {
|
|
372
583
|
description: 'Name of the CloudFormation Stack',
|
|
@@ -408,7 +619,7 @@ exports[`Assets Directory Snapshots > CDK assets > cdk/cdk/package.json should m
|
|
|
408
619
|
"@types/node": "^24.10.1",
|
|
409
620
|
"jest": "^29.7.0",
|
|
410
621
|
"ts-jest": "^29.2.5",
|
|
411
|
-
"aws-cdk": "2.
|
|
622
|
+
"aws-cdk": "2.1126.0",
|
|
412
623
|
"prettier": "^3.4.2",
|
|
413
624
|
"typescript": "~5.9.3"
|
|
414
625
|
},
|
|
@@ -439,6 +650,8 @@ test('AgentCoreStack synthesizes with empty spec', () => {
|
|
|
439
650
|
evaluators: [],
|
|
440
651
|
onlineEvalConfigs: [],
|
|
441
652
|
policyEngines: [],
|
|
653
|
+
payments: [],
|
|
654
|
+
configBundles: [],
|
|
442
655
|
agentCoreGateways: [],
|
|
443
656
|
mcpRuntimeTools: [],
|
|
444
657
|
unassignedTargets: [],
|
|
@@ -599,6 +812,8 @@ exports[`Assets Directory Snapshots > File listing > should match the expected f
|
|
|
599
812
|
"python/http/strands/base/pyproject.toml",
|
|
600
813
|
"python/http/strands/capabilities/memory/__init__.py",
|
|
601
814
|
"python/http/strands/capabilities/memory/session.py",
|
|
815
|
+
"python/http/strands/capabilities/payments/__init__.py",
|
|
816
|
+
"python/http/strands/capabilities/payments/payments.py",
|
|
602
817
|
"python/mcp/standalone/base/README.md",
|
|
603
818
|
"python/mcp/standalone/base/gitignore.template",
|
|
604
819
|
"python/mcp/standalone/base/main.py",
|
|
@@ -1063,7 +1278,9 @@ Thumbs.db
|
|
|
1063
1278
|
`;
|
|
1064
1279
|
|
|
1065
1280
|
exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/googleadk/base/main.py should match snapshot 1`] = `
|
|
1066
|
-
"
|
|
1281
|
+
"{{#if needsOs}}
|
|
1282
|
+
import os
|
|
1283
|
+
{{/if}}
|
|
1067
1284
|
from google.adk.agents import Agent
|
|
1068
1285
|
from google.adk.a2a.executor.a2a_agent_executor import A2aAgentExecutor
|
|
1069
1286
|
from google.adk.runners import Runner
|
|
@@ -1082,18 +1299,21 @@ def add_numbers(a: int, b: int) -> int:
|
|
|
1082
1299
|
|
|
1083
1300
|
tools = [add_numbers]
|
|
1084
1301
|
|
|
1085
|
-
{{#if
|
|
1086
|
-
|
|
1302
|
+
{{#if needsOs}}
|
|
1303
|
+
_MOUNT_PATHS = [
|
|
1304
|
+
{{#if sessionStorageMountPath}}"{{sessionStorageMountPath}}",{{/if}}
|
|
1305
|
+
{{#each efsMounts}}"{{mountPath}}",{{/each}}
|
|
1306
|
+
{{#each s3Mounts}}"{{mountPath}}",{{/each}}
|
|
1307
|
+
]
|
|
1087
1308
|
|
|
1088
1309
|
def _safe_resolve(path: str) -> str:
|
|
1089
|
-
|
|
1090
|
-
resolved
|
|
1091
|
-
|
|
1092
|
-
raise ValueError(f"Path '{path}' is outside the storage boundary")
|
|
1310
|
+
resolved = os.path.realpath(path)
|
|
1311
|
+
if not any(resolved == os.path.realpath(m) or resolved.startswith(os.path.realpath(m) + os.sep) for m in _MOUNT_PATHS):
|
|
1312
|
+
raise ValueError(f"Path '{path}' is not within any configured mount ({', '.join(_MOUNT_PATHS)})")
|
|
1093
1313
|
return resolved
|
|
1094
1314
|
|
|
1095
1315
|
def file_read(path: str) -> str:
|
|
1096
|
-
"""Read a file from
|
|
1316
|
+
"""Read a file from a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
1097
1317
|
try:
|
|
1098
1318
|
full_path = _safe_resolve(path)
|
|
1099
1319
|
with open(full_path) as f:
|
|
@@ -1104,7 +1324,7 @@ def file_read(path: str) -> str:
|
|
|
1104
1324
|
return f"Error reading '{path}': {e.strerror}"
|
|
1105
1325
|
|
|
1106
1326
|
def file_write(path: str, content: str) -> str:
|
|
1107
|
-
"""Write
|
|
1327
|
+
"""Write a file to a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
1108
1328
|
try:
|
|
1109
1329
|
full_path = _safe_resolve(path)
|
|
1110
1330
|
parent = os.path.dirname(full_path)
|
|
@@ -1118,25 +1338,28 @@ def file_write(path: str, content: str) -> str:
|
|
|
1118
1338
|
except OSError as e:
|
|
1119
1339
|
return f"Error writing '{path}': {e.strerror}"
|
|
1120
1340
|
|
|
1121
|
-
def list_files(
|
|
1122
|
-
"""List files in
|
|
1341
|
+
def list_files(path: str) -> str:
|
|
1342
|
+
"""List files in a mounted filesystem directory. Use the absolute path (e.g. /mnt/tools)."""
|
|
1123
1343
|
try:
|
|
1124
|
-
|
|
1125
|
-
entries = os.listdir(
|
|
1344
|
+
full_path = _safe_resolve(path)
|
|
1345
|
+
entries = os.listdir(full_path)
|
|
1126
1346
|
return "\\n".join(entries) if entries else "(empty directory)"
|
|
1127
1347
|
except ValueError as e:
|
|
1128
1348
|
return str(e)
|
|
1129
1349
|
except OSError as e:
|
|
1130
|
-
return f"Error listing '{
|
|
1350
|
+
return f"Error listing '{path}': {e.strerror}"
|
|
1131
1351
|
|
|
1132
1352
|
tools.extend([file_read, file_write, list_files])
|
|
1133
1353
|
{{/if}}
|
|
1134
1354
|
|
|
1135
1355
|
AGENT_INSTRUCTION = """
|
|
1136
1356
|
You are a helpful assistant. Use tools when appropriate.
|
|
1137
|
-
{{#if
|
|
1138
|
-
You have
|
|
1139
|
-
{{
|
|
1357
|
+
{{#if needsOs}}
|
|
1358
|
+
You have access to the following mounted filesystems. Use file_read, file_write, and list_files with full absolute paths:
|
|
1359
|
+
{{#if sessionStorageMountPath}}- {{sessionStorageMountPath}}: ephemeral session storage (lost when session ends)
|
|
1360
|
+
{{/if}}{{#each efsMounts}}- {{mountPath}}: EFS persistent storage (persists across sessions and agent restarts)
|
|
1361
|
+
{{/each}}{{#each s3Mounts}}- {{mountPath}}: S3 Files persistent storage (durable, backed by S3)
|
|
1362
|
+
{{/each}}{{/if}}
|
|
1140
1363
|
"""
|
|
1141
1364
|
|
|
1142
1365
|
agent = Agent(
|
|
@@ -1322,7 +1545,9 @@ Thumbs.db
|
|
|
1322
1545
|
`;
|
|
1323
1546
|
|
|
1324
1547
|
exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/langchain_langgraph/base/main.py should match snapshot 1`] = `
|
|
1325
|
-
"
|
|
1548
|
+
"{{#if needsOs}}
|
|
1549
|
+
import os
|
|
1550
|
+
{{/if}}
|
|
1326
1551
|
from langchain_core.tools import tool
|
|
1327
1552
|
from langgraph.prebuilt import create_react_agent
|
|
1328
1553
|
from opentelemetry.instrumentation.langchain import LangchainInstrumentor
|
|
@@ -1345,19 +1570,22 @@ def add_numbers(a: int, b: int) -> int:
|
|
|
1345
1570
|
|
|
1346
1571
|
tools = [add_numbers]
|
|
1347
1572
|
|
|
1348
|
-
{{#if
|
|
1349
|
-
|
|
1573
|
+
{{#if needsOs}}
|
|
1574
|
+
_MOUNT_PATHS = [
|
|
1575
|
+
{{#if sessionStorageMountPath}}"{{sessionStorageMountPath}}",{{/if}}
|
|
1576
|
+
{{#each efsMounts}}"{{mountPath}}",{{/each}}
|
|
1577
|
+
{{#each s3Mounts}}"{{mountPath}}",{{/each}}
|
|
1578
|
+
]
|
|
1350
1579
|
|
|
1351
1580
|
def _safe_resolve(path: str) -> str:
|
|
1352
|
-
|
|
1353
|
-
resolved
|
|
1354
|
-
|
|
1355
|
-
raise ValueError(f"Path '{path}' is outside the storage boundary")
|
|
1581
|
+
resolved = os.path.realpath(path)
|
|
1582
|
+
if not any(resolved == os.path.realpath(m) or resolved.startswith(os.path.realpath(m) + os.sep) for m in _MOUNT_PATHS):
|
|
1583
|
+
raise ValueError(f"Path '{path}' is not within any configured mount ({', '.join(_MOUNT_PATHS)})")
|
|
1356
1584
|
return resolved
|
|
1357
1585
|
|
|
1358
1586
|
@tool
|
|
1359
1587
|
def file_read(path: str) -> str:
|
|
1360
|
-
"""Read a file from
|
|
1588
|
+
"""Read a file from a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
1361
1589
|
try:
|
|
1362
1590
|
full_path = _safe_resolve(path)
|
|
1363
1591
|
with open(full_path) as f:
|
|
@@ -1369,7 +1597,7 @@ def file_read(path: str) -> str:
|
|
|
1369
1597
|
|
|
1370
1598
|
@tool
|
|
1371
1599
|
def file_write(path: str, content: str) -> str:
|
|
1372
|
-
"""Write
|
|
1600
|
+
"""Write a file to a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
1373
1601
|
try:
|
|
1374
1602
|
full_path = _safe_resolve(path)
|
|
1375
1603
|
parent = os.path.dirname(full_path)
|
|
@@ -1384,25 +1612,28 @@ def file_write(path: str, content: str) -> str:
|
|
|
1384
1612
|
return f"Error writing '{path}': {e.strerror}"
|
|
1385
1613
|
|
|
1386
1614
|
@tool
|
|
1387
|
-
def list_files(
|
|
1388
|
-
"""List files in
|
|
1615
|
+
def list_files(path: str) -> str:
|
|
1616
|
+
"""List files in a mounted filesystem directory. Use the absolute path (e.g. /mnt/tools)."""
|
|
1389
1617
|
try:
|
|
1390
|
-
|
|
1391
|
-
entries = os.listdir(
|
|
1618
|
+
full_path = _safe_resolve(path)
|
|
1619
|
+
entries = os.listdir(full_path)
|
|
1392
1620
|
return "\\n".join(entries) if entries else "(empty directory)"
|
|
1393
1621
|
except ValueError as e:
|
|
1394
1622
|
return str(e)
|
|
1395
1623
|
except OSError as e:
|
|
1396
|
-
return f"Error listing '{
|
|
1624
|
+
return f"Error listing '{path}': {e.strerror}"
|
|
1397
1625
|
|
|
1398
1626
|
tools.extend([file_read, file_write, list_files])
|
|
1399
1627
|
{{/if}}
|
|
1400
1628
|
|
|
1401
1629
|
SYSTEM_PROMPT = """
|
|
1402
1630
|
You are a helpful assistant. Use tools when appropriate.
|
|
1403
|
-
{{#if
|
|
1404
|
-
You have
|
|
1405
|
-
{{
|
|
1631
|
+
{{#if needsOs}}
|
|
1632
|
+
You have access to the following mounted filesystems. Use file_read, file_write, and list_files with full absolute paths:
|
|
1633
|
+
{{#if sessionStorageMountPath}}- {{sessionStorageMountPath}}: ephemeral session storage (lost when session ends)
|
|
1634
|
+
{{/if}}{{#each efsMounts}}- {{mountPath}}: EFS persistent storage (persists across sessions and agent restarts)
|
|
1635
|
+
{{/each}}{{#each s3Mounts}}- {{mountPath}}: S3 Files persistent storage (durable, backed by S3)
|
|
1636
|
+
{{/each}}{{/if}}
|
|
1406
1637
|
"""
|
|
1407
1638
|
|
|
1408
1639
|
model = load_model()
|
|
@@ -1607,7 +1838,7 @@ dependencies = [
|
|
|
1607
1838
|
{{/if}}{{#if (eq modelProvider "OpenAI")}}"langchain-openai >= 0.2.0",
|
|
1608
1839
|
{{/if}}"aws-opentelemetry-distro",
|
|
1609
1840
|
"opentelemetry-instrumentation-langchain >= 0.59.0",
|
|
1610
|
-
"bedrock-agentcore[a2a] >= 1.0
|
|
1841
|
+
"bedrock-agentcore[a2a] >= 1.8.0",
|
|
1611
1842
|
"botocore[crt] >= 1.35.0",
|
|
1612
1843
|
"langgraph >= 0.2.0",
|
|
1613
1844
|
]
|
|
@@ -1696,7 +1927,7 @@ from model.load import load_model
|
|
|
1696
1927
|
{{#if hasMemory}}
|
|
1697
1928
|
from memory.session import get_memory_session_manager
|
|
1698
1929
|
{{/if}}
|
|
1699
|
-
{{#if
|
|
1930
|
+
{{#if needsOs}}
|
|
1700
1931
|
import os
|
|
1701
1932
|
{{/if}}
|
|
1702
1933
|
|
|
@@ -1709,19 +1940,22 @@ def add_numbers(a: int, b: int) -> int:
|
|
|
1709
1940
|
|
|
1710
1941
|
tools = [add_numbers]
|
|
1711
1942
|
|
|
1712
|
-
{{#if
|
|
1713
|
-
|
|
1943
|
+
{{#if needsOs}}
|
|
1944
|
+
_MOUNT_PATHS = [
|
|
1945
|
+
{{#if sessionStorageMountPath}}"{{sessionStorageMountPath}}",{{/if}}
|
|
1946
|
+
{{#each efsMounts}}"{{mountPath}}",{{/each}}
|
|
1947
|
+
{{#each s3Mounts}}"{{mountPath}}",{{/each}}
|
|
1948
|
+
]
|
|
1714
1949
|
|
|
1715
1950
|
def _safe_resolve(path: str) -> str:
|
|
1716
|
-
|
|
1717
|
-
resolved
|
|
1718
|
-
|
|
1719
|
-
raise ValueError(f"Path '{path}' is outside the storage boundary")
|
|
1951
|
+
resolved = os.path.realpath(path)
|
|
1952
|
+
if not any(resolved == os.path.realpath(m) or resolved.startswith(os.path.realpath(m) + os.sep) for m in _MOUNT_PATHS):
|
|
1953
|
+
raise ValueError(f"Path '{path}' is not within any configured mount ({', '.join(_MOUNT_PATHS)})")
|
|
1720
1954
|
return resolved
|
|
1721
1955
|
|
|
1722
1956
|
@tool
|
|
1723
1957
|
def file_read(path: str) -> str:
|
|
1724
|
-
"""Read a file from
|
|
1958
|
+
"""Read a file from a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
1725
1959
|
try:
|
|
1726
1960
|
full_path = _safe_resolve(path)
|
|
1727
1961
|
with open(full_path) as f:
|
|
@@ -1733,7 +1967,7 @@ def file_read(path: str) -> str:
|
|
|
1733
1967
|
|
|
1734
1968
|
@tool
|
|
1735
1969
|
def file_write(path: str, content: str) -> str:
|
|
1736
|
-
"""Write
|
|
1970
|
+
"""Write a file to a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
1737
1971
|
try:
|
|
1738
1972
|
full_path = _safe_resolve(path)
|
|
1739
1973
|
parent = os.path.dirname(full_path)
|
|
@@ -1748,25 +1982,28 @@ def file_write(path: str, content: str) -> str:
|
|
|
1748
1982
|
return f"Error writing '{path}': {e.strerror}"
|
|
1749
1983
|
|
|
1750
1984
|
@tool
|
|
1751
|
-
def list_files(
|
|
1752
|
-
"""List files in
|
|
1985
|
+
def list_files(path: str) -> str:
|
|
1986
|
+
"""List files in a mounted filesystem directory. Use the absolute path (e.g. /mnt/tools)."""
|
|
1753
1987
|
try:
|
|
1754
|
-
|
|
1755
|
-
entries = os.listdir(
|
|
1988
|
+
full_path = _safe_resolve(path)
|
|
1989
|
+
entries = os.listdir(full_path)
|
|
1756
1990
|
return "\\n".join(entries) if entries else "(empty directory)"
|
|
1757
1991
|
except ValueError as e:
|
|
1758
1992
|
return str(e)
|
|
1759
1993
|
except OSError as e:
|
|
1760
|
-
return f"Error listing '{
|
|
1994
|
+
return f"Error listing '{path}': {e.strerror}"
|
|
1761
1995
|
|
|
1762
1996
|
tools.extend([file_read, file_write, list_files])
|
|
1763
1997
|
{{/if}}
|
|
1764
1998
|
|
|
1765
1999
|
SYSTEM_PROMPT = """
|
|
1766
2000
|
You are a helpful assistant. Use tools when appropriate.
|
|
1767
|
-
{{#if
|
|
1768
|
-
You have
|
|
1769
|
-
{{
|
|
2001
|
+
{{#if needsOs}}
|
|
2002
|
+
You have access to the following mounted filesystems. Use file_read, file_write, and list_files with full absolute paths:
|
|
2003
|
+
{{#if sessionStorageMountPath}}- {{sessionStorageMountPath}}: ephemeral session storage (lost when session ends)
|
|
2004
|
+
{{/if}}{{#each efsMounts}}- {{mountPath}}: EFS persistent storage (persists across sessions and agent restarts)
|
|
2005
|
+
{{/each}}{{#each s3Mounts}}- {{mountPath}}: S3 Files persistent storage (durable, backed by S3)
|
|
2006
|
+
{{/each}}{{/if}}
|
|
1770
2007
|
"""
|
|
1771
2008
|
|
|
1772
2009
|
{{#if hasMemory}}
|
|
@@ -2093,16 +2330,76 @@ exports[`Assets Directory Snapshots > Python framework assets > python/python/ag
|
|
|
2093
2330
|
"import os
|
|
2094
2331
|
import uvicorn
|
|
2095
2332
|
from google.adk.agents import LlmAgent
|
|
2333
|
+
from google.adk.tools import FunctionTool
|
|
2096
2334
|
from ag_ui_adk import ADKAgent, AGUIToolset, create_adk_app
|
|
2097
2335
|
from model.load import load_model
|
|
2098
2336
|
|
|
2099
2337
|
load_model()
|
|
2100
2338
|
|
|
2339
|
+
{{#if needsOs}}
|
|
2340
|
+
_MOUNT_PATHS = [
|
|
2341
|
+
{{#if sessionStorageMountPath}}"{{sessionStorageMountPath}}",{{/if}}
|
|
2342
|
+
{{#each efsMounts}}"{{mountPath}}",{{/each}}
|
|
2343
|
+
{{#each s3Mounts}}"{{mountPath}}",{{/each}}
|
|
2344
|
+
]
|
|
2345
|
+
|
|
2346
|
+
def _safe_resolve(path: str) -> str:
|
|
2347
|
+
resolved = os.path.realpath(path)
|
|
2348
|
+
if not any(resolved == os.path.realpath(m) or resolved.startswith(os.path.realpath(m) + os.sep) for m in _MOUNT_PATHS):
|
|
2349
|
+
raise ValueError(f"Path '{path}' is not within any configured mount ({', '.join(_MOUNT_PATHS)})")
|
|
2350
|
+
return resolved
|
|
2351
|
+
|
|
2352
|
+
def file_read(path: str) -> str:
|
|
2353
|
+
"""Read a file from a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
2354
|
+
try:
|
|
2355
|
+
full_path = _safe_resolve(path)
|
|
2356
|
+
with open(full_path) as f:
|
|
2357
|
+
return f.read()
|
|
2358
|
+
except ValueError as e:
|
|
2359
|
+
return str(e)
|
|
2360
|
+
except OSError as e:
|
|
2361
|
+
return f"Error reading '{path}': {e.strerror}"
|
|
2362
|
+
|
|
2363
|
+
def file_write(path: str, content: str) -> str:
|
|
2364
|
+
"""Write a file to a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
2365
|
+
try:
|
|
2366
|
+
full_path = _safe_resolve(path)
|
|
2367
|
+
parent = os.path.dirname(full_path)
|
|
2368
|
+
if parent:
|
|
2369
|
+
os.makedirs(parent, exist_ok=True)
|
|
2370
|
+
with open(full_path, "w") as f:
|
|
2371
|
+
f.write(content)
|
|
2372
|
+
return f"Written to {path}"
|
|
2373
|
+
except ValueError as e:
|
|
2374
|
+
return str(e)
|
|
2375
|
+
except OSError as e:
|
|
2376
|
+
return f"Error writing '{path}': {e.strerror}"
|
|
2377
|
+
|
|
2378
|
+
def list_files(path: str) -> str:
|
|
2379
|
+
"""List files in a mounted filesystem directory. Use the absolute path (e.g. /mnt/tools)."""
|
|
2380
|
+
try:
|
|
2381
|
+
full_path = _safe_resolve(path)
|
|
2382
|
+
entries = os.listdir(full_path)
|
|
2383
|
+
return "\\n".join(entries) if entries else "(empty directory)"
|
|
2384
|
+
except ValueError as e:
|
|
2385
|
+
return str(e)
|
|
2386
|
+
except OSError as e:
|
|
2387
|
+
return f"Error listing '{path}': {e.strerror}"
|
|
2388
|
+
|
|
2389
|
+
_fs_tools = [FunctionTool(file_read), FunctionTool(file_write), FunctionTool(list_files)]
|
|
2390
|
+
{{/if}}
|
|
2391
|
+
|
|
2101
2392
|
agent = LlmAgent(
|
|
2102
2393
|
name="{{ name }}",
|
|
2103
2394
|
model="gemini-2.5-flash",
|
|
2104
|
-
instruction="You are a helpful assistant.
|
|
2105
|
-
|
|
2395
|
+
instruction="""You are a helpful assistant.
|
|
2396
|
+
{{#if needsOs}}
|
|
2397
|
+
You have access to the following mounted filesystems. Use file_read, file_write, and list_files with full absolute paths:
|
|
2398
|
+
{{#if sessionStorageMountPath}}- {{sessionStorageMountPath}}: ephemeral session storage (lost when session ends)
|
|
2399
|
+
{{/if}}{{#each efsMounts}}- {{mountPath}}: EFS persistent storage (persists across sessions and agent restarts)
|
|
2400
|
+
{{/each}}{{#each s3Mounts}}- {{mountPath}}: S3 Files persistent storage (durable, backed by S3)
|
|
2401
|
+
{{/each}}{{/if}}""",
|
|
2402
|
+
tools=[AGUIToolset(), {{#if needsOs}}*_fs_tools{{/if}}],
|
|
2106
2403
|
)
|
|
2107
2404
|
|
|
2108
2405
|
adk_agent = ADKAgent(
|
|
@@ -2188,7 +2485,7 @@ requires-python = ">=3.10"
|
|
|
2188
2485
|
dependencies = [
|
|
2189
2486
|
"ag-ui-adk >= 0.6.0",
|
|
2190
2487
|
"ag-ui-protocol >= 0.1.10",
|
|
2191
|
-
"bedrock-agentcore >= 1.0
|
|
2488
|
+
"bedrock-agentcore >= 1.8.0",
|
|
2192
2489
|
"fastapi >= 0.115.12",
|
|
2193
2490
|
"google-adk >= 1.16.0, < 2.0.0",
|
|
2194
2491
|
"google-genai >= 1.0.0, < 2.0.0",
|
|
@@ -2301,6 +2598,62 @@ def add_numbers(a: int, b: int) -> int:
|
|
|
2301
2598
|
|
|
2302
2599
|
|
|
2303
2600
|
backend_tools = [add_numbers]
|
|
2601
|
+
|
|
2602
|
+
{{#if needsOs}}
|
|
2603
|
+
_MOUNT_PATHS = [
|
|
2604
|
+
{{#if sessionStorageMountPath}}"{{sessionStorageMountPath}}",{{/if}}
|
|
2605
|
+
{{#each efsMounts}}"{{mountPath}}",{{/each}}
|
|
2606
|
+
{{#each s3Mounts}}"{{mountPath}}",{{/each}}
|
|
2607
|
+
]
|
|
2608
|
+
|
|
2609
|
+
def _safe_resolve(path: str) -> str:
|
|
2610
|
+
resolved = os.path.realpath(path)
|
|
2611
|
+
if not any(resolved == os.path.realpath(m) or resolved.startswith(os.path.realpath(m) + os.sep) for m in _MOUNT_PATHS):
|
|
2612
|
+
raise ValueError(f"Path '{path}' is not within any configured mount ({', '.join(_MOUNT_PATHS)})")
|
|
2613
|
+
return resolved
|
|
2614
|
+
|
|
2615
|
+
@tool
|
|
2616
|
+
def file_read(path: str) -> str:
|
|
2617
|
+
"""Read a file from a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
2618
|
+
try:
|
|
2619
|
+
full_path = _safe_resolve(path)
|
|
2620
|
+
with open(full_path) as f:
|
|
2621
|
+
return f.read()
|
|
2622
|
+
except ValueError as e:
|
|
2623
|
+
return str(e)
|
|
2624
|
+
except OSError as e:
|
|
2625
|
+
return f"Error reading '{path}': {e.strerror}"
|
|
2626
|
+
|
|
2627
|
+
@tool
|
|
2628
|
+
def file_write(path: str, content: str) -> str:
|
|
2629
|
+
"""Write a file to a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
2630
|
+
try:
|
|
2631
|
+
full_path = _safe_resolve(path)
|
|
2632
|
+
parent = os.path.dirname(full_path)
|
|
2633
|
+
if parent:
|
|
2634
|
+
os.makedirs(parent, exist_ok=True)
|
|
2635
|
+
with open(full_path, "w") as f:
|
|
2636
|
+
f.write(content)
|
|
2637
|
+
return f"Written to {path}"
|
|
2638
|
+
except ValueError as e:
|
|
2639
|
+
return str(e)
|
|
2640
|
+
except OSError as e:
|
|
2641
|
+
return f"Error writing '{path}': {e.strerror}"
|
|
2642
|
+
|
|
2643
|
+
@tool
|
|
2644
|
+
def list_files(path: str) -> str:
|
|
2645
|
+
"""List files in a mounted filesystem directory. Use the absolute path (e.g. /mnt/tools)."""
|
|
2646
|
+
try:
|
|
2647
|
+
full_path = _safe_resolve(path)
|
|
2648
|
+
entries = os.listdir(full_path)
|
|
2649
|
+
return "\\n".join(entries) if entries else "(empty directory)"
|
|
2650
|
+
except ValueError as e:
|
|
2651
|
+
return str(e)
|
|
2652
|
+
except OSError as e:
|
|
2653
|
+
return f"Error listing '{path}': {e.strerror}"
|
|
2654
|
+
|
|
2655
|
+
backend_tools.extend([file_read, file_write, list_files])
|
|
2656
|
+
{{/if}}
|
|
2304
2657
|
model = load_model()
|
|
2305
2658
|
|
|
2306
2659
|
|
|
@@ -2504,7 +2857,7 @@ dependencies = [
|
|
|
2504
2857
|
{{/if}}{{#if (eq modelProvider "OpenAI")}}"langchain-openai >= 0.2.0",
|
|
2505
2858
|
{{/if}}"aws-opentelemetry-distro",
|
|
2506
2859
|
"opentelemetry-instrumentation-langchain >= 0.59.0",
|
|
2507
|
-
"bedrock-agentcore >= 1.0
|
|
2860
|
+
"bedrock-agentcore >= 1.8.0",
|
|
2508
2861
|
"botocore[crt] >= 1.35.0",
|
|
2509
2862
|
"langgraph >= 0.3.25",
|
|
2510
2863
|
"langchain >= 0.3.0",
|
|
@@ -2613,9 +2966,71 @@ def add_numbers(a: int, b: int) -> int:
|
|
|
2613
2966
|
|
|
2614
2967
|
tools = [add_numbers]
|
|
2615
2968
|
|
|
2969
|
+
{{#if needsOs}}
|
|
2970
|
+
_MOUNT_PATHS = [
|
|
2971
|
+
{{#if sessionStorageMountPath}}"{{sessionStorageMountPath}}",{{/if}}
|
|
2972
|
+
{{#each efsMounts}}"{{mountPath}}",{{/each}}
|
|
2973
|
+
{{#each s3Mounts}}"{{mountPath}}",{{/each}}
|
|
2974
|
+
]
|
|
2975
|
+
|
|
2976
|
+
def _safe_resolve(path: str) -> str:
|
|
2977
|
+
resolved = os.path.realpath(path)
|
|
2978
|
+
if not any(resolved == os.path.realpath(m) or resolved.startswith(os.path.realpath(m) + os.sep) for m in _MOUNT_PATHS):
|
|
2979
|
+
raise ValueError(f"Path '{path}' is not within any configured mount ({', '.join(_MOUNT_PATHS)})")
|
|
2980
|
+
return resolved
|
|
2981
|
+
|
|
2982
|
+
@tool
|
|
2983
|
+
def file_read(path: str) -> str:
|
|
2984
|
+
"""Read a file from a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
2985
|
+
try:
|
|
2986
|
+
full_path = _safe_resolve(path)
|
|
2987
|
+
with open(full_path) as f:
|
|
2988
|
+
return f.read()
|
|
2989
|
+
except ValueError as e:
|
|
2990
|
+
return str(e)
|
|
2991
|
+
except OSError as e:
|
|
2992
|
+
return f"Error reading '{path}': {e.strerror}"
|
|
2993
|
+
|
|
2994
|
+
@tool
|
|
2995
|
+
def file_write(path: str, content: str) -> str:
|
|
2996
|
+
"""Write a file to a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
2997
|
+
try:
|
|
2998
|
+
full_path = _safe_resolve(path)
|
|
2999
|
+
parent = os.path.dirname(full_path)
|
|
3000
|
+
if parent:
|
|
3001
|
+
os.makedirs(parent, exist_ok=True)
|
|
3002
|
+
with open(full_path, "w") as f:
|
|
3003
|
+
f.write(content)
|
|
3004
|
+
return f"Written to {path}"
|
|
3005
|
+
except ValueError as e:
|
|
3006
|
+
return str(e)
|
|
3007
|
+
except OSError as e:
|
|
3008
|
+
return f"Error writing '{path}': {e.strerror}"
|
|
3009
|
+
|
|
3010
|
+
@tool
|
|
3011
|
+
def list_files(path: str) -> str:
|
|
3012
|
+
"""List files in a mounted filesystem directory. Use the absolute path (e.g. /mnt/tools)."""
|
|
3013
|
+
try:
|
|
3014
|
+
full_path = _safe_resolve(path)
|
|
3015
|
+
entries = os.listdir(full_path)
|
|
3016
|
+
return "\\n".join(entries) if entries else "(empty directory)"
|
|
3017
|
+
except ValueError as e:
|
|
3018
|
+
return str(e)
|
|
3019
|
+
except OSError as e:
|
|
3020
|
+
return f"Error listing '{path}': {e.strerror}"
|
|
3021
|
+
|
|
3022
|
+
tools.extend([file_read, file_write, list_files])
|
|
3023
|
+
{{/if}}
|
|
3024
|
+
|
|
2616
3025
|
agent = Agent(
|
|
2617
3026
|
model=load_model(),
|
|
2618
|
-
system_prompt="You are a helpful assistant. Use tools when appropriate.
|
|
3027
|
+
system_prompt="""You are a helpful assistant. Use tools when appropriate.
|
|
3028
|
+
{{#if needsOs}}
|
|
3029
|
+
You have access to the following mounted filesystems. Use file_read, file_write, and list_files with full absolute paths:
|
|
3030
|
+
{{#if sessionStorageMountPath}}- {{sessionStorageMountPath}}: ephemeral session storage (lost when session ends)
|
|
3031
|
+
{{/if}}{{#each efsMounts}}- {{mountPath}}: EFS persistent storage (persists across sessions and agent restarts)
|
|
3032
|
+
{{/each}}{{#each s3Mounts}}- {{mountPath}}: S3 Files persistent storage (durable, backed by S3)
|
|
3033
|
+
{{/each}}{{/if}}""",
|
|
2619
3034
|
tools=tools,
|
|
2620
3035
|
)
|
|
2621
3036
|
|
|
@@ -2940,7 +3355,9 @@ Thumbs.db
|
|
|
2940
3355
|
`;
|
|
2941
3356
|
|
|
2942
3357
|
exports[`Assets Directory Snapshots > Python framework assets > python/python/http/autogen/base/main.py should match snapshot 1`] = `
|
|
2943
|
-
"
|
|
3358
|
+
"{{#if needsOs}}
|
|
3359
|
+
import os
|
|
3360
|
+
{{/if}}
|
|
2944
3361
|
from autogen_agentchat.agents import AssistantAgent
|
|
2945
3362
|
from autogen_core.tools import FunctionTool
|
|
2946
3363
|
from bedrock_agentcore.runtime import BedrockAgentCoreApp
|
|
@@ -2964,18 +3381,21 @@ add_numbers_tool = FunctionTool(
|
|
|
2964
3381
|
# Define a collection of tools used by the model
|
|
2965
3382
|
tools = [add_numbers_tool]
|
|
2966
3383
|
|
|
2967
|
-
{{#if
|
|
2968
|
-
|
|
3384
|
+
{{#if needsOs}}
|
|
3385
|
+
_MOUNT_PATHS = [
|
|
3386
|
+
{{#if sessionStorageMountPath}}"{{sessionStorageMountPath}}",{{/if}}
|
|
3387
|
+
{{#each efsMounts}}"{{mountPath}}",{{/each}}
|
|
3388
|
+
{{#each s3Mounts}}"{{mountPath}}",{{/each}}
|
|
3389
|
+
]
|
|
2969
3390
|
|
|
2970
3391
|
def _safe_resolve(path: str) -> str:
|
|
2971
|
-
|
|
2972
|
-
resolved
|
|
2973
|
-
|
|
2974
|
-
raise ValueError(f"Path '{path}' is outside the storage boundary")
|
|
3392
|
+
resolved = os.path.realpath(path)
|
|
3393
|
+
if not any(resolved == os.path.realpath(m) or resolved.startswith(os.path.realpath(m) + os.sep) for m in _MOUNT_PATHS):
|
|
3394
|
+
raise ValueError(f"Path '{path}' is not within any configured mount ({', '.join(_MOUNT_PATHS)})")
|
|
2975
3395
|
return resolved
|
|
2976
3396
|
|
|
2977
3397
|
def file_read(path: str) -> str:
|
|
2978
|
-
"""Read a file from
|
|
3398
|
+
"""Read a file from a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
2979
3399
|
try:
|
|
2980
3400
|
full_path = _safe_resolve(path)
|
|
2981
3401
|
with open(full_path) as f:
|
|
@@ -2986,7 +3406,7 @@ def file_read(path: str) -> str:
|
|
|
2986
3406
|
return f"Error reading '{path}': {e.strerror}"
|
|
2987
3407
|
|
|
2988
3408
|
def file_write(path: str, content: str) -> str:
|
|
2989
|
-
"""Write
|
|
3409
|
+
"""Write a file to a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
2990
3410
|
try:
|
|
2991
3411
|
full_path = _safe_resolve(path)
|
|
2992
3412
|
parent = os.path.dirname(full_path)
|
|
@@ -3000,29 +3420,32 @@ def file_write(path: str, content: str) -> str:
|
|
|
3000
3420
|
except OSError as e:
|
|
3001
3421
|
return f"Error writing '{path}': {e.strerror}"
|
|
3002
3422
|
|
|
3003
|
-
def list_files(
|
|
3004
|
-
"""List files in
|
|
3423
|
+
def list_files(path: str) -> str:
|
|
3424
|
+
"""List files in a mounted filesystem directory. Use the absolute path (e.g. /mnt/tools)."""
|
|
3005
3425
|
try:
|
|
3006
|
-
|
|
3007
|
-
entries = os.listdir(
|
|
3426
|
+
full_path = _safe_resolve(path)
|
|
3427
|
+
entries = os.listdir(full_path)
|
|
3008
3428
|
return "\\n".join(entries) if entries else "(empty directory)"
|
|
3009
3429
|
except ValueError as e:
|
|
3010
3430
|
return str(e)
|
|
3011
3431
|
except OSError as e:
|
|
3012
|
-
return f"Error listing '{
|
|
3432
|
+
return f"Error listing '{path}': {e.strerror}"
|
|
3013
3433
|
|
|
3014
3434
|
tools.extend([
|
|
3015
|
-
FunctionTool(file_read, description="Read a file from
|
|
3016
|
-
FunctionTool(file_write, description="Write
|
|
3017
|
-
FunctionTool(list_files, description="List files in
|
|
3435
|
+
FunctionTool(file_read, description="Read a file from a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."),
|
|
3436
|
+
FunctionTool(file_write, description="Write a file to a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."),
|
|
3437
|
+
FunctionTool(list_files, description="List files in a mounted filesystem directory. Use the absolute path (e.g. /mnt/tools)."),
|
|
3018
3438
|
])
|
|
3019
3439
|
{{/if}}
|
|
3020
3440
|
|
|
3021
3441
|
SYSTEM_MESSAGE = """
|
|
3022
3442
|
You are a helpful assistant. Use tools when appropriate.
|
|
3023
|
-
{{#if
|
|
3024
|
-
You have
|
|
3025
|
-
{{
|
|
3443
|
+
{{#if needsOs}}
|
|
3444
|
+
You have access to the following mounted filesystems. Use file_read, file_write, and list_files with full absolute paths:
|
|
3445
|
+
{{#if sessionStorageMountPath}}- {{sessionStorageMountPath}}: ephemeral session storage (lost when session ends)
|
|
3446
|
+
{{/if}}{{#each efsMounts}}- {{mountPath}}: EFS persistent storage (persists across sessions and agent restarts)
|
|
3447
|
+
{{/each}}{{#each s3Mounts}}- {{mountPath}}: S3 Files persistent storage (durable, backed by S3)
|
|
3448
|
+
{{/each}}{{/if}}
|
|
3026
3449
|
"""
|
|
3027
3450
|
|
|
3028
3451
|
@app.entrypoint
|
|
@@ -3253,7 +3676,7 @@ dependencies = [
|
|
|
3253
3676
|
"autogen-ext[mcp] >= 0.7.5",
|
|
3254
3677
|
"opentelemetry-distro",
|
|
3255
3678
|
"opentelemetry-exporter-otlp",
|
|
3256
|
-
"bedrock-agentcore >= 1.0
|
|
3679
|
+
"bedrock-agentcore >= 1.8.0",
|
|
3257
3680
|
"botocore[crt] >= 1.35.0",
|
|
3258
3681
|
"tiktoken",
|
|
3259
3682
|
{{#if (eq modelProvider "Bedrock")}}
|
|
@@ -3364,7 +3787,9 @@ Thumbs.db
|
|
|
3364
3787
|
`;
|
|
3365
3788
|
|
|
3366
3789
|
exports[`Assets Directory Snapshots > Python framework assets > python/python/http/googleadk/base/main.py should match snapshot 1`] = `
|
|
3367
|
-
"
|
|
3790
|
+
"{{#if needsOs}}
|
|
3791
|
+
import os
|
|
3792
|
+
{{/if}}
|
|
3368
3793
|
from google.adk.agents import Agent
|
|
3369
3794
|
from google.adk.runners import Runner
|
|
3370
3795
|
from google.adk.sessions import InMemorySessionService
|
|
@@ -3395,18 +3820,21 @@ def add_numbers(a: int, b: int) -> int:
|
|
|
3395
3820
|
# Define a collection of tools used by the model
|
|
3396
3821
|
tools = [add_numbers]
|
|
3397
3822
|
|
|
3398
|
-
{{#if
|
|
3399
|
-
|
|
3823
|
+
{{#if needsOs}}
|
|
3824
|
+
_MOUNT_PATHS = [
|
|
3825
|
+
{{#if sessionStorageMountPath}}"{{sessionStorageMountPath}}",{{/if}}
|
|
3826
|
+
{{#each efsMounts}}"{{mountPath}}",{{/each}}
|
|
3827
|
+
{{#each s3Mounts}}"{{mountPath}}",{{/each}}
|
|
3828
|
+
]
|
|
3400
3829
|
|
|
3401
3830
|
def _safe_resolve(path: str) -> str:
|
|
3402
|
-
|
|
3403
|
-
resolved
|
|
3404
|
-
|
|
3405
|
-
raise ValueError(f"Path '{path}' is outside the storage boundary")
|
|
3831
|
+
resolved = os.path.realpath(path)
|
|
3832
|
+
if not any(resolved == os.path.realpath(m) or resolved.startswith(os.path.realpath(m) + os.sep) for m in _MOUNT_PATHS):
|
|
3833
|
+
raise ValueError(f"Path '{path}' is not within any configured mount ({', '.join(_MOUNT_PATHS)})")
|
|
3406
3834
|
return resolved
|
|
3407
3835
|
|
|
3408
3836
|
def file_read(path: str) -> str:
|
|
3409
|
-
"""Read a file from
|
|
3837
|
+
"""Read a file from a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
3410
3838
|
try:
|
|
3411
3839
|
full_path = _safe_resolve(path)
|
|
3412
3840
|
with open(full_path) as f:
|
|
@@ -3417,7 +3845,7 @@ def file_read(path: str) -> str:
|
|
|
3417
3845
|
return f"Error reading '{path}': {e.strerror}"
|
|
3418
3846
|
|
|
3419
3847
|
def file_write(path: str, content: str) -> str:
|
|
3420
|
-
"""Write
|
|
3848
|
+
"""Write a file to a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
3421
3849
|
try:
|
|
3422
3850
|
full_path = _safe_resolve(path)
|
|
3423
3851
|
parent = os.path.dirname(full_path)
|
|
@@ -3431,25 +3859,28 @@ def file_write(path: str, content: str) -> str:
|
|
|
3431
3859
|
except OSError as e:
|
|
3432
3860
|
return f"Error writing '{path}': {e.strerror}"
|
|
3433
3861
|
|
|
3434
|
-
def list_files(
|
|
3435
|
-
"""List files in
|
|
3862
|
+
def list_files(path: str) -> str:
|
|
3863
|
+
"""List files in a mounted filesystem directory. Use the absolute path (e.g. /mnt/tools)."""
|
|
3436
3864
|
try:
|
|
3437
|
-
|
|
3438
|
-
entries = os.listdir(
|
|
3865
|
+
full_path = _safe_resolve(path)
|
|
3866
|
+
entries = os.listdir(full_path)
|
|
3439
3867
|
return "\\n".join(entries) if entries else "(empty directory)"
|
|
3440
3868
|
except ValueError as e:
|
|
3441
3869
|
return str(e)
|
|
3442
3870
|
except OSError as e:
|
|
3443
|
-
return f"Error listing '{
|
|
3871
|
+
return f"Error listing '{path}': {e.strerror}"
|
|
3444
3872
|
|
|
3445
3873
|
tools.extend([file_read, file_write, list_files])
|
|
3446
3874
|
{{/if}}
|
|
3447
3875
|
|
|
3448
3876
|
AGENT_INSTRUCTION = """
|
|
3449
3877
|
I can answer your questions using the knowledge I have!
|
|
3450
|
-
{{#if
|
|
3451
|
-
You have
|
|
3452
|
-
{{
|
|
3878
|
+
{{#if needsOs}}
|
|
3879
|
+
You have access to the following mounted filesystems. Use file_read, file_write, and list_files with full absolute paths:
|
|
3880
|
+
{{#if sessionStorageMountPath}}- {{sessionStorageMountPath}}: ephemeral session storage (lost when session ends)
|
|
3881
|
+
{{/if}}{{#each efsMounts}}- {{mountPath}}: EFS persistent storage (persists across sessions and agent restarts)
|
|
3882
|
+
{{/each}}{{#each s3Mounts}}- {{mountPath}}: S3 Files persistent storage (durable, backed by S3)
|
|
3883
|
+
{{/each}}{{/if}}
|
|
3453
3884
|
"""
|
|
3454
3885
|
|
|
3455
3886
|
# Get MCP Toolset
|
|
@@ -3777,7 +4208,9 @@ Thumbs.db
|
|
|
3777
4208
|
`;
|
|
3778
4209
|
|
|
3779
4210
|
exports[`Assets Directory Snapshots > Python framework assets > python/python/http/langchain_langgraph/base/main.py should match snapshot 1`] = `
|
|
3780
|
-
"
|
|
4211
|
+
"{{#if needsOs}}
|
|
4212
|
+
import os
|
|
4213
|
+
{{/if}}
|
|
3781
4214
|
from typing import Any
|
|
3782
4215
|
|
|
3783
4216
|
from langchain_core.messages import HumanMessage{{#if hasConfigBundle}}, SystemMessage{{/if}}
|
|
@@ -3812,9 +4245,12 @@ def get_or_create_model():
|
|
|
3812
4245
|
|
|
3813
4246
|
DEFAULT_SYSTEM_PROMPT = """
|
|
3814
4247
|
You are a helpful assistant. Use tools when appropriate.
|
|
3815
|
-
{{#if
|
|
3816
|
-
You have
|
|
3817
|
-
{{
|
|
4248
|
+
{{#if needsOs}}
|
|
4249
|
+
You have access to the following mounted filesystems. Use file_read, file_write, and list_files with full absolute paths:
|
|
4250
|
+
{{#if sessionStorageMountPath}}- {{sessionStorageMountPath}}: ephemeral session storage (lost when session ends)
|
|
4251
|
+
{{/if}}{{#each efsMounts}}- {{mountPath}}: EFS persistent storage (persists across sessions and agent restarts)
|
|
4252
|
+
{{/each}}{{#each s3Mounts}}- {{mountPath}}: S3 Files persistent storage (durable, backed by S3)
|
|
4253
|
+
{{/each}}{{/if}}
|
|
3818
4254
|
"""
|
|
3819
4255
|
|
|
3820
4256
|
|
|
@@ -3828,19 +4264,22 @@ def add_numbers(a: int, b: int) -> int:
|
|
|
3828
4264
|
# Define a collection of tools used by the model
|
|
3829
4265
|
tools = [add_numbers]
|
|
3830
4266
|
|
|
3831
|
-
{{#if
|
|
3832
|
-
|
|
4267
|
+
{{#if needsOs}}
|
|
4268
|
+
_MOUNT_PATHS = [
|
|
4269
|
+
{{#if sessionStorageMountPath}}"{{sessionStorageMountPath}}",{{/if}}
|
|
4270
|
+
{{#each efsMounts}}"{{mountPath}}",{{/each}}
|
|
4271
|
+
{{#each s3Mounts}}"{{mountPath}}",{{/each}}
|
|
4272
|
+
]
|
|
3833
4273
|
|
|
3834
4274
|
def _safe_resolve(path: str) -> str:
|
|
3835
|
-
|
|
3836
|
-
resolved
|
|
3837
|
-
|
|
3838
|
-
raise ValueError(f"Path '{path}' is outside the storage boundary")
|
|
4275
|
+
resolved = os.path.realpath(path)
|
|
4276
|
+
if not any(resolved == os.path.realpath(m) or resolved.startswith(os.path.realpath(m) + os.sep) for m in _MOUNT_PATHS):
|
|
4277
|
+
raise ValueError(f"Path '{path}' is not within any configured mount ({', '.join(_MOUNT_PATHS)})")
|
|
3839
4278
|
return resolved
|
|
3840
4279
|
|
|
3841
4280
|
@tool
|
|
3842
4281
|
def file_read(path: str) -> str:
|
|
3843
|
-
"""Read a file from
|
|
4282
|
+
"""Read a file from a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
3844
4283
|
try:
|
|
3845
4284
|
full_path = _safe_resolve(path)
|
|
3846
4285
|
with open(full_path) as f:
|
|
@@ -3852,7 +4291,7 @@ def file_read(path: str) -> str:
|
|
|
3852
4291
|
|
|
3853
4292
|
@tool
|
|
3854
4293
|
def file_write(path: str, content: str) -> str:
|
|
3855
|
-
"""Write
|
|
4294
|
+
"""Write a file to a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
3856
4295
|
try:
|
|
3857
4296
|
full_path = _safe_resolve(path)
|
|
3858
4297
|
parent = os.path.dirname(full_path)
|
|
@@ -3867,16 +4306,16 @@ def file_write(path: str, content: str) -> str:
|
|
|
3867
4306
|
return f"Error writing '{path}': {e.strerror}"
|
|
3868
4307
|
|
|
3869
4308
|
@tool
|
|
3870
|
-
def list_files(
|
|
3871
|
-
"""List files in
|
|
4309
|
+
def list_files(path: str) -> str:
|
|
4310
|
+
"""List files in a mounted filesystem directory. Use the absolute path (e.g. /mnt/tools)."""
|
|
3872
4311
|
try:
|
|
3873
|
-
|
|
3874
|
-
entries = os.listdir(
|
|
4312
|
+
full_path = _safe_resolve(path)
|
|
4313
|
+
entries = os.listdir(full_path)
|
|
3875
4314
|
return "\\n".join(entries) if entries else "(empty directory)"
|
|
3876
4315
|
except ValueError as e:
|
|
3877
4316
|
return str(e)
|
|
3878
4317
|
except OSError as e:
|
|
3879
|
-
return f"Error listing '{
|
|
4318
|
+
return f"Error listing '{path}': {e.strerror}"
|
|
3880
4319
|
|
|
3881
4320
|
tools.extend([file_read, file_write, list_files])
|
|
3882
4321
|
{{/if}}
|
|
@@ -4192,7 +4631,7 @@ dependencies = [
|
|
|
4192
4631
|
"mcp >= 1.19.0",
|
|
4193
4632
|
"langchain-mcp-adapters >= 0.1.11",
|
|
4194
4633
|
"langchain >= 1.0.3",
|
|
4195
|
-
"bedrock-agentcore >= 1.0
|
|
4634
|
+
"bedrock-agentcore >= 1.8.0",
|
|
4196
4635
|
"botocore[crt] >= 1.35.0",
|
|
4197
4636
|
{{#if (eq modelProvider "Bedrock")}}
|
|
4198
4637
|
"langchain-aws >= 1.0.0",
|
|
@@ -4305,7 +4744,9 @@ Thumbs.db
|
|
|
4305
4744
|
`;
|
|
4306
4745
|
|
|
4307
4746
|
exports[`Assets Directory Snapshots > Python framework assets > python/python/http/openaiagents/base/main.py should match snapshot 1`] = `
|
|
4308
|
-
"
|
|
4747
|
+
"{{#if needsOs}}
|
|
4748
|
+
import os
|
|
4749
|
+
{{/if}}
|
|
4309
4750
|
from agents import Agent, Runner, function_tool
|
|
4310
4751
|
from bedrock_agentcore.runtime import BedrockAgentCoreApp
|
|
4311
4752
|
from model.load import load_model
|
|
@@ -4344,19 +4785,22 @@ def add_numbers(a: int, b: int) -> int:
|
|
|
4344
4785
|
|
|
4345
4786
|
tools = [add_numbers]
|
|
4346
4787
|
|
|
4347
|
-
{{#if
|
|
4348
|
-
|
|
4788
|
+
{{#if needsOs}}
|
|
4789
|
+
_MOUNT_PATHS = [
|
|
4790
|
+
{{#if sessionStorageMountPath}}"{{sessionStorageMountPath}}",{{/if}}
|
|
4791
|
+
{{#each efsMounts}}"{{mountPath}}",{{/each}}
|
|
4792
|
+
{{#each s3Mounts}}"{{mountPath}}",{{/each}}
|
|
4793
|
+
]
|
|
4349
4794
|
|
|
4350
4795
|
def _safe_resolve(path: str) -> str:
|
|
4351
|
-
|
|
4352
|
-
resolved
|
|
4353
|
-
|
|
4354
|
-
raise ValueError(f"Path '{path}' is outside the storage boundary")
|
|
4796
|
+
resolved = os.path.realpath(path)
|
|
4797
|
+
if not any(resolved == os.path.realpath(m) or resolved.startswith(os.path.realpath(m) + os.sep) for m in _MOUNT_PATHS):
|
|
4798
|
+
raise ValueError(f"Path '{path}' is not within any configured mount ({', '.join(_MOUNT_PATHS)})")
|
|
4355
4799
|
return resolved
|
|
4356
4800
|
|
|
4357
4801
|
@function_tool
|
|
4358
4802
|
def file_read(path: str) -> str:
|
|
4359
|
-
"""Read a file from
|
|
4803
|
+
"""Read a file from a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
4360
4804
|
try:
|
|
4361
4805
|
full_path = _safe_resolve(path)
|
|
4362
4806
|
with open(full_path) as f:
|
|
@@ -4368,7 +4812,7 @@ def file_read(path: str) -> str:
|
|
|
4368
4812
|
|
|
4369
4813
|
@function_tool
|
|
4370
4814
|
def file_write(path: str, content: str) -> str:
|
|
4371
|
-
"""Write
|
|
4815
|
+
"""Write a file to a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
4372
4816
|
try:
|
|
4373
4817
|
full_path = _safe_resolve(path)
|
|
4374
4818
|
parent = os.path.dirname(full_path)
|
|
@@ -4383,25 +4827,28 @@ def file_write(path: str, content: str) -> str:
|
|
|
4383
4827
|
return f"Error writing '{path}': {e.strerror}"
|
|
4384
4828
|
|
|
4385
4829
|
@function_tool
|
|
4386
|
-
def list_files(
|
|
4387
|
-
"""List files in
|
|
4830
|
+
def list_files(path: str) -> str:
|
|
4831
|
+
"""List files in a mounted filesystem directory. Use the absolute path (e.g. /mnt/tools)."""
|
|
4388
4832
|
try:
|
|
4389
|
-
|
|
4390
|
-
entries = os.listdir(
|
|
4833
|
+
full_path = _safe_resolve(path)
|
|
4834
|
+
entries = os.listdir(full_path)
|
|
4391
4835
|
return "\\n".join(entries) if entries else "(empty directory)"
|
|
4392
4836
|
except ValueError as e:
|
|
4393
4837
|
return str(e)
|
|
4394
4838
|
except OSError as e:
|
|
4395
|
-
return f"Error listing '{
|
|
4839
|
+
return f"Error listing '{path}': {e.strerror}"
|
|
4396
4840
|
|
|
4397
4841
|
tools.extend([file_read, file_write, list_files])
|
|
4398
4842
|
{{/if}}
|
|
4399
4843
|
|
|
4400
4844
|
INSTRUCTIONS = """
|
|
4401
4845
|
You are a helpful assistant. Use tools when appropriate.
|
|
4402
|
-
{{#if
|
|
4403
|
-
You have
|
|
4404
|
-
{{
|
|
4846
|
+
{{#if needsOs}}
|
|
4847
|
+
You have access to the following mounted filesystems. Use file_read, file_write, and list_files with full absolute paths:
|
|
4848
|
+
{{#if sessionStorageMountPath}}- {{sessionStorageMountPath}}: ephemeral session storage (lost when session ends)
|
|
4849
|
+
{{/if}}{{#each efsMounts}}- {{mountPath}}: EFS persistent storage (persists across sessions and agent restarts)
|
|
4850
|
+
{{/each}}{{#each s3Mounts}}- {{mountPath}}: S3 Files persistent storage (durable, backed by S3)
|
|
4851
|
+
{{/each}}{{/if}}
|
|
4405
4852
|
"""
|
|
4406
4853
|
|
|
4407
4854
|
# Define the agent execution
|
|
@@ -4620,7 +5067,7 @@ requires-python = ">=3.10"
|
|
|
4620
5067
|
dependencies = [
|
|
4621
5068
|
"aws-opentelemetry-distro",
|
|
4622
5069
|
"openai-agents >= 0.4.2",
|
|
4623
|
-
"bedrock-agentcore >= 1.0
|
|
5070
|
+
"bedrock-agentcore >= 1.8.0",
|
|
4624
5071
|
"botocore[crt] >= 1.35.0",
|
|
4625
5072
|
{{#if hasGateway}}{{#if (includes gatewayAuthTypes "AWS_IAM")}}"mcp-proxy-for-aws >= 1.1.0",
|
|
4626
5073
|
{{/if}}{{/if}}
|
|
@@ -4736,9 +5183,12 @@ from mcp_client.client import get_streamable_http_mcp_client
|
|
|
4736
5183
|
{{#if hasMemory}}
|
|
4737
5184
|
from memory.session import get_memory_session_manager
|
|
4738
5185
|
{{/if}}
|
|
4739
|
-
{{#if
|
|
5186
|
+
{{#if needsOs}}
|
|
4740
5187
|
import os
|
|
4741
5188
|
{{/if}}
|
|
5189
|
+
{{#if hasPayment}}
|
|
5190
|
+
from capabilities.payments.payments import create_payments_plugin, PAYMENT_SYSTEM_PROMPT
|
|
5191
|
+
{{/if}}
|
|
4742
5192
|
|
|
4743
5193
|
app = BedrockAgentCoreApp()
|
|
4744
5194
|
log = app.logger
|
|
@@ -4752,9 +5202,12 @@ mcp_clients = [get_streamable_http_mcp_client()]
|
|
|
4752
5202
|
|
|
4753
5203
|
DEFAULT_SYSTEM_PROMPT = """
|
|
4754
5204
|
You are a helpful assistant. Use tools when appropriate.
|
|
4755
|
-
{{#if
|
|
4756
|
-
You have
|
|
4757
|
-
{{
|
|
5205
|
+
{{#if needsOs}}
|
|
5206
|
+
You have access to the following mounted filesystems. Use file_read, file_write, and list_files with full absolute paths:
|
|
5207
|
+
{{#if sessionStorageMountPath}}- {{sessionStorageMountPath}}: ephemeral session storage (lost when session ends)
|
|
5208
|
+
{{/if}}{{#each efsMounts}}- {{mountPath}}: EFS persistent storage (persists across sessions and agent restarts)
|
|
5209
|
+
{{/each}}{{#each s3Mounts}}- {{mountPath}}: S3 Files persistent storage (durable, backed by S3)
|
|
5210
|
+
{{/each}}{{/if}}
|
|
4758
5211
|
"""
|
|
4759
5212
|
|
|
4760
5213
|
{{#if hasConfigBundle}}
|
|
@@ -4775,19 +5228,22 @@ def add_numbers(a: int, b: int) -> int:
|
|
|
4775
5228
|
return a+b
|
|
4776
5229
|
tools.append(add_numbers)
|
|
4777
5230
|
|
|
4778
|
-
{{#if
|
|
4779
|
-
|
|
5231
|
+
{{#if needsOs}}
|
|
5232
|
+
_MOUNT_PATHS = [
|
|
5233
|
+
{{#if sessionStorageMountPath}}"{{sessionStorageMountPath}}",{{/if}}
|
|
5234
|
+
{{#each efsMounts}}"{{mountPath}}",{{/each}}
|
|
5235
|
+
{{#each s3Mounts}}"{{mountPath}}",{{/each}}
|
|
5236
|
+
]
|
|
4780
5237
|
|
|
4781
5238
|
def _safe_resolve(path: str) -> str:
|
|
4782
|
-
|
|
4783
|
-
resolved
|
|
4784
|
-
|
|
4785
|
-
raise ValueError(f"Path '{path}' is outside the storage boundary")
|
|
5239
|
+
resolved = os.path.realpath(path)
|
|
5240
|
+
if not any(resolved == os.path.realpath(m) or resolved.startswith(os.path.realpath(m) + os.sep) for m in _MOUNT_PATHS):
|
|
5241
|
+
raise ValueError(f"Path '{path}' is not within any configured mount ({', '.join(_MOUNT_PATHS)})")
|
|
4786
5242
|
return resolved
|
|
4787
5243
|
|
|
4788
5244
|
@tool
|
|
4789
5245
|
def file_read(path: str) -> str:
|
|
4790
|
-
"""Read a file from
|
|
5246
|
+
"""Read a file from a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
4791
5247
|
try:
|
|
4792
5248
|
full_path = _safe_resolve(path)
|
|
4793
5249
|
with open(full_path) as f:
|
|
@@ -4799,7 +5255,7 @@ def file_read(path: str) -> str:
|
|
|
4799
5255
|
|
|
4800
5256
|
@tool
|
|
4801
5257
|
def file_write(path: str, content: str) -> str:
|
|
4802
|
-
"""Write
|
|
5258
|
+
"""Write a file to a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
4803
5259
|
try:
|
|
4804
5260
|
full_path = _safe_resolve(path)
|
|
4805
5261
|
parent = os.path.dirname(full_path)
|
|
@@ -4814,16 +5270,16 @@ def file_write(path: str, content: str) -> str:
|
|
|
4814
5270
|
return f"Error writing '{path}': {e.strerror}"
|
|
4815
5271
|
|
|
4816
5272
|
@tool
|
|
4817
|
-
def list_files(
|
|
4818
|
-
"""List files in
|
|
5273
|
+
def list_files(path: str) -> str:
|
|
5274
|
+
"""List files in a mounted filesystem directory. Use the absolute path (e.g. /mnt/tools)."""
|
|
4819
5275
|
try:
|
|
4820
|
-
|
|
4821
|
-
entries = os.listdir(
|
|
5276
|
+
full_path = _safe_resolve(path)
|
|
5277
|
+
entries = os.listdir(full_path)
|
|
4822
5278
|
return "\\n".join(entries) if entries else "(empty directory)"
|
|
4823
5279
|
except ValueError as e:
|
|
4824
5280
|
return str(e)
|
|
4825
5281
|
except OSError as e:
|
|
4826
|
-
return f"Error listing '{
|
|
5282
|
+
return f"Error listing '{path}': {e.strerror}"
|
|
4827
5283
|
|
|
4828
5284
|
tools.extend([file_read, file_write, list_files])
|
|
4829
5285
|
{{/if}}
|
|
@@ -4868,12 +5324,12 @@ class ConfigBundleHook(HookProvider):
|
|
|
4868
5324
|
{{/if}}
|
|
4869
5325
|
|
|
4870
5326
|
{{#if hasMemory}}
|
|
5327
|
+
{{#unless hasPayment}}
|
|
4871
5328
|
def agent_factory():
|
|
4872
5329
|
cache = {}
|
|
4873
5330
|
def get_or_create_agent(session_id, user_id):
|
|
4874
5331
|
key = f"{session_id}/{user_id}"
|
|
4875
5332
|
if key not in cache:
|
|
4876
|
-
# Create an agent for the given session_id and user_id
|
|
4877
5333
|
cache[key] = Agent(
|
|
4878
5334
|
model=load_model(),
|
|
4879
5335
|
session_manager=get_memory_session_manager(session_id, user_id),
|
|
@@ -4884,6 +5340,7 @@ def agent_factory():
|
|
|
4884
5340
|
return cache[key]
|
|
4885
5341
|
return get_or_create_agent
|
|
4886
5342
|
get_or_create_agent = agent_factory()
|
|
5343
|
+
{{/unless}}
|
|
4887
5344
|
{{else}}
|
|
4888
5345
|
{{#if hasConfigBundle}}
|
|
4889
5346
|
def create_agent():
|
|
@@ -4894,6 +5351,7 @@ def create_agent():
|
|
|
4894
5351
|
hooks=[ConfigBundleHook()],
|
|
4895
5352
|
)
|
|
4896
5353
|
{{else}}
|
|
5354
|
+
{{#unless hasPayment}}
|
|
4897
5355
|
_agent = None
|
|
4898
5356
|
|
|
4899
5357
|
def get_or_create_agent():
|
|
@@ -4902,9 +5360,10 @@ def get_or_create_agent():
|
|
|
4902
5360
|
_agent = Agent(
|
|
4903
5361
|
model=load_model(),
|
|
4904
5362
|
system_prompt=DEFAULT_SYSTEM_PROMPT,
|
|
4905
|
-
tools=tools
|
|
5363
|
+
tools=tools,
|
|
4906
5364
|
)
|
|
4907
5365
|
return _agent
|
|
5366
|
+
{{/unless}}
|
|
4908
5367
|
{{/if}}
|
|
4909
5368
|
{{/if}}
|
|
4910
5369
|
|
|
@@ -4913,16 +5372,47 @@ def get_or_create_agent():
|
|
|
4913
5372
|
async def invoke(payload, context):
|
|
4914
5373
|
log.info("Invoking Agent.....")
|
|
4915
5374
|
|
|
5375
|
+
{{#if hasPayment}}
|
|
5376
|
+
user_id = payload.get("user_id") or getattr(context, "user_id", "default-user")
|
|
5377
|
+
instrument_id = payload.get("payment_instrument_id")
|
|
5378
|
+
session_id = payload.get("payment_session_id")
|
|
5379
|
+
payments_plugin = create_payments_plugin(user_id, instrument_id, session_id)
|
|
5380
|
+
plugins = [payments_plugin] if payments_plugin else []
|
|
5381
|
+
{{/if}}
|
|
5382
|
+
|
|
4916
5383
|
{{#if hasMemory}}
|
|
5384
|
+
{{#if hasPayment}}
|
|
5385
|
+
mem_session_id = getattr(context, 'session_id', 'default-session')
|
|
5386
|
+
mem_user_id = getattr(context, 'user_id', 'default-user')
|
|
5387
|
+
agent = Agent(
|
|
5388
|
+
model=load_model(),
|
|
5389
|
+
session_manager=get_memory_session_manager(mem_session_id, mem_user_id),
|
|
5390
|
+
system_prompt=DEFAULT_SYSTEM_PROMPT + PAYMENT_SYSTEM_PROMPT,
|
|
5391
|
+
tools=tools,
|
|
5392
|
+
plugins=plugins,{{#if hasConfigBundle}}
|
|
5393
|
+
hooks=[ConfigBundleHook()],{{/if}}
|
|
5394
|
+
)
|
|
5395
|
+
{{else}}
|
|
4917
5396
|
session_id = getattr(context, 'session_id', 'default-session')
|
|
4918
5397
|
user_id = getattr(context, 'user_id', 'default-user')
|
|
4919
5398
|
agent = get_or_create_agent(session_id, user_id)
|
|
5399
|
+
{{/if}}
|
|
5400
|
+
{{else}}
|
|
5401
|
+
{{#if hasPayment}}
|
|
5402
|
+
agent = Agent(
|
|
5403
|
+
model=load_model(),
|
|
5404
|
+
system_prompt=DEFAULT_SYSTEM_PROMPT + PAYMENT_SYSTEM_PROMPT,
|
|
5405
|
+
tools=tools,
|
|
5406
|
+
plugins=plugins,{{#if hasConfigBundle}}
|
|
5407
|
+
hooks=[ConfigBundleHook()],{{/if}}
|
|
5408
|
+
)
|
|
4920
5409
|
{{else}}
|
|
4921
5410
|
{{#if hasConfigBundle}}
|
|
4922
5411
|
agent = create_agent()
|
|
4923
5412
|
{{else}}
|
|
4924
5413
|
agent = get_or_create_agent()
|
|
4925
5414
|
{{/if}}
|
|
5415
|
+
{{/if}}
|
|
4926
5416
|
{{/if}}
|
|
4927
5417
|
|
|
4928
5418
|
# Execute and format response
|
|
@@ -5235,6 +5725,160 @@ def get_memory_session_manager(session_id: Optional[str], actor_id: str) -> Opti
|
|
|
5235
5725
|
"
|
|
5236
5726
|
`;
|
|
5237
5727
|
|
|
5728
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/http/strands/capabilities/payments/__init__.py should match snapshot 1`] = `
|
|
5729
|
+
""""Payment capabilities for Strands agents."""
|
|
5730
|
+
from .payments import create_payments_plugin
|
|
5731
|
+
|
|
5732
|
+
__all__ = ["create_payments_plugin"]
|
|
5733
|
+
"
|
|
5734
|
+
`;
|
|
5735
|
+
|
|
5736
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/http/strands/capabilities/payments/payments.py should match snapshot 1`] = `
|
|
5737
|
+
""""Payment capability -- auto-generated by agentcore CLI.
|
|
5738
|
+
|
|
5739
|
+
Configures AgentCorePaymentsPlugin for Strands agents.
|
|
5740
|
+
Manager config is auto-discovered from AGENTCORE_PAYMENT_*
|
|
5741
|
+
environment variables set at deploy time.
|
|
5742
|
+
|
|
5743
|
+
Uses a per-invocation factory pattern so each request gets its own
|
|
5744
|
+
plugin instance with the correct user_id, instrument_id, and session_id.
|
|
5745
|
+
This prevents concurrency bugs where one user's payment context
|
|
5746
|
+
could leak to another user's request.
|
|
5747
|
+
|
|
5748
|
+
Uses the official SDK plugin which handles:
|
|
5749
|
+
- x402 v1 (body-based) and v2 (header-based) payment detection
|
|
5750
|
+
- Automatic 402 response interception and payment processing
|
|
5751
|
+
- Retry limiting (max 3 payment retries per tool use)
|
|
5752
|
+
- Error management and logging
|
|
5753
|
+
"""
|
|
5754
|
+
import os
|
|
5755
|
+
import logging
|
|
5756
|
+
|
|
5757
|
+
import boto3
|
|
5758
|
+
from bedrock_agentcore.payments.integrations.strands import AgentCorePaymentsPlugin
|
|
5759
|
+
from bedrock_agentcore.payments.integrations.config import AgentCorePaymentsPluginConfig
|
|
5760
|
+
|
|
5761
|
+
logger = logging.getLogger(__name__)
|
|
5762
|
+
|
|
5763
|
+
PAYMENT_SYSTEM_PROMPT = """
|
|
5764
|
+
You have payment capabilities via the x402 protocol:
|
|
5765
|
+
- Use http_request to call HTTP endpoints. 402 Payment Required responses are settled automatically by the plugin and the call is retried.
|
|
5766
|
+
- Use get_payment_session to check your remaining budget before expensive operations
|
|
5767
|
+
- Use get_payment_instrument_balance to check wallet USDC balance
|
|
5768
|
+
- Use list_payment_instruments to see available payment instruments
|
|
5769
|
+
- If budget is low, inform the user before proceeding with paid requests
|
|
5770
|
+
"""
|
|
5771
|
+
|
|
5772
|
+
_manager_arn = None
|
|
5773
|
+
_connector_id = None
|
|
5774
|
+
_process_payment_role_arn = None
|
|
5775
|
+
_name_segment = None
|
|
5776
|
+
_region = None
|
|
5777
|
+
_auth_mode = None
|
|
5778
|
+
_manager_count = 0
|
|
5779
|
+
for key, value in os.environ.items():
|
|
5780
|
+
if key.startswith("AGENTCORE_PAYMENT_") and key.endswith("_MANAGER_ARN"):
|
|
5781
|
+
if _manager_arn is None:
|
|
5782
|
+
_manager_arn = value
|
|
5783
|
+
_name_segment = key[len("AGENTCORE_PAYMENT_"):-len("_MANAGER_ARN")]
|
|
5784
|
+
_manager_count += 1
|
|
5785
|
+
if _manager_count > 1:
|
|
5786
|
+
logger.warning(
|
|
5787
|
+
"Multiple payment managers detected in environment. Using the first one found. "
|
|
5788
|
+
"Remove extra AGENTCORE_PAYMENT_*_MANAGER_ARN env vars to eliminate ambiguity."
|
|
5789
|
+
)
|
|
5790
|
+
_region = os.getenv("AWS_REGION")
|
|
5791
|
+
|
|
5792
|
+
_prefix = f"AGENTCORE_PAYMENT_{_name_segment}_" if _name_segment else "AGENTCORE_PAYMENT_"
|
|
5793
|
+
_auth_mode = os.getenv(f"{_prefix}AUTH_MODE", "sigv4")
|
|
5794
|
+
_connector_id = os.getenv(f"{_prefix}CONNECTOR_ID")
|
|
5795
|
+
_process_payment_role_arn = os.getenv(f"{_prefix}PROCESS_PAYMENT_ROLE_ARN")
|
|
5796
|
+
_auto_payment = os.getenv(f"{_prefix}AUTO_PAYMENT", "true").lower() == "true"
|
|
5797
|
+
_allowlist_raw = os.getenv(f"{_prefix}TOOL_ALLOWLIST")
|
|
5798
|
+
_allowlist = _allowlist_raw.split(",") if _allowlist_raw else None
|
|
5799
|
+
_network_prefs_raw = os.getenv(f"{_prefix}NETWORK_PREFERENCES")
|
|
5800
|
+
_network_prefs = _network_prefs_raw.split(",") if _network_prefs_raw else None
|
|
5801
|
+
|
|
5802
|
+
if not _manager_arn:
|
|
5803
|
+
logger.warning("No payment manager config found in environment")
|
|
5804
|
+
if not _connector_id:
|
|
5805
|
+
logger.warning("No payment connector config found in environment")
|
|
5806
|
+
|
|
5807
|
+
|
|
5808
|
+
def _assume_role_session(role_arn):
|
|
5809
|
+
"""Assume an IAM role and return a boto3 session with temporary credentials."""
|
|
5810
|
+
sts = boto3.client("sts", region_name=_region)
|
|
5811
|
+
creds = sts.assume_role(
|
|
5812
|
+
RoleArn=role_arn,
|
|
5813
|
+
RoleSessionName="agentcore-payment-plugin",
|
|
5814
|
+
)["Credentials"]
|
|
5815
|
+
return boto3.Session(
|
|
5816
|
+
aws_access_key_id=creds["AccessKeyId"],
|
|
5817
|
+
aws_secret_access_key=creds["SecretAccessKey"],
|
|
5818
|
+
aws_session_token=creds["SessionToken"],
|
|
5819
|
+
region_name=_region,
|
|
5820
|
+
)
|
|
5821
|
+
|
|
5822
|
+
|
|
5823
|
+
def create_payments_plugin(user_id, instrument_id=None, session_id=None):
|
|
5824
|
+
"""Create a fresh plugin instance per invocation.
|
|
5825
|
+
|
|
5826
|
+
Args:
|
|
5827
|
+
user_id: From invocation context (required for SigV4, derived from JWT for bearer)
|
|
5828
|
+
instrument_id: From invocation payload (created by app backend per user)
|
|
5829
|
+
session_id: From invocation payload (created by app backend per conversation)
|
|
5830
|
+
|
|
5831
|
+
Returns:
|
|
5832
|
+
AgentCorePaymentsPlugin instance, or None if no manager is configured.
|
|
5833
|
+
"""
|
|
5834
|
+
if not _manager_arn:
|
|
5835
|
+
return None
|
|
5836
|
+
|
|
5837
|
+
config_kwargs = {
|
|
5838
|
+
"payment_manager_arn": _manager_arn,
|
|
5839
|
+
"region": _region,
|
|
5840
|
+
"payment_instrument_id": instrument_id,
|
|
5841
|
+
"payment_session_id": session_id,
|
|
5842
|
+
"payment_connector_id": _connector_id,
|
|
5843
|
+
}
|
|
5844
|
+
|
|
5845
|
+
config_kwargs["auto_payment"] = _auto_payment
|
|
5846
|
+
if _allowlist:
|
|
5847
|
+
config_kwargs["payment_tool_allowlist"] = _allowlist
|
|
5848
|
+
if _network_prefs:
|
|
5849
|
+
config_kwargs["network_preferences_config"] = _network_prefs
|
|
5850
|
+
|
|
5851
|
+
if _process_payment_role_arn:
|
|
5852
|
+
# Only pass boto3_session if SDK supports it (added in bedrock-agentcore >= 1.11).
|
|
5853
|
+
# Older SDKs use the runtime role's default credentials and can still call ProcessPayment
|
|
5854
|
+
# if the runtime role has been granted permission directly.
|
|
5855
|
+
import inspect
|
|
5856
|
+
if "boto3_session" in inspect.signature(AgentCorePaymentsPluginConfig).parameters:
|
|
5857
|
+
config_kwargs["boto3_session"] = _assume_role_session(_process_payment_role_arn)
|
|
5858
|
+
else:
|
|
5859
|
+
logger.warning(
|
|
5860
|
+
"PROCESS_PAYMENT_ROLE_ARN set but bedrock-agentcore SDK does not support boto3_session. "
|
|
5861
|
+
"Upgrade to bedrock-agentcore>=1.11 to enable cross-role payment processing."
|
|
5862
|
+
)
|
|
5863
|
+
|
|
5864
|
+
if _auth_mode == "bearer":
|
|
5865
|
+
bearer_token = os.getenv("AGENTCORE_BEARER_TOKEN")
|
|
5866
|
+
if bearer_token:
|
|
5867
|
+
config_kwargs["bearer_token"] = bearer_token
|
|
5868
|
+
else:
|
|
5869
|
+
logger.warning(
|
|
5870
|
+
"Bearer auth mode configured but AGENTCORE_BEARER_TOKEN not set. "
|
|
5871
|
+
"Falling back to SigV4. Set AGENTCORE_BEARER_TOKEN or pass bearer_token in invoke context."
|
|
5872
|
+
)
|
|
5873
|
+
config_kwargs["user_id"] = user_id or "default-user"
|
|
5874
|
+
else:
|
|
5875
|
+
config_kwargs["user_id"] = user_id or "default-user"
|
|
5876
|
+
|
|
5877
|
+
config = AgentCorePaymentsPluginConfig(**config_kwargs)
|
|
5878
|
+
return AgentCorePaymentsPlugin(config=config)
|
|
5879
|
+
"
|
|
5880
|
+
`;
|
|
5881
|
+
|
|
5238
5882
|
exports[`Assets Directory Snapshots > Python framework assets > python/python/mcp/standalone/base/README.md should match snapshot 1`] = `
|
|
5239
5883
|
"# {{ name }}
|
|
5240
5884
|
|
|
@@ -5321,10 +5965,68 @@ Thumbs.db
|
|
|
5321
5965
|
`;
|
|
5322
5966
|
|
|
5323
5967
|
exports[`Assets Directory Snapshots > Python framework assets > python/python/mcp/standalone/base/main.py should match snapshot 1`] = `
|
|
5324
|
-
"
|
|
5968
|
+
"{{#if needsOs}}
|
|
5969
|
+
import os
|
|
5970
|
+
{{/if}}
|
|
5971
|
+
from mcp.server.fastmcp import FastMCP
|
|
5325
5972
|
|
|
5326
5973
|
mcp = FastMCP("{{ name }}", host="0.0.0.0", stateless_http=True)
|
|
5327
5974
|
|
|
5975
|
+
{{#if needsOs}}
|
|
5976
|
+
_MOUNT_PATHS = [
|
|
5977
|
+
{{#if sessionStorageMountPath}}"{{sessionStorageMountPath}}",{{/if}}
|
|
5978
|
+
{{#each efsMounts}}"{{mountPath}}",{{/each}}
|
|
5979
|
+
{{#each s3Mounts}}"{{mountPath}}",{{/each}}
|
|
5980
|
+
]
|
|
5981
|
+
|
|
5982
|
+
def _safe_resolve(path: str) -> str:
|
|
5983
|
+
resolved = os.path.realpath(path)
|
|
5984
|
+
if not any(resolved == os.path.realpath(m) or resolved.startswith(os.path.realpath(m) + os.sep) for m in _MOUNT_PATHS):
|
|
5985
|
+
raise ValueError(f"Path '{path}' is not within any configured mount ({', '.join(_MOUNT_PATHS)})")
|
|
5986
|
+
return resolved
|
|
5987
|
+
|
|
5988
|
+
@mcp.tool()
|
|
5989
|
+
def file_read(path: str) -> str:
|
|
5990
|
+
"""Read a file from a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
5991
|
+
try:
|
|
5992
|
+
full_path = _safe_resolve(path)
|
|
5993
|
+
with open(full_path) as f:
|
|
5994
|
+
return f.read()
|
|
5995
|
+
except ValueError as e:
|
|
5996
|
+
return str(e)
|
|
5997
|
+
except OSError as e:
|
|
5998
|
+
return f"Error reading '{path}': {e.strerror}"
|
|
5999
|
+
|
|
6000
|
+
@mcp.tool()
|
|
6001
|
+
def file_write(path: str, content: str) -> str:
|
|
6002
|
+
"""Write a file to a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
6003
|
+
try:
|
|
6004
|
+
full_path = _safe_resolve(path)
|
|
6005
|
+
parent = os.path.dirname(full_path)
|
|
6006
|
+
if parent:
|
|
6007
|
+
os.makedirs(parent, exist_ok=True)
|
|
6008
|
+
with open(full_path, "w") as f:
|
|
6009
|
+
f.write(content)
|
|
6010
|
+
return f"Written to {path}"
|
|
6011
|
+
except ValueError as e:
|
|
6012
|
+
return str(e)
|
|
6013
|
+
except OSError as e:
|
|
6014
|
+
return f"Error writing '{path}': {e.strerror}"
|
|
6015
|
+
|
|
6016
|
+
@mcp.tool()
|
|
6017
|
+
def list_files(path: str) -> str:
|
|
6018
|
+
"""List files in a mounted filesystem directory. Use the absolute path (e.g. /mnt/tools)."""
|
|
6019
|
+
try:
|
|
6020
|
+
full_path = _safe_resolve(path)
|
|
6021
|
+
entries = os.listdir(full_path)
|
|
6022
|
+
return "\\n".join(entries) if entries else "(empty directory)"
|
|
6023
|
+
except ValueError as e:
|
|
6024
|
+
return str(e)
|
|
6025
|
+
except OSError as e:
|
|
6026
|
+
return f"Error listing '{path}': {e.strerror}"
|
|
6027
|
+
|
|
6028
|
+
{{/if}}
|
|
6029
|
+
|
|
5328
6030
|
|
|
5329
6031
|
@mcp.tool()
|
|
5330
6032
|
def add_numbers(a: int, b: int) -> int:
|