55 */
66
77import { SecretsManagerClient , GetSecretValueCommand } from '@aws-sdk/client-secrets-manager'
8+ import { fromNodeProviderChain } from '@aws-sdk/credential-providers'
9+ import express from 'express'
810import type { TestProject } from 'vitest/node'
911import type { ProvidedContext } from 'vitest'
10- import { fromNodeProviderChain } from '@aws-sdk/credential-providers'
12+
13+ import { Agent } from '../../../src/agent/agent.js'
14+ import { A2AExpressServer } from '../../../src/a2a/express-server.js'
15+ import { BedrockModel } from '../../../src/models/bedrock.js'
1116
1217/**
1318 * Load API keys as environment variables from AWS Secrets Manager
@@ -59,7 +64,7 @@ async function loadApiKeysFromSecretsManager(): Promise<void> {
5964/**
6065 * Perform shared setup for the integration tests.
6166 */
62- export async function setup ( project : TestProject ) : Promise < void > {
67+ export async function setup ( project : TestProject ) : Promise < ( ) => void > {
6368 console . log ( 'Global setup: Loading API keys from Secrets Manager...' )
6469 await loadApiKeysFromSecretsManager ( )
6570 console . log ( 'Global setup: API keys loaded into environment' )
@@ -72,6 +77,13 @@ export async function setup(project: TestProject): Promise<void> {
7277 project . provide ( 'provider-bedrock' , await getBedrockTestContext ( isCI ) )
7378 project . provide ( 'provider-anthropic' , await getAnthropicTestContext ( isCI ) )
7479 project . provide ( 'provider-gemini' , await getGeminiTestContext ( isCI ) )
80+
81+ const a2aContext = await getA2AServerContext ( project )
82+ project . provide ( 'a2a-server' , { shouldSkip : a2aContext . shouldSkip , url : a2aContext . url } )
83+
84+ return ( ) => {
85+ a2aContext . abort ?.( )
86+ }
7587}
7688
7789async function getOpenAITestContext ( isCI : boolean ) : Promise < ProvidedContext [ 'provider-openai' ] > {
@@ -149,3 +161,64 @@ async function getGeminiTestContext(_isCI: boolean): Promise<ProvidedContext['pr
149161 shouldSkip : shouldSkip ,
150162 }
151163}
164+
165+ async function getA2AServerContext (
166+ project : TestProject
167+ ) : Promise < ProvidedContext [ 'a2a-server' ] & { abort ?: ( ) => void } > {
168+ const { testFiles } = await project . globTestFiles ( )
169+ const hasA2ATests = testFiles . some ( ( f ) => f . includes ( '/a2a/' ) )
170+
171+ if ( ! hasA2ATests ) {
172+ return { shouldSkip : true , url : undefined }
173+ }
174+
175+ let credentials
176+ try {
177+ const credentialProvider = fromNodeProviderChain ( )
178+ credentials = await credentialProvider ( )
179+ } catch {
180+ console . log ( '⏭️ A2A server not available (no Bedrock credentials) - A2A integration tests will be skipped' )
181+ return { shouldSkip : true , url : undefined }
182+ }
183+
184+ const model = new BedrockModel ( { clientConfig : { credentials } } )
185+ const agent = new Agent ( {
186+ model,
187+ printer : false ,
188+ systemPrompt : 'You are a helpful assistant. Always respond in a single short sentence.' ,
189+ } )
190+
191+ const a2aServer = new A2AExpressServer ( {
192+ agent,
193+ name : 'Test A2A Agent' ,
194+ description : 'Integration test agent' ,
195+ } )
196+
197+ // Use createMiddleware() with CORS headers so browser integ tests can reach the server.
198+ // Browser tests run on a different port (Vitest dev server), making this a cross-origin request.
199+ const app = express ( )
200+ app . use ( ( _req , res , next ) => {
201+ res . setHeader ( 'Access-Control-Allow-Origin' , '*' )
202+ res . setHeader ( 'Access-Control-Allow-Methods' , '*' )
203+ res . setHeader ( 'Access-Control-Allow-Headers' , '*' )
204+ next ( )
205+ } )
206+ app . use ( a2aServer . createMiddleware ( ) )
207+
208+ return new Promise ( ( resolve , reject ) => {
209+ const server = app . listen ( 0 , '127.0.0.1' , ( ) => {
210+ const addr = server . address ( ) as { port : number }
211+ const url = `http://127.0.0.1:${ addr . port } `
212+ // Update the agent card URL to reflect the actual bound port.
213+ // createMiddleware() doesn't do this automatically (unlike serve()).
214+ a2aServer . agentCard . url = url
215+ console . log ( `⏭️ A2A server started on ${ url } ` )
216+ resolve ( {
217+ shouldSkip : false ,
218+ url,
219+ abort : ( ) => server . close ( ) ,
220+ } )
221+ } )
222+ server . on ( 'error' , reject )
223+ } )
224+ }
0 commit comments