mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 07:04:48 +00:00
Use vanilla Rivet routing in Foundry backend
This commit is contained in:
parent
4bccd5fc8d
commit
940e49fcfa
5 changed files with 26 additions and 83 deletions
|
|
@ -45,6 +45,13 @@ Use `pnpm` workspaces and Turborepo.
|
||||||
- Stop the preview stack: `just foundry-preview-down`
|
- Stop the preview stack: `just foundry-preview-down`
|
||||||
- Tail preview logs: `just foundry-preview-logs`
|
- Tail preview logs: `just foundry-preview-logs`
|
||||||
|
|
||||||
|
## Railway Logs
|
||||||
|
|
||||||
|
- Production Foundry Railway logs can be read from a linked workspace with `railway logs --deployment --lines 200` or `railway logs <deployment-id> --deployment --lines 200`.
|
||||||
|
- If Railway logs fail because the workspace is not linked to the correct project/service/environment, run:
|
||||||
|
`railway link --project 33e3e2df-32c5-41c5-a4af-dca8654acb1d --environment cf387142-61fd-4668-8cf7-b3559e0983cb --service 91c7e450-d6d2-481a-b2a4-0a916f4160fc`
|
||||||
|
- That links this directory to the `sandbox-agent` project, `production` environment, and `foundry-api` service.
|
||||||
|
|
||||||
## Frontend + Client Boundary
|
## Frontend + Client Boundary
|
||||||
|
|
||||||
- Keep a browser-friendly GUI implementation aligned with the TUI interaction model wherever possible.
|
- Keep a browser-friendly GUI implementation aligned with the TUI interaction model wherever possible.
|
||||||
|
|
@ -96,39 +103,11 @@ For all Rivet/RivetKit implementation:
|
||||||
pnpm build -F rivetkit
|
pnpm build -F rivetkit
|
||||||
```
|
```
|
||||||
|
|
||||||
## Inspector HTTP API (Workflow Debugging)
|
## Rivet Routing
|
||||||
|
|
||||||
- The Inspector HTTP routes come from RivetKit `feat: inspector http api (#4144)` and are served from the RivetKit manager endpoint (not `/api/rivet`).
|
- Mount RivetKit directly on `/api/rivet` via `registry.handler(c.req.raw)`.
|
||||||
- Resolve manager endpoint from backend metadata:
|
- Do not add an extra proxy or manager-specific route layer in the backend.
|
||||||
```bash
|
- Let RivetKit own metadata/public endpoint behavior for `/api/rivet`.
|
||||||
curl -sS http://127.0.0.1:7741/api/rivet/metadata | jq -r '.clientEndpoint'
|
|
||||||
```
|
|
||||||
- List actors:
|
|
||||||
- `GET {manager}/actors?name=task`
|
|
||||||
- Inspector endpoints (path prefix: `/gateway/{actorId}/inspector`):
|
|
||||||
- `GET /state`
|
|
||||||
- `PATCH /state`
|
|
||||||
- `GET /connections`
|
|
||||||
- `GET /rpcs`
|
|
||||||
- `POST /action/{name}`
|
|
||||||
- `GET /queue?limit=50`
|
|
||||||
- `GET /traces?startMs=0&endMs=<ms>&limit=1000`
|
|
||||||
- `GET /workflow-history`
|
|
||||||
- `GET /summary`
|
|
||||||
- Auth:
|
|
||||||
- Production: send `Authorization: Bearer $RIVET_INSPECTOR_TOKEN`.
|
|
||||||
- Development: auth can be skipped when no inspector token is configured.
|
|
||||||
- Task workflow quick inspect:
|
|
||||||
```bash
|
|
||||||
MGR="$(curl -sS http://127.0.0.1:7741/api/rivet/metadata | jq -r '.clientEndpoint')"
|
|
||||||
HID="7df7656e-bbd2-4b8c-bf0f-30d4df2f619a"
|
|
||||||
AID="$(curl -sS "$MGR/actors?name=task" \
|
|
||||||
| jq -r --arg hid "$HID" '.actors[] | select(.key | endswith("/task/\($hid)")) | .actor_id' \
|
|
||||||
| head -n1)"
|
|
||||||
curl -sS "$MGR/gateway/$AID/inspector/workflow-history" | jq .
|
|
||||||
curl -sS "$MGR/gateway/$AID/inspector/summary" | jq .
|
|
||||||
```
|
|
||||||
- If inspector routes return `404 Not Found (RivetKit)`, the running backend is on a RivetKit build that predates `#4144`; rebuild linked RivetKit and restart backend.
|
|
||||||
|
|
||||||
## Workspace + Actor Rules
|
## Workspace + Actor Rules
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ services:
|
||||||
environment:
|
environment:
|
||||||
HF_BACKEND_HOST: "0.0.0.0"
|
HF_BACKEND_HOST: "0.0.0.0"
|
||||||
HF_BACKEND_PORT: "7741"
|
HF_BACKEND_PORT: "7741"
|
||||||
HF_RIVET_MANAGER_PORT: "8750"
|
|
||||||
RIVETKIT_STORAGE_PATH: "/root/.local/share/foundry/rivetkit"
|
RIVETKIT_STORAGE_PATH: "/root/.local/share/foundry/rivetkit"
|
||||||
# Pass through credentials needed for agent execution + PR creation in dev/e2e.
|
# Pass through credentials needed for agent execution + PR creation in dev/e2e.
|
||||||
# Do not hardcode secrets; set these in your environment when starting compose.
|
# Do not hardcode secrets; set these in your environment when starting compose.
|
||||||
|
|
@ -43,8 +42,6 @@ services:
|
||||||
HF_DAYTONA_API_KEY: "${HF_DAYTONA_API_KEY:-}"
|
HF_DAYTONA_API_KEY: "${HF_DAYTONA_API_KEY:-}"
|
||||||
ports:
|
ports:
|
||||||
- "7741:7741"
|
- "7741:7741"
|
||||||
# RivetKit manager (used by browser clients after /api/rivet metadata redirect in dev)
|
|
||||||
- "8750:8750"
|
|
||||||
volumes:
|
volumes:
|
||||||
- "..:/app"
|
- "..:/app"
|
||||||
# The linked RivetKit checkout resolves from Foundry packages to /task/rivet-checkout in-container.
|
# The linked RivetKit checkout resolves from Foundry packages to /task/rivet-checkout in-container.
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ services:
|
||||||
environment:
|
environment:
|
||||||
HF_BACKEND_HOST: "0.0.0.0"
|
HF_BACKEND_HOST: "0.0.0.0"
|
||||||
HF_BACKEND_PORT: "7841"
|
HF_BACKEND_PORT: "7841"
|
||||||
HF_RIVET_MANAGER_PORT: "8850"
|
|
||||||
RIVETKIT_STORAGE_PATH: "/root/.local/share/foundry/rivetkit"
|
RIVETKIT_STORAGE_PATH: "/root/.local/share/foundry/rivetkit"
|
||||||
ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY:-}"
|
ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY:-}"
|
||||||
CLAUDE_API_KEY: "${CLAUDE_API_KEY:-${ANTHROPIC_API_KEY:-}}"
|
CLAUDE_API_KEY: "${CLAUDE_API_KEY:-${ANTHROPIC_API_KEY:-}}"
|
||||||
|
|
@ -23,7 +22,6 @@ services:
|
||||||
HF_DAYTONA_API_KEY: "${HF_DAYTONA_API_KEY:-}"
|
HF_DAYTONA_API_KEY: "${HF_DAYTONA_API_KEY:-}"
|
||||||
ports:
|
ports:
|
||||||
- "7841:7841"
|
- "7841:7841"
|
||||||
- "8850:8850"
|
|
||||||
volumes:
|
volumes:
|
||||||
- "${HOME}/.codex:/root/.codex"
|
- "${HOME}/.codex:/root/.codex"
|
||||||
- "foundry_preview_git_repos:/root/.local/share/foundry/repos"
|
- "foundry_preview_git_repos:/root/.local/share/foundry/repos"
|
||||||
|
|
|
||||||
|
|
@ -8,24 +8,6 @@ import { project } from "./project/index.js";
|
||||||
import { sandboxInstance } from "./sandbox-instance/index.js";
|
import { sandboxInstance } from "./sandbox-instance/index.js";
|
||||||
import { workspace } from "./workspace/index.js";
|
import { workspace } from "./workspace/index.js";
|
||||||
|
|
||||||
export function resolveManagerPort(): number {
|
|
||||||
const raw = process.env.HF_RIVET_MANAGER_PORT ?? process.env.RIVETKIT_MANAGER_PORT;
|
|
||||||
if (!raw) {
|
|
||||||
return 7750;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsed = Number(raw);
|
|
||||||
if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
|
|
||||||
throw new Error(`Invalid HF_RIVET_MANAGER_PORT/RIVETKIT_MANAGER_PORT: ${raw}`);
|
|
||||||
}
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveManagerHost(): string {
|
|
||||||
const raw = process.env.HF_RIVET_MANAGER_HOST ?? process.env.RIVETKIT_MANAGER_HOST;
|
|
||||||
return raw && raw.trim().length > 0 ? raw.trim() : "0.0.0.0";
|
|
||||||
}
|
|
||||||
|
|
||||||
export const registry = setup({
|
export const registry = setup({
|
||||||
use: {
|
use: {
|
||||||
workspace,
|
workspace,
|
||||||
|
|
@ -37,8 +19,6 @@ export const registry = setup({
|
||||||
projectBranchSync,
|
projectBranchSync,
|
||||||
taskStatusSync,
|
taskStatusSync,
|
||||||
},
|
},
|
||||||
managerPort: resolveManagerPort(),
|
|
||||||
managerHost: resolveManagerHost(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export * from "./context.js";
|
export * from "./context.js";
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import { cors } from "hono/cors";
|
import { cors } from "hono/cors";
|
||||||
import { initActorRuntimeContext } from "./actors/context.js";
|
import { initActorRuntimeContext } from "./actors/context.js";
|
||||||
import { registry, resolveManagerPort } from "./actors/index.js";
|
import { registry } from "./actors/index.js";
|
||||||
import { workspaceKey } from "./actors/keys.js";
|
import { workspaceKey } from "./actors/keys.js";
|
||||||
import { loadConfig } from "./config/backend.js";
|
import { loadConfig } from "./config/backend.js";
|
||||||
import { createBackends, createNotificationService } from "./notifications/index.js";
|
import { createBackends, createNotificationService } from "./notifications/index.js";
|
||||||
|
|
@ -69,17 +69,11 @@ export async function startBackend(options: BackendStartOptions = {}): Promise<v
|
||||||
const notifications = createNotificationService(backends);
|
const notifications = createNotificationService(backends);
|
||||||
initActorRuntimeContext(config, providers, notifications, driver, createDefaultAppShellServices());
|
initActorRuntimeContext(config, providers, notifications, driver, createDefaultAppShellServices());
|
||||||
|
|
||||||
registry.startRunner();
|
|
||||||
const managerOrigin = `http://127.0.0.1:${resolveManagerPort()}`;
|
|
||||||
const actorClient = createClient({
|
const actorClient = createClient({
|
||||||
endpoint: managerOrigin,
|
endpoint: `http://127.0.0.1:${config.backend.port}/api/rivet`,
|
||||||
disableMetadataLookup: true,
|
|
||||||
}) as any;
|
}) as any;
|
||||||
|
|
||||||
// Wrap in a Hono app mounted at /api/rivet to serve on the backend port.
|
// Wrap RivetKit and app routes in a single Hono app mounted at /api/rivet.
|
||||||
// Uses Bun.serve — cannot use @hono/node-server because it conflicts with
|
|
||||||
// RivetKit's internal Bun.serve manager server (Bun bug: mixing Node HTTP
|
|
||||||
// server and Bun.serve in the same process breaks Bun.serve's fetch handler).
|
|
||||||
const app = new Hono();
|
const app = new Hono();
|
||||||
const allowHeaders = [
|
const allowHeaders = [
|
||||||
"Content-Type",
|
"Content-Type",
|
||||||
|
|
@ -118,21 +112,6 @@ export async function startBackend(options: BackendStartOptions = {}): Promise<v
|
||||||
exposeHeaders,
|
exposeHeaders,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const forward = async (c: any) => {
|
|
||||||
try {
|
|
||||||
// Proxy /api/rivet traffic to the long-lived RivetKit manager rather than
|
|
||||||
// invoking RivetKit's serverless entrypoints in-process.
|
|
||||||
const requestUrl = new URL(c.req.url);
|
|
||||||
const managerPath = requestUrl.pathname.replace(/^\/api\/rivet(?=\/|$)/, "") || "/";
|
|
||||||
const targetUrl = new URL(`${managerPath}${requestUrl.search}`, managerOrigin);
|
|
||||||
return await fetch(new Request(targetUrl, c.req.raw));
|
|
||||||
} catch (err) {
|
|
||||||
if (err instanceof URIError) {
|
|
||||||
return c.text("Bad Request: Malformed URI", 400);
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const appWorkspace = async () =>
|
const appWorkspace = async () =>
|
||||||
await withRetries(
|
await withRetries(
|
||||||
|
|
@ -334,8 +313,18 @@ export async function startBackend(options: BackendStartOptions = {}): Promise<v
|
||||||
app.post("/api/rivet/app/webhooks/stripe", handleStripeWebhook);
|
app.post("/api/rivet/app/webhooks/stripe", handleStripeWebhook);
|
||||||
app.post("/api/rivet/app/stripe/webhook", handleStripeWebhook);
|
app.post("/api/rivet/app/stripe/webhook", handleStripeWebhook);
|
||||||
|
|
||||||
app.all("/api/rivet", forward);
|
app.post("/api/rivet/app/webhooks/github", async (c) => {
|
||||||
app.all("/api/rivet/*", forward);
|
const payload = await c.req.text();
|
||||||
|
await (await appWorkspace()).handleAppGithubWebhook({
|
||||||
|
payload,
|
||||||
|
signatureHeader: c.req.header("x-hub-signature-256") ?? null,
|
||||||
|
eventHeader: c.req.header("x-github-event") ?? null,
|
||||||
|
});
|
||||||
|
return c.json({ ok: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.all("/api/rivet", (c) => registry.handler(c.req.raw));
|
||||||
|
app.all("/api/rivet/*", (c) => registry.handler(c.req.raw));
|
||||||
|
|
||||||
const server = Bun.serve({
|
const server = Bun.serve({
|
||||||
fetch: app.fetch,
|
fetch: app.fetch,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue