Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
078f0f1
feat: add for toolbox-adk protocol
twishabansal Oct 24, 2025
bd08c46
fix package deps
twishabansal Oct 24, 2025
301c0d0
fix config
twishabansal Oct 24, 2025
3e31029
lint
twishabansal Oct 24, 2025
2350ece
ci: add linter workflow for toolbox-adk
twishabansal Oct 24, 2025
45e6dd8
Merge branch 'twishabansal-patch-3' into adk-protocol
twishabansal Oct 24, 2025
f8cf048
Rename lint-toolbox-adk to lint-toolbox-adk.yaml
twishabansal Oct 24, 2025
a1548bb
Rename lint-toolbox-adk to lint-toolbox-adk.yaml
twishabansal Oct 24, 2025
4c65205
Merge branch 'twishabansal-patch-3' into adk-protocol
twishabansal Oct 24, 2025
69f5174
Update test.protocol.ts
twishabansal Oct 24, 2025
97b873f
basic monorepo setup
twishabansal Oct 29, 2025
1714120
fix lint workflow
twishabansal Oct 29, 2025
1603b52
fix integration test workflow
twishabansal Oct 29, 2025
a63c833
ignore header checker for lock file
twishabansal Oct 29, 2025
e7cfcab
add lock file
twishabansal Oct 29, 2025
62900c5
fix adk files
twishabansal Oct 29, 2025
0139bb2
fix package file
twishabansal Oct 29, 2025
c6d2f6a
fix file
twishabansal Oct 29, 2025
24b6af1
add empty index file
twishabansal Oct 29, 2025
2cfb402
update lock file
twishabansal Oct 29, 2025
d1adf5e
Merge branch 'main' into monorepo-config-final
twishabansal Oct 29, 2025
f9e0cba
fix adk lint
twishabansal Oct 29, 2025
58ce458
Merge branch 'main' into adk-protocol
twishabansal Oct 29, 2025
0cae93f
Merge branch 'monorepo-config-final' into adk-protocol
twishabansal Oct 29, 2025
ae3619c
Update integration.cloudbuild.yaml
twishabansal Oct 29, 2025
fe2d29f
Update package.json
twishabansal Oct 29, 2025
9f9c947
fix package.json
twishabansal Oct 29, 2025
4a9ddcf
update deps
twishabansal Oct 29, 2025
f9a0eba
remove package lock files
twishabansal Oct 29, 2025
4de004b
remove package lock files
twishabansal Oct 29, 2025
7c4284a
Merge branch 'monorepo-config-final' into adk-protocol
twishabansal Oct 29, 2025
efd5266
fix
twishabansal Oct 24, 2025
f943345
fix tests
twishabansal Oct 24, 2025
a6654f5
uncomplicate tests
twishabansal Oct 24, 2025
587294f
uncomplicate
twishabansal Oct 24, 2025
4993456
ci: add linter workflow for toolbox-adk
twishabansal Oct 24, 2025
113ebb7
Rename lint-toolbox-adk to lint-toolbox-adk.yaml
twishabansal Oct 24, 2025
14c8d67
Merge branch 'adk-protocol' into adk-tool
twishabansal Oct 29, 2025
8a2600a
fix deps
twishabansal Oct 29, 2025
b213094
Merge branch 'main' into adk-protocol
twishabansal Oct 30, 2025
a56e145
comment out integration tests
twishabansal Oct 30, 2025
510aa14
Merge branch 'adk-protocol' into adk-tool
twishabansal Oct 30, 2025
6b9b279
Merge branch 'main' into adk-tool
twishabansal Oct 31, 2025
49cfd49
fix lock file
twishabansal Oct 31, 2025
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 packages/toolbox-adk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@
"coverage": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --config jest.config.json --coverage"
},
"dependencies": {
"@google/adk": "^0.1.2",
"@google/genai": "^1.14.0",
"@modelcontextprotocol/sdk": "1.17.5",
"@toolbox-sdk/core": "workspace:*",
"openapi-types": "^12.1.3",
"@google/genai": "^1.14.0",
"@toolbox-sdk/core": "workspace:*",
"zod": "^3.24.4"
Expand Down
140 changes: 140 additions & 0 deletions packages/toolbox-adk/src/toolbox_adk/tool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {BaseTool, RunAsyncToolRequest} from '@google/adk';
import type {FunctionDeclaration} from '@google/genai';

import {
ToolboxClient,
AuthTokenGetter,
AuthTokenGetters,
BoundParams,
BoundValue,
} from '@toolbox-sdk/core';
import {ConvertZodToFunctionDeclaration} from './protocol.js';

import {ZodObject, ZodRawShape} from 'zod';

type ResolvedPromiseType<T> = T extends Promise<infer U> ? U : T;

export type CoreTool = ResolvedPromiseType<
ReturnType<typeof ToolboxClient.prototype.loadTool>
>;

/**
* An adapter class that wraps a `CoreTool` from the `@toolbox-sdk/core`
* to make it compatible with the `@google/adk` `BaseTool` interface.
*/
export class ToolboxTool extends BaseTool {
private readonly coreTool: CoreTool;

/**
* Creates a new instance of the ADK-compatible tool wrapper.
* @param coreTool The original callable tool object from `@toolbox-sdk/core`.
*/
constructor(coreTool: CoreTool) {
super({
name: coreTool.toolName,
description: coreTool.description,
isLongRunning: false,
});
this.coreTool = coreTool;
}

/**
* Runs the tool by delegating the call to the wrapped `coreTool`.
*
* @param request The `RunAsyncToolRequest` from the ADK agent.
* @returns A promise that resolves to the tool's execution result.
*/
async runAsync(request: RunAsyncToolRequest): Promise<unknown> {
return this.coreTool(request.args);
}

/**
* Generates the `FunctionDeclaration` (JSON Schema) for this tool
* by converting the Zod schema from the `coreTool`.
*
* @returns A `FunctionDeclaration` for the LLM.
*/
override _getDeclaration(): FunctionDeclaration | undefined {
const zodSchema = this.coreTool.params as ZodObject<ZodRawShape>;

return ConvertZodToFunctionDeclaration(
this.name,
this.description,
zodSchema,
);
}

/**
* Creates a new `ToolboxTool` with additional auth token getters.
*
* @param newAuthTokenGetters A map of auth sources to token getters.
* @returns A new `ToolboxTool` instance.
*/
addAuthTokenGetters(newAuthTokenGetters: AuthTokenGetters): ToolboxTool {
const newCoreTool = this.coreTool.addAuthTokenGetters(newAuthTokenGetters);
return new ToolboxTool(newCoreTool);
}

/**
* Creates a new `ToolboxTool` with an additional auth token getter.
*
* @param authSource The name of the auth source.
* @param getIdToken The token getter function.
* @returns A new `ToolboxTool` instance.
*/
addAuthTokenGetter(
authSource: string,
getIdToken: AuthTokenGetter,
): ToolboxTool {
const newCoreTool = this.coreTool.addAuthTokenGetter(
authSource,
getIdToken,
);
return new ToolboxTool(newCoreTool);
}

/**
* Creates a new `ToolboxTool` with bound parameters.
*
* @param paramsToBind A map of parameter names to values or getters.
* @returns A new `ToolboxTool` instance.
*/
bindParams(paramsToBind: BoundParams): ToolboxTool {
const newCoreTool = this.coreTool.bindParams(paramsToBind);
return new ToolboxTool(newCoreTool);
}

/**
* Creates a new `ToolboxTool` with a single bound parameter.
*
* @param paramName The name of the parameter to bind.
* @param paramValue The value or getter to bind.
* @returns A new `ToolboxTool` instance.
*/
bindParam(paramName: string, paramValue: BoundValue): ToolboxTool {
const newCoreTool = this.coreTool.bindParam(paramName, paramValue);
return new ToolboxTool(newCoreTool);
}

/**
* Gets the underlying `CoreTool` object.
* @returns The wrapped `CoreTool` instance.
*/
public getCoreTool(): CoreTool {
return this.coreTool;
}
}
172 changes: 172 additions & 0 deletions packages/toolbox-adk/test/test.tool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {jest, describe, it, expect, beforeEach} from '@jest/globals';
import {z} from 'zod';
import {Type} from '@google/genai';
import type {FunctionDeclaration} from '@google/genai';
import type {RunAsyncToolRequest, ToolContext} from '@google/adk';
import type {ToolboxTool as ToolboxToolType} from '../src/toolbox_adk/tool.js';

const mockedConvertZod = jest.fn();

jest.unstable_mockModule('../src/toolbox_adk/protocol.js', () => ({
ConvertZodToFunctionDeclaration: mockedConvertZod,
}));

const {ToolboxTool} = (await import('../src/toolbox_adk/tool.js')) as {
ToolboxTool: typeof ToolboxToolType;
};

const mockZodSchema = z.object({
location: z.string().describe('The city'),
});

const mockDeclaration: FunctionDeclaration = {
name: 'test_tool',
description: 'A mock tool for testing',
parameters: {
type: Type.OBJECT,
properties: {
location: {
type: Type.STRING,
description: 'The city',
},
},
required: ['location'],
},
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const createMockCoreTool: any = () => {
const mockToolFn = jest.fn();

return Object.assign(mockToolFn, {
toolName: 'test_tool',
description: 'A mock tool for testing',
params: mockZodSchema,
addAuthTokenGetters: jest.fn(() => createMockCoreTool()),
addAuthTokenGetter: jest.fn(() => createMockCoreTool()),
bindParams: jest.fn(() => createMockCoreTool()),
bindParam: jest.fn(() => createMockCoreTool()),
});
};

describe('ToolboxTool', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let mockCoreTool: any;
let adkTool: ToolboxToolType;

beforeEach(() => {
// Reset our manually created mock function
mockedConvertZod.mockReset();
// Set the default return value
mockedConvertZod.mockReturnValue(mockDeclaration);
// Create a fresh mock coreTool
mockCoreTool = createMockCoreTool();
// Create a new ToolboxTool instance
adkTool = new ToolboxTool(mockCoreTool);
});

it('should correctly set name and description in the constructor', () => {
expect(adkTool.name).toBe('test_tool');
expect(adkTool.description).toBe('A mock tool for testing');
});

it('should call the underlying coreTool with correct args on runAsync', async () => {
const mockArgs = {location: 'London'};
const mockResult = {temp: 20};

mockCoreTool.mockResolvedValue(mockResult);

const request: RunAsyncToolRequest = {
args: mockArgs,
toolContext: {} as unknown as ToolContext,
};

const result = await adkTool.runAsync(request);

expect(mockCoreTool).toHaveBeenCalledWith(mockArgs);
expect(result).toBe(mockResult);
});

it('should generate the FunctionDeclaration using ConvertZodToFunctionDeclaration', () => {
const declaration = adkTool._getDeclaration();
expect(mockedConvertZod).toHaveBeenCalledWith(
'test_tool',
'A mock tool for testing',
mockZodSchema,
);
expect(declaration).toBe(mockDeclaration);
});

it('should return a new ToolboxTool wrapper on addAuthTokenGetters', () => {
const mockGetters = {google: async () => 'token'};
const newMockCoreTool = createMockCoreTool();

mockCoreTool.addAuthTokenGetters.mockReturnValue(newMockCoreTool);

const newAdkTool = adkTool.addAuthTokenGetters(mockGetters);

expect(mockCoreTool.addAuthTokenGetters).toHaveBeenCalledWith(mockGetters);
expect(newAdkTool).toBeInstanceOf(ToolboxTool);
expect(newAdkTool).not.toBe(adkTool);
expect(newAdkTool.getCoreTool()).toBe(newMockCoreTool);
});

it('should return a new ToolboxTool wrapper on addAuthTokenGetter', () => {
const mockGetter = async () => 'token';
const newMockCoreTool = createMockCoreTool();
mockCoreTool.addAuthTokenGetter.mockReturnValue(newMockCoreTool);

const newAdkTool = adkTool.addAuthTokenGetter('google', mockGetter);

expect(mockCoreTool.addAuthTokenGetter).toHaveBeenCalledWith(
'google',
mockGetter,
);
expect(newAdkTool).toBeInstanceOf(ToolboxTool);
expect(newAdkTool).not.toBe(adkTool);
expect(newAdkTool.getCoreTool()).toBe(newMockCoreTool);
});

it('should return a new ToolboxTool wrapper on bindParams', () => {
const mockParams = {api_key: '123'};
const newMockCoreTool = createMockCoreTool();
mockCoreTool.bindParams.mockReturnValue(newMockCoreTool);

const newAdkTool = adkTool.bindParams(mockParams);

expect(mockCoreTool.bindParams).toHaveBeenCalledWith(mockParams);
expect(newAdkTool).toBeInstanceOf(ToolboxTool);
expect(newAdkTool).not.toBe(adkTool);
expect(newAdkTool.getCoreTool()).toBe(newMockCoreTool);
});

it('should return a new ToolboxTool wrapper on bindParam', () => {
const newMockCoreTool = createMockCoreTool();
mockCoreTool.bindParam.mockReturnValue(newMockCoreTool);

const newAdkTool = adkTool.bindParam('api_key', '123');

expect(mockCoreTool.bindParam).toHaveBeenCalledWith('api_key', '123');
expect(newAdkTool).toBeInstanceOf(ToolboxTool);
expect(newAdkTool).not.toBe(adkTool);
expect(newAdkTool.getCoreTool()).toBe(newMockCoreTool);
});

it('should return the underlying coreTool via getCoreTool', () => {
expect(adkTool.getCoreTool()).toBe(mockCoreTool);
});
});
Loading