Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Pando Java SDK

Java 17+ SDK for the Pando AI coding assistant.

Pando can operate in three modes — all three are supported by this SDK:

Mode Class Description
Subprocess PandoClient One-shot pando -p "..." runs with JSON output
ACP stdio PandoAgent / PandoSession Long-lived JSON-RPC 2.0 session over stdin/stdout
HTTP REST PandoHttpClient Connects to a running pando serve or pando app instance

Prerequisites

  • Java 17 or later
  • Pando CLI installed and on your PATH (or set PANDO_PATH environment variable)

Installation

Add to your pom.xml:

<dependency>
  <groupId>io.pando</groupId>
  <artifactId>pando-sdk</artifactId>
  <version>0.1.0</version>
</dependency>

Or Gradle (build.gradle.kts):

implementation("io.pando:pando-sdk:0.1.0")

Quick start — Subprocess mode (PandoClient)

import io.pando.sdk.PandoClient;
import io.pando.sdk.model.RunResult;

PandoClient client = PandoClient.builder()
    .cwd("/path/to/project")
    .model("copilot.gpt-5.4")
    .timeout(java.time.Duration.ofMinutes(5))
    .build();

RunResult result = client.run("Fix all lint errors");
System.out.println(result.response());

With options

import io.pando.sdk.PandoClient.RunOptions;

RunResult result = client.run("Refactor auth module", RunOptions.builder()
    .allowAllTools(true)           // passes --yolo to auto-approve tool permissions
    .model("claude-sonnet-4-6")   // override model for this run
    .build());

Async

client.runAsync("Fix lint errors")
    .thenAccept(r -> System.out.println(r.response()))
    .join();

Streaming text

client.stream("Explain the code", line -> System.out.print(line + "\n"));

ACP stdio mode (PandoAgent + PandoSession)

Basic session

import io.pando.sdk.PandoAgent;
import io.pando.sdk.PandoSession;

try (PandoAgent agent = PandoAgent.builder()
        .cwd("/path/to/project")
        .model("claude-sonnet-4-6")
        .persona("software-engineer")
        .build()) {

    agent.connect();

    PandoSession session = agent.createSession("Refactoring task");
    String response = session.ask("Refactor the database layer");
    System.out.println(response);
}

Streaming events with Flow.Subscriber

import io.pando.sdk.events.*;
import java.util.concurrent.Flow;

session.send("Explain the codebase").subscribe(new Flow.Subscriber<AgentEvent>() {

    @Override
    public void onSubscribe(Flow.Subscription s) {
        s.request(Long.MAX_VALUE);
    }

    @Override
    public void onNext(AgentEvent event) {
        switch (event) {
            case ContentDeltaEvent e  -> System.out.print(e.delta());
            case ToolCallEvent e      -> System.out.println("\n[Tool] " + e.toolCall().name());
            case ToolResultEvent e    -> System.out.println("[Result] " + e.toolResult().content());
            case ResponseEvent e      -> System.out.println("\n[Done]");
            case ErrorEvent e         -> System.err.println("[Error] " + e.error());
            case ThinkingDeltaEvent e -> {} // reasoning content
            case SummarizeEvent e     -> System.out.println("[Context summarized]");
        }
    }

    @Override
    public void onError(Throwable t) { t.printStackTrace(); }

    @Override
    public void onComplete() {}
});

Async CompletableFuture

agent.createSessionAsync("Task")
    .thenCompose(s -> s.askAsync("Fix lint errors"))
    .thenAccept(System.out::println)
    .join();

Tool permission handler

PandoAgent agent = PandoAgent.builder()
    .cwd("/project")
    .toolPermissionHandler(req -> {
        // Deny bash tool, approve everything else
        return !req.toolName().equals("bash");
    })
    .build();

Persona management

// Set on agent (affects all future sessions)
agent.setPersona("qa");

// Set on a specific session
session.setPersona("system-engineer");

// List available personas
List<String> personas = agent.listPersonas();
// ["assistant", "software-engineer", "qa", "system-engineer"]

HTTP REST mode (PandoHttpClient)

Connects to a running pando serve or pando app instance.

import io.pando.sdk.PandoHttpClient;
import io.pando.sdk.model.SessionInfo;

PandoHttpClient client = PandoHttpClient.builder()
    .baseUrl("http://localhost:8765")
    .disableSslVerification(true)   // for self-signed dev certificates
    .timeout(java.time.Duration.ofSeconds(60))
    .build();

// Create a session
SessionInfo session = client.sessions().create("My task");

// Send a message and get full response
String response = client.sessions().ask(session.sessionId(), "Fix lint errors");
System.out.println(response);

// Stream the response
client.sessions().sendMessage(session.sessionId(), "Explain this code")
    .subscribe(new Flow.Subscriber<PandoHttpClient.HttpStreamChunk>() {
        public void onSubscribe(Flow.Subscription s) { s.request(Long.MAX_VALUE); }
        public void onNext(PandoHttpClient.HttpStreamChunk chunk) {
            if (chunk.delta() != null) System.out.print(chunk.delta());
        }
        public void onError(Throwable t) { t.printStackTrace(); }
        public void onComplete() {}
    });

// List sessions
List<SessionInfo> sessions = client.sessions().list();

