@aws/nx-plugin 1.0.0-rc.16 → 1.0.0-rc.18

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.
Files changed (30) hide show
  1. package/generators.json +9 -1
  2. package/package.json +1 -1
  3. package/src/agentcore-gateway/attach-upstream.d.ts +33 -0
  4. package/src/agentcore-gateway/attach-upstream.js +41 -0
  5. package/src/agentcore-gateway/attach-upstream.js.map +1 -0
  6. package/src/agentcore-gateway/files/project/serve-local.ts.template +3 -2
  7. package/src/agentcore-gateway/gateway-connection/generator.d.ts +21 -0
  8. package/src/agentcore-gateway/gateway-connection/generator.js +102 -0
  9. package/src/agentcore-gateway/gateway-connection/generator.js.map +1 -0
  10. package/src/agentcore-gateway/gateway-connection/schema.d.ts +12 -0
  11. package/src/agentcore-gateway/gateway-connection/schema.json +26 -0
  12. package/src/agentcore-gateway/mcp-connection/generator.js +8 -24
  13. package/src/agentcore-gateway/mcp-connection/generator.js.map +1 -1
  14. package/src/connection/generator.js +66 -63
  15. package/src/connection/generator.js.map +1 -1
  16. package/src/license/generator.js +8 -19
  17. package/src/license/generator.js.map +1 -1
  18. package/src/license/known-exceptions.d.ts +0 -1
  19. package/src/license/known-exceptions.js +1 -8
  20. package/src/license/known-exceptions.js.map +1 -1
  21. package/src/py/agent/generator.js +0 -5
  22. package/src/py/agent/generator.js.map +1 -1
  23. package/src/py/fast-api/__snapshots__/generator.spec.ts.snap +122 -14
  24. package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +122 -14
  25. package/src/utils/agent-core-constructs/files/cdk/app/agentcore-gateway/__nameKebabCase__/__nameKebabCase__.ts.template +3 -0
  26. package/src/utils/agent-core-constructs/files/cdk/core/agentcore-gateway/agentcore-gateway.ts.template +110 -28
  27. package/src/utils/agent-core-constructs/files/terraform/app/agentcore-gateway/__nameKebabCase__/__nameKebabCase__.tf.template +90 -2
  28. package/src/utils/api-constructs/files/cdk/core/api/utils/utils.ts.template +61 -7
  29. package/src/utils/versions.d.ts +1 -1
  30. package/src/utils/versions.js +1 -1
@@ -1751,6 +1751,8 @@ export type ApiIntegrations<TOperation extends string, TBaseIntegration> =
1751
1751
  * @template TDefaultIntegrationProps - Type for default integration properties
1752
1752
  * @template TDefaultIntegration - Type for default integration implementation
1753
1753
  * @template TPattern - The integration pattern ('shared' or 'isolated')
1754
+ * @template TOverridden - Union of operation names that have been overridden
1755
+ * @template TOperationOptioned - Union of operation names with per-operation options
1754
1756
  */
1755
1757
  export class IntegrationBuilder<
1756
1758
  TOperation extends string,
@@ -1759,6 +1761,8 @@ export class IntegrationBuilder<
1759
1761
  TDefaultIntegrationProps extends object,
1760
1762
  TDefaultIntegration extends TBaseIntegration,
1761
1763
  TPattern extends 'shared' | 'isolated',
