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
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ npm run build
- LLM tests also create sandboxes by default and require `E2B_API_KEY` plus the relevant provider credentials such as `OPENAI_API_KEY` or `ANTHROPIC_API_KEY`.
- If you already have a runtime server running, set `TEST_WS_URL` to reuse it instead of creating a fresh sandbox.
- Some LLM tests also require `TEST_S3_BUCKET` for artifact upload verification.
- If you want sandbox tests to run against a dev build instead of `npx -y runtimeuse`, set `RUNTIMEUSE_RUN_COMMAND`. A convenient way to get that command is `packages/runtimeuse` -> `npm run dev-publish`.
- If you want sandbox tests to run against a dev build instead of `npx -y runtimeuse@latest`, set `RUNTIMEUSE_RUN_COMMAND`. A convenient way to get that command is `packages/runtimeuse` -> `npm run dev-publish`.

## Docs Development

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Run AI agents inside sandboxes and communicate with them over WebSocket.

```bash
export OPENAI_API_KEY=your_openai_api_key
npx -y runtimeuse
npx -y runtimeuse@latest
```

This starts a WebSocket server on port 8080 using the default OpenAI handler. For fuller Claude-based sandbox examples, see [`examples/`](./examples).
Expand Down
12 changes: 6 additions & 6 deletions docs/content/docs/agent-runtime.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ The [agent runtime](https://www.npmjs.com/package/runtimeuse) is the process tha
## CLI

```bash
npx -y runtimeuse # OpenAI handler on port 8080
npx -y runtimeuse --agent claude # Claude handler
npx -y runtimeuse --port 3000 # custom port
npx -y runtimeuse --handler ./my-handler.js # custom handler entrypoint
npx -y runtimeuse@latest # OpenAI handler on port 8080
npx -y runtimeuse@latest --agent claude # Claude handler
npx -y runtimeuse@latest --port 3000 # custom port
npx -y runtimeuse@latest --handler ./my-handler.js # custom handler entrypoint
```

## Built-in Handlers
Expand All @@ -25,7 +25,7 @@ Requires `OPENAI_API_KEY` to be set in the environment. The handler runs the age

```bash
export OPENAI_API_KEY=your_openai_api_key
npx -y runtimeuse
npx -y runtimeuse@latest
```

### Claude Handler
Expand All @@ -37,7 +37,7 @@ npm install -g @anthropic-ai/claude-code
export ANTHROPIC_API_KEY=your_anthropic_api_key
export IS_SANDBOX=1
export CLAUDE_SKIP_ROOT_CHECK=1
npx -y runtimeuse --agent claude
npx -y runtimeuse@latest --agent claude
```

## Programmatic Startup
Expand Down
8 changes: 4 additions & 4 deletions docs/content/docs/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
```bash
npm install -g @anthropic-ai/claude-code
export ANTHROPIC_API_KEY=your_anthropic_api_key
npx -y runtimeuse --agent claude
npx -y runtimeuse@latest --agent claude
```

This starts the Claude Code agent on port `8080`. To use OpenAI agent instead:
```bash
export OPENAI_API_KEY=your_openai_api_key
npx -y runtimeuse
npx -y runtimeuse@latest
```

```python
Expand Down Expand Up @@ -47,7 +47,7 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
"CLAUDE_SKIP_ROOT_CHECK": "1",
}
)
.set_start_cmd("npx -y runtimeuse --agent claude", wait_for_port(8080))
.set_start_cmd("npx -y runtimeuse@latest --agent claude", wait_for_port(8080))
)

sandbox = Sandbox.create(template="runtimeuse-quickstart-claude", api_key=e2b_api_key)
Expand Down Expand Up @@ -95,7 +95,7 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
sandbox.process.execute_session_command(
"runtimeuse",
SessionExecuteRequest(
command="npx -y runtimeuse --agent claude",
command="npx -y runtimeuse@latest --agent claude",
run_async=True,
),
)
Expand Down
2 changes: 1 addition & 1 deletion docs/public/terminal.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion examples/daytona-quickstart.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ async def _start_server_and_wait(sandbox: Sandbox) -> str:
exec_resp = sandbox.process.execute_session_command(
_SESSION_ID,
SessionExecuteRequest(
command=f"npx -y runtimeuse --agent claude",
command=f"npx -y runtimeuse@latest --agent claude",
run_async=True,
),
)
Expand Down
2 changes: 1 addition & 1 deletion examples/e2b-quickstart.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def _get_env_or_fail(name: str) -> str:

