Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ class CodesAdapter(
message = message,
statusCode = response.code,
error = sandboxError ?: SandboxError(UNEXPECTED_RESPONSE),
requestId = response.header("X-Request-ID"),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ package com.alibaba.opensandbox.codeinterpreter.infrastructure.adapters.service
import com.alibaba.opensandbox.codeinterpreter.domain.models.execd.executions.RunCodeRequest
import com.alibaba.opensandbox.sandbox.HttpClientProvider
import com.alibaba.opensandbox.sandbox.config.ConnectionConfig
import com.alibaba.opensandbox.sandbox.domain.exceptions.SandboxApiException
import com.alibaba.opensandbox.sandbox.domain.models.execd.executions.ExecutionHandlers
import com.alibaba.opensandbox.sandbox.domain.models.sandboxes.SandboxEndpoint
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
Expand Down Expand Up @@ -133,4 +135,20 @@ class CodesAdapterTest {
assertEquals("/code", request.requestUrl?.encodedPath)
assertEquals("exec-123", request.requestUrl?.queryParameter("id"))
}

@Test
fun `run should expose request id on api exception`() {
mockWebServer.enqueue(
MockResponse()
.setResponseCode(500)
.addHeader("X-Request-ID", "req-kotlin-code-123")
.setBody("""{"code":"INTERNAL_ERROR","message":"boom"}"""),
)

val request = RunCodeRequest.builder().code("print('boom')").build()
val ex = assertThrows(SandboxApiException::class.java) { codesAdapter.run(request) }

assertEquals(500, ex.statusCode)
assertEquals("req-kotlin-code-123", ex.requestId)
}
}
2 changes: 1 addition & 1 deletion sdks/code-interpreter/kotlin/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ org.gradle.parallel=true

# Project metadata
project.group=com.alibaba.opensandbox
project.version=1.0.4
project.version=1.0.5
project.description=A Kotlin SDK for Code Interpreter
2 changes: 1 addition & 1 deletion sdks/code-interpreter/kotlin/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ spotless = "6.23.3"
maven-publish = "0.35.0"
dokka = "1.9.10"
jackson = "2.18.2"
sandbox = "1.0.4"
sandbox = "1.0.5"
junit-platform = "1.13.4"

[libraries]
Expand Down
2 changes: 1 addition & 1 deletion sdks/code-interpreter/python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ classifiers = [
]
dependencies = [
"pydantic>=2.4.2,<3.0",
"opensandbox>=0.1.1,<0.2.0",
"opensandbox>=0.1.5,<0.2.0",
]

[project.urls]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
ExecutionEventDispatcher,
)
from opensandbox.adapters.converter.response_handler import (
extract_request_id,
handle_api_error,
require_parsed,
)
Expand Down Expand Up @@ -287,6 +288,7 @@ async def run(
raise SandboxApiException(
message=f"Failed to run code. Status code: {response.status_code}",
status_code=response.status_code,
request_id=extract_request_id(response.headers),
)

dispatcher = ExecutionEventDispatcher(execution, handlers)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
ExceptionConverter,
)
from opensandbox.adapters.converter.response_handler import (
extract_request_id,
handle_api_error,
require_parsed,
)
Expand Down Expand Up @@ -252,6 +253,7 @@ def run(
raise SandboxApiException(
message=f"Failed to run code. Status code: {response.status_code}",
status_code=response.status_code,
request_id=extract_request_id(response.headers),
)

for line in response.iter_lines():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,12 @@ async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
)
return httpx.Response(200, headers={"Content-Type": "text/event-stream"}, content=sse, request=request)

return httpx.Response(400, content=b"bad", request=request)
return httpx.Response(
400,
headers={"x-request-id": "req-code-123"},
content=b"bad",
request=request,
)


def test_code_execution_converter_includes_context() -> None:
Expand Down Expand Up @@ -115,5 +120,6 @@ async def test_run_code_non_200_raises_api_exception() -> None:
endpoint = SandboxEndpoint(endpoint="localhost:44772", port=44772)
adapter = CodesAdapter(endpoint, cfg)

with pytest.raises(SandboxApiException):
with pytest.raises(SandboxApiException) as ei:
await adapter.run("other")
assert ei.value.request_id == "req-code-123"
1 change: 1 addition & 0 deletions sdks/sandbox/csharp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ try
catch (SandboxException ex)
{
Console.Error.WriteLine($"Sandbox Error: [{ex.Error.Code}] {ex.Error.Message}");
Console.Error.WriteLine($"Request ID: {ex.RequestId}");
}
```

Expand Down
1 change: 1 addition & 0 deletions sdks/sandbox/csharp/README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ try
catch (SandboxException ex)
{
Console.Error.WriteLine($"沙箱错误: [{ex.Error.Code}] {ex.Error.Message}");
Console.Error.WriteLine($"Request ID: {ex.RequestId}");
}
```

Expand Down
33 changes: 28 additions & 5 deletions sdks/sandbox/csharp/src/OpenSandbox/Core/Exceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,42 @@ public class SandboxException : Exception
/// </summary>
public SandboxError Error { get; }

/// <summary>
/// Gets the request ID from the server response when available.
/// </summary>
public string? RequestId { get; }

/// <summary>
/// Initializes a new instance of the <see cref="SandboxException"/> class.
/// Kept for binary compatibility with previous SDK versions.
/// </summary>
/// <param name="message">The error message.</param>
/// <param name="innerException">The inner exception.</param>
/// <param name="error">The structured error information.</param>
public SandboxException(
string? message,
Exception? innerException,
SandboxError? error)
: this(message, innerException, error, null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="SandboxException"/> class.
/// </summary>
/// <param name="message">The error message.</param>
/// <param name="innerException">The inner exception.</param>
/// <param name="error">The structured error information.</param>
/// <param name="requestId">The request ID.</param>
public SandboxException(
string? message = null,
Exception? innerException = null,
SandboxError? error = null)
SandboxError? error = null,
string? requestId = null)
: base(message ?? error?.Message, innerException)
{
Error = error ?? new SandboxError(SandboxErrorCodes.InternalUnknownError, message);
RequestId = requestId;
}
}

Expand All @@ -112,9 +135,10 @@ public class SandboxApiException : SandboxException
public int? StatusCode { get; }

/// <summary>
/// Gets the request ID from the server response.
/// Gets the request ID from the server response when available.
/// Kept on the derived type for binary compatibility with older releases.
/// </summary>
public string? RequestId { get; }
public new string? RequestId => base.RequestId;

/// <summary>
/// Gets the raw response body.
Expand All @@ -137,10 +161,9 @@ public SandboxApiException(
object? rawBody = null,
Exception? innerException = null,
SandboxError? error = null)
: base(message, innerException, error ?? new SandboxError(SandboxErrorCodes.UnexpectedResponse, message))
: base(message, innerException, error ?? new SandboxError(SandboxErrorCodes.UnexpectedResponse, message), requestId)
{
StatusCode = statusCode;
RequestId = requestId;
RawBody = rawBody;
}
}
Expand Down
36 changes: 36 additions & 0 deletions sdks/sandbox/csharp/tests/OpenSandbox.Tests/ExceptionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,30 @@ public void SandboxException_ShouldContainError()
exception.Error.Should().Be(error);
}

[Fact]
public void SandboxException_ShouldContainRequestId()
{
// Arrange & Act
var exception = new SandboxException("Exception message", requestId: "req-base-123");

// Assert
exception.RequestId.Should().Be("req-base-123");
}

[Fact]
public void SandboxException_ShouldDeclareLegacyConstructor_ForBinaryCompatibility()
{
var constructor = typeof(SandboxException).GetConstructor(
new[]
{
typeof(string),
typeof(Exception),
typeof(SandboxError)
});

constructor.Should().NotBeNull();
}

[Fact]
public void SandboxException_WithoutError_ShouldCreateDefaultError()
{
Expand Down Expand Up @@ -100,6 +124,18 @@ public void SandboxApiException_ShouldContainStatusCodeAndRequestId()
exception.Error.Code.Should().Be(SandboxErrorCodes.UnexpectedResponse);
}

[Fact]
public void SandboxApiException_ShouldDeclareRequestIdProperty_ForBinaryCompatibility()
{
var requestIdProperty = typeof(SandboxApiException).GetProperty(
"RequestId",
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.DeclaredOnly);

requestIdProperty.Should().NotBeNull();
}

[Fact]
public void SandboxApiException_WithCustomError_ShouldUseProvidedError()
{
Expand Down
1 change: 1 addition & 0 deletions sdks/sandbox/javascript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ try {
console.error(
`Sandbox Error: [${err.error.code}] ${err.error.message ?? ""}`,
);
console.error(`Request ID: ${err.requestId ?? "N/A"}`);
} else {
console.error(err);
}
Expand Down
1 change: 1 addition & 0 deletions sdks/sandbox/javascript/README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ try {
} catch (err) {
if (err instanceof SandboxException) {
console.error(`沙箱错误: [${err.error.code}] ${err.error.message ?? ""}`);
console.error(`Request ID: ${err.requestId ?? "N/A"}`);
} else {
console.error(err);
}
Expand Down
2 changes: 1 addition & 1 deletion sdks/sandbox/javascript/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@alibaba-group/opensandbox",
"version": "0.1.4",
"version": "0.1.5",
"description": "OpenSandbox TypeScript/JavaScript SDK (sandbox lifecycle + execd APIs)",
"license": "Apache-2.0",
"type": "module",
Expand Down
15 changes: 10 additions & 5 deletions sdks/sandbox/javascript/src/api/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -800,15 +800,20 @@ export interface components {
path: string;
};
/**
* @description Kubernetes PersistentVolumeClaim mount backend. References an existing
* PVC in the same namespace as the sandbox pod.
* @description Platform-managed named volume backend. A runtime-neutral abstraction
* for referencing a pre-existing, platform-managed named volume.
*
* Only available in Kubernetes runtime.
* - Kubernetes: maps to a PersistentVolumeClaim in the same namespace.
* - Docker: maps to a Docker named volume (created via `docker volume create`).
*
* The volume must already exist on the target platform before sandbox
* creation.
*/
PVC: {
/**
* @description Name of the PersistentVolumeClaim in the same namespace.
* Must be a valid Kubernetes resource name.
* @description Name of the volume on the target platform.
* In Kubernetes this is the PVC name; in Docker this is the named
* volume name. Must be a valid DNS label.
*/
claimName: string;
};
Expand Down
2 changes: 1 addition & 1 deletion sdks/sandbox/javascript/src/core/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ export const DEFAULT_READY_TIMEOUT_SECONDS = 30;
export const DEFAULT_HEALTH_CHECK_POLLING_INTERVAL_MILLIS = 200;

export const DEFAULT_REQUEST_TIMEOUT_SECONDS = 30;
export const DEFAULT_USER_AGENT = "OpenSandbox-JS-SDK/0.1.4";
export const DEFAULT_USER_AGENT = "OpenSandbox-JS-SDK/0.1.5";
9 changes: 5 additions & 4 deletions sdks/sandbox/javascript/src/core/exceptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ interface SandboxExceptionOpts {
message?: string;
cause?: unknown;
error?: SandboxError;
requestId?: string;
}

/**
Expand All @@ -55,32 +56,32 @@ export class SandboxException extends Error {
readonly name: string = "SandboxException";
readonly error: SandboxError;
readonly cause?: unknown;
readonly requestId?: string;

constructor(opts: SandboxExceptionOpts = {}) {
super(opts.message);
this.cause = opts.cause;
this.error = opts.error ?? new SandboxError(SandboxError.INTERNAL_UNKNOWN_ERROR);
this.requestId = opts.requestId;
}
}

export class SandboxApiException extends SandboxException {
readonly name: string = "SandboxApiException";
readonly statusCode?: number;
readonly requestId?: string;
readonly rawBody?: unknown;

constructor(opts: SandboxExceptionOpts & {
statusCode?: number;
requestId?: string;
rawBody?: unknown;
}) {
super({
message: opts.message,
cause: opts.cause,
error: opts.error ?? new SandboxError(SandboxError.UNEXPECTED_RESPONSE, opts.message),
requestId: opts.requestId,
});
this.statusCode = opts.statusCode;
this.requestId = opts.requestId;
this.rawBody = opts.rawBody;
}
}
Expand Down Expand Up @@ -131,4 +132,4 @@ export class InvalidArgumentException extends SandboxException {
error: new SandboxError(SandboxError.INVALID_ARGUMENT, opts.message),
});
}
}
}
5 changes: 5 additions & 0 deletions sdks/sandbox/kotlin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public class QuickStart {
} catch (SandboxException e) {
// Handle Sandbox specific exceptions
System.err.println("Sandbox Error: [" + e.getError().getCode() + "] " + e.getError().getMessage());
System.err.println("Request ID: " + e.getRequestId());
} catch (Exception e) {
e.printStackTrace();
}
Expand Down Expand Up @@ -236,6 +237,10 @@ ConnectionPool sharedPool = new ConnectionPool(50, 30, TimeUnit.SECONDS);
ConnectionConfig sharedConfig = ConnectionConfig.builder()
.apiKey("your-key")
.domain("api.opensandbox.io")
.headers(Map.of(
"X-Custom-Header", "value",
"X-Request-ID", "trace-123"
))
.connectionPool(sharedPool) // Inject shared pool
.build();
```
Expand Down
Loading