@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
|
@@ -28,12 +28,14 @@ async function main() {
|
|
|
28
28
|
const spec = await configIO.readProjectSpec();
|
|
29
29
|
const targets = await configIO.readAWSDeploymentTargets();
|
|
30
30
|
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
// dynamically and cast the resulting object.
|
|
31
|
+
// The vended CDK project compiles against the published @aws/agentcore-cdk
|
|
32
|
+
// schema type, which may lag the CLI's own AgentCoreProjectSpec (e.g. payments,
|
|
33
|
+
// harnesses, gateway fields). Cast once so those fields are reachable.
|
|
35
34
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
35
|
const specAny = spec as any;
|
|
36
|
+
|
|
37
|
+
// Extract MCP configuration from project spec.
|
|
38
|
+
// Gateway fields are stored in agentcore.json but may not yet be on the
|
|
37
39
|
const mcpSpec = specAny.agentCoreGateways?.length
|
|
38
40
|
? {
|
|
39
41
|
agentCoreGateways: specAny.agentCoreGateways,
|
|
@@ -66,6 +68,9 @@ async function main() {
|
|
|
66
68
|
codeLocation?: string;
|
|
67
69
|
tools?: { type: string; name: string }[];
|
|
68
70
|
apiKeyArn?: string;
|
|
71
|
+
efsAccessPoints?: { accessPointArn: string; mountPath: string }[];
|
|
72
|
+
s3AccessPoints?: { accessPointArn: string; mountPath: string }[];
|
|
73
|
+
apiFormat?: 'converse_stream' | 'responses' | 'chat_completions';
|
|
69
74
|
}[] = [];
|
|
70
75
|
for (const entry of specAny.harnesses ?? []) {
|
|
71
76
|
const harnessDir = path.resolve(projectRoot, entry.path);
|
|
@@ -82,6 +87,9 @@ async function main() {
|
|
|
82
87
|
codeLocation: harnessSpec.dockerfile ? harnessDir : undefined,
|
|
83
88
|
tools: harnessSpec.tools,
|
|
84
89
|
apiKeyArn: harnessSpec.model?.apiKeyArn,
|
|
90
|
+
efsAccessPoints: harnessSpec.efsAccessPoints,
|
|
91
|
+
s3AccessPoints: harnessSpec.s3AccessPoints,
|
|
92
|
+
apiFormat: harnessSpec.model?.apiFormat,
|
|
85
93
|
});
|
|
86
94
|
} catch (err) {
|
|
87
95
|
throw new Error(
|
|
@@ -105,11 +113,51 @@ async function main() {
|
|
|
105
113
|
| Record<string, { credentialProviderArn: string; clientSecretArn?: string }>
|
|
106
114
|
| undefined;
|
|
107
115
|
|
|
116
|
+
// Payment credential provider ARNs live in the same credentials map as identity credentials
|
|
117
|
+
const paymentCredentials = credentials;
|
|
118
|
+
|
|
119
|
+
const paymentSpec = specAny.payments?.length
|
|
120
|
+
? specAny.payments.map(
|
|
121
|
+
(p: {
|
|
122
|
+
name: string;
|
|
123
|
+
description?: string;
|
|
124
|
+
authorizerType: 'AWS_IAM' | 'CUSTOM_JWT';
|
|
125
|
+
authorizerConfiguration?: unknown;
|
|
126
|
+
autoPayment?: boolean;
|
|
127
|
+
paymentToolAllowlist?: string[];
|
|
128
|
+
networkPreferences?: string[];
|
|
129
|
+
connectors: { name: string; provider?: string; credentialName: string }[];
|
|
130
|
+
}) => ({
|
|
131
|
+
name: p.name,
|
|
132
|
+
description: p.description,
|
|
133
|
+
authorizerType: p.authorizerType,
|
|
134
|
+
authorizerConfiguration: p.authorizerConfiguration,
|
|
135
|
+
autoPayment: p.autoPayment,
|
|
136
|
+
paymentToolAllowlist: p.paymentToolAllowlist,
|
|
137
|
+
networkPreferences: p.networkPreferences,
|
|
138
|
+
connectors: p.connectors.map(c => {
|
|
139
|
+
const credentialProviderArn = paymentCredentials?.[c.credentialName]?.credentialProviderArn;
|
|
140
|
+
if (!credentialProviderArn) {
|
|
141
|
+
// Fail fast with an actionable message rather than passing an empty
|
|
142
|
+
// ARN that fails opaquely server-side at CreatePaymentConnector.
|
|
143
|
+
throw new Error(
|
|
144
|
+
`Payment connector "${c.name}" on manager "${p.name}" references credential ` +
|
|
145
|
+
`"${c.credentialName}", but no deployed credential provider was found for it. ` +
|
|
146
|
+
`Run \`agentcore deploy\` so the credential provider is created first.`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
return { name: c.name, provider: c.provider, credentialProviderArn };
|
|
150
|
+
}),
|
|
151
|
+
})
|
|
152
|
+
)
|
|
153
|
+
: undefined;
|
|
154
|
+
|
|
108
155
|
new AgentCoreStack(app, stackName, {
|
|
109
156
|
spec,
|
|
110
157
|
mcpSpec,
|
|
111
158
|
credentials,
|
|
112
159
|
harnesses: harnessConfigs.length > 0 ? harnessConfigs : undefined,
|
|
160
|
+
paymentSpec,
|
|
113
161
|
env,
|
|
114
162
|
description: `AgentCore stack for ${spec.name} deployed to ${target.name} (${target.region})`,
|
|
115
163
|
tags: {
|
|
@@ -124,5 +172,5 @@ async function main() {
|
|
|
124
172
|
|
|
125
173
|
main().catch((error: unknown) => {
|
|
126
174
|
console.error('AgentCore CDK synthesis failed:', error instanceof Error ? error.message : error);
|
|
127
|
-
process.
|
|
175
|
+
process.exit(1);
|
|
128
176
|
});
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AgentCoreApplication,
|
|
3
3
|
AgentCoreMcp,
|
|
4
|
+
AgentCorePaymentManager,
|
|
5
|
+
AgentCorePaymentConnector,
|
|
4
6
|
type AgentCoreProjectSpec,
|
|
5
7
|
type AgentCoreMcpSpec,
|
|
8
|
+
type CustomJWTAuthorizerConfig,
|
|
6
9
|
} from '@aws/agentcore-cdk';
|
|
7
10
|
import { CfnOutput, Stack, type StackProps } from 'aws-cdk-lib';
|
|
11
|
+
import * as iam from 'aws-cdk-lib/aws-iam';
|
|
8
12
|
import { Construct } from 'constructs';
|
|
9
13
|
|
|
10
14
|
export interface HarnessConfig {
|
|
@@ -17,6 +21,25 @@ export interface HarnessConfig {
|
|
|
17
21
|
codeLocation?: string;
|
|
18
22
|
tools?: { type: string; name: string }[];
|
|
19
23
|
apiKeyArn?: string;
|
|
24
|
+
efsAccessPoints?: { accessPointArn: string; mountPath: string }[];
|
|
25
|
+
s3AccessPoints?: { accessPointArn: string; mountPath: string }[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface PaymentConnectorSpec {
|
|
29
|
+
name: string;
|
|
30
|
+
provider: 'CoinbaseCDP' | 'StripePrivy';
|
|
31
|
+
credentialProviderArn: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface PaymentSpec {
|
|
35
|
+
name: string;
|
|
36
|
+
description?: string;
|
|
37
|
+
authorizerType: 'AWS_IAM' | 'CUSTOM_JWT';
|
|
38
|
+
authorizerConfiguration?: { customJWTAuthorizer: CustomJWTAuthorizerConfig };
|
|
39
|
+
autoPayment?: boolean;
|
|
40
|
+
paymentToolAllowlist?: string[];
|
|
41
|
+
networkPreferences?: string[];
|
|
42
|
+
connectors: PaymentConnectorSpec[];
|
|
20
43
|
}
|
|
21
44
|
|
|
22
45
|
export interface AgentCoreStackProps extends StackProps {
|
|
@@ -36,6 +59,30 @@ export interface AgentCoreStackProps extends StackProps {
|
|
|
36
59
|
* Harness role configurations.
|
|
37
60
|
*/
|
|
38
61
|
harnesses?: HarnessConfig[];
|
|
62
|
+
/**
|
|
63
|
+
* Payment specifications with resolved credential provider ARNs.
|
|
64
|
+
*/
|
|
65
|
+
paymentSpec?: PaymentSpec[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function toCdkId(name: string): string {
|
|
69
|
+
return name.replace(/_/g, '');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Decide whether a deployed runtime should receive payment env vars + IAM grants.
|
|
74
|
+
* Payments today only ships a runtime shim for Python HTTP runtimes; injecting
|
|
75
|
+
* AGENTCORE_PAYMENT_* env vars into TypeScript / MCP / A2A / AGUI runtimes
|
|
76
|
+
* would surface env vars they cannot consume and would dilute least-privilege
|
|
77
|
+
* IAM grants for runtimes that never call ProcessPayment.
|
|
78
|
+
*/
|
|
79
|
+
function isPaymentEligibleAgent(agent: { entrypoint?: string; protocol?: string }): boolean {
|
|
80
|
+
if (agent.protocol && agent.protocol !== 'HTTP') {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const entrypoint = typeof agent.entrypoint === 'string' ? agent.entrypoint : '';
|
|
84
|
+
const entrypointFile = entrypoint.split(':')[0] ?? '';
|
|
85
|
+
return entrypointFile.endsWith('.py');
|
|
39
86
|
}
|
|
40
87
|
|
|
41
88
|
/**
|
|
@@ -51,7 +98,7 @@ export class AgentCoreStack extends Stack {
|
|
|
51
98
|
constructor(scope: Construct, id: string, props: AgentCoreStackProps) {
|
|
52
99
|
super(scope, id, props);
|
|
53
100
|
|
|
54
|
-
const { spec, mcpSpec, credentials, harnesses } = props;
|
|
101
|
+
const { spec, mcpSpec, credentials, harnesses, paymentSpec } = props;
|
|
55
102
|
|
|
56
103
|
// Create AgentCoreApplication with all agents and harness roles
|
|
57
104
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -72,6 +119,122 @@ export class AgentCoreStack extends Stack {
|
|
|
72
119
|
});
|
|
73
120
|
}
|
|
74
121
|
|
|
122
|
+
// Create payment infrastructure via CFN constructs
|
|
123
|
+
if (paymentSpec && paymentSpec.length > 0) {
|
|
124
|
+
for (const payment of paymentSpec) {
|
|
125
|
+
const mgrId = toCdkId(payment.name);
|
|
126
|
+
const manager = new AgentCorePaymentManager(this, `Payment${mgrId}`, {
|
|
127
|
+
projectName: spec.name,
|
|
128
|
+
name: payment.name,
|
|
129
|
+
authorizerType: payment.authorizerType,
|
|
130
|
+
description: payment.description,
|
|
131
|
+
authorizerConfiguration: payment.authorizerConfiguration,
|
|
132
|
+
tags: spec.tags,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const prefix = `AGENTCORE_PAYMENT_${payment.name.toUpperCase().replace(/-/g, '_')}`;
|
|
136
|
+
|
|
137
|
+
// Wire env vars from construct output tokens into eligible agent environments only.
|
|
138
|
+
// See isPaymentEligibleAgent — non-Python or non-HTTP runtimes have no shim that
|
|
139
|
+
// can consume these env vars, and giving them sts:AssumeRole on the
|
|
140
|
+
// ProcessPaymentRole would broaden the privilege surface unnecessarily.
|
|
141
|
+
for (const env of this.application.environments.values()) {
|
|
142
|
+
if (!isPaymentEligibleAgent(env.agent)) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
env.runtime.addEnvironmentVariable(`${prefix}_MANAGER_ARN`, manager.paymentManagerArn);
|
|
146
|
+
env.runtime.addEnvironmentVariable(`${prefix}_PROCESS_PAYMENT_ROLE_ARN`, manager.processPaymentRoleArn);
|
|
147
|
+
|
|
148
|
+
// Grant runtime execution role permission to assume the ProcessPaymentRole.
|
|
149
|
+
// The ProcessPaymentRole's trust policy allows AccountRootPrincipal, but the
|
|
150
|
+
// caller still needs sts:AssumeRole on its own role to perform the assumption.
|
|
151
|
+
env.runtime.role.addToPrincipalPolicy(
|
|
152
|
+
new iam.PolicyStatement({
|
|
153
|
+
actions: ['sts:AssumeRole'],
|
|
154
|
+
resources: [manager.processPaymentRoleArn],
|
|
155
|
+
})
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// Grant payment data-plane actions directly to the runtime role.
|
|
159
|
+
//
|
|
160
|
+
// NOTE: This deviates from the canonical role model in the AgentCore Payments
|
|
161
|
+
// beta guide, which assigns Get/List/Create instrument+session actions to a
|
|
162
|
+
// separate ManagementRole and limits the agent's role to ProcessPayment only.
|
|
163
|
+
// The current SDK plugin (AgentCorePaymentsPlugin.generate_payment_header)
|
|
164
|
+
// calls GetPaymentInstrument internally during the 402 auto-pay path, so the
|
|
165
|
+
// runtime role needs read access. CreatePaymentSession is included so
|
|
166
|
+
// `agentcore invoke --auto-session` works without a separate ManagementRole
|
|
167
|
+
// call. Tighten this if the SDK is updated to accept pre-fetched instrument
|
|
168
|
+
// details and split create-session into a backend-only flow.
|
|
169
|
+
env.runtime.role.addToPrincipalPolicy(
|
|
170
|
+
new iam.PolicyStatement({
|
|
171
|
+
actions: [
|
|
172
|
+
'bedrock-agentcore:GetPaymentInstrument',
|
|
173
|
+
'bedrock-agentcore:ListPaymentInstruments',
|
|
174
|
+
'bedrock-agentcore:GetPaymentInstrumentBalance',
|
|
175
|
+
'bedrock-agentcore:GetPaymentSession',
|
|
176
|
+
'bedrock-agentcore:ListPaymentSessions',
|
|
177
|
+
'bedrock-agentcore:CreatePaymentSession',
|
|
178
|
+
'bedrock-agentcore:ProcessPayment',
|
|
179
|
+
],
|
|
180
|
+
resources: [manager.paymentManagerArn, `${manager.paymentManagerArn}/*`],
|
|
181
|
+
})
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
if (payment.autoPayment !== undefined) {
|
|
185
|
+
env.runtime.addEnvironmentVariable(`${prefix}_AUTO_PAYMENT`, String(payment.autoPayment));
|
|
186
|
+
}
|
|
187
|
+
if (payment.paymentToolAllowlist) {
|
|
188
|
+
env.runtime.addEnvironmentVariable(`${prefix}_TOOL_ALLOWLIST`, payment.paymentToolAllowlist.join(','));
|
|
189
|
+
}
|
|
190
|
+
if (payment.networkPreferences) {
|
|
191
|
+
env.runtime.addEnvironmentVariable(`${prefix}_NETWORK_PREFERENCES`, payment.networkPreferences.join(','));
|
|
192
|
+
}
|
|
193
|
+
if (payment.authorizerType === 'CUSTOM_JWT') {
|
|
194
|
+
env.runtime.addEnvironmentVariable(`${prefix}_AUTH_MODE`, 'bearer');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Create connectors for this manager
|
|
199
|
+
for (const connector of payment.connectors) {
|
|
200
|
+
const connId = toCdkId(connector.name);
|
|
201
|
+
const conn = new AgentCorePaymentConnector(this, `Payment${mgrId}${connId}`, {
|
|
202
|
+
projectName: spec.name,
|
|
203
|
+
paymentManager: manager,
|
|
204
|
+
connectorName: connector.name,
|
|
205
|
+
connectorType: connector.provider,
|
|
206
|
+
credentialProviderArn: connector.credentialProviderArn,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Wire first connector's ID as env var (eligible agents only)
|
|
210
|
+
if (connector === payment.connectors[0]) {
|
|
211
|
+
for (const env of this.application.environments.values()) {
|
|
212
|
+
if (!isPaymentEligibleAgent(env.agent)) continue;
|
|
213
|
+
env.runtime.addEnvironmentVariable(`${prefix}_CONNECTOR_ID`, conn.paymentConnectorId);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
new CfnOutput(this, `Payment${mgrId}${connId}ConnectorId`, {
|
|
218
|
+
value: conn.paymentConnectorId,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// CFN Outputs for post-deploy state parsing
|
|
223
|
+
new CfnOutput(this, `Payment${mgrId}ManagerArn`, {
|
|
224
|
+
value: manager.paymentManagerArn,
|
|
225
|
+
});
|
|
226
|
+
new CfnOutput(this, `Payment${mgrId}ManagerId`, {
|
|
227
|
+
value: manager.paymentManagerId,
|
|
228
|
+
});
|
|
229
|
+
new CfnOutput(this, `Payment${mgrId}ProcessPaymentRoleArn`, {
|
|
230
|
+
value: manager.processPaymentRoleArn,
|
|
231
|
+
});
|
|
232
|
+
new CfnOutput(this, `Payment${mgrId}ResourceRetrievalRoleArn`, {
|
|
233
|
+
value: manager.resourceRetrievalRoleArn,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
75
238
|
// Stack-level output
|
|
76
239
|
new CfnOutput(this, 'StackNameOutput', {
|
|
77
240
|
description: 'Name of the CloudFormation Stack',
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
{{#if needsOs}}
|
|
1
2
|
import os
|
|
3
|
+
{{/if}}
|
|
2
4
|
from google.adk.agents import Agent
|
|
3
5
|
from google.adk.a2a.executor.a2a_agent_executor import A2aAgentExecutor
|
|
4
6
|
from google.adk.runners import Runner
|
|
@@ -17,18 +19,21 @@ def add_numbers(a: int, b: int) -> int:
|
|
|
17
19
|
|
|
18
20
|
tools = [add_numbers]
|
|
19
21
|
|
|
20
|
-
{{#if
|
|
21
|
-
|
|
22
|
+
{{#if needsOs}}
|
|
23
|
+
_MOUNT_PATHS = [
|
|
24
|
+
{{#if sessionStorageMountPath}}"{{sessionStorageMountPath}}",{{/if}}
|
|
25
|
+
{{#each efsMounts}}"{{mountPath}}",{{/each}}
|
|
26
|
+
{{#each s3Mounts}}"{{mountPath}}",{{/each}}
|
|
27
|
+
]
|
|
22
28
|
|
|
23
29
|
def _safe_resolve(path: str) -> str:
|
|
24
|
-
|
|
25
|
-
resolved
|
|
26
|
-
|
|
27
|
-
raise ValueError(f"Path '{path}' is outside the storage boundary")
|
|
30
|
+
resolved = os.path.realpath(path)
|
|
31
|
+
if not any(resolved == os.path.realpath(m) or resolved.startswith(os.path.realpath(m) + os.sep) for m in _MOUNT_PATHS):
|
|
32
|
+
raise ValueError(f"Path '{path}' is not within any configured mount ({', '.join(_MOUNT_PATHS)})")
|
|
28
33
|
return resolved
|
|
29
34
|
|
|
30
35
|
def file_read(path: str) -> str:
|
|
31
|
-
"""Read a file from
|
|
36
|
+
"""Read a file from a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
32
37
|
try:
|
|
33
38
|
full_path = _safe_resolve(path)
|
|
34
39
|
with open(full_path) as f:
|
|
@@ -39,7 +44,7 @@ def file_read(path: str) -> str:
|
|
|
39
44
|
return f"Error reading '{path}': {e.strerror}"
|
|
40
45
|
|
|
41
46
|
def file_write(path: str, content: str) -> str:
|
|
42
|
-
"""Write
|
|
47
|
+
"""Write a file to a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
43
48
|
try:
|
|
44
49
|
full_path = _safe_resolve(path)
|
|
45
50
|
parent = os.path.dirname(full_path)
|
|
@@ -53,25 +58,28 @@ def file_write(path: str, content: str) -> str:
|
|
|
53
58
|
except OSError as e:
|
|
54
59
|
return f"Error writing '{path}': {e.strerror}"
|
|
55
60
|
|
|
56
|
-
def list_files(
|
|
57
|
-
"""List files in
|
|
61
|
+
def list_files(path: str) -> str:
|
|
62
|
+
"""List files in a mounted filesystem directory. Use the absolute path (e.g. /mnt/tools)."""
|
|
58
63
|
try:
|
|
59
|
-
|
|
60
|
-
entries = os.listdir(
|
|
64
|
+
full_path = _safe_resolve(path)
|
|
65
|
+
entries = os.listdir(full_path)
|
|
61
66
|
return "\n".join(entries) if entries else "(empty directory)"
|
|
62
67
|
except ValueError as e:
|
|
63
68
|
return str(e)
|
|
64
69
|
except OSError as e:
|
|
65
|
-
return f"Error listing '{
|
|
70
|
+
return f"Error listing '{path}': {e.strerror}"
|
|
66
71
|
|
|
67
72
|
tools.extend([file_read, file_write, list_files])
|
|
68
73
|
{{/if}}
|
|
69
74
|
|
|
70
75
|
AGENT_INSTRUCTION = """
|
|
71
76
|
You are a helpful assistant. Use tools when appropriate.
|
|
72
|
-
{{#if
|
|
73
|
-
You have
|
|
74
|
-
{{
|
|
77
|
+
{{#if needsOs}}
|
|
78
|
+
You have access to the following mounted filesystems. Use file_read, file_write, and list_files with full absolute paths:
|
|
79
|
+
{{#if sessionStorageMountPath}}- {{sessionStorageMountPath}}: ephemeral session storage (lost when session ends)
|
|
80
|
+
{{/if}}{{#each efsMounts}}- {{mountPath}}: EFS persistent storage (persists across sessions and agent restarts)
|
|
81
|
+
{{/each}}{{#each s3Mounts}}- {{mountPath}}: S3 Files persistent storage (durable, backed by S3)
|
|
82
|
+
{{/each}}{{/if}}
|
|
75
83
|
"""
|
|
76
84
|
|
|
77
85
|
agent = Agent(
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
{{#if needsOs}}
|
|
1
2
|
import os
|
|
3
|
+
{{/if}}
|
|
2
4
|
from langchain_core.tools import tool
|
|
3
5
|
from langgraph.prebuilt import create_react_agent
|
|
4
6
|
from opentelemetry.instrumentation.langchain import LangchainInstrumentor
|
|
@@ -21,19 +23,22 @@ def add_numbers(a: int, b: int) -> int:
|
|
|
21
23
|
|
|
22
24
|
tools = [add_numbers]
|
|
23
25
|
|
|
24
|
-
{{#if
|
|
25
|
-
|
|
26
|
+
{{#if needsOs}}
|
|
27
|
+
_MOUNT_PATHS = [
|
|
28
|
+
{{#if sessionStorageMountPath}}"{{sessionStorageMountPath}}",{{/if}}
|
|
29
|
+
{{#each efsMounts}}"{{mountPath}}",{{/each}}
|
|
30
|
+
{{#each s3Mounts}}"{{mountPath}}",{{/each}}
|
|
31
|
+
]
|
|
26
32
|
|
|
27
33
|
def _safe_resolve(path: str) -> str:
|
|
28
|
-
|
|
29
|
-
resolved
|
|
30
|
-
|
|
31
|
-
raise ValueError(f"Path '{path}' is outside the storage boundary")
|
|
34
|
+
resolved = os.path.realpath(path)
|
|
35
|
+
if not any(resolved == os.path.realpath(m) or resolved.startswith(os.path.realpath(m) + os.sep) for m in _MOUNT_PATHS):
|
|
36
|
+
raise ValueError(f"Path '{path}' is not within any configured mount ({', '.join(_MOUNT_PATHS)})")
|
|
32
37
|
return resolved
|
|
33
38
|
|
|
34
39
|
@tool
|
|
35
40
|
def file_read(path: str) -> str:
|
|
36
|
-
"""Read a file from
|
|
41
|
+
"""Read a file from a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
37
42
|
try:
|
|
38
43
|
full_path = _safe_resolve(path)
|
|
39
44
|
with open(full_path) as f:
|
|
@@ -45,7 +50,7 @@ def file_read(path: str) -> str:
|
|
|
45
50
|
|
|
46
51
|
@tool
|
|
47
52
|
def file_write(path: str, content: str) -> str:
|
|
48
|
-
"""Write
|
|
53
|
+
"""Write a file to a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
49
54
|
try:
|
|
50
55
|
full_path = _safe_resolve(path)
|
|
51
56
|
parent = os.path.dirname(full_path)
|
|
@@ -60,25 +65,28 @@ def file_write(path: str, content: str) -> str:
|
|
|
60
65
|
return f"Error writing '{path}': {e.strerror}"
|
|
61
66
|
|
|
62
67
|
@tool
|
|
63
|
-
def list_files(
|
|
64
|
-
"""List files in
|
|
68
|
+
def list_files(path: str) -> str:
|
|
69
|
+
"""List files in a mounted filesystem directory. Use the absolute path (e.g. /mnt/tools)."""
|
|
65
70
|
try:
|
|
66
|
-
|
|
67
|
-
entries = os.listdir(
|
|
71
|
+
full_path = _safe_resolve(path)
|
|
72
|
+
entries = os.listdir(full_path)
|
|
68
73
|
return "\n".join(entries) if entries else "(empty directory)"
|
|
69
74
|
except ValueError as e:
|
|
70
75
|
return str(e)
|
|
71
76
|
except OSError as e:
|
|
72
|
-
return f"Error listing '{
|
|
77
|
+
return f"Error listing '{path}': {e.strerror}"
|
|
73
78
|
|
|
74
79
|
tools.extend([file_read, file_write, list_files])
|
|
75
80
|
{{/if}}
|
|
76
81
|
|
|
77
82
|
SYSTEM_PROMPT = """
|
|
78
83
|
You are a helpful assistant. Use tools when appropriate.
|
|
79
|
-
{{#if
|
|
80
|
-
You have
|
|
81
|
-
{{
|
|
84
|
+
{{#if needsOs}}
|
|
85
|
+
You have access to the following mounted filesystems. Use file_read, file_write, and list_files with full absolute paths:
|
|
86
|
+
{{#if sessionStorageMountPath}}- {{sessionStorageMountPath}}: ephemeral session storage (lost when session ends)
|
|
87
|
+
{{/if}}{{#each efsMounts}}- {{mountPath}}: EFS persistent storage (persists across sessions and agent restarts)
|
|
88
|
+
{{/each}}{{#each s3Mounts}}- {{mountPath}}: S3 Files persistent storage (durable, backed by S3)
|
|
89
|
+
{{/each}}{{/if}}
|
|
82
90
|
"""
|
|
83
91
|
|
|
84
92
|
model = load_model()
|
|
@@ -17,7 +17,7 @@ dependencies = [
|
|
|
17
17
|
{{/if}}{{#if (eq modelProvider "OpenAI")}}"langchain-openai >= 0.2.0",
|
|
18
18
|
{{/if}}"aws-opentelemetry-distro",
|
|
19
19
|
"opentelemetry-instrumentation-langchain >= 0.59.0",
|
|
20
|
-
"bedrock-agentcore[a2a] >= 1.0
|
|
20
|
+
"bedrock-agentcore[a2a] >= 1.8.0",
|
|
21
21
|
"botocore[crt] >= 1.35.0",
|
|
22
22
|
"langgraph >= 0.2.0",
|
|
23
23
|
]
|
|
@@ -5,7 +5,7 @@ from model.load import load_model
|
|
|
5
5
|
{{#if hasMemory}}
|
|
6
6
|
from memory.session import get_memory_session_manager
|
|
7
7
|
{{/if}}
|
|
8
|
-
{{#if
|
|
8
|
+
{{#if needsOs}}
|
|
9
9
|
import os
|
|
10
10
|
{{/if}}
|
|
11
11
|
|
|
@@ -18,19 +18,22 @@ def add_numbers(a: int, b: int) -> int:
|
|
|
18
18
|
|
|
19
19
|
tools = [add_numbers]
|
|
20
20
|
|
|
21
|
-
{{#if
|
|
22
|
-
|
|
21
|
+
{{#if needsOs}}
|
|
22
|
+
_MOUNT_PATHS = [
|
|
23
|
+
{{#if sessionStorageMountPath}}"{{sessionStorageMountPath}}",{{/if}}
|
|
24
|
+
{{#each efsMounts}}"{{mountPath}}",{{/each}}
|
|
25
|
+
{{#each s3Mounts}}"{{mountPath}}",{{/each}}
|
|
26
|
+
]
|
|
23
27
|
|
|
24
28
|
def _safe_resolve(path: str) -> str:
|
|
25
|
-
|
|
26
|
-
resolved
|
|
27
|
-
|
|
28
|
-
raise ValueError(f"Path '{path}' is outside the storage boundary")
|
|
29
|
+
resolved = os.path.realpath(path)
|
|
30
|
+
if not any(resolved == os.path.realpath(m) or resolved.startswith(os.path.realpath(m) + os.sep) for m in _MOUNT_PATHS):
|
|
31
|
+
raise ValueError(f"Path '{path}' is not within any configured mount ({', '.join(_MOUNT_PATHS)})")
|
|
29
32
|
return resolved
|
|
30
33
|
|
|
31
34
|
@tool
|
|
32
35
|
def file_read(path: str) -> str:
|
|
33
|
-
"""Read a file from
|
|
36
|
+
"""Read a file from a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
34
37
|
try:
|
|
35
38
|
full_path = _safe_resolve(path)
|
|
36
39
|
with open(full_path) as f:
|
|
@@ -42,7 +45,7 @@ def file_read(path: str) -> str:
|
|
|
42
45
|
|
|
43
46
|
@tool
|
|
44
47
|
def file_write(path: str, content: str) -> str:
|
|
45
|
-
"""Write
|
|
48
|
+
"""Write a file to a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
46
49
|
try:
|
|
47
50
|
full_path = _safe_resolve(path)
|
|
48
51
|
parent = os.path.dirname(full_path)
|
|
@@ -57,25 +60,28 @@ def file_write(path: str, content: str) -> str:
|
|
|
57
60
|
return f"Error writing '{path}': {e.strerror}"
|
|
58
61
|
|
|
59
62
|
@tool
|
|
60
|
-
def list_files(
|
|
61
|
-
"""List files in
|
|
63
|
+
def list_files(path: str) -> str:
|
|
64
|
+
"""List files in a mounted filesystem directory. Use the absolute path (e.g. /mnt/tools)."""
|
|
62
65
|
try:
|
|
63
|
-
|
|
64
|
-
entries = os.listdir(
|
|
66
|
+
full_path = _safe_resolve(path)
|
|
67
|
+
entries = os.listdir(full_path)
|
|
65
68
|
return "\n".join(entries) if entries else "(empty directory)"
|
|
66
69
|
except ValueError as e:
|
|
67
70
|
return str(e)
|
|
68
71
|
except OSError as e:
|
|
69
|
-
return f"Error listing '{
|
|
72
|
+
return f"Error listing '{path}': {e.strerror}"
|
|
70
73
|
|
|
71
74
|
tools.extend([file_read, file_write, list_files])
|
|
72
75
|
{{/if}}
|
|
73
76
|
|
|
74
77
|
SYSTEM_PROMPT = """
|
|
75
78
|
You are a helpful assistant. Use tools when appropriate.
|
|
76
|
-
{{#if
|
|
77
|
-
You have
|
|
78
|
-
{{
|
|
79
|
+
{{#if needsOs}}
|
|
80
|
+
You have access to the following mounted filesystems. Use file_read, file_write, and list_files with full absolute paths:
|
|
81
|
+
{{#if sessionStorageMountPath}}- {{sessionStorageMountPath}}: ephemeral session storage (lost when session ends)
|
|
82
|
+
{{/if}}{{#each efsMounts}}- {{mountPath}}: EFS persistent storage (persists across sessions and agent restarts)
|
|
83
|
+
{{/each}}{{#each s3Mounts}}- {{mountPath}}: S3 Files persistent storage (durable, backed by S3)
|
|
84
|
+
{{/each}}{{/if}}
|
|
79
85
|
"""
|
|
80
86
|
|
|
81
87
|
{{#if hasMemory}}
|
|
@@ -1,16 +1,76 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import uvicorn
|
|
3
3
|
from google.adk.agents import LlmAgent
|
|
4
|
+
from google.adk.tools import FunctionTool
|
|
4
5
|
from ag_ui_adk import ADKAgent, AGUIToolset, create_adk_app
|
|
5
6
|
from model.load import load_model
|
|
6
7
|
|
|
7
8
|
load_model()
|
|
8
9
|
|
|
10
|
+
{{#if needsOs}}
|
|
11
|
+
_MOUNT_PATHS = [
|
|
12
|
+
{{#if sessionStorageMountPath}}"{{sessionStorageMountPath}}",{{/if}}
|
|
13
|
+
{{#each efsMounts}}"{{mountPath}}",{{/each}}
|
|
14
|
+
{{#each s3Mounts}}"{{mountPath}}",{{/each}}
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
def _safe_resolve(path: str) -> str:
|
|
18
|
+
resolved = os.path.realpath(path)
|
|
19
|
+
if not any(resolved == os.path.realpath(m) or resolved.startswith(os.path.realpath(m) + os.sep) for m in _MOUNT_PATHS):
|
|
20
|
+
raise ValueError(f"Path '{path}' is not within any configured mount ({', '.join(_MOUNT_PATHS)})")
|
|
21
|
+
return resolved
|
|
22
|
+
|
|
23
|
+
def file_read(path: str) -> str:
|
|
24
|
+
"""Read a file from a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
25
|
+
try:
|
|
26
|
+
full_path = _safe_resolve(path)
|
|
27
|
+
with open(full_path) as f:
|
|
28
|
+
return f.read()
|
|
29
|
+
except ValueError as e:
|
|
30
|
+
return str(e)
|
|
31
|
+
except OSError as e:
|
|
32
|
+
return f"Error reading '{path}': {e.strerror}"
|
|
33
|
+
|
|
34
|
+
def file_write(path: str, content: str) -> str:
|
|
35
|
+
"""Write a file to a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
|
|
36
|
+
try:
|
|
37
|
+
full_path = _safe_resolve(path)
|
|
38
|
+
parent = os.path.dirname(full_path)
|
|
39
|
+
if parent:
|
|
40
|
+
os.makedirs(parent, exist_ok=True)
|
|
41
|
+
with open(full_path, "w") as f:
|
|
42
|
+
f.write(content)
|
|
43
|
+
return f"Written to {path}"
|
|
44
|
+
except ValueError as e:
|
|
45
|
+
return str(e)
|
|
46
|
+
except OSError as e:
|
|
47
|
+
return f"Error writing '{path}': {e.strerror}"
|
|
48
|
+
|
|
49
|
+
def list_files(path: str) -> str:
|
|
50
|
+
"""List files in a mounted filesystem directory. Use the absolute path (e.g. /mnt/tools)."""
|
|
51
|
+
try:
|
|
52
|
+
full_path = _safe_resolve(path)
|
|
53
|
+
entries = os.listdir(full_path)
|
|
54
|
+
return "\n".join(entries) if entries else "(empty directory)"
|
|
55
|
+
except ValueError as e:
|
|
56
|
+
return str(e)
|
|
57
|
+
except OSError as e:
|
|
58
|
+
return f"Error listing '{path}': {e.strerror}"
|
|
59
|
+
|
|
60
|
+
_fs_tools = [FunctionTool(file_read), FunctionTool(file_write), FunctionTool(list_files)]
|
|
61
|
+
{{/if}}
|
|
62
|
+
|
|
9
63
|
agent = LlmAgent(
|
|
10
64
|
name="{{ name }}",
|
|
11
65
|
model="gemini-2.5-flash",
|
|
12
|
-
instruction="You are a helpful assistant.
|
|
13
|
-
|
|
66
|
+
instruction="""You are a helpful assistant.
|
|
67
|
+
{{#if needsOs}}
|
|
68
|
+
You have access to the following mounted filesystems. Use file_read, file_write, and list_files with full absolute paths:
|
|
69
|
+
{{#if sessionStorageMountPath}}- {{sessionStorageMountPath}}: ephemeral session storage (lost when session ends)
|
|
70
|
+
{{/if}}{{#each efsMounts}}- {{mountPath}}: EFS persistent storage (persists across sessions and agent restarts)
|
|
71
|
+
{{/each}}{{#each s3Mounts}}- {{mountPath}}: S3 Files persistent storage (durable, backed by S3)
|
|
72
|
+
{{/each}}{{/if}}""",
|
|
73
|
+
tools=[AGUIToolset(), {{#if needsOs}}*_fs_tools{{/if}}],
|
|
14
74
|
)
|
|
15
75
|
|
|
16
76
|
adk_agent = ADKAgent(
|