def _create_template_with_alias(alias: str):
anthropic_api_key = _get_env_or_fail("ANTHROPIC_API_KEY")
start_cmd = "npx -y runtimeuse --agent claude"
start_cmd = "npx -y runtimeuse@latest --agent claude"

template = (
Template()
Expand Down
4 changes: 2 additions & 2 deletions packages/runtimeuse-client-python/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ OPENAI_API_KEY="your-openai-api-key"
ANTHROPIC_API_KEY="your-anthropic-api-key"

# Optional: override the runtime start command used by sandbox tests.
# Defaults to: npx -y runtimeuse
# Defaults to: npx -y runtimeuse@latest
# Useful with `packages/runtimeuse/scripts/dev-publish.sh`, which prints a curl/unzip command.
RUNTIMEUSE_RUN_COMMAND="npx -y runtimeuse"
RUNTIMEUSE_RUN_COMMAND="npx -y runtimeuse@latest"

# Optional: reuse the E2B template between test runs.
E2B_REUSE_TEMPLATE=false
Expand Down
33 changes: 16 additions & 17 deletions packages/runtimeuse-client-python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ WORKDIR = "/runtimeuse"
async def main():
# Start the runtime in a sandbox (provider-specific)
sandbox = Sandbox.create()
sandbox.run("npx -y runtimeuse")
sandbox.run("npx -y runtimeuse@latest")
ws_url = sandbox.get_url(8080)

client = RuntimeUseClient(ws_url=ws_url)
Expand Down Expand Up @@ -173,27 +173,26 @@ except CancelledException:

### Types

| Class | Description |
| ----------------------------------------- | ------------------------------------------------------ |
| `QueryOptions` | Configuration for `client.query()` (prompt options, callbacks, timeout) |
| `QueryResult` | Return type of `query()` (`.data`, `.metadata`) |
| `ResultMessageInterface` | Wire-format result message from the runtime |
| `TextResult` | Result variant when no output schema is specified (`.text`) |
| Class | Description |
| ----------------------------------------- | ------------------------------------------------------------------------ |
| `QueryOptions` | Configuration for `client.query()` (prompt options, callbacks, timeout) |
| `QueryResult` | Return type of `query()` (`.data`, `.metadata`) |
| `ResultMessageInterface` | Wire-format result message from the runtime |
| `TextResult` | Result variant when no output schema is specified (`.text`) |
| `StructuredOutputResult` | Result variant when an output schema is specified (`.structured_output`) |

| `AssistantMessageInterface` | Intermediate assistant text messages |
| `ArtifactUploadRequestMessageInterface` | Runtime requesting a presigned URL for artifact upload |
| `ArtifactUploadResponseMessageInterface` | Response with presigned URL sent back to runtime |
| `ErrorMessageInterface` | Error from the agent runtime |
| `CommandInterface` | Pre/post invocation shell command |
| `RuntimeEnvironmentDownloadableInterface` | File to download into the runtime before invocation |
| `AssistantMessageInterface` | Intermediate assistant text messages |
| `ArtifactUploadRequestMessageInterface` | Runtime requesting a presigned URL for artifact upload |
| `ArtifactUploadResponseMessageInterface` | Response with presigned URL sent back to runtime |
| `ErrorMessageInterface` | Error from the agent runtime |
| `CommandInterface` | Pre/post invocation shell command |
| `RuntimeEnvironmentDownloadableInterface` | File to download into the runtime before invocation |

### Exceptions

| Class | Description |
| -------------------- | ------------------------------------------- |
| Class | Description |
| -------------------- | --------------------------------------------------------------------------------- |
| `AgentRuntimeError` | Raised when the agent runtime returns an error (carries `.error` and `.metadata`) |
| `CancelledException` | Raised when `client.abort()` is called during a query |
| `CancelledException` | Raised when `client.abort()` is called during a query |

## Related Docs

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

_logger = logging.getLogger(__name__)

_DEFAULT_RUN_COMMAND = "npx -y runtimeuse@latest"
_DEFAULT_RUN_COMMAND = "npx -y runtimeuse@latest@latest"


def _get_env_or_fail(name: str) -> str:
Expand Down
12 changes: 8 additions & 4 deletions packages/runtimeuse/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Run the runtime inside any sandbox:

```bash
export OPENAI_API_KEY=your_openai_api_key
npx -y runtimeuse
npx -y runtimeuse@latest
```

This starts a WebSocket server on port 8080 using the OpenAI agent handler (default). You can choose between built-in handlers:
Expand All @@ -27,8 +27,8 @@ This starts a WebSocket server on port 8080 using the OpenAI agent handler (defa
The Claude handler requires the `claude` CLI to be installed in the sandbox environment.

```bash
npx -y runtimeuse # OpenAI (default)
npx -y runtimeuse --agent claude # Claude
npx -y runtimeuse@latest # OpenAI (default)
npx -y runtimeuse@latest --agent claude # Claude
```

Use it programmatically:
Expand Down Expand Up @@ -113,7 +113,11 @@ sender.sendErrorMessage("Something went wrong", { code: "TIMEOUT" });
```typescript
type AgentResult =
| { type: "text"; text: string; metadata?: Record<string, unknown> }
| { type: "structured_output"; structuredOutput: Record<string, unknown>; metadata?: Record<string, unknown> };
| {
type: "structured_output";
structuredOutput: Record<string, unknown>;
metadata?: Record<string, unknown>;
};
```

## Server Options
Expand Down
66 changes: 66 additions & 0 deletions packages/runtimeuse/src/cli.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { describe, it, expect } from "vitest";
import { parseArgs } from "./cli.js";

describe("parseArgs", () => {
const noopHelp = (): never => {
throw new Error("help called");
};

it("parses --key value (space-separated)", () => {
expect(parseArgs(["--agent", "claude"], noopHelp)).toEqual({
agent: "claude",
});
});

it("parses --key=value (equals-separated)", () => {
expect(parseArgs(["--agent=claude"], noopHelp)).toEqual({
agent: "claude",
});
});

it("handles a mix of space and equals styles", () => {
expect(
parseArgs(["--agent=claude", "--port", "3000"], noopHelp),
).toEqual({ agent: "claude", port: "3000" });
});

it("handles equals style followed by space style", () => {
expect(
parseArgs(["--port=8080", "--handler", "./my-handler.js"], noopHelp),
).toEqual({ port: "8080", handler: "./my-handler.js" });
});

it("handles value containing an equals sign", () => {
expect(parseArgs(["--handler=path/to/file=v2.js"], noopHelp)).toEqual({
handler: "path/to/file=v2.js",
});
});

it("returns empty object for no args", () => {
expect(parseArgs([], noopHelp)).toEqual({});
});

it("ignores bare flags without a following value", () => {
expect(parseArgs(["--verbose"], noopHelp)).toEqual({});
});

it("last value wins when a key is repeated", () => {
expect(
parseArgs(["--port", "3000", "--port=4000"], noopHelp),
).toEqual({ port: "4000" });
});

it("calls onHelp for -h", () => {
expect(() => parseArgs(["-h"], noopHelp)).toThrow("help called");
});

it("calls onHelp for --help", () => {
expect(() => parseArgs(["--help"], noopHelp)).toThrow("help called");
});

it("skips non-flag arguments", () => {
expect(parseArgs(["positional", "--port", "8080"], noopHelp)).toEqual({
port: "8080",
});
});
});
32 changes: 21 additions & 11 deletions packages/runtimeuse/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,23 @@ Options:
process.exit(0);
}

function parseArgs(args: string[]): Record<string, string> {
export function parseArgs(
args: string[],
onHelp: () => never = usage,
): Record<string, string> {
const result: Record<string, string> = {};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === "-h" || arg === "--help") {
usage();
onHelp();
}
if (arg.startsWith("--") && i + 1 < args.length) {
const key = arg.slice(2);
result[key] = args[++i];
if (arg.startsWith("--")) {
const eqIdx = arg.indexOf("=");
if (eqIdx !== -1) {
result[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1);
} else if (i + 1 < args.length) {
result[arg.slice(2)] = args[++i];
}
}
}
return result;
Expand Down Expand Up @@ -60,8 +67,9 @@ function getBuiltinHandler(agent: BuiltinAgent): AgentHandler {
async function loadHandler(handlerPath: string): Promise<AgentHandler> {
const resolved = path.resolve(handlerPath);
const mod = await import(resolved);
const handler: AgentHandler | undefined =
mod.default?.run ? mod.default : mod.handler;
const handler: AgentHandler | undefined = mod.default?.run
? mod.default
: mod.handler;

if (!handler?.run) {
console.error(
Expand Down Expand Up @@ -99,7 +107,9 @@ async function main() {
await server.startListening();
}

main().catch((err) => {
console.error(err);
process.exit(1);
});
if (!process.env.VITEST) {
main().catch((err) => {
console.error(err);
process.exit(1);
});
}