diff --git a/src/agentengines.ts b/src/agentengines.ts index ac654d4e..1bccd3d5 100644 --- a/src/agentengines.ts +++ b/src/agentengines.ts @@ -19,13 +19,73 @@ export class AgentEngines extends BaseModule { public readonly sandboxes: Sandboxes; public readonly memories: Memories; + /** Cached resource name of the lazily-created default agent engine. */ + private defaultAgentEngineName?: string; + /** In-flight promise so concurrent callers share a single default agent engine. */ + private defaultAgentEnginePromise?: Promise; + constructor(private readonly apiClient: ApiClient) { super(); this.sessions = new Sessions(apiClient); - this.sandboxes = new Sandboxes(apiClient); + this.sandboxes = new Sandboxes(apiClient, () => + this.ensureDefaultAgentEngine(), + ); this.memories = new Memories(apiClient); } + /** + * Returns the resource name of a shared "default" agent engine for this + * project and location, creating it lazily if it does not exist. This backs + * sandbox calls where the caller does not supply an agent engine name, so + * repeated calls reuse one agent engine instead of creating many. + */ + private async ensureDefaultAgentEngine(): Promise { + if (this.defaultAgentEngineName) { + return this.defaultAgentEngineName; + } + if (!this.defaultAgentEnginePromise) { + this.defaultAgentEnginePromise = this.resolveDefaultAgentEngine().catch( + (e) => { + // Allow a later call to retry if resolution failed. + this.defaultAgentEnginePromise = undefined; + throw e; + }, + ); + } + return this.defaultAgentEnginePromise; + } + + private async resolveDefaultAgentEngine(): Promise { + const displayName = 'default-agent-platform-sandbox-host'; + // Reuse an existing default agent engine if one already exists. + const listResponse = await this.listInternal({}); + const existing = listResponse.reasoningEngines?.find( + (reasoningEngine) => + reasoningEngine.displayName === displayName && !!reasoningEngine.name, + ); + if (existing?.name) { + this.defaultAgentEngineName = existing.name; + return existing.name; + } + // Otherwise create one and wait for the operation to complete. + let operation = await this.createInternal({config: {displayName}}); + while (!operation.done) { + await new Promise((resolve) => setTimeout(resolve, 5000)); + operation = await this.getAgentOperationInternal({ + operationName: operation.name!, + }); + } + if (operation.error) { + throw new Error( + `Failed to create default agent engine: ${JSON.stringify( + operation.error, + )}`, + ); + } + this.defaultAgentEngineName = operation.response!.name!; + return this.defaultAgentEngineName; + } + async createInternal( params: types.CreateAgentEngineRequestParameters, ): Promise { diff --git a/src/client.ts b/src/client.ts index f530da00..114a1b69 100644 --- a/src/client.ts +++ b/src/client.ts @@ -6,6 +6,7 @@ import {ApiClient, NodeAuth, NodeDownloader, NodeUploader,} from '@google/genai/vertex_internal'; import {AgentEngines} from './agentengines'; +import {Sandboxes} from './sandboxes'; import {Skills} from './skills'; import {Prompts} from './prompts'; @@ -78,4 +79,12 @@ export class Client { } return this._agentEnginesInternal; } + + /** + * Provides access to the Agent Platform Sandboxes API, including its + * `environments`, `templates`, and `snapshots` submodules. + */ + public get sandboxes(): Sandboxes { + return this._agentEnginesInternal.sandboxes; + } } diff --git a/src/converters/_sandboxenvironments_converters.ts b/src/converters/_sandboxenvironments_converters.ts new file mode 100644 index 00000000..810641b8 --- /dev/null +++ b/src/converters/_sandboxenvironments_converters.ts @@ -0,0 +1,195 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +// Code generated by the Google Gen AI SDK generator DO NOT EDIT. + +import * as common from '@google/genai/vertex_internal'; +import * as types from '../types.js'; + +export function createAgentEngineSandboxConfigToVertex( + fromObject: types.CreateAgentEngineSandboxConfig, + parentObject: Record, +): Record { + const toObject: Record = {}; + + const fromDisplayName = common.getValueByPath(fromObject, ['displayName']); + if (parentObject !== undefined && fromDisplayName != null) { + common.setValueByPath(parentObject, ['displayName'], fromDisplayName); + } + + const fromDescription = common.getValueByPath(fromObject, ['description']); + if (parentObject !== undefined && fromDescription != null) { + common.setValueByPath(parentObject, ['description'], fromDescription); + } + + const fromTtl = common.getValueByPath(fromObject, ['ttl']); + if (parentObject !== undefined && fromTtl != null) { + common.setValueByPath(parentObject, ['ttl'], fromTtl); + } + + const fromSandboxEnvironmentTemplate = common.getValueByPath(fromObject, [ + 'sandboxEnvironmentTemplate', + ]); + if (parentObject !== undefined && fromSandboxEnvironmentTemplate != null) { + common.setValueByPath( + parentObject, + ['sandboxEnvironmentTemplate'], + fromSandboxEnvironmentTemplate, + ); + } + + const fromSandboxEnvironmentSnapshot = common.getValueByPath(fromObject, [ + 'sandboxEnvironmentSnapshot', + ]); + if (parentObject !== undefined && fromSandboxEnvironmentSnapshot != null) { + common.setValueByPath( + parentObject, + ['sandboxEnvironmentSnapshot'], + fromSandboxEnvironmentSnapshot, + ); + } + + const fromOwner = common.getValueByPath(fromObject, ['owner']); + if (parentObject !== undefined && fromOwner != null) { + common.setValueByPath(parentObject, ['owner'], fromOwner); + } + + return toObject; +} + +export function createAgentEngineSandboxRequestParametersToVertex( + fromObject: types.CreateAgentEngineSandboxRequestParameters, +): Record { + const toObject: Record = {}; + + const fromName = common.getValueByPath(fromObject, ['name']); + if (fromName != null) { + common.setValueByPath(toObject, ['_url', 'name'], fromName); + } + + const fromSpec = common.getValueByPath(fromObject, ['spec']); + if (fromSpec != null) { + common.setValueByPath(toObject, ['spec'], fromSpec); + } + + const fromConfig = common.getValueByPath(fromObject, ['config']); + if (fromConfig != null) { + createAgentEngineSandboxConfigToVertex(fromConfig, toObject); + } + + return toObject; +} + +export function deleteAgentEngineSandboxRequestParametersToVertex( + fromObject: types.DeleteAgentEngineSandboxRequestParameters, +): Record { + const toObject: Record = {}; + + const fromName = common.getValueByPath(fromObject, ['name']); + if (fromName != null) { + common.setValueByPath(toObject, ['_url', 'name'], fromName); + } + + return toObject; +} + +export function executeCodeAgentEngineSandboxRequestParametersToVertex( + fromObject: types.ExecuteCodeAgentEngineSandboxRequestParameters, +): Record { + const toObject: Record = {}; + + const fromName = common.getValueByPath(fromObject, ['name']); + if (fromName != null) { + common.setValueByPath(toObject, ['_url', 'name'], fromName); + } + + const fromInputs = common.getValueByPath(fromObject, ['inputs']); + if (fromInputs != null) { + let transformedList = fromInputs; + if (Array.isArray(transformedList)) { + transformedList = transformedList.map((item) => { + return item; + }); + } + common.setValueByPath(toObject, ['inputs'], transformedList); + } + + return toObject; +} + +export function getAgentEngineSandboxOperationParametersToVertex( + fromObject: types.GetAgentEngineSandboxOperationParameters, +): Record { + const toObject: Record = {}; + + const fromOperationName = common.getValueByPath(fromObject, [ + 'operationName', + ]); + if (fromOperationName != null) { + common.setValueByPath( + toObject, + ['_url', 'operationName'], + fromOperationName, + ); + } + + return toObject; +} + +export function getAgentEngineSandboxRequestParametersToVertex( + fromObject: types.GetAgentEngineSandboxRequestParameters, +): Record { + const toObject: Record = {}; + + const fromName = common.getValueByPath(fromObject, ['name']); + if (fromName != null) { + common.setValueByPath(toObject, ['_url', 'name'], fromName); + } + + return toObject; +} + +export function listAgentEngineSandboxesConfigToVertex( + fromObject: types.ListAgentEngineSandboxesConfig, + parentObject: Record, +): Record { + const toObject: Record = {}; + + const fromPageSize = common.getValueByPath(fromObject, ['pageSize']); + if (parentObject !== undefined && fromPageSize != null) { + common.setValueByPath(parentObject, ['_query', 'pageSize'], fromPageSize); + } + + const fromPageToken = common.getValueByPath(fromObject, ['pageToken']); + if (parentObject !== undefined && fromPageToken != null) { + common.setValueByPath(parentObject, ['_query', 'pageToken'], fromPageToken); + } + + const fromFilter = common.getValueByPath(fromObject, ['filter']); + if (parentObject !== undefined && fromFilter != null) { + common.setValueByPath(parentObject, ['_query', 'filter'], fromFilter); + } + + return toObject; +} + +export function listAgentEngineSandboxesRequestParametersToVertex( + fromObject: types.ListAgentEngineSandboxesRequestParameters, +): Record { + const toObject: Record = {}; + + const fromName = common.getValueByPath(fromObject, ['name']); + if (fromName != null) { + common.setValueByPath(toObject, ['_url', 'name'], fromName); + } + + const fromConfig = common.getValueByPath(fromObject, ['config']); + if (fromConfig != null) { + listAgentEngineSandboxesConfigToVertex(fromConfig, toObject); + } + + return toObject; +} diff --git a/src/sandboxenvironments.ts b/src/sandboxenvironments.ts new file mode 100644 index 00000000..66a51af7 --- /dev/null +++ b/src/sandboxenvironments.ts @@ -0,0 +1,347 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +// Code generated by the Google Gen AI SDK generator DO NOT EDIT. + +import * as common from '@google/genai/vertex_internal'; +import {ApiClient, BaseModule} from '@google/genai/vertex_internal'; +import * as converters from './converters/_sandboxenvironments_converters.js'; +import * as types from './types.js'; + +export class SandboxEnvironments extends BaseModule { + constructor( + private readonly apiClient: ApiClient, + private readonly ensureDefaultParent?: () => Promise, + ) { + super(); + } + + /** + * Resolves the parent agent engine name, falling back to a shared default + * agent engine when the caller does not provide one. + */ + private async resolveParentName(name?: string): Promise { + if (name) { + return name; + } + if (!this.ensureDefaultParent) { + throw new Error('An agent engine name is required.'); + } + return this.ensureDefaultParent(); + } + + /** + * Creates a sandbox environment and returns the operation. If `name` (the + * parent agent engine) is omitted, a shared default agent engine is used. + */ + async create( + params: Omit & { + name?: string; + }, + ): Promise { + const name = await this.resolveParentName(params.name); + return this.createInternal({...params, name}); + } + + /** Deletes the sandbox environment and returns the operation. */ + async delete( + params: types.DeleteAgentEngineSandboxRequestParameters, + ): Promise { + return this.deleteInternal(params); + } + + /** Executes code in the sandbox environment. */ + async executeCode( + params: types.ExecuteCodeAgentEngineSandboxRequestParameters, + ): Promise { + return this.executeCodeInternal(params); + } + + /** Returns the sandbox environment with the specified name. */ + async get( + params: types.GetAgentEngineSandboxRequestParameters, + ): Promise { + return this.getInternal(params); + } + + /** + * Lists the sandbox environments. If `name` (the parent agent engine) is + * omitted, a shared default agent engine is used. + */ + async list( + params: Omit & { + name?: string; + } = {}, + ): Promise { + const name = await this.resolveParentName(params.name); + return this.listInternal({...params, name}); + } + + /** Returns the sandbox operation with the specified name. */ + async getSandboxOperation( + params: types.GetAgentEngineSandboxOperationParameters, + ): Promise { + return this.getSandboxOperationInternal(params); + } + + async createInternal( + params: types.CreateAgentEngineSandboxRequestParameters, + ): Promise { + let response: Promise; + + let path: string = ''; + let queryParams: Record = {}; + if (this.apiClient.isVertexAI()) { + const body = + converters.createAgentEngineSandboxRequestParametersToVertex(params); + path = common.formatMap( + '{name}/sandboxEnvironments', + body['_url'] as Record, + ); + queryParams = body['_query'] as Record; + delete body['_url']; + delete body['_query']; + delete body['config']; + + response = this.apiClient + .request({ + path: path, + queryParams: queryParams, + body: JSON.stringify(body), + httpMethod: 'POST', + httpOptions: params.config?.httpOptions, + abortSignal: params.config?.abortSignal, + }) + .then((httpResponse) => { + return httpResponse.json(); + }) as Promise; + + return response.then((resp) => { + return resp as types.AgentEngineSandboxOperation; + }); + } else { + throw new Error( + 'This method is only supported by the Gemini Enterprise Agent Platform (previously known as Vertex AI).', + ); + } + } + + async deleteInternal( + params: types.DeleteAgentEngineSandboxRequestParameters, + ): Promise { + let response: Promise; + + let path: string = ''; + let queryParams: Record = {}; + if (this.apiClient.isVertexAI()) { + const body = + converters.deleteAgentEngineSandboxRequestParametersToVertex(params); + path = common.formatMap( + '{name}', + body['_url'] as Record, + ); + queryParams = body['_query'] as Record; + delete body['_url']; + delete body['_query']; + delete body['config']; + + response = this.apiClient + .request({ + path: path, + queryParams: queryParams, + body: JSON.stringify(body), + httpMethod: 'DELETE', + httpOptions: params.config?.httpOptions, + abortSignal: params.config?.abortSignal, + }) + .then((httpResponse) => { + return httpResponse.json(); + }) as Promise; + + return response.then((resp) => { + return resp as types.DeleteAgentEngineSandboxOperation; + }); + } else { + throw new Error( + 'This method is only supported by the Gemini Enterprise Agent Platform (previously known as Vertex AI).', + ); + } + } + + async executeCodeInternal( + params: types.ExecuteCodeAgentEngineSandboxRequestParameters, + ): Promise { + let response: Promise; + + let path: string = ''; + let queryParams: Record = {}; + if (this.apiClient.isVertexAI()) { + const body = + converters.executeCodeAgentEngineSandboxRequestParametersToVertex( + params, + ); + path = common.formatMap( + '{name}/:execute', + body['_url'] as Record, + ); + queryParams = body['_query'] as Record; + delete body['_url']; + delete body['_query']; + delete body['config']; + + response = this.apiClient + .request({ + path: path, + queryParams: queryParams, + body: JSON.stringify(body), + httpMethod: 'POST', + httpOptions: params.config?.httpOptions, + abortSignal: params.config?.abortSignal, + }) + .then((httpResponse) => { + return httpResponse.json(); + }) as Promise; + + return response.then((resp) => { + const typedResp = new types.ExecuteSandboxEnvironmentResponse(); + Object.assign(typedResp, resp); + return typedResp; + }); + } else { + throw new Error( + 'This method is only supported by the Gemini Enterprise Agent Platform (previously known as Vertex AI).', + ); + } + } + + async getInternal( + params: types.GetAgentEngineSandboxRequestParameters, + ): Promise { + let response: Promise; + + let path: string = ''; + let queryParams: Record = {}; + if (this.apiClient.isVertexAI()) { + const body = + converters.getAgentEngineSandboxRequestParametersToVertex(params); + path = common.formatMap( + '{name}', + body['_url'] as Record, + ); + queryParams = body['_query'] as Record; + delete body['_url']; + delete body['_query']; + delete body['config']; + + response = this.apiClient + .request({ + path: path, + queryParams: queryParams, + body: JSON.stringify(body), + httpMethod: 'GET', + httpOptions: params.config?.httpOptions, + abortSignal: params.config?.abortSignal, + }) + .then((httpResponse) => { + return httpResponse.json(); + }) as Promise; + + return response.then((resp) => { + return resp as types.SandboxEnvironment; + }); + } else { + throw new Error( + 'This method is only supported by the Gemini Enterprise Agent Platform (previously known as Vertex AI).', + ); + } + } + + async listInternal( + params: types.ListAgentEngineSandboxesRequestParameters, + ): Promise { + let response: Promise; + + let path: string = ''; + let queryParams: Record = {}; + if (this.apiClient.isVertexAI()) { + const body = + converters.listAgentEngineSandboxesRequestParametersToVertex(params); + path = common.formatMap( + '{name}/sandboxEnvironments', + body['_url'] as Record, + ); + queryParams = body['_query'] as Record; + delete body['_url']; + delete body['_query']; + delete body['config']; + + response = this.apiClient + .request({ + path: path, + queryParams: queryParams, + body: JSON.stringify(body), + httpMethod: 'GET', + httpOptions: params.config?.httpOptions, + abortSignal: params.config?.abortSignal, + }) + .then((httpResponse) => { + return httpResponse.json(); + }) as Promise; + + return response.then((resp) => { + const typedResp = new types.ListAgentEngineSandboxesResponse(); + Object.assign(typedResp, resp); + return typedResp; + }); + } else { + throw new Error( + 'This method is only supported by the Gemini Enterprise Agent Platform (previously known as Vertex AI).', + ); + } + } + + async getSandboxOperationInternal( + params: types.GetAgentEngineSandboxOperationParameters, + ): Promise { + let response: Promise; + + let path: string = ''; + let queryParams: Record = {}; + if (this.apiClient.isVertexAI()) { + const body = + converters.getAgentEngineSandboxOperationParametersToVertex(params); + path = common.formatMap( + '{operationName}', + body['_url'] as Record, + ); + queryParams = body['_query'] as Record; + delete body['_url']; + delete body['_query']; + delete body['config']; + + response = this.apiClient + .request({ + path: path, + queryParams: queryParams, + body: JSON.stringify(body), + httpMethod: 'GET', + httpOptions: params.config?.httpOptions, + abortSignal: params.config?.abortSignal, + }) + .then((httpResponse) => { + return httpResponse.json(); + }) as Promise; + + return response.then((resp) => { + return resp as types.AgentEngineSandboxOperation; + }); + } else { + throw new Error( + 'This method is only supported by the Gemini Enterprise Agent Platform (previously known as Vertex AI).', + ); + } + } +} diff --git a/src/sandboxes.ts b/src/sandboxes.ts index 9114124e..28bd1aee 100644 --- a/src/sandboxes.ts +++ b/src/sandboxes.ts @@ -9,18 +9,92 @@ import * as common from '@google/genai/vertex_internal'; import {ApiClient, BaseModule} from '@google/genai/vertex_internal'; import * as converters from './converters/_sandboxes_converters.js'; +import {SandboxEnvironments} from './sandboxenvironments.js'; import {SandboxSnapshots} from './sandboxsnapshots.js'; import {SandboxTemplates} from './sandboxtemplates.js'; import * as types from './types.js'; export class Sandboxes extends BaseModule { + public readonly environments: SandboxEnvironments; public readonly templates: SandboxTemplates; public readonly snapshots: SandboxSnapshots; - constructor(private readonly apiClient: ApiClient) { + constructor( + private readonly apiClient: ApiClient, + private readonly ensureDefaultParent?: () => Promise, + ) { super(); - this.templates = new SandboxTemplates(apiClient); - this.snapshots = new SandboxSnapshots(apiClient); + this.environments = new SandboxEnvironments(apiClient, ensureDefaultParent); + this.templates = new SandboxTemplates(apiClient, ensureDefaultParent); + this.snapshots = new SandboxSnapshots(apiClient, ensureDefaultParent); + } + + /** + * Resolves the parent agent engine name, falling back to a shared default + * agent engine when the caller does not provide one. + */ + private async resolveParentName(name?: string): Promise { + if (name) { + return name; + } + if (!this.ensureDefaultParent) { + throw new Error('An agent engine name is required.'); + } + return this.ensureDefaultParent(); + } + + /** + * Creates a sandbox environment and returns the operation. If `name` (the + * parent agent engine) is omitted, a shared default agent engine is used. + */ + async create( + params: Omit & { + name?: string; + }, + ): Promise { + const name = await this.resolveParentName(params.name); + return this.createInternal({...params, name}); + } + + /** Deletes the sandbox environment and returns the operation. */ + async delete( + params: types.DeleteAgentEngineSandboxRequestParameters, + ): Promise { + return this.deleteInternal(params); + } + + /** Executes code in the sandbox environment. */ + async executeCode( + params: types.ExecuteCodeAgentEngineSandboxRequestParameters, + ): Promise { + return this.executeCodeInternal(params); + } + + /** Returns the sandbox environment with the specified name. */ + async get( + params: types.GetAgentEngineSandboxRequestParameters, + ): Promise { + return this.getInternal(params); + } + + /** + * Lists the sandbox environments. If `name` (the parent agent engine) is + * omitted, a shared default agent engine is used. + */ + async list( + params: Omit & { + name?: string; + } = {}, + ): Promise { + const name = await this.resolveParentName(params.name); + return this.listInternal({...params, name}); + } + + /** Returns the sandbox operation with the specified name. */ + async getSandboxOperation( + params: types.GetAgentEngineSandboxOperationParameters, + ): Promise { + return this.getSandboxOperationInternal(params); } async createInternal( diff --git a/src/sandboxsnapshots.ts b/src/sandboxsnapshots.ts index bfc069eb..199a72f7 100644 --- a/src/sandboxsnapshots.ts +++ b/src/sandboxsnapshots.ts @@ -12,10 +12,64 @@ import * as converters from './converters/_sandboxsnapshots_converters.js'; import * as types from './types.js'; export class SandboxSnapshots extends BaseModule { - constructor(private readonly apiClient: ApiClient) { + constructor( + private readonly apiClient: ApiClient, + private readonly ensureDefaultParent?: () => Promise, + ) { super(); } + /** + * Resolves the parent agent engine name, falling back to a shared default + * agent engine when the caller does not provide one. + */ + private async resolveParentName(name?: string): Promise { + if (name) { + return name; + } + if (!this.ensureDefaultParent) { + throw new Error('An agent engine name is required.'); + } + return this.ensureDefaultParent(); + } + + /** Creates a snapshot of a running sandbox and returns the operation. */ + async create( + params: types.CreateSandboxEnvironmentSnapshotRequestParameters, + ): Promise { + return this.createInternal(params); + } + + /** Returns the sandbox environment snapshot with the specified name. */ + async get( + params: types.GetSandboxEnvironmentSnapshotRequestParameters, + ): Promise { + return this.getInternal(params); + } + + /** + * Lists the sandbox environment snapshots. If `name` (the parent agent + * engine) is omitted, a shared default agent engine is used. + */ + async list( + params: Omit< + types.ListSandboxEnvironmentSnapshotsRequestParameters, + 'name' + > & { + name?: string; + } = {}, + ): Promise { + const name = await this.resolveParentName(params.name); + return this.listInternal({...params, name}); + } + + /** Deletes the sandbox environment snapshot and returns the operation. */ + async delete( + params: types.DeleteSandboxEnvironmentSnapshotRequestParameters, + ): Promise { + return this.deleteInternal(params); + } + async createInternal( params: types.CreateSandboxEnvironmentSnapshotRequestParameters, ): Promise { diff --git a/src/sandboxtemplates.ts b/src/sandboxtemplates.ts index 0a1c275f..1dc0ba65 100644 --- a/src/sandboxtemplates.ts +++ b/src/sandboxtemplates.ts @@ -12,10 +12,73 @@ import * as converters from './converters/_sandboxtemplates_converters.js'; import * as types from './types.js'; export class SandboxTemplates extends BaseModule { - constructor(private readonly apiClient: ApiClient) { + constructor( + private readonly apiClient: ApiClient, + private readonly ensureDefaultParent?: () => Promise, + ) { super(); } + /** + * Resolves the parent agent engine name, falling back to a shared default + * agent engine when the caller does not provide one. + */ + private async resolveParentName(name?: string): Promise { + if (name) { + return name; + } + if (!this.ensureDefaultParent) { + throw new Error('An agent engine name is required.'); + } + return this.ensureDefaultParent(); + } + + /** + * Creates a sandbox environment template and returns the operation. If `name` + * (the parent agent engine) is omitted, a shared default agent engine is used. + */ + async create( + params: Omit< + types.CreateSandboxEnvironmentTemplateRequestParameters, + 'name' + > & { + name?: string; + }, + ): Promise { + const name = await this.resolveParentName(params.name); + return this.createInternal({...params, name}); + } + + /** Returns the sandbox environment template with the specified name. */ + async get( + params: types.GetSandboxEnvironmentTemplateRequestParameters, + ): Promise { + return this.getInternal(params); + } + + /** + * Lists the sandbox environment templates. If `name` (the parent agent + * engine) is omitted, a shared default agent engine is used. + */ + async list( + params: Omit< + types.ListSandboxEnvironmentTemplatesRequestParameters, + 'name' + > & { + name?: string; + } = {}, + ): Promise { + const name = await this.resolveParentName(params.name); + return this.listInternal({...params, name}); + } + + /** Deletes the sandbox environment template and returns the operation. */ + async delete( + params: types.DeleteSandboxEnvironmentTemplateRequestParameters, + ): Promise { + return this.deleteInternal(params); + } + async createInternal( params: types.CreateSandboxEnvironmentTemplateRequestParameters, ): Promise { diff --git a/system_test/sandbox_templates_snapshots_e2e_test.ts b/system_test/sandbox_templates_snapshots_e2e_test.ts index 852cb2d9..4fe30251 100644 --- a/system_test/sandbox_templates_snapshots_e2e_test.ts +++ b/system_test/sandbox_templates_snapshots_e2e_test.ts @@ -10,7 +10,7 @@ import * as types from '../src/types.js'; const PROJECT = process.env['GCLOUD_PROJECT']; const LOCATION = 'us-central1'; -// This end-to-end test provisions a real Agent Engine sandbox (a running +// This end-to-end test provisions a real sandbox environment (a running // container VM), which requires a project with sandbox provisioning enabled. // That infrastructure is not available in the standard client-library CI // project, so the test only runs when RUN_SANDBOX_E2E is set (e.g. against a @@ -19,27 +19,11 @@ const LOCATION = 'us-central1'; const sandboxE2eDescribe = process.env['RUN_SANDBOX_E2E'] === 'true' ? describe : xdescribe; -/** Polls an Agent Engine operation until it completes. */ -async function pollAgentOperation(client: Client, operationName: string) { - for (let pollCount = 0; pollCount < 120; pollCount++) { - const status = await client.agentEnginesInternal.getAgentOperationInternal({ - operationName, - }); - if (status.done) { - return status; - } - await new Promise((resolve) => setTimeout(resolve, 5000)); - } - throw new Error(`Operation ${operationName} did not complete within the timeout.`); -} - -/** Polls a sandbox operation until it completes. */ +/** Polls a sandbox environment operation until it completes. */ async function pollSandboxOperation(client: Client, operationName: string) { for (let pollCount = 0; pollCount < 120; pollCount++) { - const status = - await client.agentEnginesInternal.sandboxes.getSandboxOperationInternal({ - operationName, - }); + const status = await client.sandboxes.environments.getSandboxOperation( + {operationName}); if (status.done) { return status; } @@ -51,9 +35,8 @@ async function pollSandboxOperation(client: Client, operationName: string) { /** Polls a sandbox template operation until it completes. */ async function pollTemplateOperation(client: Client, operationName: string) { for (let pollCount = 0; pollCount < 120; pollCount++) { - const status = - await client.agentEnginesInternal.sandboxes.templates - .getSandboxEnvironmentTemplateOperation({operationName}); + const status = await client.sandboxes.templates + .getSandboxEnvironmentTemplateOperation({operationName}); if (status.done) { return status; } @@ -65,9 +48,8 @@ async function pollTemplateOperation(client: Client, operationName: string) { /** Polls a sandbox snapshot operation until it completes. */ async function pollSnapshotOperation(client: Client, operationName: string) { for (let pollCount = 0; pollCount < 120; pollCount++) { - const status = - await client.agentEnginesInternal.sandboxes.snapshots - .getSandboxSnapshotOperation({operationName}); + const status = await client.sandboxes.snapshots.getSandboxSnapshotOperation( + {operationName}); if (status.done) { return status; } @@ -76,9 +58,22 @@ async function pollSnapshotOperation(client: Client, operationName: string) { throw new Error(`Operation ${operationName} did not complete within the timeout.`); } -sandboxE2eDescribe('agentEngineSandboxTemplatesAndSnapshots', () => { +/** Returns the config for a computer-use sandbox template with internet access. */ +function computerUseTemplateConfig(): types.CreateSandboxEnvironmentTemplateConfig { + return { + defaultContainerEnvironment: { + defaultContainerCategory: + types.DefaultContainerCategory.DEFAULT_CONTAINER_CATEGORY_COMPUTER_USE, + }, + egressControlConfig: {internetAccess: true}, + }; +} + +// The sandbox API is used directly from the top-level `client.sandboxes` +// accessor. The parent agent engine name is omitted, so the SDK lazily reuses a +// shared default agent engine for this project + location. +sandboxE2eDescribe('sandboxTemplatesAndSnapshots', () => { let client: Client; - let agentEngineName: string|undefined; beforeAll(async () => { jasmine.DEFAULT_TIMEOUT_INTERVAL = 600000; @@ -86,148 +81,107 @@ sandboxE2eDescribe('agentEngineSandboxTemplatesAndSnapshots', () => { project: PROJECT as string, location: LOCATION, }); - - // Create a temporary Agent Engine to own the sandboxes. - const createOp = await client.agentEnginesInternal.createInternal({ - config: { - displayName: 'sandbox-templates-snapshots-sample', - labels: {'test-label': 'test-value'}, - }, - }); - const status = await pollAgentOperation(client, createOp.name!); - agentEngineName = status.response?.name; }, 600000); - afterAll(async () => { - if (agentEngineName) { - try { - await client.agentEnginesInternal.deleteInternal({name: agentEngineName}); - } catch (e) { - console.error('Failed to delete Agent Engine during cleanup:', e); - } - } - }); - it('should create, get, list, and delete a sandbox template', async () => { - // 1. Create a sandbox environment template. - const createOp = - await client.agentEnginesInternal.sandboxes.templates.createInternal({ - name: agentEngineName!, - displayName: 'Example Sandbox Template', - config: { - defaultContainerEnvironment: { - defaultContainerCategory: types.DefaultContainerCategory - .DEFAULT_CONTAINER_CATEGORY_COMPUTER_USE, - }, - egressControlConfig: {internetAccess: true}, - }, - }); + // 1. Create a sandbox environment template (no agent engine name needed). + const createOp = await client.sandboxes.templates.create({ + displayName: 'Example Sandbox Template', + config: computerUseTemplateConfig(), + }); const status = await pollTemplateOperation(client, createOp.name!); const templateName = status.response?.name; expect(templateName).toBeDefined(); // 2. Get the template. const template = - await client.agentEnginesInternal.sandboxes.templates.getInternal({ - name: templateName!, - }); + await client.sandboxes.templates.get({name: templateName!}); expect(template.name).toEqual(templateName); // 3. List templates. - const listResponse = - await client.agentEnginesInternal.sandboxes.templates.listInternal({ - name: agentEngineName!, - }); + const listResponse = await client.sandboxes.templates.list(); expect(listResponse.sandboxEnvironmentTemplates).toBeDefined(); // 4. Delete the template. const deleteOp = - await client.agentEnginesInternal.sandboxes.templates.deleteInternal({ - name: templateName!, - }); + await client.sandboxes.templates.delete({name: templateName!}); expect(deleteOp.name).toBeDefined(); }); - it('should create, get, list, and delete a sandbox snapshot', async () => { - // Snapshots capture the state of a running sandbox, and only - // template-based (e.g. computer-use) sandboxes are snapshottable. Create a - // computer-use template, then a sandbox from it, before snapshotting. - const templateCreateOp = - await client.agentEnginesInternal.sandboxes.templates.createInternal({ - name: agentEngineName!, - displayName: 'Snapshot Source Template', - config: { - defaultContainerEnvironment: { - defaultContainerCategory: types.DefaultContainerCategory - .DEFAULT_CONTAINER_CATEGORY_COMPUTER_USE, - }, - egressControlConfig: {internetAccess: true}, - }, - }); + it('should create, snapshot, restore, and delete a template-based sandbox', async () => { + // Snapshots capture the state of a running sandbox, and only template-based + // (e.g. computer-use) sandboxes are snapshottable. Create a computer-use + // template, then a sandbox from it, before snapshotting. + const templateCreateOp = await client.sandboxes.templates.create({ + displayName: 'Snapshot Source Template', + config: computerUseTemplateConfig(), + }); const templateStatus = await pollTemplateOperation(client, templateCreateOp.name!); const templateName = templateStatus.response?.name; expect(templateName).toBeDefined(); + let sandboxName: string|undefined; + let restoredSandboxName: string|undefined; try { - // Create a sandbox from the template so it has a snapshottable state. - const sandboxCreateOp = - await client.agentEnginesInternal.sandboxes.createInternal({ - name: agentEngineName!, - config: {sandboxEnvironmentTemplate: templateName!}, - }); + // Create a sandbox environment from the template. + const sandboxCreateOp = await client.sandboxes.environments.create({ + config: { + displayName: 'Snapshot Source Sandbox', + sandboxEnvironmentTemplate: templateName!, + }, + }); const sandboxStatus = await pollSandboxOperation(client, sandboxCreateOp.name!); - const sandboxName = sandboxStatus.response?.name; + sandboxName = sandboxStatus.response?.name; expect(sandboxName).toBeDefined(); - try { - // 1. Create a snapshot of the sandbox. - const createOp = - await client.agentEnginesInternal.sandboxes.snapshots.createInternal( - { - sourceSandboxEnvironmentName: sandboxName!, - config: { - displayName: 'Example Sandbox Snapshot', - ttl: '3600s', - }, - }); - const status = await pollSnapshotOperation(client, createOp.name!); - const snapshotName = status.response?.name; - expect(snapshotName).toBeDefined(); - - // 2. Get the snapshot. - const snapshot = - await client.agentEnginesInternal.sandboxes.snapshots.getInternal({ - name: snapshotName!, - }); - expect(snapshot.name).toEqual(snapshotName); - - // 3. List snapshots. - const listResponse = - await client.agentEnginesInternal.sandboxes.snapshots.listInternal({ - name: agentEngineName!, - }); - expect(listResponse.sandboxEnvironmentSnapshots).toBeDefined(); - - // 4. Delete the snapshot. - const deleteOp = - await client.agentEnginesInternal.sandboxes.snapshots.deleteInternal( - { - name: snapshotName!, - }); - expect(deleteOp.name).toBeDefined(); - } finally { - // Clean up the source sandbox. - await client.agentEnginesInternal.sandboxes.deleteInternal({ - name: sandboxName!, - }); - } + // 1. Create a snapshot of the sandbox. + const createOp = await client.sandboxes.snapshots.create({ + sourceSandboxEnvironmentName: sandboxName!, + config: { + displayName: 'Example Sandbox Snapshot', + ttl: '3600s', + }, + }); + const status = await pollSnapshotOperation(client, createOp.name!); + const snapshotName = status.response?.name; + expect(snapshotName).toBeDefined(); + + // 2. Get the snapshot. + const snapshot = + await client.sandboxes.snapshots.get({name: snapshotName!}); + expect(snapshot.name).toEqual(snapshotName); + + // 3. List snapshots. + const listResponse = await client.sandboxes.snapshots.list(); + expect(listResponse.sandboxEnvironmentSnapshots).toBeDefined(); + + // 4. Restore a new sandbox environment from the snapshot. + const restoreOp = await client.sandboxes.environments.create({ + config: { + displayName: 'Restored Sandbox', + sandboxEnvironmentSnapshot: snapshotName!, + }, + }); + const restoreStatus = await pollSandboxOperation(client, restoreOp.name!); + restoredSandboxName = restoreStatus.response?.name; + expect(restoredSandboxName).toBeDefined(); + + // 5. Delete the snapshot. + const deleteOp = + await client.sandboxes.snapshots.delete({name: snapshotName!}); + expect(deleteOp.name).toBeDefined(); } finally { + // Clean up the restored sandbox and the source sandbox. + if (restoredSandboxName) { + await client.sandboxes.environments.delete({name: restoredSandboxName}); + } + if (sandboxName) { + await client.sandboxes.environments.delete({name: sandboxName}); + } // Clean up the source template. - await client.agentEnginesInternal.sandboxes.templates.deleteInternal({ - name: templateName!, - }); + await client.sandboxes.templates.delete({name: templateName!}); } }); });