diff --git a/dev/src/utils/agent_loader.ts b/dev/src/utils/agent_loader.ts index d5097c482..15775c498 100644 --- a/dev/src/utils/agent_loader.ts +++ b/dev/src/utils/agent_loader.ts @@ -49,6 +49,31 @@ const FILE_MODULE_TYPE_EXTENSION_MAP = { [FileModuleType.ESM]: '.mjs', }; +/** + * Packages that must never be inlined into the bundled agent file. + * + * See http://mikro-orm.io/docs/deployment#deploy-a-bundle-of-entities-and-dependencies-with-esbuild for more details + */ +const EXTERNAL_PACKAGES = [ + 'sqlite3', + 'better-sqlite3', + 'mysql', + 'mysql2', + // Native addons must remain external so Node can resolve their + // platform-specific assets at runtime. + 'onnxruntime-node', + 'oracledb', + 'pg-native', + 'pg-query-stream', + 'tedious', + 'libsql', + // Optional peer dependencies of vite and eslint that are not + // installed and MUST NOT be bundled. + 'lightningcss', + 'jiti', + 'jiti/package.json', +]; + /** * Metadata for a file. */ @@ -187,26 +212,9 @@ export class AgentFile { bundle: this.options.bundle, minify: this.options.bundle, 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: [ - 'sqlite3', - 'better-sqlite3', - 'mysql', - 'mysql2', - // Native addons must remain external so Node can resolve their - // platform-specific assets at runtime. - 'onnxruntime-node', - 'oracledb', - 'pg-native', - 'pg-query-stream', - 'tedious', - 'libsql', - // Optional peer dependencies of vite and eslint that are not - // installed and MUST NOT be bundled. - 'lightningcss', - 'jiti', - 'jiti/package.json', - ], + // esbuild rejects `external` unless `bundle` is enabled, so the + // allowlist is only passed when the agent file is actually bundled. + ...(this.options.bundle ? {external: EXTERNAL_PACKAGES} : {}), }); this.cleanupDirPath = outputDir; diff --git a/dev/test/utils/agent_loader_test.ts b/dev/test/utils/agent_loader_test.ts index 2285df895..fef6f1cc7 100644 --- a/dev/test/utils/agent_loader_test.ts +++ b/dev/test/utils/agent_loader_test.ts @@ -500,6 +500,70 @@ describe('AgentLoader', () => { await agentFile.dispose(); }); + it('omits esbuild "external" when bundling is disabled', async () => { + const agentPath = path.join(tempAgentsDir, 'agent2.ts'); + await fs.writeFile(agentPath, agent2TsContent); + + (esbuild.build as Mock).mockImplementation(async () => { + await fs.writeFile(compiledPath('agent2.cjs'), agent2CjsContentMocked); + return Promise.resolve(); + }); + + const agentFile = new AgentFile(agentPath, { + compile: true, + bundle: false, + }); + const agent = await agentFile.load(); + + expect(agent.name).toEqual('agent2'); + const buildOptions = (esbuild.build as Mock).mock.calls[0][0]; + expect(buildOptions).not.toHaveProperty('external'); + expect(buildOptions).toMatchObject({bundle: false, minify: false}); + + await agentFile.dispose(); + }); + + it('omits esbuild "external" when bundle option is not provided', async () => { + const agentPath = path.join(tempAgentsDir, 'agent2.ts'); + await fs.writeFile(agentPath, agent2TsContent); + + (esbuild.build as Mock).mockImplementation(async () => { + await fs.writeFile(compiledPath('agent2.cjs'), agent2CjsContentMocked); + return Promise.resolve(); + }); + + const agentFile = new AgentFile(agentPath, {compile: true}); + const agent = await agentFile.load(); + + expect(agent.name).toEqual('agent2'); + expect((esbuild.build as Mock).mock.calls[0][0]).not.toHaveProperty( + 'external', + ); + + await agentFile.dispose(); + }); + + it('compiles and loads a .ts agent with real esbuild when bundle is disabled', async () => { + const {build: realEsbuildBuild} = + await vi.importActual('esbuild'); + (esbuild.build as Mock).mockImplementationOnce(realEsbuildBuild); + + const agentPath = path.join(tempAgentsDir, 'agent2.ts'); + await fs.writeFile(agentPath, agent2TsContent); + + const agentFile = new AgentFile(agentPath, { + compile: true, + bundle: false, + }); + const agent = await agentFile.load(); + + expect(agent.name).toEqual('agent2'); + const compiled = await fs.readFile(compiledPath('agent2.cjs'), 'utf8'); + expect(compiled).toContain('require("@google/adk")'); + + await agentFile.dispose(); + }); + it('throws specific error if file does not exist', async () => { const agentPath = path.join(tempAgentsDir, 'non_existent.js'); const agentFile = new AgentFile(agentPath);