mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 07:04:48 +00:00
Configure lefthook formatter checks (#231)
* Add lefthook formatter checks * Fix SDK mode hydration * Stabilize SDK mode integration test
This commit is contained in:
parent
0471214d65
commit
d2346bafb3
282 changed files with 5840 additions and 8399 deletions
|
|
@ -21,10 +21,7 @@ describe("OpenCode-compatible Event Streaming", () => {
|
|||
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
}
|
||||
|
||||
async function initSessionViaHttp(
|
||||
sessionId: string,
|
||||
body: Record<string, unknown>
|
||||
): Promise<void> {
|
||||
async function initSessionViaHttp(sessionId: string, body: Record<string, unknown>): Promise<void> {
|
||||
const response = await fetch(`${handle.baseUrl}/opencode/session/${sessionId}/init`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
|
@ -113,10 +110,7 @@ describe("OpenCode-compatible Event Streaming", () => {
|
|||
for await (const event of (eventStream as any).stream) {
|
||||
events.push(event);
|
||||
// Look for message part updates or completion
|
||||
if (
|
||||
event.type === "message.part.updated" ||
|
||||
event.type === "session.idle"
|
||||
) {
|
||||
if (event.type === "message.part.updated" || event.type === "session.idle") {
|
||||
if (events.length >= 3) {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
|
|
@ -175,10 +169,7 @@ describe("OpenCode-compatible Event Streaming", () => {
|
|||
const statuses: string[] = [];
|
||||
|
||||
const collectIdle = new Promise<void>((resolve, reject) => {
|
||||
const timeout = setTimeout(
|
||||
() => reject(new Error("Timed out waiting for session.idle")),
|
||||
15_000
|
||||
);
|
||||
const timeout = setTimeout(() => reject(new Error("Timed out waiting for session.idle")), 15_000);
|
||||
(async () => {
|
||||
try {
|
||||
for await (const event of (eventStream as any).stream) {
|
||||
|
|
@ -223,10 +214,7 @@ describe("OpenCode-compatible Event Streaming", () => {
|
|||
let busySnapshot: string | undefined;
|
||||
|
||||
const waitForIdle = new Promise<void>((resolve, reject) => {
|
||||
const timeout = setTimeout(
|
||||
() => reject(new Error("Timed out waiting for busy status snapshot + session.idle")),
|
||||
15_000
|
||||
);
|
||||
const timeout = setTimeout(() => reject(new Error("Timed out waiting for busy status snapshot + session.idle")), 15_000);
|
||||
(async () => {
|
||||
try {
|
||||
for await (const event of (eventStream as any).stream) {
|
||||
|
|
@ -276,10 +264,7 @@ describe("OpenCode-compatible Event Streaming", () => {
|
|||
const idles: any[] = [];
|
||||
|
||||
const collectErrorAndIdle = new Promise<void>((resolve, reject) => {
|
||||
const timeout = setTimeout(
|
||||
() => reject(new Error("Timed out waiting for session.error + session.idle")),
|
||||
15_000
|
||||
);
|
||||
const timeout = setTimeout(() => reject(new Error("Timed out waiting for session.error + session.idle")), 15_000);
|
||||
(async () => {
|
||||
try {
|
||||
for await (const event of (eventStream as any).stream) {
|
||||
|
|
@ -428,9 +413,7 @@ describe("OpenCode-compatible Event Streaming", () => {
|
|||
expect(idleEvents.length).toBe(1);
|
||||
|
||||
// All tool parts should have been emitted before idle
|
||||
const toolParts = allEvents.filter(
|
||||
(e) => e.type === "message.part.updated" && e.properties?.part?.type === "tool"
|
||||
);
|
||||
const toolParts = allEvents.filter((e) => e.type === "message.part.updated" && e.properties?.part?.type === "tool");
|
||||
expect(toolParts.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
|
|
@ -459,12 +442,7 @@ describe("OpenCode-compatible Event Streaming", () => {
|
|||
if (!targetMessageId && partType === "tool" && typeof messageId === "string") {
|
||||
targetMessageId = messageId;
|
||||
}
|
||||
if (
|
||||
targetMessageId &&
|
||||
messageId === targetMessageId &&
|
||||
typeof partId === "string" &&
|
||||
!seenPartIds.includes(partId)
|
||||
) {
|
||||
if (targetMessageId && messageId === targetMessageId && typeof partId === "string" && !seenPartIds.includes(partId)) {
|
||||
seenPartIds.push(partId);
|
||||
}
|
||||
}
|
||||
|
|
@ -497,17 +475,12 @@ describe("OpenCode-compatible Event Streaming", () => {
|
|||
expect(targetMessageId).toBeTruthy();
|
||||
expect(seenPartIds.length).toBeGreaterThan(0);
|
||||
|
||||
const response = await fetch(
|
||||
`${handle.baseUrl}/opencode/session/${sessionId}/message/${targetMessageId}`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${handle.token}` },
|
||||
}
|
||||
);
|
||||
const response = await fetch(`${handle.baseUrl}/opencode/session/${sessionId}/message/${targetMessageId}`, {
|
||||
headers: { Authorization: `Bearer ${handle.token}` },
|
||||
});
|
||||
expect(response.ok).toBe(true);
|
||||
const message = (await response.json()) as any;
|
||||
const returnedPartIds = (message?.parts ?? [])
|
||||
.map((part: any) => part?.id)
|
||||
.filter((id: any) => typeof id === "string");
|
||||
const returnedPartIds = (message?.parts ?? []).map((part: any) => part?.id).filter((id: any) => typeof id === "string");
|
||||
|
||||
const expectedSet = new Set(seenPartIds);
|
||||
const returnedFiltered = returnedPartIds.filter((id: string) => expectedSet.has(id));
|
||||
|
|
|
|||
|
|
@ -33,10 +33,7 @@ function findBinary(): string | null {
|
|||
}
|
||||
|
||||
// Check cargo build outputs (relative to tests/opencode-compat/helpers)
|
||||
const cargoPaths = [
|
||||
resolve(__dirname, "../../../../../../target/debug/sandbox-agent"),
|
||||
resolve(__dirname, "../../../../../../target/release/sandbox-agent"),
|
||||
];
|
||||
const cargoPaths = [resolve(__dirname, "../../../../../../target/debug/sandbox-agent"), resolve(__dirname, "../../../../../../target/release/sandbox-agent")];
|
||||
|
||||
for (const p of cargoPaths) {
|
||||
if (existsSync(p)) {
|
||||
|
|
@ -65,12 +62,7 @@ async function getFreePort(host: string): Promise<number> {
|
|||
/**
|
||||
* Wait for the server to become healthy
|
||||
*/
|
||||
async function waitForHealth(
|
||||
baseUrl: string,
|
||||
token: string,
|
||||
timeoutMs: number,
|
||||
child: ChildProcess
|
||||
): Promise<void> {
|
||||
async function waitForHealth(baseUrl: string, token: string, timeoutMs: number, child: ChildProcess): Promise<void> {
|
||||
const start = Date.now();
|
||||
let lastError: string | undefined;
|
||||
|
||||
|
|
@ -130,9 +122,7 @@ export interface SpawnOptions {
|
|||
export async function spawnSandboxAgent(options: SpawnOptions = {}): Promise<SandboxAgentHandle> {
|
||||
const binaryPath = findBinary();
|
||||
if (!binaryPath) {
|
||||
throw new Error(
|
||||
"sandbox-agent binary not found. Run 'cargo build -p sandbox-agent' first or set SANDBOX_AGENT_BIN."
|
||||
);
|
||||
throw new Error("sandbox-agent binary not found. Run 'cargo build -p sandbox-agent' first or set SANDBOX_AGENT_BIN.");
|
||||
}
|
||||
|
||||
const host = options.host ?? "127.0.0.1";
|
||||
|
|
@ -222,7 +212,7 @@ export async function buildSandboxAgent(): Promise<void> {
|
|||
|
||||
console.log("Building sandbox-agent...");
|
||||
const projectRoot = resolve(__dirname, "../../../../../..");
|
||||
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const proc = spawn("cargo", ["build", "-p", "sandbox-agent"], {
|
||||
cwd: projectRoot,
|
||||
|
|
|
|||
|
|
@ -60,8 +60,6 @@ describe("OpenCode-compatible Model API", () => {
|
|||
expect(providerIds.has("claude")).toBe(true);
|
||||
expect(providerIds.has("codex")).toBe(true);
|
||||
expect(providerIds.has("pi")).toBe(true);
|
||||
expect(
|
||||
providerIds.has("opencode") || Array.from(providerIds).some((id) => id.startsWith("opencode:"))
|
||||
).toBe(true);
|
||||
expect(providerIds.has("opencode") || Array.from(providerIds).some((id) => id.startsWith("opencode:"))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -53,11 +53,7 @@ describe("OpenCode-compatible Permission API", () => {
|
|||
throw new Error("Timed out waiting for permission request");
|
||||
}
|
||||
|
||||
async function waitForCondition(
|
||||
check: () => boolean | Promise<boolean>,
|
||||
timeoutMs = 10_000,
|
||||
intervalMs = 100,
|
||||
) {
|
||||
async function waitForCondition(check: () => boolean | Promise<boolean>, timeoutMs = 10_000, intervalMs = 100) {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
if (await check()) {
|
||||
|
|
@ -68,11 +64,7 @@ describe("OpenCode-compatible Permission API", () => {
|
|||
throw new Error("Timed out waiting for condition");
|
||||
}
|
||||
|
||||
async function waitForValue<T>(
|
||||
getValue: () => T | undefined | Promise<T | undefined>,
|
||||
timeoutMs = 10_000,
|
||||
intervalMs = 100,
|
||||
): Promise<T> {
|
||||
async function waitForValue<T>(getValue: () => T | undefined | Promise<T | undefined>, timeoutMs = 10_000, intervalMs = 100): Promise<T> {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
const value = await getValue();
|
||||
|
|
@ -175,13 +167,7 @@ describe("OpenCode-compatible Permission API", () => {
|
|||
});
|
||||
expect(firstReply.error).toBeUndefined();
|
||||
|
||||
await waitForCondition(() =>
|
||||
repliedEvents.some(
|
||||
(event) =>
|
||||
event?.properties?.requestID === firstRequestId &&
|
||||
event?.properties?.reply === "always",
|
||||
),
|
||||
);
|
||||
await waitForCondition(() => repliedEvents.some((event) => event?.properties?.requestID === firstRequestId && event?.properties?.reply === "always"));
|
||||
|
||||
await client.session.prompt({
|
||||
sessionID: sessionId,
|
||||
|
|
@ -190,11 +176,7 @@ describe("OpenCode-compatible Permission API", () => {
|
|||
});
|
||||
|
||||
const autoReplyEvent = await waitForValue(() =>
|
||||
repliedEvents.find(
|
||||
(event) =>
|
||||
event?.properties?.requestID !== firstRequestId &&
|
||||
event?.properties?.reply === "always",
|
||||
),
|
||||
repliedEvents.find((event) => event?.properties?.requestID !== firstRequestId && event?.properties?.reply === "always"),
|
||||
);
|
||||
const autoRequestId = autoReplyEvent?.properties?.requestID;
|
||||
expect(autoRequestId).toBeDefined();
|
||||
|
|
|
|||
|
|
@ -15,11 +15,7 @@
|
|||
|
||||
import { describe, it, expect, beforeAll, beforeEach, afterEach } from "vitest";
|
||||
import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk";
|
||||
import {
|
||||
spawnSandboxAgent,
|
||||
buildSandboxAgent,
|
||||
type SandboxAgentHandle,
|
||||
} from "./helpers/spawn";
|
||||
import { spawnSandboxAgent, buildSandboxAgent, type SandboxAgentHandle } from "./helpers/spawn";
|
||||
|
||||
const MODEL = process.env.TEST_AGENT_MODEL;
|
||||
|
||||
|
|
@ -62,22 +58,11 @@ describe.skipIf(!MODEL)("Real agent round-trip", () => {
|
|||
* Uses a manual iterator to avoid closing the stream (for-await-of calls
|
||||
* iterator.return() on early exit, which would close the SSE connection).
|
||||
*/
|
||||
function collectUntilIdle(
|
||||
iter: AsyncIterator<any>,
|
||||
timeoutMs = 30_000,
|
||||
): Promise<{ events: any[]; text: string }> {
|
||||
function collectUntilIdle(iter: AsyncIterator<any>, timeoutMs = 30_000): Promise<{ events: any[]; text: string }> {
|
||||
const events: any[] = [];
|
||||
let text = "";
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(
|
||||
() =>
|
||||
reject(
|
||||
new Error(
|
||||
`Timed out after ${timeoutMs}ms. Events: ${JSON.stringify(events.map((e) => e.type))}`,
|
||||
),
|
||||
),
|
||||
timeoutMs,
|
||||
);
|
||||
const timeout = setTimeout(() => reject(new Error(`Timed out after ${timeoutMs}ms. Events: ${JSON.stringify(events.map((e) => e.type))}`)), timeoutMs);
|
||||
(async () => {
|
||||
try {
|
||||
while (true) {
|
||||
|
|
@ -88,10 +73,7 @@ describe.skipIf(!MODEL)("Real agent round-trip", () => {
|
|||
return;
|
||||
}
|
||||
events.push(event);
|
||||
if (
|
||||
event.type === "message.part.updated" &&
|
||||
event.properties?.part?.type === "text"
|
||||
) {
|
||||
if (event.type === "message.part.updated" && event.properties?.part?.type === "text") {
|
||||
// Prefer the delta (chunk) if present; otherwise use the full
|
||||
// accumulated part.text (for non-streaming single-shot events).
|
||||
text += event.properties.delta ?? event.properties.part.text ?? "";
|
||||
|
|
@ -103,11 +85,7 @@ describe.skipIf(!MODEL)("Real agent round-trip", () => {
|
|||
}
|
||||
if (event.type === "session.error") {
|
||||
clearTimeout(timeout);
|
||||
reject(
|
||||
new Error(
|
||||
`session.error: ${JSON.stringify(event.properties?.error)}`,
|
||||
),
|
||||
);
|
||||
reject(new Error(`session.error: ${JSON.stringify(event.properties?.error)}`));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,10 +55,7 @@ describe("OpenCode-compatible Session API", () => {
|
|||
return (sessions ?? []).find((item: any) => item.id === sessionId);
|
||||
}
|
||||
|
||||
async function initSessionViaHttp(
|
||||
sessionId: string,
|
||||
body: Record<string, unknown> = {}
|
||||
): Promise<{ response: Response; data: any }> {
|
||||
async function initSessionViaHttp(sessionId: string, body: Record<string, unknown> = {}): Promise<{ response: Response; data: any }> {
|
||||
const response = await fetch(`${handle.baseUrl}/opencode/session/${sessionId}/init`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
|
@ -300,7 +297,7 @@ describe("OpenCode-compatible Session API", () => {
|
|||
});
|
||||
expect(changed.response.status).toBe(400);
|
||||
expect(changed.data?.errors?.[0]?.message).toBe(
|
||||
"OpenCode compatibility currently does not support changing the model after creating a session. Export with /export and load in to a new session."
|
||||
"OpenCode compatibility currently does not support changing the model after creating a session. Export with /export and load in to a new session.",
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -337,9 +334,7 @@ describe("OpenCode-compatible Session API", () => {
|
|||
|
||||
it("should keep session.get available during first prompt after /new-style creation", async () => {
|
||||
const providers = await getProvidersViaHttp();
|
||||
const providerId = providers.connected.find(
|
||||
(provider) => provider !== "mock" && typeof providers.default?.[provider] === "string"
|
||||
);
|
||||
const providerId = providers.connected.find((provider) => provider !== "mock" && typeof providers.default?.[provider] === "string");
|
||||
if (!providerId) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -478,7 +473,7 @@ describe("OpenCode-compatible Session API", () => {
|
|||
|
||||
expect(response.status).toBe(400);
|
||||
expect(data?.errors?.[0]?.message).toBe(
|
||||
"OpenCode compatibility currently does not support changing the model after creating a session. Export with /export and load in to a new session."
|
||||
"OpenCode compatibility currently does not support changing the model after creating a session. Export with /export and load in to a new session.",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
@ -511,7 +506,7 @@ describe("OpenCode-compatible Session API", () => {
|
|||
|
||||
expect(response.status).toBe(400);
|
||||
expect(data?.errors?.[0]?.message).toBe(
|
||||
"OpenCode compatibility currently does not support changing the model after creating a session. Export with /export and load in to a new session."
|
||||
"OpenCode compatibility currently does not support changing the model after creating a session. Export with /export and load in to a new session.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue