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
15 changes: 15 additions & 0 deletions core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,21 @@ export {
export {RunSkillScriptTool} from './tools/skill/run_skill_script_tool.js';

export * from './integrations/agent_registry/agent_registry.js';
export {GcsAdminToolset} from './integrations/gcs/admin_toolset.js';
export type {GcsAdminToolsetOptions} from './integrations/gcs/admin_toolset.js';
export {
GCS_DEFAULT_SCOPES,
GcsCredentialsConfig,
} from './integrations/gcs/credentials.js';
export type {GcsCredentialsConfigOptions} from './integrations/gcs/credentials.js';
export {GcsToolset} from './integrations/gcs/storage_toolset.js';
export type {GcsToolsetOptions} from './integrations/gcs/toolset_base.js';
export {
GCS_TOOL_NAME_PREFIX,
GcsCapability,
GcsToolStatus,
} from './integrations/gcs/types.js';
export type {GcsToolResult, GcsToolSettings} from './integrations/gcs/types.js';
export * from './telemetry/google_cloud.js';
export * from './telemetry/setup.js';
export * from './tools/mcp/load_mcp_resource_tool.js';
Expand Down
179 changes: 179 additions & 0 deletions core/src/integrations/gcs/admin_tool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {BucketMetadata, Storage} from '@google-cloud/storage';
import {z} from 'zod';

import {BaseTool} from '../../tools/base_tool.js';
import {FunctionTool} from '../../tools/function_tool.js';
import {GcsClientProvider} from './client.js';
import {listResult, pageOptions, toErrorResult} from './helpers.js';
import {GCS_TOOL_NAME_PREFIX, GcsToolResult, GcsToolStatus} from './types.js';

const projectIdSchema = z.string().describe('The Google Cloud project id.');

async function listBuckets(
storage: Storage,
args: {project_id: string; page_size?: number; page_token?: string},
): Promise<GcsToolResult> {
try {
const [buckets, nextQuery] = await storage.getBuckets({
project: args.project_id,
...pageOptions(args.page_size, args.page_token),
});
return listResult(
buckets.map((bucket) => bucket.name),
nextQuery,
args.page_size,
);
} catch (error: unknown) {
return toErrorResult(error);
}
}

async function createBucket(
storage: Storage,
args: {bucket_name: string; location?: string},
): Promise<GcsToolResult> {
try {
const [bucket] = await storage.createBucket(
args.bucket_name,
args.location !== undefined ? {location: args.location} : {},
);
return {
status: GcsToolStatus.SUCCESS,
results: `Bucket ${bucket.name} created successfully.`,
};
} catch (error: unknown) {
return toErrorResult(error);
}
}

async function updateBucket(
storage: Storage,
args: {
bucket_name: string;
versioning_enabled?: boolean;
uniform_bucket_level_access_enabled?: boolean;
},
): Promise<GcsToolResult> {
try {
const metadata: BucketMetadata = {
...(args.versioning_enabled !== undefined
? {versioning: {enabled: args.versioning_enabled}}
: {}),
...(args.uniform_bucket_level_access_enabled !== undefined
? {
iamConfiguration: {
uniformBucketLevelAccess: {
enabled: args.uniform_bucket_level_access_enabled,
},
},
}
: {}),
};
if (Object.keys(metadata).length > 0) {
await storage.bucket(args.bucket_name).setMetadata(metadata);
}
return {
status: GcsToolStatus.SUCCESS,
results: `Bucket ${args.bucket_name} updated successfully.`,
};
} catch (error: unknown) {
return toErrorResult(error);
}
}

async function deleteBucket(
storage: Storage,
args: {bucket_name: string},
): Promise<GcsToolResult> {
try {
await storage.bucket(args.bucket_name).delete();
return {
status: GcsToolStatus.SUCCESS,
results: `Bucket ${args.bucket_name} deleted successfully.`,
};
} catch (error: unknown) {
return toErrorResult(error);
}
}

/** Bucket administration tools that only read from Cloud Storage. */
export function createAdminReadTools(getClient: GcsClientProvider): BaseTool[] {
return [
new FunctionTool({
name: `${GCS_TOOL_NAME_PREFIX}_list_buckets`,
description: 'List GCS bucket names in a Google Cloud project.',
parameters: z.object({
project_id: projectIdSchema,
page_size: z
.number()
.int()
.min(1)
.optional()
.describe(
'The maximum number of buckets to return in a single page.',
),
page_token: z
.string()
.optional()
.describe(
'A page token, received from a previous list_buckets call.',
),
}),
execute: (args) => listBuckets(getClient(args.project_id), args),
}),
];
}

/** Bucket administration tools that mutate Cloud Storage. */
export function createAdminWriteTools(
getClient: GcsClientProvider,
): BaseTool[] {
return [
new FunctionTool({
name: `${GCS_TOOL_NAME_PREFIX}_create_bucket`,
description: 'Create a new GCS bucket.',
parameters: z.object({
project_id: projectIdSchema,
bucket_name: z
.string()
.describe('The name of the GCS bucket to create.'),
location: z.string().optional().describe('The location of the bucket.'),
}),
execute: (args) => createBucket(getClient(args.project_id), args),
}),
new FunctionTool({
name: `${GCS_TOOL_NAME_PREFIX}_update_bucket`,
description: 'Update properties of a GCS bucket.',
parameters: z.object({
bucket_name: z
.string()
.describe('The name of the GCS bucket to update.'),
versioning_enabled: z
.boolean()
.optional()
.describe('Whether to enable versioning for the bucket.'),
uniform_bucket_level_access_enabled: z
.boolean()
.optional()
.describe('Whether to enable uniform bucket-level access.'),
}),
execute: (args) => updateBucket(getClient(), args),
}),
new FunctionTool({
name: `${GCS_TOOL_NAME_PREFIX}_delete_bucket`,
description: 'Delete a GCS bucket.',
parameters: z.object({
bucket_name: z
.string()
.describe('The name of the GCS bucket to delete.'),
}),
execute: (args) => deleteBucket(getClient(), args),
}),
];
}
29 changes: 29 additions & 0 deletions core/src/integrations/gcs/admin_toolset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {experimental} from '../../utils/experimental.js';
import {createAdminReadTools, createAdminWriteTools} from './admin_tool.js';
import {GcsToolsetBase, GcsToolsetOptions} from './toolset_base.js';

/** Options for {@link GcsAdminToolset}. */
export type GcsAdminToolsetOptions = GcsToolsetOptions;

/**
* Toolset for Cloud Storage bucket administration (Experimental).
*
* The tool names are `gcs_list_buckets` and, with the
* `GcsCapability.READ_WRITE` capability, `gcs_create_bucket`,
* `gcs_update_bucket` and `gcs_delete_bucket`.
*
* This toolset is deliberately separate from `GcsToolset`: granting it hands
* an agent bucket-level privileges, including bucket deletion.
*/
@experimental
export class GcsAdminToolset extends GcsToolsetBase {
constructor(options: GcsAdminToolsetOptions = {}) {
super(options, {read: createAdminReadTools, write: createAdminWriteTools});
}
}
32 changes: 32 additions & 0 deletions core/src/integrations/gcs/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {Storage} from '@google-cloud/storage';

import {version} from '../../version.js';
import {GcsCredentialsConfig} from './credentials.js';

/** User agent reported by every Cloud Storage client the tools build. */
const USER_AGENT = `adk-gcs-tool google-adk/${version}`;

/** Supplies the Cloud Storage client a tool should operate through. */
export type GcsClientProvider = (project?: string) => Storage;

/**
* Builds a Cloud Storage client. Without a credentials config the client is
* built from Application Default Credentials.
*/
export function createGcsClient(
credentialsConfig?: GcsCredentialsConfig,
project?: string,
): Storage {
return new Storage({
...(credentialsConfig
? credentialsConfig.toStorageOptions(project)
: {...(project ? {projectId: project} : {})}),
userAgent: USER_AGENT,
});
}
98 changes: 98 additions & 0 deletions core/src/integrations/gcs/credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {StorageOptions} from '@google-cloud/storage';

import {experimental} from '../../utils/experimental.js';

/** Scopes requested when none are configured explicitly. */
export const GCS_DEFAULT_SCOPES = [
'https://www.googleapis.com/auth/devstorage.full_control',
];

/** Options for {@link GcsCredentialsConfig}. */
export interface GcsCredentialsConfigOptions {
/**
* Ready-made options for the Cloud Storage client, and the place to pass an
* already-authenticated client as `{authClient}`. That client is used for
* every end user, so only set it when it is allowed to reach every end
* user's data. Mutually exclusive with `clientId`, `clientSecret` and
* `scopes`.
*/
storageOptions?: StorageOptions;
/** The OAuth client id to use. Requires `clientSecret`. */
clientId?: string;
/** The OAuth client secret to use. Requires `clientId`. */
clientSecret?: string;
/** The scopes to request. Defaults to {@link GCS_DEFAULT_SCOPES}. */
scopes?: string[];
/** The Google Cloud project the client operates against. */
projectId?: string;
}

/**
* Credentials configuration for the GCS toolsets (Experimental).
*
* Known limitation: an OAuth client id and secret alone carry no access
* token. adk-python mints one by driving the interactive OAuth consent flow,
* which adk-js does not implement yet, so requests made through the
* `clientId`/`clientSecret` path fail at request time and surface as a normal
* `{status: 'ERROR'}` tool result. Pass an authenticated client through
* `storageOptions`, or pass no credentials config at all and rely on
* Application Default Credentials.
*/
@experimental
export class GcsCredentialsConfig {
readonly storageOptions?: StorageOptions;
readonly clientId?: string;
readonly clientSecret?: string;
readonly scopes: string[];
readonly projectId?: string;

constructor(options: GcsCredentialsConfigOptions) {
if (
options.storageOptions &&
(options.clientId || options.clientSecret || options.scopes)
) {
throw new Error(
'If storageOptions are provided, clientId, clientSecret and scopes must not be provided.',
);
}
if (
!options.storageOptions &&
!(options.clientId && options.clientSecret)
) {
throw new Error(
'Must provide either storageOptions, or both clientId and clientSecret.',
);
}

this.storageOptions = options.storageOptions;
this.clientId = options.clientId;
this.clientSecret = options.clientSecret;
this.scopes = options.scopes ?? GCS_DEFAULT_SCOPES;
this.projectId = options.projectId;
}

/**
* Builds the options used to construct a Cloud Storage client.
*
* @param project Overrides the configured `projectId` when supplied.
*/
toStorageOptions(project?: string): StorageOptions {
const projectId = project ?? this.projectId;
return {
...(this.storageOptions ?? {
clientOptions: {
clientId: this.clientId,
clientSecret: this.clientSecret,
},
scopes: this.scopes,
}),
...(projectId ? {projectId} : {}),
};
}
}
Loading
Loading