55 */
66
77import { describe , it , expect , vi , beforeEach , afterEach } from 'vitest' ;
8+ import * as crypto from 'node:crypto' ;
89import { AgentRegistry , getModelConfigAlias } from './registry.js' ;
910import { makeFakeConfig } from '../test-utils/config.js' ;
1011import type { AgentDefinition , LocalAgentDefinition } from './types.js' ;
@@ -29,10 +30,23 @@ import { SimpleExtensionLoader } from '../utils/extensionLoader.js';
2930import type { ToolRegistry } from '../tools/tool-registry.js' ;
3031import { ThinkingLevel } from '@google/genai' ;
3132import type { AcknowledgedAgentsService } from './acknowledgedAgents.js' ;
33+ import * as sdkClient from '@a2a-js/sdk/client' ;
34+ import { safeFetch } from '../utils/fetch.js' ;
3235import { PolicyDecision } from '../policy/types.js' ;
3336import { A2AAuthProviderFactory } from './auth-provider/factory.js' ;
3437import type { A2AAuthProvider } from './auth-provider/types.js' ;
3538
39+ vi . mock ( '@a2a-js/sdk/client' , async ( importOriginal ) => {
40+ const actual = await importOriginal ( ) ;
41+ return {
42+ ...( actual as Record < string , unknown > ) ,
43+ DefaultAgentCardResolver : vi . fn ( ) . mockImplementation ( ( options ) => ( {
44+ fetchImpl : options ?. fetchImpl ,
45+ resolve : vi . fn ( ) . mockResolvedValue ( { name : 'RemoteAgent' } ) ,
46+ } ) ) ,
47+ } ;
48+ } ) ;
49+
3650vi . mock ( './agentLoader.js' , ( ) => ( {
3751 loadAgentsFromDirectory : vi
3852 . fn ( )
@@ -417,7 +431,7 @@ describe('AgentRegistry', () => {
417431 expect ( registry . getDefinition ( 'extension-agent' ) ) . toBeUndefined ( ) ;
418432 } ) ;
419433
420- it ( 'should use agentCardUrl as hash for acknowledgement of remote agents' , async ( ) => {
434+ it ( 'should use agentCardUrl and content-based hash for acknowledgement of remote agents' , async ( ) => {
421435 mockConfig = makeMockedConfig ( { enableAgents : true } ) ;
422436 // Trust the folder so it attempts to load project agents
423437 vi . spyOn ( mockConfig , 'isTrustedFolder' ) . mockReturnValue ( true ) ;
@@ -453,21 +467,75 @@ describe('AgentRegistry', () => {
453467 clearCache : vi . fn ( ) ,
454468 } as unknown as A2AClientManager ) ;
455469
470+ // Mock the resolver to return a consistent card content for hashing
471+ const mockCardContent = { name : 'RemoteAgent' } ;
472+ const expectedContentHash = crypto
473+ . createHash ( 'sha256' )
474+ . update ( JSON . stringify ( mockCardContent ) )
475+ . digest ( 'hex' ) ;
476+ const expectedHash = `https://example.com/card#${ expectedContentHash } ` ;
477+
478+ vi . mocked ( sdkClient . DefaultAgentCardResolver ) . mockImplementation (
479+ ( ) =>
480+ ( {
481+ resolve : vi . fn ( ) . mockResolvedValue ( mockCardContent ) ,
482+ } ) as unknown as sdkClient . DefaultAgentCardResolver ,
483+ ) ;
484+
456485 await registry . initialize ( ) ;
457486
458- // Verify ackService was called with the URL, not the file hash
487+ // Verify ackService was called with the content-based hash
459488 expect ( ackService . isAcknowledged ) . toHaveBeenCalledWith (
460489 expect . anything ( ) ,
461490 'RemoteAgent' ,
462- 'https://example.com/card' ,
491+ expectedHash ,
463492 ) ;
464493
465- // Also verify that the agent's metadata was updated to use the URL as hash
466- // Use getDefinition because registerAgent might have been called
494+ // Also verify that the agent's metadata was updated to use the content-based hash
467495 expect ( registry . getDefinition ( 'RemoteAgent' ) ?. metadata ?. hash ) . toBe (
468- 'https://example.com/card' ,
496+ expectedHash ,
469497 ) ;
470498 } ) ;
499+
500+ it ( 'should use safeFetch in DefaultAgentCardResolver during initialization' , async ( ) => {
501+ mockConfig = makeMockedConfig ( { enableAgents : true } ) ;
502+ vi . spyOn ( mockConfig , 'isTrustedFolder' ) . mockReturnValue ( true ) ;
503+ vi . spyOn ( mockConfig , 'getFolderTrust' ) . mockReturnValue ( true ) ;
504+
505+ const registry = new TestableAgentRegistry ( mockConfig ) ;
506+
507+ const remoteAgent : AgentDefinition = {
508+ kind : 'remote' ,
509+ name : 'RemoteAgent' ,
510+ description : 'A remote agent' ,
511+ agentCardUrl : 'https://example.com/card' ,
512+ inputConfig : { inputSchema : { type : 'object' } } ,
513+ } ;
514+
515+ vi . mocked ( tomlLoader . loadAgentsFromDirectory ) . mockResolvedValue ( {
516+ agents : [ remoteAgent ] ,
517+ errors : [ ] ,
518+ } ) ;
519+
520+ // Track constructor calls
521+ const resolverMock = vi . mocked ( sdkClient . DefaultAgentCardResolver ) ;
522+
523+ await registry . initialize ( ) ;
524+
525+ // Find the call for our remote agent
526+ const call = resolverMock . mock . calls . find ( ( args ) => {
527+ const options = args [ 0 ] as { fetchImpl ?: typeof fetch } ;
528+ // We look for a call that was provided with a fetch implementation.
529+ // In our current implementation, we wrap safeFetch.
530+ return typeof options ?. fetchImpl === 'function' ;
531+ } ) ;
532+
533+ expect ( call ) . toBeDefined ( ) ;
534+ const options = call ?. [ 0 ] as { fetchImpl ?: typeof fetch } ;
535+
536+ // We passed safeFetch directly
537+ expect ( options ?. fetchImpl ) . toBe ( safeFetch ) ;
538+ } ) ;
471539 } ) ;
472540
473541 describe ( 'registration logic' , ( ) => {
@@ -874,6 +942,17 @@ describe('AgentRegistry', () => {
874942 ) ;
875943 } ) ;
876944
945+ it ( 'should maintain registration under canonical name' , async ( ) => {
946+ const originalName = 'my-agent' ;
947+ const definition = { ...MOCK_AGENT_V1 , name : originalName } ;
948+
949+ await registry . testRegisterAgent ( definition ) ;
950+
951+ const registered = registry . getDefinition ( originalName ) ;
952+ expect ( registered ) . toBeDefined ( ) ;
953+ expect ( registry . getAllAgentNames ( ) ) . toEqual ( [ originalName ] ) ;
954+ } ) ;
955+
877956 it ( 'should reject an agent definition missing a name' , async ( ) => {
878957 const invalidAgent = { ...MOCK_AGENT_V1 , name : '' } ;
879958 const debugWarnSpy = vi
0 commit comments