Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions dev/src/utils/agent_loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
20 changes: 20 additions & 0 deletions dev/test/utils/agent_loader_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;');
Expand Down
22 changes: 18 additions & 4 deletions tests/integration/app_loader/app_loader_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -62,7 +62,7 @@ describe('App loader CLI integration', () => {
await fs
.unlink(path.join(projectPath, 'package-lock.json'))
.catch(() => {});
}, TEST_EXECUTION_TIMEOUT);
});
},
);
});
Expand All @@ -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',
Expand Down Expand Up @@ -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
Expand All @@ -138,5 +152,5 @@ describe('AgentLoader discovery and loading integration', () => {
await fs
.unlink(path.join(projectPath, 'package-lock.json'))
.catch(() => {});
}, TEST_EXECUTION_TIMEOUT);
});
});
Loading