// Models
List<io.pando.sdk.model.ModelInfo> models = client.models().list();
client.models().setActive("claude-sonnet-4-6");

// Personas
List<String> personas = client.personas().list();

AG-UI / GenUI mode (PandoAguiClient)

AG-UI is the protocol CopilotKit and other Generative-UI frontends speak to agent backends. Pando serves it from pando agui-serve --port 8090 (or pando serve --agui-port 8090); it is off by default and requires a bearer token and an origin allow-list, because it exposes a code-executing agent to a browser.

The client lives in io.pando.sdk.agui and uses the built-in java.net.http.HttpClient plus Jackson — no extra dependency.

import io.pando.sdk.agui.*;

PandoAguiClient client = PandoAguiClient.builder()
        .baseUrl("http://localhost:8090")
        .token(System.getenv("PANDO_TOKEN"))
        .agent("coder")
        .build();

// Discovery: which agents exist, their model, which capabilities are on
AguiInfo info = client.info();

client.run(RunOptions.prompt("Summarise the repo"), event -> {
    switch (event.type()) {
        case "TEXT_MESSAGE_CONTENT" -> System.out.print(event.string("delta"));
        case "STATE_SNAPSHOT" -> {
            PandoState state = event.snapshot().orElseThrow();
            System.out.println(state.todos() + " " + state.subAgents());
        }
        case "RUN_FINISHED" -> {
            if (AguiEvent.OUTCOME_INTERRUPT.equals(event.string("outcome"))) {
                // The agent called one of your tools: run it, then run again on the
                // same thread with AguiMessage.toolResult(...), which resumes it.
            }
        }
        default -> { }
    }
});

String text = client.runText("What does cmd/root.go do?");

Reactive streaming uses the same Flow.Publisher idiom as PandoHttpClient:

Flow.Publisher<AguiEvent> events = client.stream(RunOptions.prompt("Refactor the parser"));

Frontend tools and human-in-the-loop approvals:

RunOptions options = RunOptions.builder()
        .message(AguiMessage.user("chart the commits"))
        .tool(AguiTool.of("show_chart", "Renders a chart", Map.of("type", "object")))
        .threadId(threadId)
        .build();

// A TOOL_CALL_START whose name is PandoAguiClient.PERMISSION_TOOL_NAME is an approval
// prompt; parse its arguments with PandoPermissionRequest.fromArguments(args) and answer
// with AguiMessage.toolResult(callId, PandoPermissionRequest.answer(true)).
Type Purpose
PandoAguiClient Run/discovery client (run, stream, runText, info)
AguiEvent One protocol event, with snapshot() and delta() helpers
RunOptions Builder for a run (prompt or transcript, tools, context, state, agent)
PandoState The shared-state document (STATE_SNAPSHOT)
AguiMessage / AguiTool / AguiContext Request payload types
AguiInfo The /info discovery document
PandoAguiClient.parseSse The event-stream parser, if you issue the request yourself
PandoPermissionRequest Human-in-the-loop approvals

Exception handling

All exceptions extend PandoException (unchecked):

import io.pando.sdk.exception.*;

try {
    RunResult result = client.run("Fix errors");
} catch (PandoBinaryNotFoundException e) {
    System.err.println("Install pando: " + e.getMessage());
    System.err.println("Searched: " + e.getSearchedPaths());
} catch (PandoTimeoutException e) {
    System.err.println("Timed out after: " + e.getTimeout());
} catch (PandoConnectionException e) {
    System.err.println("Process error (exit " + e.getExitCode() + "): " + e.getMessage());
} catch (PandoRpcException e) {
    System.err.println("RPC error " + e.getCode() + ": " + e.getRpcMessage());
} catch (PandoSessionException e) {
    System.err.println("Session error: " + e.getMessage());
} catch (PandoException e) {
    System.err.println("Pando error: " + e.getMessage());
}

Exception hierarchy:

PandoException (extends RuntimeException)
├── PandoBinaryNotFoundException   — pando binary not found
├── PandoConnectionException       — process died or I/O failure
├── PandoSessionException          — invalid session state
├── PandoTimeoutException          — operation timed out
└── PandoRpcException              — JSON-RPC error response

Thread safety

  • JsonRpcTransport is fully thread-safe. Multiple PandoSessions can send concurrent requests through the same underlying transport without synchronization on the caller side.
  • PandoAgent and PandoClient are thread-safe after construction.
  • PandoSession.send() creates a fresh SubmissionPublisher per call. Each publisher is independent and can have multiple subscribers.
  • The background reader thread in JsonRpcTransport is a daemon thread and does not prevent JVM shutdown.

Binary resolution

The binary is resolved in this order:

  1. Explicit pandoPath(...) on the builder.
  2. PANDO_PATH environment variable.
  3. Directories listed in the system PATH.

On Windows, both pando and pando.exe are checked.

// Use a specific binary
PandoClient.builder().pandoPath("/usr/local/bin/pando").build();

// Or set env var
// export PANDO_PATH=/opt/pando/bin/pando

Building from source

cd sdk/java
mvn clean package
mvn test

Requires Java 17+ and Maven 3.8+.

About

JAVA sdk for pando agent

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages