diff --git a/dev/src/utils/agent_loader.ts b/dev/src/utils/agent_loader.ts index 448a32aeb..f74a133ed 100644 --- a/dev/src/utils/agent_loader.ts +++ b/dev/src/utils/agent_loader.ts @@ -191,6 +191,11 @@ export class AgentFile { plugins: [replaceDirnamePlugin(filePath, originalDir), shimPlugin()], // See http://mikro-orm.io/docs/deployment#deploy-a-bundle-of-entities-and-dependencies-with-esbuild for more details external: [ + // Resolve the ADK runtime from the project's node_modules (see + // linkProjectNodeModules) instead of embedding a copy per agent, so a + // directory of N agents loads one shared ADK rather than N of them. + '@google/adk', + '@google/adk-devtools', 'sqlite3', 'better-sqlite3', 'mysql', diff --git a/dev/test/utils/agent_loader_test.ts b/dev/test/utils/agent_loader_test.ts index d53160d9d..b14f9871a 100644 --- a/dev/test/utils/agent_loader_test.ts +++ b/dev/test/utils/agent_loader_test.ts @@ -289,6 +289,26 @@ describe('AgentLoader', () => { await expect(fs.access(compiledAgentPath)).rejects.toThrow(); }); + it('marks the ADK packages external so each agent does not embed a copy', async () => { + const agentPath = path.join(tempAgentsDir, 'agent_external.ts'); + await fs.writeFile(agentPath, agent2TsContent); + + const compiledAgentPath = compiledPath('agent_external.cjs'); + (esbuild.build as Mock).mockImplementation(async () => { + await fs.writeFile(compiledAgentPath, agent2CjsContentMocked); + return Promise.resolve(); + }); + + const agentFile = new AgentFile(agentPath); + await agentFile.load(); + + expect((esbuild.build as Mock).mock.calls[0][0].external).toEqual( + expect.arrayContaining(['@google/adk', '@google/adk-devtools']), + ); + + await agentFile.dispose(); + }); + it('throws if rootAgent is not found', async () => { const agentPath = path.join(tempAgentsDir, 'bad_agent.js'); await fs.writeFile(agentPath, 'exports.someOther = 1;'); diff --git a/tests/integration/app_loader/app_loader_test.ts b/tests/integration/app_loader/app_loader_test.ts index 4367307b8..0caec2d28 100644 --- a/tests/integration/app_loader/app_loader_test.ts +++ b/tests/integration/app_loader/app_loader_test.ts @@ -29,7 +29,7 @@ describe('App loader CLI integration', () => { beforeAll(async () => { await execAsync('npm install', {cwd: projectPath}); - }, TEST_EXECUTION_TIMEOUT); + }); it( 'should run app via package.json start script and get responses', @@ -62,7 +62,7 @@ describe('App loader CLI integration', () => { await fs .unlink(path.join(projectPath, 'package-lock.json')) .catch(() => {}); - }, TEST_EXECUTION_TIMEOUT); + }); }, ); }); @@ -77,7 +77,7 @@ describe('AgentLoader discovery and loading integration', () => { beforeAll(async () => { await execAsync('npm install', {cwd: projectPath}); loader = new AgentLoader(projectPath); - }, TEST_EXECUTION_TIMEOUT); + }); it( 'should discover apps vs agents across directories and standalone files', @@ -127,6 +127,20 @@ describe('AgentLoader discovery and loading integration', () => { TEST_EXECUTION_TIMEOUT, ); + it( + 'compiles an agent without embedding the ADK runtime', + async () => { + const appFile = await loader.getAppFile('service_alpha'); + await appFile.load(); + + // The ADK runtime is ~5.6MB minified, so an artifact this small can only + // be importing it rather than inlining a private copy of it. + const {size} = await fs.stat(appFile.getFilePath()); + expect(size).toBeLessThan(64 * 1024); + }, + TEST_EXECUTION_TIMEOUT, + ); + afterAll(async () => { await loader.disposeAll(); await fs @@ -138,5 +152,5 @@ describe('AgentLoader discovery and loading integration', () => { await fs .unlink(path.join(projectPath, 'package-lock.json')) .catch(() => {}); - }, TEST_EXECUTION_TIMEOUT); + }); });