1764
+ TOverridden extends string = never,
1765
+ TOperationOptioned extends string = never,
1762
1766
  > {
1763
1767
  /** Options for the integration builder */
1764
1768
  private options: IntegrationBuilderProps<
@@ -1771,6 +1775,11 @@ export class IntegrationBuilder<
1771
1775
  /** Map of operation names to their custom integrations */
1772
1776
  private integrations: Partial<TIntegrations> = {};
1773
1777
 
1778
+ /** Map of default integration keys to per-operation default option overrides */
1779
+ private perOperationOptions: Partial<
1780
+ Record<string, Partial<TDefaultIntegrationProps>>
1781
+ > = {};
1782
+
1774
1783
  /**
1775
1784
  * Create an Integration Builder for an HTTP API with a shared pattern
1776
1785
  */
@@ -1925,11 +1934,16 @@ export class IntegrationBuilder<
1925
1934
  /**
1926
1935
  * Overrides default integrations with custom implementations for specific operations.
1927
1936
  *
1937
+ * Operations which have per-operation options set via withOperationOptions cannot be
1938
+ * overridden, since these still use the default integration.
1939
+ *
1928
1940
  * @param overrides - Map of operation names to their custom integration implementations
1929
1941
  * @returns The builder instance with updated type information reflecting the overrides
1930
1942
  */
1931
1943
  public withOverrides<
1932
- TOverrideIntegrations extends Partial<Record<TOperation, TBaseIntegration>>,
1944
+ TOverrideIntegrations extends Partial<
1945
+ Record<Exclude<TOperation, TOperationOptioned>, TBaseIntegration>
1946
+ >,
1933
1947
  >(overrides: TOverrideIntegrations) {
1934
1948
  this.integrations = { ...this.integrations, ...overrides };
1935
1949
  // Re-type to include the overridden integration types.
@@ -1945,7 +1959,9 @@ export class IntegrationBuilder<
1945
1959
  MergedIntegrations,
1946
1960
  TDefaultIntegrationProps,
1947
1961
  TDefaultIntegration,
1948
- TPattern
1962
+ TPattern,
1963
+ TOverridden | Extract<keyof TOverrideIntegrations, string>,
1964
+ TOperationOptioned
1949
1965
  >;
1950
1966
  }
1951
1967
 
@@ -1964,6 +1980,43 @@ export class IntegrationBuilder<
1964
1980
  return this;
1965
1981
  }
1966
1982
 
1983
+ /**
1984
+ * Updates the default integration options for specific operations, without
1985
+ * affecting the others. The given options are merged with the defaults (and
1986
+ * any options set via withDefaultOptions) when building each default integration.
1987
+ *
1988
+ * Operations which have been replaced via withOverrides cannot be targeted, since
1989
+ * they no longer use the default integration.
1990
+ *
1991
+ * @param options - Map of operation names to partial default integration options
1992
+ * @returns The builder instance
1993
+ */
1994
+ public withOperationOptions<
1995
+ TOptions extends Partial<
1996
+ Record<
1997
+ Exclude<DefaultIntegrationKeys<TPattern, TOperation>, TOverridden>,
1998
+ Partial<TDefaultIntegrationProps>
1999
+ >
2000
+ >,
2001
+ >(options: TOptions) {
2002
+ Object.entries(options).forEach(([op, opOptions]) => {
2003
+ this.perOperationOptions[op] = {
2004
+ ...this.perOperationOptions[op],
2005
+ ...(opOptions as Partial<TDefaultIntegrationProps>),
2006
+ };
2007
+ });
2008
+ return this as unknown as IntegrationBuilder<
2009
+ TOperation,
2010
+ TBaseIntegration,
2011
+ TIntegrations,
2012
+ TDefaultIntegrationProps,
2013
+ TDefaultIntegration,
2014
+ TPattern,
2015
+ TOverridden,
2016
+ TOperationOptioned | Extract<keyof TOptions, string>
2017
+ >;
2018
+ }
2019
+
1967
2020
  /**
1968
2021
  * Builds and returns the complete set of integrations.
1969
2022
  *
@@ -1978,6 +2031,10 @@ export class IntegrationBuilder<
1978
2031
  */
1979
2032
  public build(): TIntegrations {
1980
2033
  const options = this.options;
2034
+ const buildOptionsFor = (op: string): TDefaultIntegrationProps => ({
2035
+ ...options.defaultIntegrationOptions,
2036
+ ...this.perOperationOptions[op],
2037
+ });
1981
2038
  return {
1982
2039
  ...Object.fromEntries(
1983
2040
  options.pattern === 'shared'
@@ -1986,7 +2043,7 @@ export class IntegrationBuilder<
1986
2043
  '$router',
1987
2044
  options.buildDefaultIntegration(
1988
2045
  '$router',
1989
- options.defaultIntegrationOptions,
2046
+ buildOptionsFor('$router'),
1990
2047
  ),
1991
2048
  ],
1992
2049
  ]
@@ -1997,10 +2054,7 @@ export class IntegrationBuilder<
1997
2054
  )
