Skip to content
Open
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
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ jobs:
- { suffix: "-copilot", dockerfile: "Dockerfile.copilot", artifact: "copilot" }
- { suffix: "-opencode", dockerfile: "Dockerfile.opencode", artifact: "opencode" }
- { suffix: "-cursor", dockerfile: "Dockerfile.cursor", artifact: "cursor" }
- { suffix: "-grok", dockerfile: "Dockerfile.grok", artifact: "grok" }
platform:
- { os: linux/amd64, runner: ubuntu-latest }
- { os: linux/arm64, runner: ubuntu-24.04-arm }
Expand Down Expand Up @@ -135,6 +136,7 @@ jobs:
- { suffix: "-copilot", artifact: "copilot" }
- { suffix: "-opencode", artifact: "opencode" }
- { suffix: "-cursor", artifact: "cursor" }
- { suffix: "-grok", artifact: "grok" }
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down Expand Up @@ -185,6 +187,7 @@ jobs:
- { suffix: "-copilot" }
- { suffix: "-opencode" }
- { suffix: "-cursor" }
- { suffix: "-grok" }
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/docker-smoke-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
- { dockerfile: Dockerfile.copilot, suffix: "-copilot", agent: "copilot", agent_args: "--acp" }
- { dockerfile: Dockerfile.opencode, suffix: "-opencode", agent: "opencode", agent_args: "acp" }
- { dockerfile: Dockerfile.cursor, suffix: "-cursor", agent: "cursor-agent", agent_args: "acp" }
- { dockerfile: Dockerfile.grok, suffix: "-grok", agent: "grok", agent_args: "agent stdio" }
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
Expand Down
40 changes: 40 additions & 0 deletions Dockerfile.grok
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# --- Build stage ---
FROM rust:1-bookworm AS builder
WORKDIR /build
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo 'fn main() {}' > src/main.rs && cargo build --release && rm -rf src
COPY src/ src/
RUN touch src/main.rs && cargo build --release

# --- Runtime stage ---
FROM node:22-bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl procps tini && rm -rf /var/lib/apt/lists/*

# Install Grok Build CLI as node user (installs to ~/.grok/bin/grok)
# Pin version via ARG; update when upgrading.
ARG GROK_VERSION=0.1.210
USER node
RUN curl -fsSL https://x.ai/cli/install.sh | bash -s ${GROK_VERSION}
USER root

# Ensure grok is on PATH for all users
ENV PATH="/home/node/.grok/bin:$PATH"

# Install gh CLI
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
-o /usr/share/keyrings/githubcli-archive-keyring.gpg && \
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
> /etc/apt/sources.list.d/github-cli.list && \
apt-get update && apt-get install -y --no-install-recommends gh && \
rm -rf /var/lib/apt/lists/*

ENV HOME=/home/node
WORKDIR /home/node

COPY --from=builder --chown=node:node /build/target/release/openab /usr/local/bin/openab

USER node
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD pgrep -x openab || exit 1
ENTRYPOINT ["tini", "--"]
CMD ["openab", "run", "-c", "/etc/openab/config.toml"]
6 changes: 6 additions & 0 deletions config.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ working_dir = "/home/agent"
# working_dir = "/home/agent"
# env = {} # Auth via: kubectl exec -it <pod> -- cursor-agent login

# [agent]
# command = "grok"
# args = ["agent", "stdio"]
# working_dir = "/home/node"
# env = { GROK_CODE_XAI_API_KEY = "${GROK_CODE_XAI_API_KEY}" }

[pool]
max_sessions = 10
session_ttl_hours = 24
Expand Down
35 changes: 35 additions & 0 deletions src/acp/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,41 @@ impl AcpConnection {
load_session = self.supports_load_session,
"initialized"
);

// If the agent requires authentication (e.g. Grok Build), handle it
if let Some(auth_methods) = result.and_then(|r| r.get("authMethods")) {
self.authenticate(auth_methods).await?;
}
Ok(())
}

async fn authenticate(&mut self, auth_methods: &Value) -> Result<()> {
let methods: Vec<&str> = auth_methods
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|m| m.get("id").and_then(|id| id.as_str()))
.collect()
})
.unwrap_or_default();

// Prefer API key auth, fall back to cached token
let method_id = if methods.contains(&"xai.api_key") {
"xai.api_key"
} else if methods.contains(&"cached_token") {
"cached_token"
} else {
return Err(anyhow!("no supported auth method (available: {methods:?})"));
};

info!(method = method_id, "authenticating");
let resp = self.send_request(
"authenticate",
Some(json!({"methodId": method_id, "meta": {"headless": true}})),
)
.await?;
debug!(result = ?resp.result, "authenticate response");
info!("authenticated");
Ok(())
}

Expand Down
Loading