mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 07:04:48 +00:00
chore: update landing and inspector content
This commit is contained in:
parent
c7fbb33fed
commit
30c4ad6b39
8 changed files with 128 additions and 129 deletions
|
|
@ -9,6 +9,7 @@ Docs: https://rivet.dev/docs/
|
||||||
- **Universal session schema**: Universal schema to store agent transcripts
|
- **Universal session schema**: Universal schema to store agent transcripts
|
||||||
- **Supports your sandbox provider**: Daytona, E2B, Vercel Sandboxes, and more
|
- **Supports your sandbox provider**: Daytona, E2B, Vercel Sandboxes, and more
|
||||||
- **Lightweight, portable Rust binary**: Install anywhere with 1 curl command
|
- **Lightweight, portable Rust binary**: Install anywhere with 1 curl command
|
||||||
|
- **Automatic agent installation**: Agents are installed on-demand when first used
|
||||||
- **OpenAPI spec**: https://rivet.dev/docs/api
|
- **OpenAPI spec**: https://rivet.dev/docs/api
|
||||||
|
|
||||||
Roadmap:
|
Roadmap:
|
||||||
|
|
@ -16,8 +17,6 @@ Roadmap:
|
||||||
- [ ] Python SDK
|
- [ ] Python SDK
|
||||||
- [ ] Automatic MCP & skill & hook configuration
|
- [ ] Automatic MCP & skill & hook configuration
|
||||||
- [ ] Todo lists
|
- [ ] Todo lists
|
||||||
- [ ] Session diff
|
|
||||||
- [ ] Subagents
|
|
||||||
|
|
||||||
## Agent Compatibility
|
## Agent Compatibility
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { Daytona } from "@daytonaio/sdk";
|
import { Daytona } from "@daytonaio/sdk";
|
||||||
import { pathToFileURL } from "node:url";
|
import { pathToFileURL, fileURLToPath } from "node:url";
|
||||||
|
import { dirname, resolve } from "node:path";
|
||||||
import {
|
import {
|
||||||
ensureUrl,
|
ensureUrl,
|
||||||
logInspectorUrl,
|
logInspectorUrl,
|
||||||
|
|
@ -7,8 +8,8 @@ import {
|
||||||
waitForHealth,
|
waitForHealth,
|
||||||
} from "../shared/sandbox-agent-client.ts";
|
} from "../shared/sandbox-agent-client.ts";
|
||||||
|
|
||||||
const INSTALL_SCRIPT = "curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh";
|
|
||||||
const DEFAULT_PORT = 3000;
|
const DEFAULT_PORT = 3000;
|
||||||
|
const BINARY_PATH = resolve(dirname(fileURLToPath(import.meta.url)), "../../target/release/sandbox-agent");
|
||||||
|
|
||||||
export async function setupDaytonaSandboxAgent(): Promise<{
|
export async function setupDaytonaSandboxAgent(): Promise<{
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
|
|
@ -21,57 +22,47 @@ export async function setupDaytonaSandboxAgent(): Promise<{
|
||||||
const language = process.env.DAYTONA_LANGUAGE || "typescript";
|
const language = process.env.DAYTONA_LANGUAGE || "typescript";
|
||||||
|
|
||||||
const daytona = new Daytona();
|
const daytona = new Daytona();
|
||||||
const sandbox = await daytona.create({
|
console.log("Creating sandbox...");
|
||||||
language,
|
const sandbox = await daytona.create({ language });
|
||||||
});
|
|
||||||
|
|
||||||
await sandbox.process.executeCommand(`bash -lc "${INSTALL_SCRIPT}"`);
|
console.log("Uploading sandbox-agent...");
|
||||||
|
await sandbox.fs.uploadFile(BINARY_PATH, "/home/daytona/sandbox-agent");
|
||||||
|
await sandbox.fs.setFilePermissions("/home/daytona/sandbox-agent", { mode: "755" });
|
||||||
|
|
||||||
const tokenFlag = token ? "--token $SANDBOX_TOKEN" : "--no-token";
|
console.log("Starting server...");
|
||||||
const serverCommand = `nohup sandbox-agent server ${tokenFlag} --host 0.0.0.0 --port ${port} >/tmp/sandbox-agent.log 2>&1 &`;
|
const tokenFlag = token ? `--token ${token}` : "--no-token";
|
||||||
await sandbox.process.executeCommand(`bash -lc "${serverCommand}"`);
|
await sandbox.process.executeCommand(
|
||||||
|
`nohup /home/daytona/sandbox-agent server ${tokenFlag} --host 0.0.0.0 --port ${port} >/tmp/sandbox-agent.log 2>&1 &`
|
||||||
|
);
|
||||||
|
|
||||||
const preview = await sandbox.getPreviewLink(port);
|
const preview = await sandbox.getPreviewLink(port);
|
||||||
const extraHeaders: Record<string, string> = {};
|
const extraHeaders: Record<string, string> = {
|
||||||
|
"x-daytona-skip-preview-warning": "true",
|
||||||
|
};
|
||||||
if (preview.token) {
|
if (preview.token) {
|
||||||
extraHeaders["x-daytona-preview-token"] = preview.token;
|
extraHeaders["x-daytona-preview-token"] = preview.token;
|
||||||
}
|
}
|
||||||
extraHeaders["x-daytona-skip-preview-warning"] = "true";
|
|
||||||
|
|
||||||
const baseUrl = ensureUrl(preview.url);
|
const baseUrl = ensureUrl(preview.url);
|
||||||
|
console.log("Waiting for health...");
|
||||||
await waitForHealth({ baseUrl, token, extraHeaders });
|
await waitForHealth({ baseUrl, token, extraHeaders });
|
||||||
logInspectorUrl({ baseUrl, token });
|
logInspectorUrl({ baseUrl, token });
|
||||||
|
|
||||||
const cleanup = async () => {
|
|
||||||
try {
|
|
||||||
await sandbox.delete(60);
|
|
||||||
} catch {
|
|
||||||
// ignore cleanup errors
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
baseUrl,
|
baseUrl,
|
||||||
token,
|
token,
|
||||||
extraHeaders,
|
extraHeaders,
|
||||||
cleanup,
|
cleanup: async () => {
|
||||||
|
try { await sandbox.delete(60); } catch {}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
const { baseUrl, token, extraHeaders, cleanup } = await setupDaytonaSandboxAgent();
|
const { baseUrl, token, extraHeaders, cleanup } = await setupDaytonaSandboxAgent();
|
||||||
|
|
||||||
const exitHandler = async () => {
|
process.on("SIGINT", () => void cleanup().then(() => process.exit(0)));
|
||||||
await cleanup();
|
process.on("SIGTERM", () => void cleanup().then(() => process.exit(0)));
|
||||||
process.exit(0);
|
|
||||||
};
|
|
||||||
|
|
||||||
process.on("SIGINT", () => {
|
|
||||||
void exitHandler();
|
|
||||||
});
|
|
||||||
process.on("SIGTERM", () => {
|
|
||||||
void exitHandler();
|
|
||||||
});
|
|
||||||
|
|
||||||
await runPrompt({ baseUrl, token, extraHeaders });
|
await runPrompt({ baseUrl, token, extraHeaders });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -719,7 +719,6 @@
|
||||||
|
|
||||||
.empty-state .button {
|
.empty-state .button {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
min-width: 180px;
|
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1032,8 +1031,8 @@
|
||||||
/* Setup Row */
|
/* Setup Row */
|
||||||
.setup-row {
|
.setup-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-end;
|
||||||
gap: 8px;
|
gap: 12px;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
background: var(--surface-2);
|
background: var(--surface-2);
|
||||||
border-top: 1px solid var(--border);
|
border-top: 1px solid var(--border);
|
||||||
|
|
@ -1041,6 +1040,19 @@
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.setup-field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setup-label {
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
.setup-select {
|
.setup-select {
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
border: 1px solid var(--border-2);
|
border: 1px solid var(--border-2);
|
||||||
|
|
|
||||||
|
|
@ -503,16 +503,38 @@ export default function App() {
|
||||||
offsetRef.current = 0;
|
offsetRef.current = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopy = async (entry: RequestLog) => {
|
const handleCopy = (entry: RequestLog) => {
|
||||||
try {
|
const text = entry.curl;
|
||||||
await navigator.clipboard.writeText(entry.curl);
|
const onSuccess = () => {
|
||||||
setCopiedLogId(entry.id);
|
setCopiedLogId(entry.id);
|
||||||
window.setTimeout(() => setCopiedLogId(null), 1500);
|
window.setTimeout(() => setCopiedLogId(null), 1500);
|
||||||
} catch {
|
};
|
||||||
setCopiedLogId(null);
|
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
|
navigator.clipboard.writeText(text).then(onSuccess).catch(() => {
|
||||||
|
fallbackCopy(text, onSuccess);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
fallbackCopy(text, onSuccess);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fallbackCopy = (text: string, onSuccess?: () => void) => {
|
||||||
|
const textarea = document.createElement("textarea");
|
||||||
|
textarea.value = text;
|
||||||
|
textarea.style.position = "fixed";
|
||||||
|
textarea.style.opacity = "0";
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
textarea.select();
|
||||||
|
try {
|
||||||
|
document.execCommand("copy");
|
||||||
|
onSuccess?.();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to copy:", err);
|
||||||
|
}
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
};
|
||||||
|
|
||||||
const selectQuestionOption = (requestId: string, optionLabel: string) => {
|
const selectQuestionOption = (requestId: string, optionLabel: string) => {
|
||||||
setQuestionSelections((prev) => ({
|
setQuestionSelections((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
|
|
@ -887,9 +909,7 @@ export default function App() {
|
||||||
onDebugTabChange={setDebugTab}
|
onDebugTabChange={setDebugTab}
|
||||||
events={events}
|
events={events}
|
||||||
offset={offset}
|
offset={offset}
|
||||||
onFetchEvents={fetchEvents}
|
|
||||||
onResetEvents={resetEvents}
|
onResetEvents={resetEvents}
|
||||||
eventsLoading={eventsLoading}
|
|
||||||
eventsError={eventError}
|
eventsError={eventError}
|
||||||
requestLog={requestLog}
|
requestLog={requestLog}
|
||||||
copiedLogId={copiedLogId}
|
copiedLogId={copiedLogId}
|
||||||
|
|
|
||||||
|
|
@ -29,57 +29,69 @@ const ChatSetup = ({
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="setup-row">
|
<div className="setup-row">
|
||||||
<select
|
<div className="setup-field">
|
||||||
className="setup-select"
|
<span className="setup-label">Mode</span>
|
||||||
value={agentMode}
|
<select
|
||||||
onChange={(e) => onAgentModeChange(e.target.value)}
|
className="setup-select"
|
||||||
title="Mode"
|
value={agentMode}
|
||||||
disabled={!hasSession || modesLoading || Boolean(modesError)}
|
onChange={(e) => onAgentModeChange(e.target.value)}
|
||||||
>
|
title="Mode"
|
||||||
{modesLoading ? (
|
disabled={!hasSession || modesLoading || Boolean(modesError)}
|
||||||
<option value="">Loading modes...</option>
|
>
|
||||||
) : modesError ? (
|
{modesLoading ? (
|
||||||
<option value="">{modesError}</option>
|
<option value="">Loading modes...</option>
|
||||||
) : activeModes.length > 0 ? (
|
) : modesError ? (
|
||||||
activeModes.map((mode) => (
|
<option value="">{modesError}</option>
|
||||||
<option key={mode.id} value={mode.id}>
|
) : activeModes.length > 0 ? (
|
||||||
{mode.name || mode.id}
|
activeModes.map((mode) => (
|
||||||
</option>
|
<option key={mode.id} value={mode.id}>
|
||||||
))
|
{mode.name || mode.id}
|
||||||
) : (
|
</option>
|
||||||
<option value="">Mode</option>
|
))
|
||||||
)}
|
) : (
|
||||||
</select>
|
<option value="">Mode</option>
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<select
|
<div className="setup-field">
|
||||||
className="setup-select"
|
<span className="setup-label">Permission</span>
|
||||||
value={permissionMode}
|
<select
|
||||||
onChange={(e) => onPermissionModeChange(e.target.value)}
|
className="setup-select"
|
||||||
title="Permission Mode"
|
value={permissionMode}
|
||||||
disabled={!hasSession}
|
onChange={(e) => onPermissionModeChange(e.target.value)}
|
||||||
>
|
title="Permission Mode"
|
||||||
<option value="default">Default</option>
|
disabled={!hasSession}
|
||||||
<option value="plan">Plan</option>
|
>
|
||||||
<option value="bypass">Bypass</option>
|
<option value="default">Default</option>
|
||||||
</select>
|
<option value="plan">Plan</option>
|
||||||
|
<option value="bypass">Bypass</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<input
|
<div className="setup-field">
|
||||||
className="setup-input"
|
<span className="setup-label">Model</span>
|
||||||
value={model}
|
<input
|
||||||
onChange={(e) => onModelChange(e.target.value)}
|
className="setup-input"
|
||||||
placeholder="Model"
|
value={model}
|
||||||
title="Model"
|
onChange={(e) => onModelChange(e.target.value)}
|
||||||
disabled={!hasSession}
|
placeholder="Model"
|
||||||
/>
|
title="Model"
|
||||||
|
disabled={!hasSession}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<input
|
<div className="setup-field">
|
||||||
className="setup-input"
|
<span className="setup-label">Variant</span>
|
||||||
value={variant}
|
<input
|
||||||
onChange={(e) => onVariantChange(e.target.value)}
|
className="setup-input"
|
||||||
placeholder="Variant"
|
value={variant}
|
||||||
title="Variant"
|
onChange={(e) => onVariantChange(e.target.value)}
|
||||||
disabled={!hasSession}
|
placeholder="Variant"
|
||||||
/>
|
title="Variant"
|
||||||
|
disabled={!hasSession}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,7 @@ const DebugPanel = ({
|
||||||
onDebugTabChange,
|
onDebugTabChange,
|
||||||
events,
|
events,
|
||||||
offset,
|
offset,
|
||||||
onFetchEvents,
|
|
||||||
onResetEvents,
|
onResetEvents,
|
||||||
eventsLoading,
|
|
||||||
eventsError,
|
eventsError,
|
||||||
requestLog,
|
requestLog,
|
||||||
copiedLogId,
|
copiedLogId,
|
||||||
|
|
@ -32,9 +30,7 @@ const DebugPanel = ({
|
||||||
onDebugTabChange: (tab: DebugTab) => void;
|
onDebugTabChange: (tab: DebugTab) => void;
|
||||||
events: UniversalEvent[];
|
events: UniversalEvent[];
|
||||||
offset: number;
|
offset: number;
|
||||||
onFetchEvents: () => void;
|
|
||||||
onResetEvents: () => void;
|
onResetEvents: () => void;
|
||||||
eventsLoading: boolean;
|
|
||||||
eventsError: string | null;
|
eventsError: string | null;
|
||||||
requestLog: RequestLog[];
|
requestLog: RequestLog[];
|
||||||
copiedLogId: number | null;
|
copiedLogId: number | null;
|
||||||
|
|
@ -80,9 +76,7 @@ const DebugPanel = ({
|
||||||
<EventsTab
|
<EventsTab
|
||||||
events={events}
|
events={events}
|
||||||
offset={offset}
|
offset={offset}
|
||||||
onFetch={onFetchEvents}
|
|
||||||
onClear={onResetEvents}
|
onClear={onResetEvents}
|
||||||
loading={eventsLoading}
|
|
||||||
error={eventsError}
|
error={eventsError}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,12 @@ import { getEventCategory, getEventClass, getEventIcon, getEventKey, getEventTyp
|
||||||
const EventsTab = ({
|
const EventsTab = ({
|
||||||
events,
|
events,
|
||||||
offset,
|
offset,
|
||||||
onFetch,
|
|
||||||
onClear,
|
onClear,
|
||||||
loading,
|
|
||||||
error
|
error
|
||||||
}: {
|
}: {
|
||||||
events: UniversalEvent[];
|
events: UniversalEvent[];
|
||||||
offset: number;
|
offset: number;
|
||||||
onFetch: () => void;
|
|
||||||
onClear: () => void;
|
onClear: () => void;
|
||||||
loading: boolean;
|
|
||||||
error: string | null;
|
error: string | null;
|
||||||
}) => {
|
}) => {
|
||||||
const [collapsedEvents, setCollapsedEvents] = useState<Record<string, boolean>>({});
|
const [collapsedEvents, setCollapsedEvents] = useState<Record<string, boolean>>({});
|
||||||
|
|
@ -64,9 +60,6 @@ const EventsTab = ({
|
||||||
<div className="inline-row" style={{ marginBottom: 12, justifyContent: "space-between" }}>
|
<div className="inline-row" style={{ marginBottom: 12, justifyContent: "space-between" }}>
|
||||||
<span className="card-meta">Offset: {offset}</span>
|
<span className="card-meta">Offset: {offset}</span>
|
||||||
<div className="inline-row">
|
<div className="inline-row">
|
||||||
<button className="button ghost small" onClick={onFetch} disabled={loading}>
|
|
||||||
{loading ? "Loading..." : "Fetch"}
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="button ghost small"
|
className="button ghost small"
|
||||||
|
|
@ -74,7 +67,7 @@ const EventsTab = ({
|
||||||
disabled={events.length === 0}
|
disabled={events.length === 0}
|
||||||
title="Copy all events as JSON"
|
title="Copy all events as JSON"
|
||||||
>
|
>
|
||||||
{copied ? "Copied" : "Copy"}
|
{copied ? "Copied" : "Copy JSON"}
|
||||||
</button>
|
</button>
|
||||||
<button className="button ghost small" onClick={onClear}>
|
<button className="button ghost small" onClick={onClear}>
|
||||||
Clear
|
Clear
|
||||||
|
|
@ -86,7 +79,7 @@ const EventsTab = ({
|
||||||
|
|
||||||
{events.length === 0 ? (
|
{events.length === 0 ? (
|
||||||
<div className="card-meta">
|
<div className="card-meta">
|
||||||
{loading ? "Loading events..." : "No events yet. Start streaming to receive events."}
|
No events yet. Start streaming to receive events.
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="event-list">
|
<div className="event-list">
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1 @@
|
||||||
# Vercel AI SDK Schemas
|
hello
|
||||||
|
|
||||||
This package extracts JSON Schema for `UIMessage` from the Vercel AI SDK v6 TypeScript types.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
- Install dependencies in this folder.
|
|
||||||
- Run the extractor:
|
|
||||||
|
|
||||||
```
|
|
||||||
pnpm install
|
|
||||||
pnpm extract
|
|
||||||
```
|
|
||||||
|
|
||||||
Optional flags:
|
|
||||||
- `--version=6.x.y` to pin an exact version
|
|
||||||
- `--major=6` to select the latest version for a major (default: 6)
|
|
||||||
|
|
||||||
Output:
|
|
||||||
- `artifacts/json-schema/ui-message.json`
|
|
||||||
|
|
||||||
The registry response is cached under `.cache/` for 24 hours. The extractor downloads the AI SDK package
|
|
||||||
and the minimal dependency tree needed for TypeScript type resolution into a temporary folder.
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue