Skip to content

Latest commit

 

History

History
415 lines (332 loc) · 14.5 KB

File metadata and controls

415 lines (332 loc) · 14.5 KB

CreateOS Java SDK

Launch an isolated cloud sandbox, run real commands, stream output, move files, open a preview URL, and tear everything down from Java.

Your first sandbox

Add the published SDK on Maven Central to your project's pom.xml. Replace YOUR_VERSION with the current published version shown there:

<dependencies>
  <dependency>
    <groupId>sh.createos</groupId>
    <artifactId>createos-java-sdk</artifactId>
    <version>YOUR_VERSION</version>
  </dependency>
</dependencies>

No package-download token or extra Maven repository is needed.

The compile-checked hello-world example creates a sandbox, runs a command, prints its output, and always destroys the resource. Set CREATEOS_API_KEY in your environment before running it:

package sh.createos.examples.helloworld;

import sh.createos.CreateOsClient;
import sh.createos.Sandbox;
import sh.createos.model.CreateSandboxRequest;
import sh.createos.model.RunCommandRequest;

public final class HelloWorld {
  private HelloWorld() {}

  public static void main(String[] arguments) {
    CreateOsClient client =
        CreateOsClient.builder().apiKey(System.getenv("CREATEOS_API_KEY")).build();
    Sandbox sandbox =
        client.createSandbox(
            CreateSandboxRequest.builder("s-4vcpu-4gb")
                .rootFileSystem("devbox:1")
                .build());
    try {
      var response =
          sandbox.runCommand(
              RunCommandRequest.of(
                  "sh", "-c", "printf 'Java says hello from %s\\n' \"$(uname -m)\""));
      System.out.print(response.result().standardOutput());
    } finally {
      sandbox.destroy();
    }
  }
}
Java says hello from x86_64

CreateOsClient.builder().apiKey(apiKey) configures authentication explicitly. Do not commit a real API key to source control; inject it through your application's secret manager. Additional builder methods configure the endpoint, default timeout, HTTP client, user agent, and retry policy:

CreateOsClient client =
    CreateOsClient.builder()
        .apiKey(apiKey)
        .baseUri(URI.create("http://localhost:8080"))
        .timeout(Duration.ofSeconds(30))
        .build();

CreateOsClient.builder().build() also reads CREATEOS_API_KEY automatically. CREATEOS_SANDBOX_BASE_URL overrides the default control-plane URL. Explicit builder values take precedence.

The SDK targets Java 17 and uses the JDK HTTP client. Public wire models are immutable records, API failures remain inspectable through ApiException, and the build enforces the Google Java Style Guide.

Delegate access to one sandbox

An owner can create one delegated token for a sandbox. Plaintext is returned only on creation or rotation; inspection provides a redacted hint.

var created = sandbox.createAccessToken();
Sandbox worker = sandbox.withAccessToken(created.token());
var result = worker.runCommand(RunCommandRequest.of("echo", "hello"));
var metadata = sandbox.getAccessToken();
var replacement = sandbox.rotateAccessToken();
sandbox.disableAccessToken();

Use the owner's handle for token management. The delegated handle can operate its bound sandbox, including commands, files, processes, computer use, pause, resume, and destroy; it cannot manage tokens or account resources. Creating another enabled token returns HTTP 409; rotation requires an existing token. Disabling is idempotent. Revocation is immediate in the home region and propagates asynchronously to peer regions.

Documentation

  • CreateOS Sandbox overview explains the sandbox model, lifecycle, networking, storage, and isolation.
  • CreateOS Sandbox documentation contains the REST API reference and product guides.
  • Javadocs are generated with mvn javadoc:javadoc and published as an attached artifact by mvn verify.
  • Runnable examples cover commands, files, streaming, ingress, snapshots, networking, templates, managed processes, and desktop use.
  • Contributing guide documents development checks and commit conventions.
  • Security policy explains private vulnerability reporting and the SDK security boundary.
  • Changelog tracks release-visible changes.

SDKs

Stream output as it happens

Long-running commands do not need to disappear behind a buffered HTTP call:

try (var stream =
    sandbox.streamCommand(
        RunCommandRequest.of(
            "sh", "-c", "for n in 1 2 3; do echo step $n; sleep 1; done"))) {
  for (var event = stream.receive(); event != null; event = stream.receive()) {
    switch (event.type()) {
      case STDOUT -> System.out.print(event.data());
      case STDERR -> System.err.print(event.data());
      case EXIT -> System.out.println("exit code: " + event.exitCode());
      case ERROR -> System.err.println(event.message());
      case HEARTBEAT -> {}
    }
  }
}

The compile-checked command-streaming example uploads a Python program and consumes normalized stdout, stderr, exit, error, and heartbeat events. Closing a command or process stream closes its HTTP response body. Downloads, screenshots, and template log streams must also be closed.

Move files without shell escaping

byte[] configuration = "{\"mode\":\"production\"}".getBytes(StandardCharsets.UTF_8);
sandbox.files().upload("/workspace/config.json", new ByteArrayInputStream(configuration));

try (InputStream file = sandbox.files().download("/workspace/config.json")) {
  String contents = new String(file.readAllBytes(), StandardCharsets.UTF_8);
  System.out.println(contents);
}

For large transfers, override the timeout for that operation without changing the client's default timeout:

RequestOptions transferOptions =
    new RequestOptions(Map.of(), Duration.ofMinutes(30), null, false);

sandbox.files().upload(remotePath, source, transferOptions);
try (InputStream file = sandbox.files().download(remotePath, transferOptions)) {
  file.transferTo(destination);
}