1998
2055
  .map((op) => [
1999
2056
  op,
2000
- options.buildDefaultIntegration(
2001
- op,
2002
- options.defaultIntegrationOptions,
2003
- ),
2057
+ options.buildDefaultIntegration(op, buildOptionsFor(op)),
2004
2058
  ]),
2005
2059
  ),
2006
2060
  ...this.integrations,
@@ -2708,6 +2762,8 @@ export type ApiIntegrations<TOperation extends string, TBaseIntegration> =
2708
2762
  * @template TDefaultIntegrationProps - Type for default integration properties
2709
2763
  * @template TDefaultIntegration - Type for default integration implementation
2710
2764
  * @template TPattern - The integration pattern ('shared' or 'isolated')
2765
+ * @template TOverridden - Union of operation names that have been overridden
2766
+ * @template TOperationOptioned - Union of operation names with per-operation options
2711
2767
  */
2712
2768
  export class IntegrationBuilder<
2713
2769
  TOperation extends string,
@@ -2716,6 +2772,8 @@ export class IntegrationBuilder<
2716
2772
  TDefaultIntegrationProps extends object,
2717
2773
  TDefaultIntegration extends TBaseIntegration,
2718
2774
  TPattern extends 'shared' | 'isolated',
2775
+ TOverridden extends string = never,
2776
+ TOperationOptioned extends string = never,
2719
2777
  > {
2720
2778
  /** Options for the integration builder */
2721
2779
  private options: IntegrationBuilderProps<
@@ -2728,6 +2786,11 @@ export class IntegrationBuilder<
2728
2786
  /** Map of operation names to their custom integrations */
2729
2787
  private integrations: Partial<TIntegrations> = {};
2730
2788
 
2789
+ /** Map of default integration keys to per-operation default option overrides */
2790
+ private perOperationOptions: Partial<
2791
+ Record<string, Partial<TDefaultIntegrationProps>>
2792
+ > = {};
2793
+
2731
2794
  /**
2732
2795
  * Create an Integration Builder for an HTTP API with a shared pattern
2733
2796
  */
@@ -2882,11 +2945,16 @@ export class IntegrationBuilder<
2882
2945
  /**
2883
2946
  * Overrides default integrations with custom implementations for specific operations.
2884
2947
  *
2948
+ * Operations which have per-operation options set via withOperationOptions cannot be
2949
+ * overridden, since these still use the default integration.
2950
+ *
2885
2951
  * @param overrides - Map of operation names to their custom integration implementations
2886
2952
  * @returns The builder instance with updated type information reflecting the overrides
2887
2953
  */
2888
2954
  public withOverrides<
2889
- TOverrideIntegrations extends Partial<Record<TOperation, TBaseIntegration>>,
2955
+ TOverrideIntegrations extends Partial<
2956
+ Record<Exclude<TOperation, TOperationOptioned>, TBaseIntegration>
2957
+ >,
2890
2958
  >(overrides: TOverrideIntegrations) {
2891
2959
  this.integrations = { ...this.integrations, ...overrides };
2892
2960
  // Re-type to include the overridden integration types.
@@ -2902,7 +2970,9 @@ export class IntegrationBuilder<
2902
2970
  MergedIntegrations,
2903
2971
  TDefaultIntegrationProps,
2904
2972
  TDefaultIntegration,
2905
- TPattern
2973
+ TPattern,
2974
+ TOverridden | Extract<keyof TOverrideIntegrations, string>,
2975
+ TOperationOptioned
2906
2976
  >;
2907
2977
  }
2908
2978
 
@@ -2921,6 +2991,43 @@ export class IntegrationBuilder<
2921
2991
  return this;
2922
2992
  }
2923
2993
 
2994
+ /**
2995
+ * Updates the default integration options for specific operations, without
2996
+ * affecting the others. The given options are merged with the defaults (and
2997
+ * any options set via withDefaultOptions) when building each default integration.
2998
+ *
2999
+ * Operations which have been replaced via withOverrides cannot be targeted, since
3000
+ * they no longer use the default integration.
3001
+ *
3002
+ * @param options - Map of operation names to partial default integration options
3003
+ * @returns The builder instance
3004
+ */
3005
+ public withOperationOptions<
3006
+ TOptions extends Partial<
3007
+ Record<
3008
+ Exclude<DefaultIntegrationKeys<TPattern, TOperation>, TOverridden>,
3009
+ Partial<TDefaultIntegrationProps>
3010
+ >
3011
+ >,
3012
+ >(options: TOptions) {
3013
+ Object.entries(options).forEach(([op, opOptions]) => {
3014
+ this.perOperationOptions[op] = {
3015
+ ...this.perOperationOptions[op],
3016
+ ...(opOptions as Partial<TDefaultIntegrationProps>),
3017
+ };
3018
+ });
3019
+ return this as unknown as IntegrationBuilder<
3020
+ TOperation,
3021
+ TBaseIntegration,
3022
+ TIntegrations,
3023
+ TDefaultIntegrationProps,
3024
+ TDefaultIntegration,
3025
+ TPattern,
3026
+ TOverridden,
3027
+ TOperationOptioned | Extract<keyof TOptions, string>
3028
+ >;
3029
+ }
3030
+
2924
3031
  /**
2925
3032
  * Builds and returns the complete set of integrations.
2926
3033
  *
@@ -2935,6 +3042,10 @@ export class IntegrationBuilder<
2935
3042
  */
2936
3043
  public build(): TIntegrations {
2937
3044
  const options = this.options;
3045
+ const buildOptionsFor = (op: string): TDefaultIntegrationProps => ({
3046
+ ...options.defaultIntegrationOptions,
3047
+ ...this.perOperationOptions[op],
3048
+ });
2938
3049
  return {
2939
3050
  ...Object.fromEntries(
2940
3051
  options.pattern === 'shared'
@@ -2943,7 +3054,7 @@ export class IntegrationBuilder<
2943
3054
  '$router',
2944
3055
  options.buildDefaultIntegration(
2945
3056
  '$router',
2946
- options.defaultIntegrationOptions,
3057
+ buildOptionsFor('$router'),
2947
3058
  ),
2948
3059
  ],
2949
3060
  ]
@@ -2954,10 +3065,7 @@ export class IntegrationBuilder<
2954
3065
  )
2955
3066
  .map((op) => [
2956
3067
  op,
2957
- options.buildDefaultIntegration(
2958
- op,
2959
- options.defaultIntegrationOptions,
2960
- ),
3068
+ options.buildDefaultIntegration(op, buildOptionsFor(op)),
2961
3069
  ]),
2962
3070
  ),
2963
3071
  ...this.integrations,
@@ -18,6 +18,9 @@ import { findWorkspaceRoot } from '../../../core/workspace.js';
18
18
  <%_ } _%>
19
19
  */
