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
7 changes: 5 additions & 2 deletions core/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function build({

if (bundle) {
buildOptions.entryPoints = [`./src/${entry}`];
buildOptions.outfile = `./dist/${targetDir}/index.js`;
buildOptions.outfile = `./dist/${targetDir}/${entry.replace(/\.ts$/, '.js')}`;
} else {
buildOptions.entryPoints = ['./src/**/*.ts'];
buildOptions.outdir = `./dist/${targetDir}`;
Expand Down Expand Up @@ -101,12 +101,15 @@ async function main() {
await Promise.all([
build({targetDir: 'esm', platform: 'node', format: 'esm', bundle}),
build({targetDir: 'cjs', platform: 'node', format: 'cjs', bundle}),
// esbuild rejects `alias` without `bundle`, so the browser shims only
// reach a bundled web output. `prepublishOnly` runs the non-bundled
// `npm run build`, so the web target ignores the flag and always bundles.
build({
targetDir: 'web',
platform: 'browser',
format: 'esm',
entry: 'index_web.ts',
bundle,
bundle: true,
}),
]);

Expand Down
15 changes: 0 additions & 15 deletions core/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,6 @@ export {
LoadArtifactsTool,
} from './tools/load_artifacts_tool.js';
export {LOAD_MEMORY, LoadMemoryTool} from './tools/load_memory_tool.js';
export {LOAD_WEB_PAGE, loadWebPage} from './tools/load_web_page.js';
export type {LoadWebPageOptions} from './tools/load_web_page.js';
export {LongRunningFunctionTool} from './tools/long_running_tool.js';
export {
PRELOAD_MEMORY,
Expand Down Expand Up @@ -289,21 +287,8 @@ export type {TaskExecutable} from './utils/task.js';
export {GoogleLLMVariant} from './utils/variant_utils.js';
export {version} from './version.js';

export {GCPSkillRegistry} from './skills/gcp_skill_registry.js';
export type {GCPSkillRegistryOptions} from './skills/gcp_skill_registry.js';
export {
loadAllSkillsInDir,
loadSkillFromDir,
loadSkillFromZipBuffer,
validateSkillDir,
} from './skills/loader.js';
export type {Frontmatter, Resources, Script, Skill} from './skills/skill.js';
export type {SkillRegistry} from './skills/skill_registry.js';
export {ListSkillsTool} from './tools/skill/list_skills_tool.js';
export {LoadSkillResourceTool} from './tools/skill/load_skill_resource_tool.js';
export {LoadSkillTool} from './tools/skill/load_skill_tool.js';
export {SearchSkillsTool} from './tools/skill/search_skills_tool.js';
export {SkillToolset} from './tools/skill/skill_toolset.js';

export * from './artifacts/base_artifact_service.js';
export * from './features/feature_registry.js';
Expand Down
10 changes: 10 additions & 0 deletions core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,26 @@ export type {
VertexAiCreateSessionRequest,
VertexAiSessionServiceOptions,
} from './sessions/vertex_ai_session_service.js';
export {GCPSkillRegistry} from './skills/gcp_skill_registry.js';
export type {GCPSkillRegistryOptions} from './skills/gcp_skill_registry.js';
export {
loadAllSkillsInDir,
loadSkillFromDir,
loadSkillFromZipBuffer,
validateSkillDir,
} from './skills/loader.js';
export {LOAD_WEB_PAGE, loadWebPage} from './tools/load_web_page.js';
export type {LoadWebPageOptions} from './tools/load_web_page.js';
export {ListSkillsTool} from './tools/skill/list_skills_tool.js';
export {LoadSkillResourceTool} from './tools/skill/load_skill_resource_tool.js';
export {LoadSkillTool} from './tools/skill/load_skill_tool.js';
export {
RunSkillInlineScriptErrorCode,
RunSkillInlineScriptTool,
} from './tools/skill/run_skill_inline_script_tool.js';
export {RunSkillScriptTool} from './tools/skill/run_skill_script_tool.js';
export {SearchSkillsTool} from './tools/skill/search_skills_tool.js';
export {SkillToolset} from './tools/skill/skill_toolset.js';

export * from './integrations/agent_registry/agent_registry.js';
export * from './telemetry/google_cloud.js';
Expand Down
97 changes: 97 additions & 0 deletions core/test/index_web_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import * as adk from '@google/adk';
import esbuild from 'esbuild';
import {isBuiltin} from 'node:module';
import {fileURLToPath} from 'node:url';
import {describe, expect, it} from 'vitest';
import * as web from '../src/index_web.js';

/**
* The Node built-ins the browser build is allowed to reach, because
* `buildOptions.alias` in `core/build.js` swaps each one for a browser
* implementation. Keep this set in step with that alias table.
*
* The `import {createRequire} from 'module'` banner is excluded on purpose: it
* is injected after the module graph is resolved, so it never appears in the
* metafile, and removing it from the browser build is separately queued work.
*/
const ALIASED_NODE_BUILTINS = new Set(['node:async_hooks']);

/** Symbols that are Node-only and must not reach the browser entry point. */
const NODE_ONLY_EXPORTS = [
'LOAD_WEB_PAGE',
'loadWebPage',
'GCPSkillRegistry',
'loadAllSkillsInDir',
'loadSkillFromDir',
'loadSkillFromZipBuffer',
'validateSkillDir',
'ListSkillsTool',
'LoadSkillResourceTool',
'LoadSkillTool',
'SearchSkillsTool',
'SkillToolset',
] as const;

const coreDir = fileURLToPath(new URL('..', import.meta.url));

/** Budget (ms) for the bundle below: it walks the whole browser export graph. */
const BUNDLE_TIMEOUT_MS = 30000;

describe('browser entry point', () => {
it(
'reaches no Node built-in the browser build does not alias',
async () => {
const result = await esbuild.build({
absWorkingDir: coreDir,
entryPoints: ['./src/index_web.ts'],
platform: 'browser',
format: 'esm',
target: ['chrome58', 'firefox57', 'safari11'],
bundle: true,
packages: 'external',
logLevel: 'silent',
metafile: true,
write: false,
});

const [output] = Object.values(result.metafile.outputs);
const imported = new Set(
output.imports.map((entry) => entry.path).filter(isBuiltin),
);

expect(imported).toEqual(ALIASED_NODE_BUILTINS);
},
BUNDLE_TIMEOUT_MS,
);

it('does not export the Node-only skills and web-page symbols', () => {
for (const name of NODE_ONLY_EXPORTS) {
expect(
web,
`${name} leaked into the browser entry point`,
).not.toHaveProperty(name);
}
});

it('still exports the platform-neutral symbols', () => {
expect(web).toHaveProperty('LlmAgent');
expect(web).toHaveProperty('InMemorySessionService');
});
});

describe('Node entry point', () => {
it('still exports the Node-only skills and web-page symbols', () => {
for (const name of NODE_ONLY_EXPORTS) {
expect(
adk,
`${name} disappeared from the Node entry point`,
).toHaveProperty(name);
}
});
});
Loading