The timeout covers the complete transfer, including reading the downloaded body. Uploads are not retried because an arbitrary InputStream may not be safe to replay after a partial write.

The compile-checked files-and-snapshots example uploads files, pauses a sandbox, creates a copy-on-write fork, resumes both sandboxes, and verifies that their filesystems diverge.

Keep a process alive after disconnecting

Managed processes are resources rather than fragile terminal sessions. Start one, reconnect from its output sequence, send input or signals, and wait for either the leader or its complete process tree:

ManagedProcess process =
    sandbox
        .processes()
        .create(
            new CreateProcessRequest(
                "python3",
                List.of("-m", "http.server", "8080"),
                null,
                Map.of(),
                null));

ManagedProcess completed =
    sandbox
        .processes()
        .waitFor(process.processId(), "tree", Duration.ofSeconds(30));

The compile-checked managed-process example also demonstrates output replay, standard input, interactive PTYs, terminal resize, signals, and process-tree deletion.

Turn a service into a URL

Create with ingress enabled, wait for the server to listen, then ask the sandbox for its public URL:

Sandbox sandbox =
    client.createSandbox(
        CreateSandboxRequest.builder("s-4vcpu-4gb")
            .rootFileSystem("devbox:1")
            .ingressEnabled(true)
            .build());

sandbox
    .processes()
    .create(
        new CreateProcessRequest(
            "python3",
            List.of("-m", "http.server", "8080", "--bind", "0.0.0.0"),
            null,
            Map.of(),
            null));

sandbox.waitForPort("127.0.0.1", 8080, Duration.ofSeconds(15));
URI previewUrl = sandbox.previewUrl(8080);
System.out.println(previewUrl);

The compile-checked ingress-preview example runs the complete flow and fetches the public response.

Everything is already connected

Account-level services are initialized by CreateOsClient:

TemplatesService templates = client.templates();
NetworksService networks = client.networks();
DisksService disks = client.disks();

List<Template> customTemplates = templates.list(PaginationOptions.ALL);
System.out.printf(
    "%d templates ready; networks=%s disks=%s%n",
    customTemplates.size(), networks.getClass().getSimpleName(), disks.getClass().getSimpleName());

Sandbox-level services are initialized when a handle is created or retrieved:

sandbox.files();
sandbox.processes();
sandbox.computer().mouse();
sandbox.computer().keyboard();
sandbox.computer().windows();
sandbox.computer().screens();

The compile-checked custom-template example builds a Docker-enabled root filesystem, follows build logs, creates a sandbox from the finished template, and runs containers.

Connect sandboxes on a private network

Create an overlay network, attach a running sandbox, and inspect the resulting membership. Cleanup runs in reverse dependency order:

Network network = client.networks().create("agent-mesh");
try {
  sandbox.attachNetwork(network.id());
  try {
    Network connected = client.networks().get(network.id());
    for (Network.Member member : connected.members()) {
      System.out.printf(
          "sandbox=%s private-ip=%s status=%s%n",
          member.sandboxId(), member.ipAddress(), member.status());
    }
  } finally {
    sandbox.detachNetwork(network.id());
  }
} finally {
  client.networks().delete(network.id());
}

The compile-checked private-network example runs this complete lifecycle.

Lifecycle reads like the domain

sandbox.pause();
sandbox.waitUntilPaused(Duration.ofMinutes(2));

Sandbox clone = sandbox.fork(ForkSandboxRequest.DEFAULT);
try {
  sandbox.resume();
  sandbox.waitUntilRunning(Duration.ofMinutes(2));
} finally {
  clone.destroy();
  sandbox.destroy();
}

The Sandbox handle caches the latest server projection safely. Lifecycle mutations and refresh() update it, while id(), name(), status(), ipAddress(), and data() provide thread-safe reads.

Build reusable templates

Build a sandbox root filesystem from a Dockerfile, follow its build logs, and wait until the template is ready before creating a sandbox from its ID. See the custom template example for the complete workflow and cleanup.

Automate a desktop

The desktop root filesystem supports screenshots, mouse and keyboard control, clipboard access, and temporary noVNC connections. The desktop example exercises these operations.

Errors stay inspectable

try {
  sandbox.refresh();
} catch (ApiException exception) {
  System.err.printf(
      "HTTP %d, code=%d, request=%s%n",
      exception.statusCode(), exception.serviceCode(), exception.requestId());
}

ApiException also exposes the request method, relative endpoint, bounded response body, and response headers. Errors are unchecked so applications can handle them at the appropriate boundary. Lifecycle wait exhaustion throws IllegalStateException with the target state and timeout.

Examples

Runnable examples live under examples/. Every example listed here is compiled during mvn verify, checked with Google Java style, and validated by ExamplesDocumentationTest:

Run commands are documented in the examples index. Examples are compiled into target/example-classes and excluded from the SDK JAR. Live execution is opt-in because it creates real CreateOS resources.

Development

Use Java 17 or newer and Maven 3.9 or newer:

mvn install
make format
make check
make test

Commits follow Conventional Commits and are validated locally and in pull requests. See CONTRIBUTING.md for accepted types and examples.

CI compiles the SDK and every example on Java 17, 21, and 25; runs JUnit and the coverage gate; verifies Google Java formatting; requires zero Checkstyle warnings; and builds source and Javadoc JARs.

Package layout

src/main/java/sh/createos/          client and resource services
src/main/java/sh/createos/model/    immutable public contracts
src/main/java/sh/createos/internal/ transport and stream decoding
examples/                           runnable, compile-checked programs

About CreateOS

CreateOS is an execution and governance platform for AI agents and applications. Learn more about isolated Firecracker-based workloads on the CreateOS Sandbox product page.

License

This SDK is available under the MIT License.