20
20
  export class <%- nameClassName %> extends AgentCoreGateway {
21
+ /** Default Gateway target name when added to another Gateway. */
22
+ public readonly gatewayName = '<%- nameKebabCase %>';
23
+
21
24
  constructor(scope: Construct, id: string) {
22
25
  super(scope, id, {
23
26
  <%_ if (cedarPolicy) { _%>
@@ -187,34 +187,7 @@ export class AgentCoreGateway extends Construct implements iam.IGrantable {
187
187
  cdk.Fn.split('/', cdk.Fn.join('%3A', cdk.Fn.split(':', arn))),
188
188
  );
189
189
  const endpoint = `https://bedrock-agentcore.${region}.amazonaws.com/runtimes/${encodedArn}/invocations?qualifier=DEFAULT`;
190
- // The construct id is derived from the target name so the two change
191
- // together — target names are unique per gateway, and replacing a target
192
- // under an unchanged name fails with AlreadyExists.
193
- const target = new agentcore.CfnGatewayTarget(
194
- this,
195
- `Target-${props.gatewayTargetName}`,
196
- {
197
- gatewayIdentifier: this.gateway.gatewayId,
198
- name: props.gatewayTargetName,
199
- targetConfiguration: {
200
- mcp: {
201
- mcpServer: {
202
- endpoint,
203
- },
204
- },
205
- },
206
- credentialProviderConfigurations: [
207
- {
208
- credentialProviderType: 'GATEWAY_IAM_ROLE',
209
- credentialProvider: {
210
- iamCredentialProvider: {
211
- service: 'bedrock-agentcore',
212
- },
213
- },
214
- },
215
- ],
216
- },
217
- );
190
+ const target = this.addTarget(props.gatewayTargetName, endpoint);
218
191
 
219
192
  // Grant the gateway role invoke access to the target runtime before the
220
193
  // target is created — AgentCore probes the target during creation, which
@@ -253,6 +226,115 @@ export class AgentCoreGateway extends Construct implements iam.IGrantable {
253
226
  }
254
227
  target.node.addDependency(probe.trigger);
255
228
 
229
+ return target;
230
+ }
231
+
232
+ /**
233
+ * Add another Gateway as a target of this Gateway, exposing its tools
234
+ * under `<targetName>___<innerTargetName>___<toolName>` names.
235
+ *
236
+ * The target name defaults to the gateway construct's `gatewayName`
237
+ * (its project's class name in kebab-case, e.g. `MyGateway` ->
238
+ * `my-gateway`). It prefixes Cedar action names and the gateway's tool
239
+ * names, so renaming it later is a breaking change for consumers.
240
+ *
241
+ * Both gateways evaluate their own Cedar policies: this gateway
242
+ * authorizes the prefixed action, then the target gateway authorizes the
243
+ * inner action for this gateway's role.
244
+ */
245
+ public addGateway(
246
+ gateway: Construct & {
247
+ readonly gateway: agentcore.Gateway;
248
+ readonly gatewayName: string;
249
+ },
250
+ props?: {
251
+ gatewayTargetName?: string;
252
+ },
253
+ ): agentcore.CfnGatewayTarget {
254
+ const target = this.addGatewayTarget({
255
+ gatewayTargetName: props?.gatewayTargetName ?? gateway.gatewayName,
256
+ // The Gateway L2 only populates gatewayUrl when created from its own
257
+ // props; fall back to the CloudFormation attribute otherwise.
258
+ gatewayUrl:
259
+ gateway.gateway.gatewayUrl ??
260
+ (gateway.gateway.node.defaultChild as agentcore.CfnGateway)
261
+ .attrGatewayUrl,
262
+ gatewayArn: gateway.gateway.gatewayArn,
263
+ });
264
+ // AgentCore fetches the target gateway's tools during target creation,
265
+ // so the target gateway and all of its own targets must exist first.
266
+ // Construct dependencies resolve at synth time, covering targets added
267
+ // to the target gateway after this call.
268
+ target.node.addDependency(gateway);
269
+ return target;
270
+ }
271
+
272
+ /**
273
+ * Register another Gateway's MCP endpoint as a target of this Gateway.
274
+ * Prefer {@link addGateway} when the construct is in scope — it also
275
+ * orders this gateway's target after the target gateway's own targets,
276
+ * so its tools are registered before they are fetched.
277
+ */
278
+ public addGatewayTarget(props: {
279
+ gatewayTargetName: string;
280
+ gatewayUrl: string;
281
+ gatewayArn: string;
282
+ }): agentcore.CfnGatewayTarget {
283
+ const target = this.addTarget(props.gatewayTargetName, props.gatewayUrl);
284
+
285
+ // Grant the gateway role invoke access to the target gateway before the
286
+ // target is created — AgentCore fetches the target's tools during
287
+ // creation, which fails if the permission is not yet in place.
288
+ const grant = this.gateway.role.addToPrincipalPolicy(
289
+ new iam.PolicyStatement({
290
+ actions: ['bedrock-agentcore:InvokeGateway'],
291
+ resources: [props.gatewayArn],
292
+ }),
293
+ );
294
+ if (grant.policyDependable) {
295
+ target.node.addDependency(grant.policyDependable);
296
+ }
297
+
298
+ return target;
299
+ }
300
+
301
+ /**
302
+ * Create a Gateway target for an MCP endpoint, with outbound calls signed
303
+ * using this gateway's own execution role.
304
+ */
305
+ private addTarget(
306
+ gatewayTargetName: string,
307
+ endpoint: string,
308
+ ): agentcore.CfnGatewayTarget {
309
+ // The construct id is derived from the target name so the two change
310
+ // together — target names are unique per gateway, and replacing a target
311
+ // under an unchanged name fails with AlreadyExists.
312
+ const target = new agentcore.CfnGatewayTarget(
313
+ this,
314
+ `Target-${gatewayTargetName}`,
315
+ {
316
+ gatewayIdentifier: this.gateway.gatewayId,
317
+ name: gatewayTargetName,
318
+ targetConfiguration: {
319
+ mcp: {
320
+ mcpServer: {
321
+ endpoint,
322
+ },
323
+ },
324
+ },
325
+ credentialProviderConfigurations: [
326
+ {
327
+ credentialProviderType: 'GATEWAY_IAM_ROLE',
328
+ credentialProvider: {
329
+ iamCredentialProvider: {
330
+ service: 'bedrock-agentcore',
331
+ },
332
+ },
333
+ },
334
+ ],
335
+ },
336
+ );
337
+
256
338
  // Policies referencing this target's actions (e.g.
257
339
  // AgentCore::Action::"<targetName>___<tool>") only validate once the
258
340
  // target has registered them, so create policies after targets.
@@ -28,6 +28,12 @@ variable "additional_iam_policy_statements" {
28
28
  }))
29
29
  default = []
30
30
  }
31
+
32
+ variable "tool_dependencies" {
33
+ description = "Target ids whose tools must be queryable before `gateway_url` is consumable. Set this to the gateway's own target ids (e.g. from agentcore-gateway#mcp-connection) when another gateway aggregates this one: the readiness probe waits until this gateway serves those tools, since a parent gateway fetches them at target creation."
34
+ type = list(string)
35
+ default = []
36
+ }
31
37
  <%_ if (cedarPolicy) { _%>
32
38
 
33
39
  variable "policy_dependencies" {
@@ -210,6 +216,86 @@ resource "aws_bedrockagentcore_policy" "policies" {
210
216
  }
211
217
  <%_ } _%>
212
218
 
219
+ # A parent gateway fetches this gateway's tools when it registers it as a
220
+ # target, so this gateway must already serve them — its own targets register
221
+ # their tools asynchronously after creation. Probe `tools/list` over SigV4
222
+ # until the configured `tool_dependencies` are served, and route `gateway_url`
223
+ # through this resource so a parent gateway target waits for readiness. Only
224
+ # runs when this gateway is aggregated by another (empty `tool_dependencies`).
225
+ resource "null_resource" "gateway_ready" {
226
+ count = length(var.tool_dependencies) > 0 ? 1 : 0
227
+
228
+ triggers = {
229
+ gateway_url = aws_bedrockagentcore_gateway.this.gateway_url
230
+ region = local.aws_region
231
+ tool_dependencies = join(",", var.tool_dependencies)
232
+ }
233
+
234
+ provisioner "local-exec" {
235
+ command = <<-EOT
236
+ uv run --with boto3 --with httpx --with mcp python -c "
237
+ import asyncio
238
+ import time
239
+
240
+ import boto3
241
+ import httpx
242
+ from botocore.auth import SigV4Auth
243
+ from botocore.awsrequest import AWSRequest
244
+ from mcp import ClientSession
245
+ from mcp.client.streamable_http import streamablehttp_client
246
+
247
+ gateway_url = '${self.triggers.gateway_url}'
248
+ region = '${self.triggers.region}'
249
+ credentials = boto3.Session(region_name=region).get_credentials()
250
+ signer = SigV4Auth(credentials, 'bedrock-agentcore', region)
251
+
252
+
253
+ class SigV4Auth_(httpx.Auth):
254
+ requires_request_body = True
255
+
256
+ def auth_flow(self, request):
257
+ aws_request = AWSRequest(
258
+ method=request.method,
259
+ url=str(request.url),
260
+ data=request.content,
261
+ headers=dict(request.headers),
262
+ )
263
+ signer.add_auth(aws_request)
264
+ request.headers.update(dict(aws_request.headers))
265
+ yield request
266
+
267
+
268
+ async def list_tools():
269
+ async with streamablehttp_client(
270
+ gateway_url, auth=SigV4Auth_(), timeout=120
271
+ ) as (read, write, _):
272
+ async with ClientSession(read, write) as session:
273
+ await session.initialize()
274
+ result = await session.list_tools()
275
+ return [t.name for t in result.tools]
276
+
277
+
278
+ # The built-in semantic-search tool is always present, so require at least one
279
+ # more tool before declaring the gateway ready to serve its aggregated targets.
280
+ deadline = time.time() + 5 * 60
281
+ while True:
282
+ try:
283
+ tools = asyncio.run(list_tools())
284
+ if len([t for t in tools if t != 'x_amz_bedrock_agentcore_search']) > 0:
285
+ break
286
+ last = f'gateway serves no aggregated tools yet (got {tools})'
287
+ except Exception as e:
288
+ last = str(e)
289
+ if time.time() >= deadline:
290
+ raise SystemExit(f'Gateway {gateway_url} not ready: {last}')
291
+ time.sleep(5)
292
+
293
+ print(f'Gateway {gateway_url} is serving tools')
294
+ "
295
+ EOT
296
+ }
297
+ }
298
+
213
299
  # Add gateway URL to runtime config
214
300
  module "add_gateway_url_to_runtime_config" {
215
301
  source = "../../../core/runtime-config/entry"
@@ -232,8 +318,10 @@ output "gateway_arn" {
232
318
  }
233
319
 
234
320
  output "gateway_url" {
235
- description = "MCP endpoint URL of the Bedrock AgentCore Gateway"
236
- value = aws_bedrockagentcore_gateway.this.gateway_url
321
+ description = "MCP endpoint URL of the Bedrock AgentCore Gateway. When `tool_dependencies` is set, this value flows through the readiness probe so consumers (e.g. a parent gateway target) wait until the gateway serves its aggregated tools."
322
+ value = length(var.tool_dependencies) > 0 ? (
323
+ null_resource.gateway_ready[0].triggers.gateway_url
324
+ ) : aws_bedrockagentcore_gateway.this.gateway_url
237
325
  }
238
326
 
239
327
  output "gateway_role_arn" {
@@ -148,6 +148,8 @@ export type ApiIntegrations<TOperation extends string, TBaseIntegration> =
148
148
  * @template TDefaultIntegrationProps - Type for default integration properties
149
149
  * @template TDefaultIntegration - Type for default integration implementation
150
150
  * @template TPattern - The integration pattern ('shared' or 'isolated')
151
+ * @template TOverridden - Union of operation names that have been overridden
152
+ * @template TOperationOptioned - Union of operation names with per-operation options
151
153
  */
152
154
  export class IntegrationBuilder<
153
155
  TOperation extends string,
@@ -156,6 +158,8 @@ export class IntegrationBuilder<
156
158
  TDefaultIntegrationProps extends object,
157
159
  TDefaultIntegration extends TBaseIntegration,
158
160
  TPattern extends 'shared' | 'isolated',
161
+ TOverridden extends string = never,
162
+ TOperationOptioned extends string = never,
159
163
  > {
160
164
  /** Options for the integration builder */
161
165
  private options: IntegrationBuilderProps<
@@ -168,6 +172,11 @@ export class IntegrationBuilder<
168
172
  /** Map of operation names to their custom integrations */
169
173
  private integrations: Partial<TIntegrations> = {};
170
174
 
175
+ /** Map of default integration keys to per-operation default option overrides */
176
+ private perOperationOptions: Partial<
177
+ Record<string, Partial<TDefaultIntegrationProps>>
178
+ > = {};
179
+
171
180
  /**
172
181
  * Create an Integration Builder for an HTTP API with a shared pattern
173
182
  */
@@ -322,11 +331,16 @@ export class IntegrationBuilder<
322
331
  /**
323
332
  * Overrides default integrations with custom implementations for specific operations.
324
333
  *
334
+ * Operations which have per-operation options set via withOperationOptions cannot be
335
+ * overridden, since these still use the default integration.
336
+ *
325
337
  * @param overrides - Map of operation names to their custom integration implementations
326
338
  * @returns The builder instance with updated type information reflecting the overrides
327
339
  */
328
340
  public withOverrides<
329
- TOverrideIntegrations extends Partial<Record<TOperation, TBaseIntegration>>,
341
+ TOverrideIntegrations extends Partial<
342
+ Record<Exclude<TOperation, TOperationOptioned>, TBaseIntegration>
343
+ >,
330
344
  >(overrides: TOverrideIntegrations) {
331
345
  this.integrations = { ...this.integrations, ...overrides };
332
346
  // Re-type to include the overridden integration types.
@@ -342,7 +356,9 @@ export class IntegrationBuilder<
342
356
  MergedIntegrations,
343
357
  TDefaultIntegrationProps,
344
358
  TDefaultIntegration,
345
- TPattern
359
+ TPattern,
360
+ TOverridden | Extract<keyof TOverrideIntegrations, string>,
361
+ TOperationOptioned
346
362
  >;
347
363
  }
348
364
 
@@ -361,6 +377,43 @@ export class IntegrationBuilder<
361
377
  return this;
362
378
  }
363
379
 
380
+ /**
381
+ * Updates the default integration options for specific operations, without
382
+ * affecting the others. The given options are merged with the defaults (and
383
+ * any options set via withDefaultOptions) when building each default integration.
384
+ *
385
+ * Operations which have been replaced via withOverrides cannot be targeted, since
386
+ * they no longer use the default integration.
387
+ *
388
+ * @param options - Map of operation names to partial default integration options
389
+ * @returns The builder instance
390
+ */
391
+ public withOperationOptions<
392
+ TOptions extends Partial<
393
+ Record<
394
+ Exclude<DefaultIntegrationKeys<TPattern, TOperation>, TOverridden>,
395
+ Partial<TDefaultIntegrationProps>
396
+ >
397
+ >,
398
+ >(options: TOptions) {
399
+ Object.entries(options).forEach(([op, opOptions]) => {
400
+ this.perOperationOptions[op] = {
401
+ ...this.perOperationOptions[op],
402
+ ...(opOptions as Partial<TDefaultIntegrationProps>),
403
+ };
404
+ });
405
+ return this as unknown as IntegrationBuilder<
406
+ TOperation,
407
+ TBaseIntegration,
408
+ TIntegrations,
409
+ TDefaultIntegrationProps,
410
+ TDefaultIntegration,
411
+ TPattern,
412
+ TOverridden,
413
+ TOperationOptioned | Extract<keyof TOptions, string>
414
+ >;
415
+ }
416
+
364
417
  /**
365
418
  * Builds and returns the complete set of integrations.
366
419
  *
@@ -375,6 +428,10 @@ export class IntegrationBuilder<
375
428
  */
376
429
  public build(): TIntegrations {
377
430
  const options = this.options;
431
+ const buildOptionsFor = (op: string): TDefaultIntegrationProps => ({
432
+ ...options.defaultIntegrationOptions,
433
+ ...this.perOperationOptions[op],
434
+ });
378
435
  return {
379
436
  ...Object.fromEntries(
380
437
  options.pattern === 'shared'
@@ -383,7 +440,7 @@ export class IntegrationBuilder<
383
440
  '$router',
384
441
  options.buildDefaultIntegration(
385
442
  '$router',
386
- options.defaultIntegrationOptions,
443
+ buildOptionsFor('$router'),
387
444
  ),
388
445
  ],
389
446
  ]
@@ -394,10 +451,7 @@ export class IntegrationBuilder<
394
451
  )
395
452
  .map((op) => [
396
453
  op,
397
- options.buildDefaultIntegration(
398
- op,
399
- options.defaultIntegrationOptions,
400
- ),
454
+ options.buildDefaultIntegration(op, buildOptionsFor(op)),
401
455
  ]),
402
456
  ),
403
457
  ...this.integrations,
@@ -135,7 +135,7 @@ export declare const withVersions: (deps: ITsDepVersion[]) => {
135
135
  */
136
136
  export declare const PY_VERSIONS: {
137
137
  readonly 'ag-ui-protocol': "==0.1.19";
138
- readonly 'ag-ui-strands': "==0.1.9";
138
+ readonly 'ag-ui-strands': "==0.2.1";
139
139
  readonly 'aws-lambda-powertools': "==3.29.0";
140
140
  readonly 'aws-lambda-powertools[tracer]': "==3.29.0";
141
141
  readonly 'aws-lambda-powertools[parser]': "==3.29.0";
@@ -136,7 +136,7 @@ exports.withVersions = withVersions;
136
136
  */
137
137
  exports.PY_VERSIONS = {
138
138
  'ag-ui-protocol': '==0.1.19',
139
- 'ag-ui-strands': '==0.1.9',
139
+ 'ag-ui-strands': '==0.2.1',
140
140
  'aws-lambda-powertools': '==3.29.0',
141
141
  'aws-lambda-powertools[tracer]': '==3.29.0',
142
142
  'aws-lambda-powertools[parser]': '==3.29.0',