From e127cd14b3fdb446ee3d3695fc27be6730800e95 Mon Sep 17 00:00:00 2001 From: Oanakiaja <281723571@qq.com> Date: Wed, 11 Feb 2026 23:46:55 +0800 Subject: [PATCH] feat(sdk): regenerate Python and JS SDKs for API v1.1.0 - Regenerate Python SDK (0.0.22) and JS SDK (1.0.8) from OpenAPI 1.1.0 - Add auth resource module, Node.js session management APIs - Remove cdp and webUi modules (not intended for public API) - Fix JS SDK type regressions: restore BodyInit types, isFailedResponse type guards, and error handling patterns in all API clients --- sdk/js/package.json | 2 +- sdk/js/src/Client.ts | 57 +-- sdk/js/src/api/client/index.ts | 1 - sdk/js/src/api/index.ts | 1 - .../src/api/resources/auth/client/Client.ts | 136 ++++++ .../auth/client/authenticate.ts} | 12 +- .../api/resources/auth/client/createTicket.ts | 36 ++ sdk/js/src/api/resources/auth/client/index.ts | 2 + sdk/js/src/api/resources/auth/index.ts | 1 + .../src/api/resources/code/client/Client.ts | 2 + .../requests/StrReplaceEditorRequest.ts | 10 + sdk/js/src/api/resources/index.ts | 1 + .../src/api/resources/nodejs/client/Client.ts | 417 +++++++++++++++++- .../client/requests/NodeJsExecuteRequest.ts | 2 + .../api/resources/sandbox/client/Client.ts | 4 +- .../shell/client/requests/ShellExecRequest.ts | 2 + sdk/js/src/api/types/ActionResponse.ts | 12 +- sdk/js/src/api/types/BrowserInfoResult.ts | 6 +- sdk/js/src/api/types/NodeJsRuntimeInfo.ts | 4 + .../src/api/types/StrReplaceEditorResult.ts | 2 + sdk/python/agent_sandbox/__init__.py | 4 +- sdk/python/agent_sandbox/auth/__init__.py | 4 + sdk/python/agent_sandbox/auth/client.py | 175 ++++++++ sdk/python/agent_sandbox/auth/raw_client.py | 176 ++++++++ sdk/python/agent_sandbox/client.py | 111 +---- sdk/python/agent_sandbox/code/client.py | 4 + sdk/python/agent_sandbox/code/raw_client.py | 4 + sdk/python/agent_sandbox/file/client.py | 50 +++ sdk/python/agent_sandbox/file/raw_client.py | 50 +++ sdk/python/agent_sandbox/nodejs/client.py | 10 + sdk/python/agent_sandbox/nodejs/raw_client.py | 10 + sdk/python/agent_sandbox/providers/README.md | 44 -- .../agent_sandbox/providers/__init__.py | 6 - sdk/python/agent_sandbox/providers/base.py | 87 ---- sdk/python/agent_sandbox/providers/sign.py | 187 -------- .../agent_sandbox/providers/volcengine.py | 336 -------------- sdk/python/agent_sandbox/raw_client.py | 96 ---- sdk/python/agent_sandbox/sandbox/client.py | 8 +- .../agent_sandbox/sandbox/raw_client.py | 8 +- sdk/python/agent_sandbox/shell/client.py | 10 + sdk/python/agent_sandbox/shell/raw_client.py | 10 + .../agent_sandbox/types/action_response.py | 20 +- .../types/browser_info_result.py | 12 +- .../types/node_js_runtime_info.py | 10 + .../types/str_replace_editor_result.py | 5 + sdk/python/pyproject.toml | 4 +- website/docs/public/v1/openapi.json | 350 ++++++++++++++- 47 files changed, 1556 insertions(+), 945 deletions(-) delete mode 100644 sdk/js/src/api/client/index.ts create mode 100644 sdk/js/src/api/resources/auth/client/Client.ts rename sdk/js/src/api/{client/serveTerminalTerminalGet.ts => resources/auth/client/authenticate.ts} (60%) create mode 100644 sdk/js/src/api/resources/auth/client/createTicket.ts create mode 100644 sdk/js/src/api/resources/auth/client/index.ts create mode 100644 sdk/js/src/api/resources/auth/index.ts create mode 100644 sdk/python/agent_sandbox/auth/__init__.py create mode 100644 sdk/python/agent_sandbox/auth/client.py create mode 100644 sdk/python/agent_sandbox/auth/raw_client.py delete mode 100644 sdk/python/agent_sandbox/providers/README.md delete mode 100644 sdk/python/agent_sandbox/providers/__init__.py delete mode 100644 sdk/python/agent_sandbox/providers/base.py delete mode 100644 sdk/python/agent_sandbox/providers/sign.py delete mode 100644 sdk/python/agent_sandbox/providers/volcengine.py delete mode 100644 sdk/python/agent_sandbox/raw_client.py diff --git a/sdk/js/package.json b/sdk/js/package.json index 1ff9820..19ad2e8 100644 --- a/sdk/js/package.json +++ b/sdk/js/package.json @@ -1,6 +1,6 @@ { "name": "@agent-infra/sandbox", - "version": "1.0.7", + "version": "1.0.8", "description": "Node.js SDK for AIO Sandbox integration providing tools and interfaces", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.mjs", diff --git a/sdk/js/src/Client.ts b/sdk/js/src/Client.ts index e15af23..fdcde68 100644 --- a/sdk/js/src/Client.ts +++ b/sdk/js/src/Client.ts @@ -1,6 +1,6 @@ // This file was auto-generated by Fern from our API Definition. -import * as Sandbox from "./api/index.js"; +import { Auth } from "./api/resources/auth/client/Client.js"; import { Browser } from "./api/resources/browser/client/Client.js"; import { Code } from "./api/resources/code/client/Client.js"; import { File_ } from "./api/resources/file/client/Client.js"; @@ -33,6 +33,7 @@ export class SandboxClient { protected _code: Code | undefined; protected _util: Util | undefined; protected _skills: Skills | undefined; + protected _auth: Auth | undefined; constructor(_options: SandboxClient.Options) { this._options = { @@ -89,58 +90,8 @@ export class SandboxClient { return (this._skills ??= new Skills(this._options)); } - /** - * Serve the terminal HTML page - * - * @param {SandboxClient.RequestOptions} requestOptions - Request-specific configuration. - * - * @example - * await client.serveTerminalTerminalGet() - */ - public serveTerminalTerminalGet( - requestOptions?: SandboxClient.RequestOptions, - ): core.HttpResponsePromise> { - return core.HttpResponsePromise.fromPromise(this.__serveTerminalTerminalGet(requestOptions)); + public get auth(): Auth { + return (this._auth ??= new Auth(this._options)); } - private async __serveTerminalTerminalGet( - requestOptions?: SandboxClient.RequestOptions, - ): Promise>> { - const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); - const _response = await (this._options.fetcher ?? core.fetcher)({ - url: core.url.join( - (await core.Supplier.get(this._options.baseUrl)) ?? - (await core.Supplier.get(this._options.environment)), - "terminal", - ), - method: "GET", - headers: _headers, - queryParameters: requestOptions?.queryParams, - timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, - maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, - abortSignal: requestOptions?.abortSignal, - fetchFn: this._options?.fetch, - logging: this._options.logging, - }); - if (_response.ok) { - return { - data: { - ok: true, - body: _response.body, - headers: _response.headers, - rawResponse: _response.rawResponse, - }, - rawResponse: _response.rawResponse, - }; - } - - return { - data: { - ok: false, - error: Sandbox.serveTerminalTerminalGet.Error._unknown(core.isFailedResponse(_response) ? _response.error : { reason: "unknown", errorMessage: "Unknown error" }), - rawResponse: _response.rawResponse, - }, - rawResponse: _response.rawResponse, - }; - } } diff --git a/sdk/js/src/api/client/index.ts b/sdk/js/src/api/client/index.ts deleted file mode 100644 index 408f23c..0000000 --- a/sdk/js/src/api/client/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * as serveTerminalTerminalGet from "./serveTerminalTerminalGet.js"; diff --git a/sdk/js/src/api/index.ts b/sdk/js/src/api/index.ts index 0ef16e7..ba75aa0 100644 --- a/sdk/js/src/api/index.ts +++ b/sdk/js/src/api/index.ts @@ -1,3 +1,2 @@ -export * from "./client/index.js"; export * from "./resources/index.js"; export * from "./types/index.js"; diff --git a/sdk/js/src/api/resources/auth/client/Client.ts b/sdk/js/src/api/resources/auth/client/Client.ts new file mode 100644 index 0000000..3f4546f --- /dev/null +++ b/sdk/js/src/api/resources/auth/client/Client.ts @@ -0,0 +1,136 @@ +// This file was auto-generated by Fern from our API Definition. + +import type { BaseClientOptions, BaseRequestOptions } from "../../../../BaseClient.js"; +import { mergeHeaders } from "../../../../core/headers.js"; +import * as core from "../../../../core/index.js"; +import * as Sandbox from "../../../index.js"; + +export declare namespace Auth { + export interface Options extends BaseClientOptions {} + + export interface RequestOptions extends BaseRequestOptions {} +} + +export class Auth { + protected readonly _options: Auth.Options; + + constructor(_options: Auth.Options) { + this._options = _options; + } + + /** + * Create and return a short-lived authentication ticket. + * + * This is a non-idempotent action; each call creates a new, unique ticket. + * + * @param {Auth.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.auth.createTicket() + */ + public createTicket( + requestOptions?: Auth.RequestOptions, + ): core.HttpResponsePromise, Sandbox.auth.createTicket.Error>> { + return core.HttpResponsePromise.fromPromise(this.__createTicket(requestOptions)); + } + + private async __createTicket( + requestOptions?: Auth.RequestOptions, + ): Promise, Sandbox.auth.createTicket.Error>>> { + const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); + const _response = await (this._options.fetcher ?? core.fetcher)({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + "tickets", + ), + method: "POST", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { + data: { + ok: true, + body: _response.body as Record, + headers: _response.headers, + rawResponse: _response.rawResponse, + }, + rawResponse: _response.rawResponse, + }; + } + + return { + data: { + ok: false, + error: Sandbox.auth.createTicket.Error._unknown(core.isFailedResponse(_response) ? _response.error : { reason: "unknown", errorMessage: "Unknown error" }), + rawResponse: _response.rawResponse, + }, + rawResponse: _response.rawResponse, + }; + } + + /** + * Authenticate a request using ticket or JWT. + * + * This endpoint receives authentication subrequests (e.g., from Nginx auth_request). + * It validates the request based on either a ticket in the 'x-original-uri' + * header or a JWT in the 'Authorization' header. + * + * @param {Auth.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.auth.authenticate() + */ + public authenticate( + requestOptions?: Auth.RequestOptions, + ): core.HttpResponsePromise, Sandbox.auth.authenticate.Error>> { + return core.HttpResponsePromise.fromPromise(this.__authenticate(requestOptions)); + } + + private async __authenticate( + requestOptions?: Auth.RequestOptions, + ): Promise, Sandbox.auth.authenticate.Error>>> { + const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); + const _response = await (this._options.fetcher ?? core.fetcher)({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + "auth", + ), + method: "GET", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { + data: { + ok: true, + body: _response.body as Record, + headers: _response.headers, + rawResponse: _response.rawResponse, + }, + rawResponse: _response.rawResponse, + }; + } + + return { + data: { + ok: false, + error: Sandbox.auth.authenticate.Error._unknown(core.isFailedResponse(_response) ? _response.error : { reason: "unknown", errorMessage: "Unknown error" }), + rawResponse: _response.rawResponse, + }, + rawResponse: _response.rawResponse, + }; + } +} diff --git a/sdk/js/src/api/client/serveTerminalTerminalGet.ts b/sdk/js/src/api/resources/auth/client/authenticate.ts similarity index 60% rename from sdk/js/src/api/client/serveTerminalTerminalGet.ts rename to sdk/js/src/api/resources/auth/client/authenticate.ts index 1313369..bc834ea 100644 --- a/sdk/js/src/api/client/serveTerminalTerminalGet.ts +++ b/sdk/js/src/api/resources/auth/client/authenticate.ts @@ -1,9 +1,9 @@ // This file was auto-generated by Fern from our API Definition. -import type * as core from "../../core/index.js"; -import type * as Sandbox from "../index.js"; +import type * as core from "../../../../core/index.js"; +import type * as Sandbox from "../../../index.js"; -export type Error = Sandbox.serveTerminalTerminalGet.Error._Unknown; +export type Error = Sandbox.auth.authenticate.Error._Unknown; export namespace Error { export interface _Unknown { @@ -17,7 +17,7 @@ export namespace Error { } export const Error = { - _unknown: (fetcherError: core.Fetcher.Error): Sandbox.serveTerminalTerminalGet.Error._Unknown => { + _unknown: (fetcherError: core.Fetcher.Error): Sandbox.auth.authenticate.Error._Unknown => { return { statusCode: undefined, content: fetcherError, @@ -25,8 +25,8 @@ export const Error = { }, _visit: <_Result>( - value: Sandbox.serveTerminalTerminalGet.Error, - visitor: Sandbox.serveTerminalTerminalGet.Error._Visitor<_Result>, + value: Sandbox.auth.authenticate.Error, + visitor: Sandbox.auth.authenticate.Error._Visitor<_Result>, ): _Result => { switch (value.statusCode) { default: diff --git a/sdk/js/src/api/resources/auth/client/createTicket.ts b/sdk/js/src/api/resources/auth/client/createTicket.ts new file mode 100644 index 0000000..6938b9f --- /dev/null +++ b/sdk/js/src/api/resources/auth/client/createTicket.ts @@ -0,0 +1,36 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as core from "../../../../core/index.js"; +import type * as Sandbox from "../../../index.js"; + +export type Error = Sandbox.auth.createTicket.Error._Unknown; + +export namespace Error { + export interface _Unknown { + statusCode: void; + content: core.Fetcher.Error; + } + + export interface _Visitor<_Result> { + _other: (value: core.Fetcher.Error) => _Result; + } +} + +export const Error = { + _unknown: (fetcherError: core.Fetcher.Error): Sandbox.auth.createTicket.Error._Unknown => { + return { + statusCode: undefined, + content: fetcherError, + }; + }, + + _visit: <_Result>( + value: Sandbox.auth.createTicket.Error, + visitor: Sandbox.auth.createTicket.Error._Visitor<_Result>, + ): _Result => { + switch (value.statusCode) { + default: + return visitor._other(value as any); + } + }, +} as const; diff --git a/sdk/js/src/api/resources/auth/client/index.ts b/sdk/js/src/api/resources/auth/client/index.ts new file mode 100644 index 0000000..1a891ac --- /dev/null +++ b/sdk/js/src/api/resources/auth/client/index.ts @@ -0,0 +1,2 @@ +export * as authenticate from "./authenticate.js"; +export * as createTicket from "./createTicket.js"; diff --git a/sdk/js/src/api/resources/auth/index.ts b/sdk/js/src/api/resources/auth/index.ts new file mode 100644 index 0000000..914b8c3 --- /dev/null +++ b/sdk/js/src/api/resources/auth/index.ts @@ -0,0 +1 @@ +export * from "./client/index.js"; diff --git a/sdk/js/src/api/resources/code/client/Client.ts b/sdk/js/src/api/resources/code/client/Client.ts index 794c1e8..b39a8a3 100644 --- a/sdk/js/src/api/resources/code/client/Client.ts +++ b/sdk/js/src/api/resources/code/client/Client.ts @@ -103,6 +103,8 @@ export class Code { /** * Return metadata about supported code runtimes * + * Note: Version info is cached at service level (first call only runs subprocess). + * * @param {Code.RequestOptions} requestOptions - Request-specific configuration. * * @example diff --git a/sdk/js/src/api/resources/file/client/requests/StrReplaceEditorRequest.ts b/sdk/js/src/api/resources/file/client/requests/StrReplaceEditorRequest.ts index 5dde584..fb49e77 100644 --- a/sdk/js/src/api/resources/file/client/requests/StrReplaceEditorRequest.ts +++ b/sdk/js/src/api/resources/file/client/requests/StrReplaceEditorRequest.ts @@ -26,4 +26,14 @@ export interface StrReplaceEditorRequest { view_range?: number[]; /** Optional parameter of `str_replace` command. When specified, controls how multiple occurrences are handled: 'ALL' replaces all occurrences, 'FIRST' replaces only the first, 'LAST' replaces only the last. If not specified, requires unique match (original behavior). */ replace_mode?: Sandbox.StrReplaceEditorRequestReplaceMode; + /** Optional parameter for `view` command on PDF files. Specifies page range [start, end] (1-indexed). E.g., [1, 5] reads pages 1-5. */ + page_range?: number[]; + /** Optional parameter for `view` command on Excel files. Specifies which sheet to read. If not provided, all sheets are returned. */ + sheet_name?: string; + /** Optional parameter for `view` command on Excel files. Specifies row range [start, end] (1-indexed). E.g., [1, 100] reads rows 1-100. */ + row_range?: number[]; + /** Optional parameter for `view` command on PPTX files. Specifies slide range [start, end] (1-indexed). E.g., [1, 5] reads slides 1-5. */ + slide_range?: number[]; + /** Optional parameter for `view` command. If true, returns file metadata (total pages, sheets, slides, etc.) in the response. */ + enable_metadata?: boolean; } diff --git a/sdk/js/src/api/resources/index.ts b/sdk/js/src/api/resources/index.ts index ffda870..28fe9d1 100644 --- a/sdk/js/src/api/resources/index.ts +++ b/sdk/js/src/api/resources/index.ts @@ -1,3 +1,4 @@ +export * as auth from "./auth/index.js"; export * from "./browser/client/requests/index.js"; export * as browser from "./browser/index.js"; export * from "./browser/types/index.js"; diff --git a/sdk/js/src/api/resources/nodejs/client/Client.ts b/sdk/js/src/api/resources/nodejs/client/Client.ts index f27be7f..e58ff43 100644 --- a/sdk/js/src/api/resources/nodejs/client/Client.ts +++ b/sdk/js/src/api/resources/nodejs/client/Client.ts @@ -22,7 +22,16 @@ export class Nodejs { * Execute JavaScript code using Node.js * * This endpoint allows you to execute JavaScript code and get results back. - * Each request creates a fresh execution environment that's cleaned up automatically. + * + * For stateless execution (default): + * - Each request creates a fresh execution environment + * - Environment is cleaned up automatically after execution + * + * For stateful execution (stateful=True): + * - Uses persistent REPL session that maintains state between requests + * - Variables, functions, and imports persist across calls + * - Returns session_id to continue the session in subsequent requests + * - Supports async/await at top level * * @param {Sandbox.NodeJsExecuteRequest} request * @param {Nodejs.RequestOptions} requestOptions - Request-specific configuration. @@ -105,7 +114,10 @@ export class Nodejs { } /** - * Get information about Node.js runtime and available languages + * Get information about Node.js REPL runtime, including installed packages + * + * Returns Node.js version, npm version, and lists of installed packages + * from both the runtime directory and global npm directory. * * @param {Nodejs.RequestOptions} requestOptions - Request-specific configuration. * @@ -160,4 +172,405 @@ export class Nodejs { rawResponse: _response.rawResponse, }; } + + /** + * List all active Node.js REPL sessions + * + * Returns information about all active sessions including their state, + * working directory, and idle time. + * + * @param {Nodejs.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.nodejs.listSessions() + */ + public listSessions( + requestOptions?: Nodejs.RequestOptions, + ): core.HttpResponsePromise< + core.APIResponse + > { + return core.HttpResponsePromise.fromPromise(this.__listSessions(requestOptions)); + } + + private async __listSessions( + requestOptions?: Nodejs.RequestOptions, + ): Promise< + core.WithRawResponse< + core.APIResponse + > + > { + const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); + const _response = await (this._options.fetcher ?? core.fetcher)({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + "v1/nodejs/sessions", + ), + method: "GET", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { + data: { + ok: true, + body: _response.body as Sandbox.ResponseNodeJsSessionListResponse, + headers: _response.headers, + rawResponse: _response.rawResponse, + }, + rawResponse: _response.rawResponse, + }; + } + + return { + data: { + ok: false, + error: Sandbox.nodejs.listSessions.Error._unknown(core.isFailedResponse(_response) ? _response.error : { reason: "unknown", errorMessage: "Unknown error" }), + rawResponse: _response.rawResponse, + }, + rawResponse: _response.rawResponse, + }; + } + + /** + * Create a new Node.js REPL session + * + * Creates a new persistent REPL session with configurable working directory + * and idle timeout. Use the returned session_id in subsequent execute requests. + * + * @param {Sandbox.NodeJsCreateSessionRequest} request + * @param {Nodejs.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.nodejs.createSession() + */ + public createSession( + request: Sandbox.NodeJsCreateSessionRequest = {}, + requestOptions?: Nodejs.RequestOptions, + ): core.HttpResponsePromise< + core.APIResponse + > { + return core.HttpResponsePromise.fromPromise(this.__createSession(request, requestOptions)); + } + + private async __createSession( + request: Sandbox.NodeJsCreateSessionRequest = {}, + requestOptions?: Nodejs.RequestOptions, + ): Promise< + core.WithRawResponse< + core.APIResponse + > + > { + const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); + const _response = await (this._options.fetcher ?? core.fetcher)({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + "v1/nodejs/sessions", + ), + method: "POST", + headers: _headers, + contentType: "application/json", + queryParameters: requestOptions?.queryParams, + requestType: "json", + body: request, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { + data: { + ok: true, + body: _response.body as Sandbox.ResponseNodeJsCreateSessionResponse, + headers: _response.headers, + rawResponse: _response.rawResponse, + }, + rawResponse: _response.rawResponse, + }; + } + + if (!_response.ok && core.isFailedResponse(_response) && _response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 422: + return { + data: { + ok: false, + error: Sandbox.nodejs.createSession.Error.unprocessableEntityError( + _response.error.body as Sandbox.HttpValidationError, + ), + rawResponse: _response.rawResponse, + }, + rawResponse: _response.rawResponse, + }; + } + } + + return { + data: { + ok: false, + error: Sandbox.nodejs.createSession.Error._unknown(core.isFailedResponse(_response) ? _response.error : { reason: "unknown", errorMessage: "Unknown error" }), + rawResponse: _response.rawResponse, + }, + rawResponse: _response.rawResponse, + }; + } + + /** + * Get information about a specific Node.js REPL session + * + * Returns detailed information about a session including its state, + * working directory, creation time, and idle time. + * + * @param {string} sessionId + * @param {Nodejs.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.nodejs.getSession("session_id") + */ + public getSession( + sessionId: string, + requestOptions?: Nodejs.RequestOptions, + ): core.HttpResponsePromise< + core.APIResponse + > { + return core.HttpResponsePromise.fromPromise(this.__getSession(sessionId, requestOptions)); + } + + private async __getSession( + sessionId: string, + requestOptions?: Nodejs.RequestOptions, + ): Promise< + core.WithRawResponse> + > { + const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); + const _response = await (this._options.fetcher ?? core.fetcher)({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + `v1/nodejs/sessions/${core.url.encodePathParam(sessionId)}`, + ), + method: "GET", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { + data: { + ok: true, + body: _response.body as Sandbox.ResponseNodeJsSessionResponse, + headers: _response.headers, + rawResponse: _response.rawResponse, + }, + rawResponse: _response.rawResponse, + }; + } + + if (!_response.ok && core.isFailedResponse(_response) && _response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 422: + return { + data: { + ok: false, + error: Sandbox.nodejs.getSession.Error.unprocessableEntityError( + _response.error.body as Sandbox.HttpValidationError, + ), + rawResponse: _response.rawResponse, + }, + rawResponse: _response.rawResponse, + }; + } + } + + return { + data: { + ok: false, + error: Sandbox.nodejs.getSession.Error._unknown(core.isFailedResponse(_response) ? _response.error : { reason: "unknown", errorMessage: "Unknown error" }), + rawResponse: _response.rawResponse, + }, + rawResponse: _response.rawResponse, + }; + } + + /** + * Delete a Node.js REPL session + * + * Terminates the session and releases all associated resources. + * + * @param {string} sessionId + * @param {Nodejs.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.nodejs.deleteSession("session_id") + */ + public deleteSession( + sessionId: string, + requestOptions?: Nodejs.RequestOptions, + ): core.HttpResponsePromise< + core.APIResponse + > { + return core.HttpResponsePromise.fromPromise(this.__deleteSession(sessionId, requestOptions)); + } + + private async __deleteSession( + sessionId: string, + requestOptions?: Nodejs.RequestOptions, + ): Promise< + core.WithRawResponse< + core.APIResponse + > + > { + const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); + const _response = await (this._options.fetcher ?? core.fetcher)({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + `v1/nodejs/sessions/${core.url.encodePathParam(sessionId)}`, + ), + method: "DELETE", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { + data: { + ok: true, + body: _response.body as Sandbox.ResponseNodeJsDeleteSessionResponse, + headers: _response.headers, + rawResponse: _response.rawResponse, + }, + rawResponse: _response.rawResponse, + }; + } + + if (!_response.ok && core.isFailedResponse(_response) && _response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 422: + return { + data: { + ok: false, + error: Sandbox.nodejs.deleteSession.Error.unprocessableEntityError( + _response.error.body as Sandbox.HttpValidationError, + ), + rawResponse: _response.rawResponse, + }, + rawResponse: _response.rawResponse, + }; + } + } + + return { + data: { + ok: false, + error: Sandbox.nodejs.deleteSession.Error._unknown(core.isFailedResponse(_response) ? _response.error : { reason: "unknown", errorMessage: "Unknown error" }), + rawResponse: _response.rawResponse, + }, + rawResponse: _response.rawResponse, + }; + } + + /** + * Update a Node.js REPL session configuration + * + * Updates session properties like maximum idle time or working directory. + * + * @param {string} sessionId + * @param {Sandbox.NodeJsUpdateSessionRequest} request + * @param {Nodejs.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.nodejs.updateSession("session_id") + */ + public updateSession( + sessionId: string, + request: Sandbox.NodeJsUpdateSessionRequest = {}, + requestOptions?: Nodejs.RequestOptions, + ): core.HttpResponsePromise< + core.APIResponse + > { + return core.HttpResponsePromise.fromPromise(this.__updateSession(sessionId, request, requestOptions)); + } + + private async __updateSession( + sessionId: string, + request: Sandbox.NodeJsUpdateSessionRequest = {}, + requestOptions?: Nodejs.RequestOptions, + ): Promise< + core.WithRawResponse< + core.APIResponse + > + > { + const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers); + const _response = await (this._options.fetcher ?? core.fetcher)({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + `v1/nodejs/sessions/${core.url.encodePathParam(sessionId)}`, + ), + method: "PATCH", + headers: _headers, + contentType: "application/json", + queryParameters: requestOptions?.queryParams, + requestType: "json", + body: request, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { + data: { + ok: true, + body: _response.body as Sandbox.ResponseNodeJsUpdateSessionResponse, + headers: _response.headers, + rawResponse: _response.rawResponse, + }, + rawResponse: _response.rawResponse, + }; + } + + if (!_response.ok && core.isFailedResponse(_response) && _response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 422: + return { + data: { + ok: false, + error: Sandbox.nodejs.updateSession.Error.unprocessableEntityError( + _response.error.body as Sandbox.HttpValidationError, + ), + rawResponse: _response.rawResponse, + }, + rawResponse: _response.rawResponse, + }; + } + } + + return { + data: { + ok: false, + error: Sandbox.nodejs.updateSession.Error._unknown(core.isFailedResponse(_response) ? _response.error : { reason: "unknown", errorMessage: "Unknown error" }), + rawResponse: _response.rawResponse, + }, + rawResponse: _response.rawResponse, + }; + } } diff --git a/sdk/js/src/api/resources/nodejs/client/requests/NodeJsExecuteRequest.ts b/sdk/js/src/api/resources/nodejs/client/requests/NodeJsExecuteRequest.ts index d962a7e..e9cb479 100644 --- a/sdk/js/src/api/resources/nodejs/client/requests/NodeJsExecuteRequest.ts +++ b/sdk/js/src/api/resources/nodejs/client/requests/NodeJsExecuteRequest.ts @@ -21,4 +21,6 @@ export interface NodeJsExecuteRequest { session_id?: string; /** Working directory for code execution */ cwd?: string; + /** Node.js version to use: "node20", "node22", "node24", or aliases "20", "22", "24" */ + version?: string; } diff --git a/sdk/js/src/api/resources/sandbox/client/Client.ts b/sdk/js/src/api/resources/sandbox/client/Client.ts index 0f7adcb..b873856 100644 --- a/sdk/js/src/api/resources/sandbox/client/Client.ts +++ b/sdk/js/src/api/resources/sandbox/client/Client.ts @@ -74,7 +74,7 @@ export class SandboxService { } /** - * Get installed packages by language + * Get installed Python packages * * @param {SandboxService.RequestOptions} requestOptions - Request-specific configuration. * @@ -129,7 +129,7 @@ export class SandboxService { } /** - * Get installed packages by language + * Get installed Node.js packages * * @param {SandboxService.RequestOptions} requestOptions - Request-specific configuration. * diff --git a/sdk/js/src/api/resources/shell/client/requests/ShellExecRequest.ts b/sdk/js/src/api/resources/shell/client/requests/ShellExecRequest.ts index 0d87424..bda66a5 100644 --- a/sdk/js/src/api/resources/shell/client/requests/ShellExecRequest.ts +++ b/sdk/js/src/api/resources/shell/client/requests/ShellExecRequest.ts @@ -23,4 +23,6 @@ export interface ShellExecRequest { no_change_timeout?: number; /** If True, preserve symlinks in working directory path (pwd shows symlink path). If False, symlinks are resolved to physical paths. Defaults to False for backward compatibility. */ preserve_symlinks?: boolean; + /** If True, truncate output when it exceeds 30000 characters (default: True) */ + truncate?: boolean; } diff --git a/sdk/js/src/api/types/ActionResponse.ts b/sdk/js/src/api/types/ActionResponse.ts index b3405b7..cd7a6d2 100644 --- a/sdk/js/src/api/types/ActionResponse.ts +++ b/sdk/js/src/api/types/ActionResponse.ts @@ -5,12 +5,16 @@ import type * as Sandbox from "../index.js"; /** * Response model for browser actions. * - * Provides backward compatibility: + * Inherits from Response for unified API format, with backward compatibility: * - Old format: resp.json()['status'], resp.json()['action_performed'] - * - New format: resp.json()['data']['status'], resp.json()['data']['action_performed'] + * - New format: resp.json()['success'], resp.json()['message'], resp.json()['data'] */ export interface ActionResponse { - status: "success"; - action_performed: string; + /** Whether the operation was successful */ + success?: boolean; + message?: string; + /** Data returned from the operation */ data?: Sandbox.ActionData; + status?: "success"; + action_performed?: string; } diff --git a/sdk/js/src/api/types/BrowserInfoResult.ts b/sdk/js/src/api/types/BrowserInfoResult.ts index 2c572d1..9cddd0f 100644 --- a/sdk/js/src/api/types/BrowserInfoResult.ts +++ b/sdk/js/src/api/types/BrowserInfoResult.ts @@ -12,6 +12,10 @@ export interface BrowserInfoResult { cdp_url: string; /** VNC URL */ vnc_url: string; - /** Viewport size */ + /** CDP UI URL (browser-ui) */ + cdp_ui_url: string; + /** Display size (from xrandr / env vars) */ viewport: Sandbox.BrowserViewport; + /** Actual Chrome page viewport (window.innerWidth/Height via CDP). Smaller than viewport because Chrome UI chrome takes space. */ + page_viewport?: Sandbox.BrowserViewport; } diff --git a/sdk/js/src/api/types/NodeJsRuntimeInfo.ts b/sdk/js/src/api/types/NodeJsRuntimeInfo.ts index b670b95..82abe92 100644 --- a/sdk/js/src/api/types/NodeJsRuntimeInfo.ts +++ b/sdk/js/src/api/types/NodeJsRuntimeInfo.ts @@ -24,4 +24,8 @@ export interface NodeJsRuntimeInfo { global_packages?: Sandbox.NodeJsPackageInfo[]; /** Error message if runtime info retrieval failed */ error?: string; + /** Available Node.js versions (e.g., node20, node22, node24) */ + available_versions?: string[]; + /** Currently active Node.js version */ + current_version?: string; } diff --git a/sdk/js/src/api/types/StrReplaceEditorResult.ts b/sdk/js/src/api/types/StrReplaceEditorResult.ts index 26daefa..216bda7 100644 --- a/sdk/js/src/api/types/StrReplaceEditorResult.ts +++ b/sdk/js/src/api/types/StrReplaceEditorResult.ts @@ -16,4 +16,6 @@ export interface StrReplaceEditorResult { old_content?: string; /** New file content after operation */ new_content?: string; + /** File metadata (only returned when enable_metadata=true for binary files) */ + metadata?: Record; } diff --git a/sdk/python/agent_sandbox/__init__.py b/sdk/python/agent_sandbox/__init__.py index 00edd41..e13e5c2 100644 --- a/sdk/python/agent_sandbox/__init__.py +++ b/sdk/python/agent_sandbox/__init__.py @@ -148,7 +148,7 @@ WaitAction, ) from .errors import UnprocessableEntityError - from . import browser, code, file, jupyter, mcp, nodejs, sandbox, shell, skills, util + from . import auth, browser, code, file, jupyter, mcp, nodejs, sandbox, shell, skills, util from .browser import ( Action, Action_Click, @@ -332,6 +332,7 @@ "ValidationError": ".types", "ValidationErrorLocItem": ".types", "WaitAction": ".types", + "auth": ".", "browser": ".", "code": ".", "file": ".", @@ -526,6 +527,7 @@ def __dir__(): "ValidationError", "ValidationErrorLocItem", "WaitAction", + "auth", "browser", "code", "file", diff --git a/sdk/python/agent_sandbox/auth/__init__.py b/sdk/python/agent_sandbox/auth/__init__.py new file mode 100644 index 0000000..5cde020 --- /dev/null +++ b/sdk/python/agent_sandbox/auth/__init__.py @@ -0,0 +1,4 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + diff --git a/sdk/python/agent_sandbox/auth/client.py b/sdk/python/agent_sandbox/auth/client.py new file mode 100644 index 0000000..9c75551 --- /dev/null +++ b/sdk/python/agent_sandbox/auth/client.py @@ -0,0 +1,175 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawAuthClient, RawAuthClient + + +class AuthClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawAuthClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawAuthClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawAuthClient + """ + return self._raw_client + + def create_ticket( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Optional[typing.Any]]: + """ + Create and return a short-lived authentication ticket. + + This is a non-idempotent action; each call creates a new, unique ticket. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Optional[typing.Any]] + Successful Response + + Examples + -------- + from agent_sandbox import Sandbox + + client = Sandbox( + base_url="https://yourhost.com/path/to/api", + ) + client.auth.create_ticket() + """ + _response = self._raw_client.create_ticket(request_options=request_options) + return _response.data + + def authenticate(self, *, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, str]: + """ + Authenticate a request using ticket or JWT. + + This endpoint receives authentication subrequests (e.g., from Nginx auth_request). + It validates the request based on either a ticket in the 'x-original-uri' + header or a JWT in the 'Authorization' header. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, str] + Successful Response + + Examples + -------- + from agent_sandbox import Sandbox + + client = Sandbox( + base_url="https://yourhost.com/path/to/api", + ) + client.auth.authenticate() + """ + _response = self._raw_client.authenticate(request_options=request_options) + return _response.data + + +class AsyncAuthClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawAuthClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawAuthClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawAuthClient + """ + return self._raw_client + + async def create_ticket( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, typing.Optional[typing.Any]]: + """ + Create and return a short-lived authentication ticket. + + This is a non-idempotent action; each call creates a new, unique ticket. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, typing.Optional[typing.Any]] + Successful Response + + Examples + -------- + import asyncio + + from agent_sandbox import AsyncSandbox + + client = AsyncSandbox( + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.auth.create_ticket() + + + asyncio.run(main()) + """ + _response = await self._raw_client.create_ticket(request_options=request_options) + return _response.data + + async def authenticate(self, *, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, str]: + """ + Authenticate a request using ticket or JWT. + + This endpoint receives authentication subrequests (e.g., from Nginx auth_request). + It validates the request based on either a ticket in the 'x-original-uri' + header or a JWT in the 'Authorization' header. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, str] + Successful Response + + Examples + -------- + import asyncio + + from agent_sandbox import AsyncSandbox + + client = AsyncSandbox( + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.auth.authenticate() + + + asyncio.run(main()) + """ + _response = await self._raw_client.authenticate(request_options=request_options) + return _response.data diff --git a/sdk/python/agent_sandbox/auth/raw_client.py b/sdk/python/agent_sandbox/auth/raw_client.py new file mode 100644 index 0000000..6dffd32 --- /dev/null +++ b/sdk/python/agent_sandbox/auth/raw_client.py @@ -0,0 +1,176 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions + + +class RawAuthClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def create_ticket( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.Dict[str, typing.Optional[typing.Any]]]: + """ + Create and return a short-lived authentication ticket. + + This is a non-idempotent action; each call creates a new, unique ticket. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.Dict[str, typing.Optional[typing.Any]]] + Successful Response + """ + _response = self._client_wrapper.httpx_client.request( + "tickets", + method="POST", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Optional[typing.Any]], + parse_obj_as( + type_=typing.Dict[str, typing.Optional[typing.Any]], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def authenticate( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.Dict[str, str]]: + """ + Authenticate a request using ticket or JWT. + + This endpoint receives authentication subrequests (e.g., from Nginx auth_request). + It validates the request based on either a ticket in the 'x-original-uri' + header or a JWT in the 'Authorization' header. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.Dict[str, str]] + Successful Response + """ + _response = self._client_wrapper.httpx_client.request( + "auth", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, str], + parse_obj_as( + type_=typing.Dict[str, str], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawAuthClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def create_ticket( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.Dict[str, typing.Optional[typing.Any]]]: + """ + Create and return a short-lived authentication ticket. + + This is a non-idempotent action; each call creates a new, unique ticket. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Optional[typing.Any]]] + Successful Response + """ + _response = await self._client_wrapper.httpx_client.request( + "tickets", + method="POST", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Optional[typing.Any]], + parse_obj_as( + type_=typing.Dict[str, typing.Optional[typing.Any]], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def authenticate( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.Dict[str, str]]: + """ + Authenticate a request using ticket or JWT. + + This endpoint receives authentication subrequests (e.g., from Nginx auth_request). + It validates the request based on either a ticket in the 'x-original-uri' + header or a JWT in the 'Authorization' header. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, str]] + Successful Response + """ + _response = await self._client_wrapper.httpx_client.request( + "auth", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, str], + parse_obj_as( + type_=typing.Dict[str, str], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/sdk/python/agent_sandbox/client.py b/sdk/python/agent_sandbox/client.py index 31051af..f9a609e 100644 --- a/sdk/python/agent_sandbox/client.py +++ b/sdk/python/agent_sandbox/client.py @@ -6,10 +6,9 @@ import httpx from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from .core.request_options import RequestOptions -from .raw_client import AsyncRawSandbox, RawSandbox if typing.TYPE_CHECKING: + from .auth.client import AsyncAuthClient, AuthClient from .browser.client import AsyncBrowserClient, BrowserClient from .code.client import AsyncCodeClient, CodeClient from .file.client import AsyncFileClient, FileClient @@ -74,7 +73,6 @@ def __init__( else httpx.Client(timeout=_defaulted_timeout), timeout=_defaulted_timeout, ) - self._raw_client = RawSandbox(client_wrapper=self._client_wrapper) self._sandbox: typing.Optional[SandboxClient] = None self._shell: typing.Optional[ShellClient] = None self._file: typing.Optional[FileClient] = None @@ -85,45 +83,7 @@ def __init__( self._code: typing.Optional[CodeClient] = None self._util: typing.Optional[UtilClient] = None self._skills: typing.Optional[SkillsClient] = None - - @property - def with_raw_response(self) -> RawSandbox: - """ - Retrieves a raw implementation of this client that returns raw responses. - - Returns - ------- - RawSandbox - """ - return self._raw_client - - def serve_terminal_terminal_get( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Optional[typing.Any]: - """ - Serve the terminal HTML page - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Optional[typing.Any] - Successful Response - - Examples - -------- - from agent_sandbox import Sandbox - - client = Sandbox( - base_url="https://yourhost.com/path/to/api", - ) - client.serve_terminal_terminal_get() - """ - _response = self._raw_client.serve_terminal_terminal_get(request_options=request_options) - return _response.data + self._auth: typing.Optional[AuthClient] = None @property def sandbox(self): @@ -205,6 +165,15 @@ def skills(self): self._skills = SkillsClient(client_wrapper=self._client_wrapper) return self._skills + @property + def auth(self): + if self._auth is None: + from .auth.client import AuthClient # noqa: E402 + + self._auth = AuthClient(client_wrapper=self._client_wrapper) + return self._auth + + class AsyncSandbox: """ @@ -258,7 +227,6 @@ def __init__( else httpx.AsyncClient(timeout=_defaulted_timeout), timeout=_defaulted_timeout, ) - self._raw_client = AsyncRawSandbox(client_wrapper=self._client_wrapper) self._sandbox: typing.Optional[AsyncSandboxClient] = None self._shell: typing.Optional[AsyncShellClient] = None self._file: typing.Optional[AsyncFileClient] = None @@ -269,53 +237,7 @@ def __init__( self._code: typing.Optional[AsyncCodeClient] = None self._util: typing.Optional[AsyncUtilClient] = None self._skills: typing.Optional[AsyncSkillsClient] = None - - @property - def with_raw_response(self) -> AsyncRawSandbox: - """ - Retrieves a raw implementation of this client that returns raw responses. - - Returns - ------- - AsyncRawSandbox - """ - return self._raw_client - - async def serve_terminal_terminal_get( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Optional[typing.Any]: - """ - Serve the terminal HTML page - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Optional[typing.Any] - Successful Response - - Examples - -------- - import asyncio - - from agent_sandbox import AsyncSandbox - - client = AsyncSandbox( - base_url="https://yourhost.com/path/to/api", - ) - - - async def main() -> None: - await client.serve_terminal_terminal_get() - - - asyncio.run(main()) - """ - _response = await self._raw_client.serve_terminal_terminal_get(request_options=request_options) - return _response.data + self._auth: typing.Optional[AsyncAuthClient] = None @property def sandbox(self): @@ -396,3 +318,12 @@ def skills(self): self._skills = AsyncSkillsClient(client_wrapper=self._client_wrapper) return self._skills + + @property + def auth(self): + if self._auth is None: + from .auth.client import AsyncAuthClient # noqa: E402 + + self._auth = AsyncAuthClient(client_wrapper=self._client_wrapper) + return self._auth + diff --git a/sdk/python/agent_sandbox/code/client.py b/sdk/python/agent_sandbox/code/client.py index ec23528..0b3a956 100644 --- a/sdk/python/agent_sandbox/code/client.py +++ b/sdk/python/agent_sandbox/code/client.py @@ -97,6 +97,8 @@ def get_info(self, *, request_options: typing.Optional[RequestOptions] = None) - """ Return metadata about supported code runtimes + Note: Version info is cached at service level (first call only runs subprocess). + Parameters ---------- request_options : typing.Optional[RequestOptions] @@ -212,6 +214,8 @@ async def get_info(self, *, request_options: typing.Optional[RequestOptions] = N """ Return metadata about supported code runtimes + Note: Version info is cached at service level (first call only runs subprocess). + Parameters ---------- request_options : typing.Optional[RequestOptions] diff --git a/sdk/python/agent_sandbox/code/raw_client.py b/sdk/python/agent_sandbox/code/raw_client.py index 8ba6d48..676585d 100644 --- a/sdk/python/agent_sandbox/code/raw_client.py +++ b/sdk/python/agent_sandbox/code/raw_client.py @@ -113,6 +113,8 @@ def get_info( """ Return metadata about supported code runtimes + Note: Version info is cached at service level (first call only runs subprocess). + Parameters ---------- request_options : typing.Optional[RequestOptions] @@ -239,6 +241,8 @@ async def get_info( """ Return metadata about supported code runtimes + Note: Version info is cached at service level (first call only runs subprocess). + Parameters ---------- request_options : typing.Optional[RequestOptions] diff --git a/sdk/python/agent_sandbox/file/client.py b/sdk/python/agent_sandbox/file/client.py index 92e76ff..9bc17f6 100644 --- a/sdk/python/agent_sandbox/file/client.py +++ b/sdk/python/agent_sandbox/file/client.py @@ -453,6 +453,11 @@ def str_replace_editor( insert_line: typing.Optional[int] = OMIT, view_range: typing.Optional[typing.Sequence[int]] = OMIT, replace_mode: typing.Optional[StrReplaceEditorRequestReplaceMode] = OMIT, + page_range: typing.Optional[typing.Sequence[int]] = OMIT, + sheet_name: typing.Optional[str] = OMIT, + row_range: typing.Optional[typing.Sequence[int]] = OMIT, + slide_range: typing.Optional[typing.Sequence[int]] = OMIT, + enable_metadata: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> ResponseStrReplaceEditorResult: """ @@ -489,6 +494,21 @@ def str_replace_editor( replace_mode : typing.Optional[StrReplaceEditorRequestReplaceMode] Optional parameter of `str_replace` command. When specified, controls how multiple occurrences are handled: 'ALL' replaces all occurrences, 'FIRST' replaces only the first, 'LAST' replaces only the last. If not specified, requires unique match (original behavior). + page_range : typing.Optional[typing.Sequence[int]] + Optional parameter for `view` command on PDF files. Specifies page range [start, end] (1-indexed). E.g., [1, 5] reads pages 1-5. + + sheet_name : typing.Optional[str] + Optional parameter for `view` command on Excel files. Specifies which sheet to read. If not provided, all sheets are returned. + + row_range : typing.Optional[typing.Sequence[int]] + Optional parameter for `view` command on Excel files. Specifies row range [start, end] (1-indexed). E.g., [1, 100] reads rows 1-100. + + slide_range : typing.Optional[typing.Sequence[int]] + Optional parameter for `view` command on PPTX files. Specifies slide range [start, end] (1-indexed). E.g., [1, 5] reads slides 1-5. + + enable_metadata : typing.Optional[bool] + Optional parameter for `view` command. If true, returns file metadata (total pages, sheets, slides, etc.) in the response. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -518,6 +538,11 @@ def str_replace_editor( insert_line=insert_line, view_range=view_range, replace_mode=replace_mode, + page_range=page_range, + sheet_name=sheet_name, + row_range=row_range, + slide_range=slide_range, + enable_metadata=enable_metadata, request_options=request_options, ) return _response.data @@ -1021,6 +1046,11 @@ async def str_replace_editor( insert_line: typing.Optional[int] = OMIT, view_range: typing.Optional[typing.Sequence[int]] = OMIT, replace_mode: typing.Optional[StrReplaceEditorRequestReplaceMode] = OMIT, + page_range: typing.Optional[typing.Sequence[int]] = OMIT, + sheet_name: typing.Optional[str] = OMIT, + row_range: typing.Optional[typing.Sequence[int]] = OMIT, + slide_range: typing.Optional[typing.Sequence[int]] = OMIT, + enable_metadata: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> ResponseStrReplaceEditorResult: """ @@ -1057,6 +1087,21 @@ async def str_replace_editor( replace_mode : typing.Optional[StrReplaceEditorRequestReplaceMode] Optional parameter of `str_replace` command. When specified, controls how multiple occurrences are handled: 'ALL' replaces all occurrences, 'FIRST' replaces only the first, 'LAST' replaces only the last. If not specified, requires unique match (original behavior). + page_range : typing.Optional[typing.Sequence[int]] + Optional parameter for `view` command on PDF files. Specifies page range [start, end] (1-indexed). E.g., [1, 5] reads pages 1-5. + + sheet_name : typing.Optional[str] + Optional parameter for `view` command on Excel files. Specifies which sheet to read. If not provided, all sheets are returned. + + row_range : typing.Optional[typing.Sequence[int]] + Optional parameter for `view` command on Excel files. Specifies row range [start, end] (1-indexed). E.g., [1, 100] reads rows 1-100. + + slide_range : typing.Optional[typing.Sequence[int]] + Optional parameter for `view` command on PPTX files. Specifies slide range [start, end] (1-indexed). E.g., [1, 5] reads slides 1-5. + + enable_metadata : typing.Optional[bool] + Optional parameter for `view` command. If true, returns file metadata (total pages, sheets, slides, etc.) in the response. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1094,6 +1139,11 @@ async def main() -> None: insert_line=insert_line, view_range=view_range, replace_mode=replace_mode, + page_range=page_range, + sheet_name=sheet_name, + row_range=row_range, + slide_range=slide_range, + enable_metadata=enable_metadata, request_options=request_options, ) return _response.data diff --git a/sdk/python/agent_sandbox/file/raw_client.py b/sdk/python/agent_sandbox/file/raw_client.py index d674e96..f2591db 100644 --- a/sdk/python/agent_sandbox/file/raw_client.py +++ b/sdk/python/agent_sandbox/file/raw_client.py @@ -637,6 +637,11 @@ def str_replace_editor( insert_line: typing.Optional[int] = OMIT, view_range: typing.Optional[typing.Sequence[int]] = OMIT, replace_mode: typing.Optional[StrReplaceEditorRequestReplaceMode] = OMIT, + page_range: typing.Optional[typing.Sequence[int]] = OMIT, + sheet_name: typing.Optional[str] = OMIT, + row_range: typing.Optional[typing.Sequence[int]] = OMIT, + slide_range: typing.Optional[typing.Sequence[int]] = OMIT, + enable_metadata: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[ResponseStrReplaceEditorResult]: """ @@ -673,6 +678,21 @@ def str_replace_editor( replace_mode : typing.Optional[StrReplaceEditorRequestReplaceMode] Optional parameter of `str_replace` command. When specified, controls how multiple occurrences are handled: 'ALL' replaces all occurrences, 'FIRST' replaces only the first, 'LAST' replaces only the last. If not specified, requires unique match (original behavior). + page_range : typing.Optional[typing.Sequence[int]] + Optional parameter for `view` command on PDF files. Specifies page range [start, end] (1-indexed). E.g., [1, 5] reads pages 1-5. + + sheet_name : typing.Optional[str] + Optional parameter for `view` command on Excel files. Specifies which sheet to read. If not provided, all sheets are returned. + + row_range : typing.Optional[typing.Sequence[int]] + Optional parameter for `view` command on Excel files. Specifies row range [start, end] (1-indexed). E.g., [1, 100] reads rows 1-100. + + slide_range : typing.Optional[typing.Sequence[int]] + Optional parameter for `view` command on PPTX files. Specifies slide range [start, end] (1-indexed). E.g., [1, 5] reads slides 1-5. + + enable_metadata : typing.Optional[bool] + Optional parameter for `view` command. If true, returns file metadata (total pages, sheets, slides, etc.) in the response. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -693,6 +713,11 @@ def str_replace_editor( "insert_line": insert_line, "view_range": view_range, "replace_mode": replace_mode, + "page_range": page_range, + "sheet_name": sheet_name, + "row_range": row_range, + "slide_range": slide_range, + "enable_metadata": enable_metadata, }, headers={ "content-type": "application/json", @@ -1337,6 +1362,11 @@ async def str_replace_editor( insert_line: typing.Optional[int] = OMIT, view_range: typing.Optional[typing.Sequence[int]] = OMIT, replace_mode: typing.Optional[StrReplaceEditorRequestReplaceMode] = OMIT, + page_range: typing.Optional[typing.Sequence[int]] = OMIT, + sheet_name: typing.Optional[str] = OMIT, + row_range: typing.Optional[typing.Sequence[int]] = OMIT, + slide_range: typing.Optional[typing.Sequence[int]] = OMIT, + enable_metadata: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[ResponseStrReplaceEditorResult]: """ @@ -1373,6 +1403,21 @@ async def str_replace_editor( replace_mode : typing.Optional[StrReplaceEditorRequestReplaceMode] Optional parameter of `str_replace` command. When specified, controls how multiple occurrences are handled: 'ALL' replaces all occurrences, 'FIRST' replaces only the first, 'LAST' replaces only the last. If not specified, requires unique match (original behavior). + page_range : typing.Optional[typing.Sequence[int]] + Optional parameter for `view` command on PDF files. Specifies page range [start, end] (1-indexed). E.g., [1, 5] reads pages 1-5. + + sheet_name : typing.Optional[str] + Optional parameter for `view` command on Excel files. Specifies which sheet to read. If not provided, all sheets are returned. + + row_range : typing.Optional[typing.Sequence[int]] + Optional parameter for `view` command on Excel files. Specifies row range [start, end] (1-indexed). E.g., [1, 100] reads rows 1-100. + + slide_range : typing.Optional[typing.Sequence[int]] + Optional parameter for `view` command on PPTX files. Specifies slide range [start, end] (1-indexed). E.g., [1, 5] reads slides 1-5. + + enable_metadata : typing.Optional[bool] + Optional parameter for `view` command. If true, returns file metadata (total pages, sheets, slides, etc.) in the response. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1393,6 +1438,11 @@ async def str_replace_editor( "insert_line": insert_line, "view_range": view_range, "replace_mode": replace_mode, + "page_range": page_range, + "sheet_name": sheet_name, + "row_range": row_range, + "slide_range": slide_range, + "enable_metadata": enable_metadata, }, headers={ "content-type": "application/json", diff --git a/sdk/python/agent_sandbox/nodejs/client.py b/sdk/python/agent_sandbox/nodejs/client.py index 2987b5b..50f035f 100644 --- a/sdk/python/agent_sandbox/nodejs/client.py +++ b/sdk/python/agent_sandbox/nodejs/client.py @@ -42,6 +42,7 @@ def execute_code( stateful: typing.Optional[bool] = OMIT, session_id: typing.Optional[str] = OMIT, cwd: typing.Optional[str] = OMIT, + version: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> ResponseNodeJsExecuteResponse: """ @@ -82,6 +83,9 @@ def execute_code( cwd : typing.Optional[str] Working directory for code execution + version : typing.Optional[str] + Node.js version to use: "node20", "node22", "node24", or aliases "20", "22", "24" + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -109,6 +113,7 @@ def execute_code( stateful=stateful, session_id=session_id, cwd=cwd, + version=version, request_options=request_options, ) return _response.data @@ -362,6 +367,7 @@ async def execute_code( stateful: typing.Optional[bool] = OMIT, session_id: typing.Optional[str] = OMIT, cwd: typing.Optional[str] = OMIT, + version: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> ResponseNodeJsExecuteResponse: """ @@ -402,6 +408,9 @@ async def execute_code( cwd : typing.Optional[str] Working directory for code execution + version : typing.Optional[str] + Node.js version to use: "node20", "node22", "node24", or aliases "20", "22", "24" + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -437,6 +446,7 @@ async def main() -> None: stateful=stateful, session_id=session_id, cwd=cwd, + version=version, request_options=request_options, ) return _response.data diff --git a/sdk/python/agent_sandbox/nodejs/raw_client.py b/sdk/python/agent_sandbox/nodejs/raw_client.py index 2e3736f..faece00 100644 --- a/sdk/python/agent_sandbox/nodejs/raw_client.py +++ b/sdk/python/agent_sandbox/nodejs/raw_client.py @@ -37,6 +37,7 @@ def execute_code( stateful: typing.Optional[bool] = OMIT, session_id: typing.Optional[str] = OMIT, cwd: typing.Optional[str] = OMIT, + version: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[ResponseNodeJsExecuteResponse]: """ @@ -77,6 +78,9 @@ def execute_code( cwd : typing.Optional[str] Working directory for code execution + version : typing.Optional[str] + Node.js version to use: "node20", "node22", "node24", or aliases "20", "22", "24" + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -96,6 +100,7 @@ def execute_code( "stateful": stateful, "session_id": session_id, "cwd": cwd, + "version": version, }, headers={ "content-type": "application/json", @@ -468,6 +473,7 @@ async def execute_code( stateful: typing.Optional[bool] = OMIT, session_id: typing.Optional[str] = OMIT, cwd: typing.Optional[str] = OMIT, + version: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[ResponseNodeJsExecuteResponse]: """ @@ -508,6 +514,9 @@ async def execute_code( cwd : typing.Optional[str] Working directory for code execution + version : typing.Optional[str] + Node.js version to use: "node20", "node22", "node24", or aliases "20", "22", "24" + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -527,6 +536,7 @@ async def execute_code( "stateful": stateful, "session_id": session_id, "cwd": cwd, + "version": version, }, headers={ "content-type": "application/json", diff --git a/sdk/python/agent_sandbox/providers/README.md b/sdk/python/agent_sandbox/providers/README.md deleted file mode 100644 index 44ee833..0000000 --- a/sdk/python/agent_sandbox/providers/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# Cloud Providers - -This module provides cloud provider implementations for sandbox management. Currently supports Volcengine as a cloud provider. - -## Supported Providers - -### Volcengine - -The Volcengine provider uses the Volcengine VEFAAS (Volcengine Function as a Service) API to manage sandbox instances. - -#### Features - -- Create sandbox instances -- Delete sandbox instances -- Get sandbox details -- List all sandboxes - -#### Configuration - -```python -from agent_sandbox.providers import VolcengineProvider - -provider = VolcengineProvider( - access_key="yourAccessKeyId", - secret_key="yourAccessKeySecret", - region="cn-beijing", # optional, defaults to cn-beijing - client_side_validation=True # optional, defaults to True -) -``` - -#### Usage - -Refer to the [examples/provider_volcengine.py](../../examples/provider_volcengine.py) for usage. - - -## Adding New Providers - -To add a new cloud provider: - -1. Create a new provider class that inherits from `BaseProvider` -2. Implement the required methods: `create_sandbox`, `delete_sandbox`, `get_sandbox`, `list_sandboxes` -3. Add the provider to the `__init__.py` file -4. Update this README with provider-specific documentation - diff --git a/sdk/python/agent_sandbox/providers/__init__.py b/sdk/python/agent_sandbox/providers/__init__.py deleted file mode 100644 index ec9dd0c..0000000 --- a/sdk/python/agent_sandbox/providers/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -from .base import BaseProvider -from .volcengine import VolcengineProvider - -__all__ = ["BaseProvider", "VolcengineProvider"] diff --git a/sdk/python/agent_sandbox/providers/base.py b/sdk/python/agent_sandbox/providers/base.py deleted file mode 100644 index 3fd7a48..0000000 --- a/sdk/python/agent_sandbox/providers/base.py +++ /dev/null @@ -1,87 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from abc import ABC, abstractmethod - -from ..types.sandbox_response import SandboxResponse - - -class BaseProvider(ABC): - """ - Base class for cloud provider implementations. - - All cloud providers should inherit from this class and implement - the required sandbox management methods. - """ - - @abstractmethod - def create_sandbox(self, function_id: str, **kwargs) -> SandboxResponse: - """ - Create a new sandbox instance. - - Parameters - ---------- - function_id : str - The function ID for the sandbox - - Returns - ------- - SandboxResponse - The response containing sandbox creation details - """ - pass - - @abstractmethod - def delete_sandbox(self, function_id: str, sandbox_id: str, **kwargs) -> SandboxResponse: - """ - Delete an existing sandbox instance. - - Parameters - ---------- - function_id : str - The function ID of the sandbox - sandbox_id : str - The ID of the sandbox to delete - - Returns - ------- - SandboxResponse - The response containing deletion status - """ - pass - - @abstractmethod - def get_sandbox(self, function_id: str, sandbox_id: str, **kwargs) -> SandboxResponse: - """ - Get details of an existing sandbox instance. - - Parameters - ---------- - function_id : str - The function ID of the sandbox - sandbox_id : str - The ID of the sandbox to retrieve - - Returns - ------- - SandboxResponse - The response containing sandbox details - """ - pass - - @abstractmethod - def list_sandboxes(self, function_id: str, **kwargs) -> SandboxResponse: - """ - List all sandbox instances for a function. - - Parameters - ---------- - function_id : str - The function ID to list sandboxes for - - Returns - ------- - SandboxResponse - The response containing list of sandboxes - """ - pass diff --git a/sdk/python/agent_sandbox/providers/sign.py b/sdk/python/agent_sandbox/providers/sign.py deleted file mode 100644 index da5b3d3..0000000 --- a/sdk/python/agent_sandbox/providers/sign.py +++ /dev/null @@ -1,187 +0,0 @@ -# coding:utf-8 -""" -Copyright (year) Beijing Volcano Engine Technology Ltd. - -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 datetime -import hashlib -import hmac -import json -import os -from urllib.parse import quote -import base64 -import datetime -import os -import base64 -import json -import logging -import requests - -logger = logging.getLogger(__name__) - -# 以下参数视服务不同而不同,一个服务内通常是一致的 -Service = "apig" -Version = "2021-03-03" -Region = "cn-beijing" -Host = "iam.volcengineapi.com" -ContentType = "application/x-www-form-urlencoded" - -AK_KEY = "VOLCENGINE_ACCESS_KEY" -SK_KEY = "VOLCENGINE_SECRET_KEY" - -ALT_AK_KEY = 'VOLC_ACCESSKEY' -ALT_SK_KEY = 'VOLC_SECRETKEY' - -# 请求的凭证,从IAM或者STS服务中获取 -AK = os.getenv(AK_KEY) or os.getenv(ALT_AK_KEY) -SK = os.getenv(SK_KEY) or os.getenv(ALT_SK_KEY) - - -# 当使用临时凭证时,需要使用到SessionToken传入Header,并计算进SignedHeader中,请自行在header参数中添加X-Security-Token头 -# SessionToken = "" - - -def norm_query(params): - query = "" - for key in sorted(params.keys()): - if type(params[key]) == list: - for k in params[key]: - query = ( - query + quote(key, safe="-_.~") + "=" + quote(k, safe="-_.~") + "&" - ) - else: - query = (query + quote(key, safe="-_.~") + "=" + quote(params[key], safe="-_.~") + "&") - query = query[:-1] - return query.replace("+", "%20") - - -# 第一步:准备辅助函数。 -# sha256 非对称加密 -def hmac_sha256(key: bytes, content: str): - return hmac.new(key, content.encode("utf-8"), hashlib.sha256).digest() - - -# sha256 hash算法 -def hash_sha256(content: str): - return hashlib.sha256(content.encode("utf-8")).hexdigest() - - -# 第二步:签名请求函数 -def request(method, date, query, header, ak, sk, token, action, body, region = None): - # 第三步:创建身份证明。其中的 Service 和 Region 字段是固定的。ak 和 sk 分别代表 - # AccessKeyID 和 SecretAccessKey。同时需要初始化签名结构体。一些签名计算时需要的属性也在这里处理。 - # 初始化身份证明结构体 - - credential = { - "access_key_id": ak, - "secret_access_key": sk, - "service": Service, - "region": region or Region, - } - - if token is not None: - credential["session_token"] = token - - if action in ['CodeUploadCallback', 'CreateDependencyInstallTask', 'GetDependencyInstallTaskStatus', - 'GetDependencyInstallTaskLogDownloadURI', 'ListTriggers', 'CreateApplication', 'ReleaseApplication', - 'GetApplication']: - credential["service"] = "vefaas" - - content_type = ContentType - version = Version - if method == "POST": - content_type = "application/json" - - if action == "CreateRoute" or action == "ListRoutes": - version = "2022-11-12" - - # 初始化签名结构体 - request_param = { - "body": body, - "host": Host, - "path": "/", - "method": method, - "content_type": content_type, - "date": date, - "query": {"Action": action, "Version": version, **query}, - } - if body is None: - request_param["body"] = "" - # 第四步:接下来开始计算签名。在计算签名前,先准备好用于接收签算结果的 signResult 变量,并设置一些参数。 - # 初始化签名结果的结构体 - x_date = request_param["date"].strftime("%Y%m%dT%H%M%SZ") - short_x_date = x_date[:8] - x_content_sha256 = hash_sha256(request_param["body"]) - sign_result = { - "Host": request_param["host"], - "X-Content-Sha256": x_content_sha256, - "X-Date": x_date, - "Content-Type": request_param["content_type"], - } - # 第五步:计算 Signature 签名。 - signed_headers_str = ";".join( - ["content-type", "host", "x-content-sha256", "x-date"] - ) - # signed_headers_str = signed_headers_str + ";x-security-token" - canonical_request_str = "\n".join( - [request_param["method"].upper(), - request_param["path"], - norm_query(request_param["query"]), - "\n".join( - [ - "content-type:" + request_param["content_type"], - "host:" + request_param["host"], - "x-content-sha256:" + x_content_sha256, - "x-date:" + x_date, - ] - ), - "", - signed_headers_str, - x_content_sha256, - ] - ) - - # 打印正规化的请求用于调试比对 - logger.debug("canonical_request=%s", canonical_request_str) - hashed_canonical_request = hash_sha256(canonical_request_str) - - # 打印hash值用于调试比对 - logger.debug("hashed_canonical_request=%s", hashed_canonical_request) - credential_scope = "/".join([short_x_date, credential["region"], credential["service"], "request"]) - string_to_sign = "\n".join(["HMAC-SHA256", x_date, credential_scope, hashed_canonical_request]) - - # 打印最终计算的签名字符串用于调试比对 - logger.debug("string_to_sign=%s", string_to_sign) - k_date = hmac_sha256(credential["secret_access_key"].encode("utf-8"), short_x_date) - k_region = hmac_sha256(k_date, credential["region"]) - k_service = hmac_sha256(k_region, credential["service"]) - k_signing = hmac_sha256(k_service, "request") - signature = hmac_sha256(k_signing, string_to_sign).hex() - - sign_result["Authorization"] = "HMAC-SHA256 Credential={}, SignedHeaders={}, Signature={}".format( - credential["access_key_id"] + "/" + credential_scope, - signed_headers_str, - signature, - ) - header = {**header, **sign_result} - header = {**header, **{"X-Security-Token": token}} - # 第六步:将 Signature 签名写入 HTTP Header 中,并发送 HTTP 请求。 - r = requests.request(method=method, - url="https://{}{}".format(request_param["host"], request_param["path"]), - headers=header, - params=request_param["query"], - data=request_param["body"], - ) - return r.json() diff --git a/sdk/python/agent_sandbox/providers/volcengine.py b/sdk/python/agent_sandbox/providers/volcengine.py deleted file mode 100644 index 5b89f63..0000000 --- a/sdk/python/agent_sandbox/providers/volcengine.py +++ /dev/null @@ -1,336 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -import datetime -import json -import logging - - -import volcenginesdkvefaas -import volcenginesdkcore - -from .base import BaseProvider -from ..types.sandbox_response import SandboxResponse -from .sign import request - -logger = logging.getLogger(__name__) - - -class VolcengineProvider(BaseProvider): - """ - Volcengine cloud provider implementation for sandbox management. - - This provider uses the Volcengine VEFAAS (Volcengine Function as a Service) - API to manage sandbox instances. - """ - - def __init__( - self, - access_key: str, - secret_key: str, - region: str = "cn-beijing", - client_side_validation: bool = True - ): - """ - Initialize the Volcengine provider. - - Parameters - ---------- - access_key : str - Volcengine access key ID - secret_key : str - Volcengine secret access key - region : str, optional - Volcengine region, defaults to "cn-beijing" - client_side_validation : bool, optional - Enable client-side validation, defaults to True - """ - - self.access_key = access_key - self.secret_key = secret_key - self.region = region - self.client_side_validation = client_side_validation - - # Initialize Volcengine configuration - configuration = volcenginesdkcore.Configuration() - configuration.ak = access_key - configuration.sk = secret_key - configuration.region = region - configuration.client_side_validation = client_side_validation - volcenginesdkcore.Configuration.set_default(configuration) - - # Initialize VEFAAS client - self._client = volcenginesdkvefaas.VEFAASApi(volcenginesdkcore.ApiClient(configuration)) - - def create_sandbox( - self, - function_id: str, - timeout: int = 30, - **kwargs - ) -> SandboxResponse: - """ - Create a new sandbox instance using Volcengine VEFAAS. - - Parameters - ---------- - function_id : str - The function ID for the sandbox - timeout: int - The timeout for the sandbox creation in minutes - **kwargs - Additional parameters for sandbox creation - - Returns - ------- - str - The ID of the created sandbox - Exception - The exception raised if the sandbox creation fails - ------- - """ - try: - request = volcenginesdkvefaas.CreateSandboxRequest( - function_id=function_id, - timeout=timeout, - **kwargs - ) - response = self._client.create_sandbox(request) - return response.sandbox_id - - except Exception as e: - return e - - def delete_sandbox(self, function_id: str, sandbox_id: str, **kwargs) -> SandboxResponse: - """ - Delete an existing sandbox instance. - - Parameters - ---------- - function_id : str - The function ID of the sandbox - sandbox_id : str - The ID of the sandbox to delete - **kwargs - Additional parameters for sandbox deletion - - Returns - ------- - SandboxResponse - The response containing deletion status - """ - try: - request = volcenginesdkvefaas.KillSandboxRequest( - function_id=function_id, - sandbox_id=sandbox_id, - **kwargs - ) - response = self._client.kill_sandbox(request) - - return response - - except Exception as e: - return e - - def _append_instance_query_struct(self, domains_info: typing.List[typing.Dict[str, typing.Any]], instance_name: str) -> typing.List[typing.Dict[str, typing.Any]]: - """Append ?faasInstanceName= to domain field of structured domain objects, preserving type.""" - result: typing.List[typing.Dict[str, typing.Any]] = [] - for info in domains_info: - domain_str = info.get("domain") or info.get("Domain") or "" - if not domain_str: - continue - if "?" in domain_str: - new_domain = f"{domain_str}&faasInstanceName={instance_name}" - else: - new_domain = f"{domain_str}?faasInstanceName={instance_name}" - result.append({ - "domain": new_domain, - "type": info.get("type") or info.get("Type"), - }) - return result - - def get_sandbox(self, function_id: str, sandbox_id: str, **kwargs) -> SandboxResponse: - """ - Get details of an existing sandbox instance. - - Parameters - ---------- - function_id : str - The function ID of the sandbox - sandbox_id : str - The ID of the sandbox to retrieve - **kwargs - Additional parameters for sandbox retrieval - - Returns - ------- - SandboxResponse - The response containing sandbox details - """ - try: - request = volcenginesdkvefaas.DescribeSandboxRequest( - function_id=function_id, - sandbox_id=sandbox_id, - **kwargs - ) - response = self._client.describe_sandbox(request) - - out = response.to_dict() - base_domains = self.get_apig_domains(function_id) - domains_struct = self._append_instance_query_struct(base_domains, sandbox_id) - out["domains"] = domains_struct - return out - - except Exception as e: - return e - - def list_sandboxes(self, function_id: str, **kwargs) -> SandboxResponse: - """ - List all sandbox instances for a function. - - Parameters - ---------- - function_id : str - The function ID to list sandboxes for - **kwargs - Additional parameters for listing sandboxes - - Returns - ------- - SandboxResponse - The response containing list of sandboxes - """ - try: - request = volcenginesdkvefaas.ListSandboxesRequest( - function_id=function_id, - **kwargs - ) - response = self._client.list_sandboxes(request) - - # Attach domains with instanceName query to each sandbox item using instance_id - base_domains = self.get_apig_domains(function_id) - - sandboxes = response.sandboxes - normalized: typing.List[typing.Dict[str, typing.Any]] = [] - for sb in sandboxes: - item = sb.to_dict() - instance_id = item.get("id") or item.get("sandbox_id") - domains_struct = self._append_instance_query_struct(base_domains, instance_id) if instance_id else base_domains - item["domains"] = domains_struct - normalized.append(item) - - return normalized - - except Exception as e: - return e - - - def _get_apig_trigger(self, function_id: str) -> str: - """ - Get the UpstreamId from APIG triggers for a given function. - - Parameters - ---------- - function_id : str - The function ID to get triggers for - - Returns - ------- - str - The UpstreamId from the first APIG trigger found, or None if not found - """ - body = { - "FunctionId": function_id, - } - now = datetime.datetime.utcnow() - response = request("POST", now, {}, {}, self.access_key, self.secret_key,"", "ListTriggers", json.dumps(body)) - - # Parse the response to extract UpstreamId from APIG triggers - if response and isinstance(response, dict): - result = response.get("Result", {}) - items = result.get("Items", []) - - for item in items: - if item.get("Type") == "apig": - detailed_config = item.get("DetailedConfig", "{}") - try: - config = json.loads(detailed_config) - upstream_id = config.get("UpstreamId") - if upstream_id: - return upstream_id - except json.JSONDecodeError: - print(f"Failed to parse DetailedConfig: {detailed_config}") - continue - - return None - - def _get_apig_domains(self, upstream_id: str) -> list: - """ - Get structured domains from APIG routes using the upstream ID. - - Parameters - ---------- - upstream_id : str - The upstream ID to get routes for - - Returns - ------- - list - List of domains from the routes, or empty list if not found - """ - body = { - "UpstreamId": upstream_id, - "PageSize": 100, - "PageNumber": 1 - } - - now = datetime.datetime.utcnow() - response = request("POST", now, {}, {}, self.access_key, self.secret_key, "", "ListRoutes", json.dumps(body)) - - # Extract domains from the response and append MatchContent path, preserving type - domains: typing.List[typing.Dict[str, typing.Any]] = [] - if response and isinstance(response, dict): - result = response.get("Result", {}) - routes = result.get("Items", []) - for route in routes: - # derive path prefix from match rule - path_prefix = "" - try: - match_rule = route.get("MatchRule") or {} - path_rule = match_rule.get("Path") or {} - match_content = path_rule.get("MatchContent") - if isinstance(match_content, str): - path_prefix = match_content - except Exception: - path_prefix = "" - - route_domains = route.get("Domains", []) - for domain_info in route_domains: - base = domain_info.get("Domain") - if not base: - continue - # Append path prefix (e.g., "/v1") to the domain string, and preserve type - domains.append({ - "domain": f"{base}{path_prefix}", - "type": domain_info.get("Type") or domain_info.get("type"), - }) - - return domains - - def get_apig_domains(self, function_id: str) -> list: - """ - Get domains for APIG triggers of a given function. - - Parameters - ---------- - function_id : str - The function ID to get domains for - - Returns - ------- - list - List of domains from APIG routes, or empty list if not found - """ - upstream_id = self._get_apig_trigger(function_id) - if upstream_id: - return self._get_apig_domains(upstream_id) - return [] diff --git a/sdk/python/agent_sandbox/raw_client.py b/sdk/python/agent_sandbox/raw_client.py deleted file mode 100644 index 326afd7..0000000 --- a/sdk/python/agent_sandbox/raw_client.py +++ /dev/null @@ -1,96 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from .core.api_error import ApiError -from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from .core.http_response import AsyncHttpResponse, HttpResponse -from .core.pydantic_utilities import parse_obj_as -from .core.request_options import RequestOptions - - -class RawSandbox: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def serve_terminal_terminal_get( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Optional[typing.Any]]: - """ - Serve the terminal HTML page - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[typing.Optional[typing.Any]] - Successful Response - """ - _response = self._client_wrapper.httpx_client.request( - "terminal", - method="GET", - request_options=request_options, - ) - try: - if _response is None or not _response.text.strip(): - return HttpResponse(response=_response, data=None) - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - -class AsyncRawSandbox: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def serve_terminal_terminal_get( - self, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Optional[typing.Any]]: - """ - Serve the terminal HTML page - - Parameters - ---------- - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[typing.Optional[typing.Any]] - Successful Response - """ - _response = await self._client_wrapper.httpx_client.request( - "terminal", - method="GET", - request_options=request_options, - ) - try: - if _response is None or not _response.text.strip(): - return AsyncHttpResponse(response=_response, data=None) - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/sdk/python/agent_sandbox/sandbox/client.py b/sdk/python/agent_sandbox/sandbox/client.py index 7cac3d8..515ef67 100644 --- a/sdk/python/agent_sandbox/sandbox/client.py +++ b/sdk/python/agent_sandbox/sandbox/client.py @@ -52,7 +52,7 @@ def get_context(self, *, request_options: typing.Optional[RequestOptions] = None def get_python_packages(self, *, request_options: typing.Optional[RequestOptions] = None) -> Response: """ - Get installed packages by language + Get installed Python packages Parameters ---------- @@ -78,7 +78,7 @@ def get_python_packages(self, *, request_options: typing.Optional[RequestOptions def get_nodejs_packages(self, *, request_options: typing.Optional[RequestOptions] = None) -> Response: """ - Get installed packages by language + Get installed Node.js packages Parameters ---------- @@ -154,7 +154,7 @@ async def main() -> None: async def get_python_packages(self, *, request_options: typing.Optional[RequestOptions] = None) -> Response: """ - Get installed packages by language + Get installed Python packages Parameters ---------- @@ -188,7 +188,7 @@ async def main() -> None: async def get_nodejs_packages(self, *, request_options: typing.Optional[RequestOptions] = None) -> Response: """ - Get installed packages by language + Get installed Node.js packages Parameters ---------- diff --git a/sdk/python/agent_sandbox/sandbox/raw_client.py b/sdk/python/agent_sandbox/sandbox/raw_client.py index 7aaa537..b500a19 100644 --- a/sdk/python/agent_sandbox/sandbox/raw_client.py +++ b/sdk/python/agent_sandbox/sandbox/raw_client.py @@ -52,7 +52,7 @@ def get_context(self, *, request_options: typing.Optional[RequestOptions] = None def get_python_packages(self, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[Response]: """ - Get installed packages by language + Get installed Python packages Parameters ---------- @@ -86,7 +86,7 @@ def get_python_packages(self, *, request_options: typing.Optional[RequestOptions def get_nodejs_packages(self, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[Response]: """ - Get installed packages by language + Get installed Node.js packages Parameters ---------- @@ -163,7 +163,7 @@ async def get_python_packages( self, *, request_options: typing.Optional[RequestOptions] = None ) -> AsyncHttpResponse[Response]: """ - Get installed packages by language + Get installed Python packages Parameters ---------- @@ -199,7 +199,7 @@ async def get_nodejs_packages( self, *, request_options: typing.Optional[RequestOptions] = None ) -> AsyncHttpResponse[Response]: """ - Get installed packages by language + Get installed Node.js packages Parameters ---------- diff --git a/sdk/python/agent_sandbox/shell/client.py b/sdk/python/agent_sandbox/shell/client.py index 63565e5..cca5d2a 100644 --- a/sdk/python/agent_sandbox/shell/client.py +++ b/sdk/python/agent_sandbox/shell/client.py @@ -45,6 +45,7 @@ def exec_command( strict: typing.Optional[bool] = OMIT, no_change_timeout: typing.Optional[int] = OMIT, preserve_symlinks: typing.Optional[bool] = OMIT, + truncate: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> ResponseShellCommandResult: """ @@ -77,6 +78,9 @@ def exec_command( preserve_symlinks : typing.Optional[bool] If True, preserve symlinks in working directory path (pwd shows symlink path). If False, symlinks are resolved to physical paths. Defaults to False for backward compatibility. + truncate : typing.Optional[bool] + If True, truncate output when it exceeds 30000 characters (default: True) + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -105,6 +109,7 @@ def exec_command( strict=strict, no_change_timeout=no_change_timeout, preserve_symlinks=preserve_symlinks, + truncate=truncate, request_options=request_options, ) return _response.data @@ -495,6 +500,7 @@ async def exec_command( strict: typing.Optional[bool] = OMIT, no_change_timeout: typing.Optional[int] = OMIT, preserve_symlinks: typing.Optional[bool] = OMIT, + truncate: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> ResponseShellCommandResult: """ @@ -527,6 +533,9 @@ async def exec_command( preserve_symlinks : typing.Optional[bool] If True, preserve symlinks in working directory path (pwd shows symlink path). If False, symlinks are resolved to physical paths. Defaults to False for backward compatibility. + truncate : typing.Optional[bool] + If True, truncate output when it exceeds 30000 characters (default: True) + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -563,6 +572,7 @@ async def main() -> None: strict=strict, no_change_timeout=no_change_timeout, preserve_symlinks=preserve_symlinks, + truncate=truncate, request_options=request_options, ) return _response.data diff --git a/sdk/python/agent_sandbox/shell/raw_client.py b/sdk/python/agent_sandbox/shell/raw_client.py index 7614c7c..4361959 100644 --- a/sdk/python/agent_sandbox/shell/raw_client.py +++ b/sdk/python/agent_sandbox/shell/raw_client.py @@ -40,6 +40,7 @@ def exec_command( strict: typing.Optional[bool] = OMIT, no_change_timeout: typing.Optional[int] = OMIT, preserve_symlinks: typing.Optional[bool] = OMIT, + truncate: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[ResponseShellCommandResult]: """ @@ -72,6 +73,9 @@ def exec_command( preserve_symlinks : typing.Optional[bool] If True, preserve symlinks in working directory path (pwd shows symlink path). If False, symlinks are resolved to physical paths. Defaults to False for backward compatibility. + truncate : typing.Optional[bool] + If True, truncate output when it exceeds 30000 characters (default: True) + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -92,6 +96,7 @@ def exec_command( "strict": strict, "no_change_timeout": no_change_timeout, "preserve_symlinks": preserve_symlinks, + "truncate": truncate, }, headers={ "content-type": "application/json", @@ -687,6 +692,7 @@ async def exec_command( strict: typing.Optional[bool] = OMIT, no_change_timeout: typing.Optional[int] = OMIT, preserve_symlinks: typing.Optional[bool] = OMIT, + truncate: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[ResponseShellCommandResult]: """ @@ -719,6 +725,9 @@ async def exec_command( preserve_symlinks : typing.Optional[bool] If True, preserve symlinks in working directory path (pwd shows symlink path). If False, symlinks are resolved to physical paths. Defaults to False for backward compatibility. + truncate : typing.Optional[bool] + If True, truncate output when it exceeds 30000 characters (default: True) + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -739,6 +748,7 @@ async def exec_command( "strict": strict, "no_change_timeout": no_change_timeout, "preserve_symlinks": preserve_symlinks, + "truncate": truncate, }, headers={ "content-type": "application/json", diff --git a/sdk/python/agent_sandbox/types/action_response.py b/sdk/python/agent_sandbox/types/action_response.py index 4f853d8..a8b24c9 100644 --- a/sdk/python/agent_sandbox/types/action_response.py +++ b/sdk/python/agent_sandbox/types/action_response.py @@ -11,14 +11,24 @@ class ActionResponse(UniversalBaseModel): """ Response model for browser actions. - Provides backward compatibility: + Inherits from Response for unified API format, with backward compatibility: - Old format: resp.json()['status'], resp.json()['action_performed'] - - New format: resp.json()['data']['status'], resp.json()['data']['action_performed'] + - New format: resp.json()['success'], resp.json()['message'], resp.json()['data'] """ - status: typing.Literal["success"] = "success" - action_performed: str - data: typing.Optional[ActionData] = None + success: typing.Optional[bool] = pydantic.Field(default=None) + """ + Whether the operation was successful + """ + + message: typing.Optional[str] = None + data: typing.Optional[ActionData] = pydantic.Field(default=None) + """ + Data returned from the operation + """ + + status: typing.Optional[typing.Literal["success"]] = None + action_performed: typing.Optional[str] = None if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 diff --git a/sdk/python/agent_sandbox/types/browser_info_result.py b/sdk/python/agent_sandbox/types/browser_info_result.py index c681a58..f8eadad 100644 --- a/sdk/python/agent_sandbox/types/browser_info_result.py +++ b/sdk/python/agent_sandbox/types/browser_info_result.py @@ -27,9 +27,19 @@ class BrowserInfoResult(UniversalBaseModel): VNC URL """ + cdp_ui_url: str = pydantic.Field() + """ + CDP UI URL (browser-ui) + """ + viewport: BrowserViewport = pydantic.Field() """ - Viewport size + Display size (from xrandr / env vars) + """ + + page_viewport: typing.Optional[BrowserViewport] = pydantic.Field(default=None) + """ + Actual Chrome page viewport (window.innerWidth/Height via CDP). Smaller than viewport because Chrome UI chrome takes space. """ if IS_PYDANTIC_V2: diff --git a/sdk/python/agent_sandbox/types/node_js_runtime_info.py b/sdk/python/agent_sandbox/types/node_js_runtime_info.py index 006add1..119c1a6 100644 --- a/sdk/python/agent_sandbox/types/node_js_runtime_info.py +++ b/sdk/python/agent_sandbox/types/node_js_runtime_info.py @@ -57,6 +57,16 @@ class NodeJsRuntimeInfo(UniversalBaseModel): Error message if runtime info retrieval failed """ + available_versions: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Available Node.js versions (e.g., node20, node22, node24) + """ + + current_version: typing.Optional[str] = pydantic.Field(default=None) + """ + Currently active Node.js version + """ + if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 else: diff --git a/sdk/python/agent_sandbox/types/str_replace_editor_result.py b/sdk/python/agent_sandbox/types/str_replace_editor_result.py index f90bd3b..199eb7b 100644 --- a/sdk/python/agent_sandbox/types/str_replace_editor_result.py +++ b/sdk/python/agent_sandbox/types/str_replace_editor_result.py @@ -41,6 +41,11 @@ class StrReplaceEditorResult(UniversalBaseModel): New file content after operation """ + metadata: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = pydantic.Field(default=None) + """ + File metadata (only returned when enable_metadata=true for binary files) + """ + if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 else: diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index b62c44a..cc76a0e 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta" [project] name = "agent-sandbox" -version = "0.0.21" -description = "Python SDK for the All-in-One Sandbox API, >=1.0.0.156" +version = "0.0.22" +description = "Python SDK for the All-in-One Sandbox API, >=1.1.0" readme = "README.md" license = { text = "Apache-2.0" } authors = [ diff --git a/website/docs/public/v1/openapi.json b/website/docs/public/v1/openapi.json index 187a9b4..f6ca006 100644 --- a/website/docs/public/v1/openapi.json +++ b/website/docs/public/v1/openapi.json @@ -3,7 +3,7 @@ "info": { "title": "FastAPI", "description": "\n- Browser\n - CDP: [/cdp/json/version](/cdp/json/version)\n- Jupyter\n - Notebook: [/jupyter](/jupyter)\n- MCP\n - Streamable HTTP: [/mcp](/mcp) or [/v1/mcp](/v1/mcp)\n", - "version": "1.0.0.160" + "version": "1.1.0" }, "paths": { "/v1/sandbox": { @@ -30,7 +30,7 @@ "get": { "tags": ["sandbox"], "summary": "Python Packages", - "description": "Get installed packages by language", + "description": "Get installed Python packages", "operationId": "python_packages_v1_sandbox_packages_python_get", "responses": { "200": { @@ -50,7 +50,7 @@ "get": { "tags": ["sandbox"], "summary": "Nodejs Packages", - "description": "Get installed packages by language", + "description": "Get installed Node.js packages", "operationId": "nodejs_packages_v1_sandbox_packages_nodejs_get", "responses": { "200": { @@ -1526,7 +1526,7 @@ "get": { "tags": ["code"], "summary": "Code Info", - "description": "Return metadata about supported code runtimes", + "description": "Return metadata about supported code runtimes\n\nNote: Version info is cached at service level (first call only runs subprocess).", "operationId": "code_info_v1_code_info_get", "responses": { "200": { @@ -1751,8 +1751,220 @@ "x-fern-sdk-method-name": "get_content" } }, + "/tickets": { + "post": { + "tags": ["auth"], + "summary": "Create Ticket", + "description": "Create and return a short-lived authentication ticket.\n\nThis is a non-idempotent action; each call creates a new, unique ticket.", + "operationId": "create_ticket_tickets_post", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "additionalProperties": true, + "type": "object", + "title": "Response Create Ticket Tickets Post" + } + } + } + } + }, + "x-fern-sdk-group-name": "auth", + "x-fern-sdk-method-name": "create_ticket" + } + }, + "/auth": { + "get": { + "tags": ["auth"], + "summary": "Authenticate Request", + "description": "Authenticate a request using ticket or JWT.\n\nThis endpoint receives authentication subrequests (e.g., from Nginx auth_request).\nIt validates the request based on either a ticket in the 'x-original-uri'\nheader or a JWT in the 'Authorization' header.", + "operationId": "authenticate_request_auth_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "additionalProperties": { "type": "string" }, + "type": "object", + "title": "Response Authenticate Request Auth Get" + } + } + } + } + }, + "x-fern-sdk-group-name": "auth", + "x-fern-sdk-method-name": "authenticate" + } + }, + "/cdp/json": { + "get": { + "tags": ["cdp"], + "summary": "Cdp Json", + "description": "Get list of inspectable pages with rewritten URLs.", + "operationId": "cdp_json_cdp_json_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + } + }, + "x-fern-sdk-group-name": "cdp", + "x-fern-sdk-method-name": "get_pages" + } + }, + "/cdp/json/version": { + "get": { + "tags": ["cdp"], + "summary": "Cdp Json Version", + "description": "Get browser version information with rewritten URLs.", + "operationId": "cdp_json_version_cdp_json_version_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + } + }, + "x-fern-sdk-group-name": "cdp", + "x-fern-sdk-method-name": "get_version" + } + }, + "/cdp/json/list": { + "get": { + "tags": ["cdp"], + "summary": "Cdp Json List", + "description": "Get list of inspectable pages (alias for /json).", + "operationId": "cdp_json_list_cdp_json_list_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + } + }, + "x-fern-sdk-group-name": "cdp", + "x-fern-sdk-method-name": "list_pages" + } + }, + "/cdp/json/protocol": { + "get": { + "tags": ["cdp"], + "summary": "Cdp Json Protocol", + "description": "Get the Chrome DevTools Protocol schema.", + "operationId": "cdp_json_protocol_cdp_json_protocol_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + } + }, + "x-fern-sdk-group-name": "cdp", + "x-fern-sdk-method-name": "get_protocol" + } + }, + "/cdp/json/new": { + "put": { + "tags": ["cdp"], + "summary": "Cdp Json New", + "description": "Open a new tab/page.", + "operationId": "cdp_json_new_cdp_json_new_put", + "parameters": [ + { + "name": "url", + "in": "query", + "required": false, + "schema": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Url" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + }, + "x-fern-sdk-group-name": "cdp", + "x-fern-sdk-method-name": "new_page" + } + }, + "/cdp/json/activate/{target_id}": { + "get": { + "tags": ["cdp"], + "summary": "Cdp Json Activate", + "description": "Activate (bring to front) a page.", + "operationId": "cdp_json_activate_cdp_json_activate__target_id__get", + "parameters": [ + { + "name": "target_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Target Id" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + }, + "x-fern-sdk-group-name": "cdp", + "x-fern-sdk-method-name": "activate_page" + } + }, + "/cdp/json/close/{target_id}": { + "get": { + "tags": ["cdp"], + "summary": "Cdp Json Close", + "description": "Close a page.", + "operationId": "cdp_json_close_cdp_json_close__target_id__get", + "parameters": [ + { + "name": "target_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Target Id" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + }, + "x-fern-sdk-group-name": "cdp", + "x-fern-sdk-method-name": "close_page" + } + }, "/terminal": { "get": { + "tags": ["web-ui"], "summary": "Serve Terminal", "description": "Serve the terminal HTML page", "operationId": "serve_terminal_terminal_get", @@ -1763,6 +1975,20 @@ } } } + }, + "/browser-ui": { + "get": { + "tags": ["web-ui"], + "summary": "Serve Browser Ui", + "description": "Serve the browser-ui HTML page (CDP remote casting)", + "operationId": "serve_browser_ui_browser_ui_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + } + } + } } }, "components": { @@ -1779,19 +2005,38 @@ }, "ActionResponse": { "properties": { - "status": { "type": "string", "const": "success", "title": "Status" }, - "action_performed": { "type": "string", "title": "Action Performed" }, + "success": { + "type": "boolean", + "title": "Success", + "description": "Whether the operation was successful", + "default": true + }, + "message": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Message" + }, "data": { "anyOf": [ { "$ref": "#/components/schemas/ActionData" }, { "type": "null" } - ] + ], + "description": "Data returned from the operation" + }, + "status": { + "type": "string", + "const": "success", + "title": "Status", + "default": "success" + }, + "action_performed": { + "type": "string", + "title": "Action Performed", + "default": "" } }, "type": "object", - "required": ["status", "action_performed"], "title": "ActionResponse", - "description": "Response model for browser actions.\n\nProvides backward compatibility:\n- Old format: resp.json()['status'], resp.json()['action_performed']\n- New format: resp.json()['data']['status'], resp.json()['data']['action_performed']" + "description": "Response model for browser actions.\n\nInherits from Response for unified API format, with backward compatibility:\n- Old format: resp.json()['status'], resp.json()['action_performed']\n- New format: resp.json()['success'], resp.json()['message'], resp.json()['data']" }, "ActiveSessionsResult": { "properties": { @@ -1993,13 +2238,31 @@ "title": "Vnc Url", "description": "VNC URL" }, + "cdp_ui_url": { + "type": "string", + "title": "Cdp Ui Url", + "description": "CDP UI URL (browser-ui)" + }, "viewport": { "$ref": "#/components/schemas/BrowserViewport", - "description": "Viewport size" + "description": "Display size (from xrandr / env vars)" + }, + "page_viewport": { + "anyOf": [ + { "$ref": "#/components/schemas/BrowserViewport" }, + { "type": "null" } + ], + "description": "Actual Chrome page viewport (window.innerWidth/Height via CDP). Smaller than viewport because Chrome UI chrome takes space." } }, "type": "object", - "required": ["user_agent", "cdp_url", "vnc_url", "viewport"], + "required": [ + "user_agent", + "cdp_url", + "vnc_url", + "cdp_ui_url", + "viewport" + ], "title": "BrowserInfoResult", "description": "Browser Info result" }, @@ -3330,6 +3593,11 @@ "anyOf": [{ "type": "string" }, { "type": "null" }], "title": "Cwd", "description": "Working directory for code execution" + }, + "version": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Version", + "description": "Node.js version to use: \"node20\", \"node22\", \"node24\", or aliases \"20\", \"22\", \"24\"" } }, "type": "object", @@ -3518,6 +3786,18 @@ "anyOf": [{ "type": "string" }, { "type": "null" }], "title": "Error", "description": "Error message if runtime info retrieval failed" + }, + "available_versions": { + "items": { "type": "string" }, + "type": "array", + "title": "Available Versions", + "description": "Available Node.js versions (e.g., node20, node22, node24)", + "default": [] + }, + "current_version": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Current Version", + "description": "Currently active Node.js version" } }, "type": "object", @@ -5023,6 +5303,12 @@ "title": "Preserve Symlinks", "description": "If True, preserve symlinks in working directory path (pwd shows symlink path). If False, symlinks are resolved to physical paths. Defaults to False for backward compatibility.", "default": false + }, + "truncate": { + "type": "boolean", + "title": "Truncate", + "description": "If True, truncate output when it exceeds 30000 characters (default: True)", + "default": true } }, "type": "object", @@ -5385,6 +5671,41 @@ ], "title": "Replace Mode", "description": "Optional parameter of `str_replace` command. When specified, controls how multiple occurrences are handled: 'ALL' replaces all occurrences, 'FIRST' replaces only the first, 'LAST' replaces only the last. If not specified, requires unique match (original behavior)." + }, + "page_range": { + "anyOf": [ + { "items": { "type": "integer" }, "type": "array" }, + { "type": "null" } + ], + "title": "Page Range", + "description": "Optional parameter for `view` command on PDF files. Specifies page range [start, end] (1-indexed). E.g., [1, 5] reads pages 1-5." + }, + "sheet_name": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Sheet Name", + "description": "Optional parameter for `view` command on Excel files. Specifies which sheet to read. If not provided, all sheets are returned." + }, + "row_range": { + "anyOf": [ + { "items": { "type": "integer" }, "type": "array" }, + { "type": "null" } + ], + "title": "Row Range", + "description": "Optional parameter for `view` command on Excel files. Specifies row range [start, end] (1-indexed). E.g., [1, 100] reads rows 1-100." + }, + "slide_range": { + "anyOf": [ + { "items": { "type": "integer" }, "type": "array" }, + { "type": "null" } + ], + "title": "Slide Range", + "description": "Optional parameter for `view` command on PPTX files. Specifies slide range [start, end] (1-indexed). E.g., [1, 5] reads slides 1-5." + }, + "enable_metadata": { + "anyOf": [{ "type": "boolean" }, { "type": "null" }], + "title": "Enable Metadata", + "description": "Optional parameter for `view` command. If true, returns file metadata (total pages, sheets, slides, etc.) in the response.", + "default": false } }, "type": "object", @@ -5423,6 +5744,13 @@ "anyOf": [{ "type": "string" }, { "type": "null" }], "title": "New Content", "description": "New file content after operation" + }, + "metadata": { + "anyOf": [ + { "additionalProperties": true, "type": "object" }, + { "type": "null" } + ], + "description": "File metadata (only returned when enable_metadata=true for binary files)" } }, "type": "object",