@rivet-dev/agentos-core 0.0.0-agentos-dylib-plugin.473700e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +204 -0
- package/dist/agent-os.d.ts +560 -0
- package/dist/agent-os.js +3624 -0
- package/dist/agent-session-types.d.ts +67 -0
- package/dist/agent-session-types.js +1 -0
- package/dist/agents.d.ts +63 -0
- package/dist/agents.js +42 -0
- package/dist/base-filesystem.d.ts +34 -0
- package/dist/base-filesystem.js +173 -0
- package/dist/cron/cron-manager.d.ts +22 -0
- package/dist/cron/cron-manager.js +160 -0
- package/dist/cron/index.d.ts +5 -0
- package/dist/cron/index.js +3 -0
- package/dist/cron/parse-schedule.d.ts +25 -0
- package/dist/cron/parse-schedule.js +66 -0
- package/dist/cron/schedule-driver.d.ts +19 -0
- package/dist/cron/schedule-driver.js +1 -0
- package/dist/cron/timer-driver.d.ts +17 -0
- package/dist/cron/timer-driver.js +61 -0
- package/dist/cron/types.d.ts +55 -0
- package/dist/cron/types.js +1 -0
- package/dist/filesystem-snapshot.d.ts +14 -0
- package/dist/filesystem-snapshot.js +105 -0
- package/dist/host-dir-mount.d.ts +38 -0
- package/dist/host-dir-mount.js +34 -0
- package/dist/host-tools-zod.d.ts +7 -0
- package/dist/host-tools-zod.js +243 -0
- package/dist/host-tools.d.ts +45 -0
- package/dist/host-tools.js +26 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/js-bridge.d.ts +3 -0
- package/dist/js-bridge.js +2 -0
- package/dist/json-rpc.d.ts +50 -0
- package/dist/json-rpc.js +22 -0
- package/dist/layers.d.ts +51 -0
- package/dist/layers.js +185 -0
- package/dist/overlay-filesystem.d.ts +19 -0
- package/dist/overlay-filesystem.js +645 -0
- package/dist/packages.d.ts +127 -0
- package/dist/packages.js +335 -0
- package/dist/runtime-compat.d.ts +471 -0
- package/dist/runtime-compat.js +2009 -0
- package/dist/runtime.d.ts +336 -0
- package/dist/runtime.js +1 -0
- package/dist/sidecar/agent-os-protocol.d.ts +219 -0
- package/dist/sidecar/agent-os-protocol.js +594 -0
- package/dist/sidecar/binary.d.ts +9 -0
- package/dist/sidecar/binary.js +30 -0
- package/dist/sidecar/cargo.d.ts +2 -0
- package/dist/sidecar/cargo.js +142 -0
- package/dist/sidecar/native-process-client.d.ts +2 -0
- package/dist/sidecar/native-process-client.js +1 -0
- package/dist/sidecar/permissions.d.ts +3 -0
- package/dist/sidecar/permissions.js +54 -0
- package/dist/sidecar/process.d.ts +6 -0
- package/dist/sidecar/process.js +8 -0
- package/dist/sidecar/rpc-client.d.ts +244 -0
- package/dist/sidecar/rpc-client.js +2028 -0
- package/dist/test/docker.d.ts +133 -0
- package/dist/test/docker.js +336 -0
- package/dist/test/file-system.d.ts +39 -0
- package/dist/test/file-system.js +562 -0
- package/dist/test/mock-s3.d.ts +42 -0
- package/dist/test/mock-s3.js +292 -0
- package/dist/test/runtime.d.ts +11 -0
- package/dist/test/runtime.js +9 -0
- package/dist/test/sandbox-agent.d.ts +9 -0
- package/dist/test/sandbox-agent.js +395 -0
- package/dist/test/terminal-harness.d.ts +27 -0
- package/dist/test/terminal-harness.js +122 -0
- package/dist/types.d.ts +10 -0
- package/dist/types.js +1 -0
- package/fixtures/AGENTOS_SYSTEM_PROMPT.md +21 -0
- package/package.json +111 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker test helpers for spinning up containers in integration tests.
|
|
3
|
+
*
|
|
4
|
+
* Uses the Docker CLI via child_process.execFile so no Docker SDK dependency
|
|
5
|
+
* is required. Each helper returns a handle with a `stop()` method that
|
|
6
|
+
* removes the container.
|
|
7
|
+
*/
|
|
8
|
+
export interface PortMapping {
|
|
9
|
+
/** Host port. Use 0 for a random available port. */
|
|
10
|
+
host: number;
|
|
11
|
+
/** Container port. */
|
|
12
|
+
container: number;
|
|
13
|
+
/** Protocol (default "tcp"). */
|
|
14
|
+
protocol?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface HealthCheck {
|
|
17
|
+
/** Shell command to run inside the container. */
|
|
18
|
+
command: string;
|
|
19
|
+
/** How often to check (default "2s"). */
|
|
20
|
+
interval?: string;
|
|
21
|
+
/** How long to wait before first check (default "1s"). */
|
|
22
|
+
startPeriod?: string;
|
|
23
|
+
/** Per-check timeout (default "5s"). */
|
|
24
|
+
timeout?: string;
|
|
25
|
+
/** Number of consecutive failures before unhealthy (default 5). */
|
|
26
|
+
retries?: number;
|
|
27
|
+
}
|
|
28
|
+
export interface StartContainerOptions {
|
|
29
|
+
/** Docker image (e.g. "minio/minio:latest"). */
|
|
30
|
+
image: string;
|
|
31
|
+
/** Optional container name. A random name is generated if omitted. */
|
|
32
|
+
name?: string;
|
|
33
|
+
/** Port mappings. */
|
|
34
|
+
ports?: PortMapping[];
|
|
35
|
+
/** Environment variables. */
|
|
36
|
+
env?: Record<string, string>;
|
|
37
|
+
/** Docker health check configuration. */
|
|
38
|
+
healthCheck?: HealthCheck;
|
|
39
|
+
/** Command and arguments to pass to the container entrypoint. */
|
|
40
|
+
command?: string[];
|
|
41
|
+
/** Maximum time in ms to wait for the container to become healthy (default 30000). */
|
|
42
|
+
healthTimeout?: number;
|
|
43
|
+
}
|
|
44
|
+
export interface ContainerHandle {
|
|
45
|
+
/** Docker container ID. */
|
|
46
|
+
id: string;
|
|
47
|
+
/** Container name. */
|
|
48
|
+
name: string;
|
|
49
|
+
/** Resolved host ports keyed by container port (e.g. "9000/tcp" → 49152). */
|
|
50
|
+
ports: Record<string, number>;
|
|
51
|
+
/** Stop and remove the container. */
|
|
52
|
+
stop: () => Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Start a Docker container and optionally wait for it to become healthy.
|
|
56
|
+
*/
|
|
57
|
+
export declare function startContainer(options: StartContainerOptions): Promise<ContainerHandle>;
|
|
58
|
+
export interface MinioContainerOptions {
|
|
59
|
+
/** MinIO image (default "minio/minio:latest"). */
|
|
60
|
+
image?: string;
|
|
61
|
+
/** Root user (default "minioadmin"). */
|
|
62
|
+
rootUser?: string;
|
|
63
|
+
/** Root password (default "minioadmin"). */
|
|
64
|
+
rootPassword?: string;
|
|
65
|
+
/** Name of the test bucket to create (default "test-bucket"). */
|
|
66
|
+
bucket?: string;
|
|
67
|
+
/** Health timeout in ms (default 30000). */
|
|
68
|
+
healthTimeout?: number;
|
|
69
|
+
}
|
|
70
|
+
export interface MinioContainerHandle extends ContainerHandle {
|
|
71
|
+
/** S3-compatible endpoint URL (e.g. "http://127.0.0.1:49152"). */
|
|
72
|
+
endpoint: string;
|
|
73
|
+
/** Access key (root user). */
|
|
74
|
+
accessKeyId: string;
|
|
75
|
+
/** Secret key (root password). */
|
|
76
|
+
secretAccessKey: string;
|
|
77
|
+
/** Name of the created bucket. */
|
|
78
|
+
bucket: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Start a MinIO container, wait for it to be healthy, and create a test
|
|
82
|
+
* bucket using `mc mb`.
|
|
83
|
+
*/
|
|
84
|
+
export declare function startMinioContainer(options?: MinioContainerOptions): Promise<MinioContainerHandle>;
|
|
85
|
+
export interface SandboxAgentContainerOptions {
|
|
86
|
+
/** Sandbox Agent Docker image (default "sandbox-agent-test:dev"). */
|
|
87
|
+
image?: string;
|
|
88
|
+
/** Container port the server listens on (default 2468). */
|
|
89
|
+
port?: number;
|
|
90
|
+
/** Health timeout in ms (default 60000). */
|
|
91
|
+
healthTimeout?: number;
|
|
92
|
+
}
|
|
93
|
+
export interface SandboxAgentContainerHandle extends ContainerHandle {
|
|
94
|
+
/** Base URL for the Sandbox Agent API (e.g. "http://127.0.0.1:49152"). */
|
|
95
|
+
baseUrl: string;
|
|
96
|
+
/** Connected SandboxAgent client. */
|
|
97
|
+
client: import("sandbox-agent").SandboxAgent;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Start a Sandbox Agent container and return a connected SandboxAgent client.
|
|
101
|
+
*/
|
|
102
|
+
export declare function startSandboxAgentContainer(options?: SandboxAgentContainerOptions): Promise<SandboxAgentContainerHandle>;
|
|
103
|
+
export interface PostgresContainerOptions {
|
|
104
|
+
/** Postgres Docker image (default "postgres:16-alpine"). */
|
|
105
|
+
image?: string;
|
|
106
|
+
/** Database user (default "test"). */
|
|
107
|
+
user?: string;
|
|
108
|
+
/** Database password (default "test"). */
|
|
109
|
+
password?: string;
|
|
110
|
+
/** Database name (default "testdb"). */
|
|
111
|
+
database?: string;
|
|
112
|
+
/** Health timeout in ms (default 30000). */
|
|
113
|
+
healthTimeout?: number;
|
|
114
|
+
}
|
|
115
|
+
export interface PostgresContainerHandle extends ContainerHandle {
|
|
116
|
+
/** Full connection string (e.g. "postgresql://test:test@127.0.0.1:5432/testdb"). */
|
|
117
|
+
connectionString: string;
|
|
118
|
+
/** Host (always 127.0.0.1). */
|
|
119
|
+
host: string;
|
|
120
|
+
/** Resolved host port. */
|
|
121
|
+
port: number;
|
|
122
|
+
/** Database user. */
|
|
123
|
+
user: string;
|
|
124
|
+
/** Database password. */
|
|
125
|
+
password: string;
|
|
126
|
+
/** Database name. */
|
|
127
|
+
database: string;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Start a Postgres container, wait for it to be healthy, and return
|
|
131
|
+
* connection details.
|
|
132
|
+
*/
|
|
133
|
+
export declare function startPostgresContainer(options?: PostgresContainerOptions): Promise<PostgresContainerHandle>;
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker test helpers for spinning up containers in integration tests.
|
|
3
|
+
*
|
|
4
|
+
* Uses the Docker CLI via child_process.execFile so no Docker SDK dependency
|
|
5
|
+
* is required. Each helper returns a handle with a `stop()` method that
|
|
6
|
+
* removes the container.
|
|
7
|
+
*/
|
|
8
|
+
import { execFile as execFileCb } from "node:child_process";
|
|
9
|
+
import { randomUUID } from "node:crypto";
|
|
10
|
+
import { promisify } from "node:util";
|
|
11
|
+
const execFile = promisify(execFileCb);
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Generic container starter
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
/**
|
|
16
|
+
* Start a Docker container and optionally wait for it to become healthy.
|
|
17
|
+
*/
|
|
18
|
+
export async function startContainer(options) {
|
|
19
|
+
const containerName = options.name ?? `test-${randomUUID().slice(0, 8)}`;
|
|
20
|
+
const args = ["run", "--detach", "--name", containerName, "--rm"];
|
|
21
|
+
// Port mappings
|
|
22
|
+
if (options.ports) {
|
|
23
|
+
for (const p of options.ports) {
|
|
24
|
+
const proto = p.protocol ?? "tcp";
|
|
25
|
+
args.push("--publish", `${p.host}:${p.container}/${proto}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Environment variables
|
|
29
|
+
if (options.env) {
|
|
30
|
+
for (const [k, v] of Object.entries(options.env)) {
|
|
31
|
+
args.push("--env", `${k}=${v}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Health check
|
|
35
|
+
if (options.healthCheck) {
|
|
36
|
+
const hc = options.healthCheck;
|
|
37
|
+
args.push("--health-cmd", hc.command);
|
|
38
|
+
if (hc.interval)
|
|
39
|
+
args.push("--health-interval", hc.interval);
|
|
40
|
+
if (hc.startPeriod)
|
|
41
|
+
args.push("--health-start-period", hc.startPeriod);
|
|
42
|
+
if (hc.timeout)
|
|
43
|
+
args.push("--health-timeout", hc.timeout);
|
|
44
|
+
if (hc.retries !== undefined)
|
|
45
|
+
args.push("--health-retries", String(hc.retries));
|
|
46
|
+
}
|
|
47
|
+
// Image
|
|
48
|
+
args.push(options.image);
|
|
49
|
+
// Command
|
|
50
|
+
if (options.command) {
|
|
51
|
+
args.push(...options.command);
|
|
52
|
+
}
|
|
53
|
+
// Start the container
|
|
54
|
+
let stdout;
|
|
55
|
+
try {
|
|
56
|
+
const result = await execFile("docker", args);
|
|
57
|
+
stdout = result.stdout;
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
if (err instanceof Error &&
|
|
61
|
+
"code" in err &&
|
|
62
|
+
err.code === "ENOENT") {
|
|
63
|
+
throw new Error("Docker CLI not found. Ensure Docker is installed and the 'docker' command is on your PATH.");
|
|
64
|
+
}
|
|
65
|
+
// Re-check for daemon not running (docker exits with non-zero and stderr mentions "daemon")
|
|
66
|
+
if (err instanceof Error &&
|
|
67
|
+
err.message?.includes("Cannot connect to the Docker daemon")) {
|
|
68
|
+
throw new Error("Cannot connect to the Docker daemon. Is Docker running?");
|
|
69
|
+
}
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
const containerId = stdout.trim();
|
|
73
|
+
const stop = async () => {
|
|
74
|
+
try {
|
|
75
|
+
await execFile("docker", ["rm", "--force", containerId]);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Container may already be stopped.
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
// Wait for healthy if a health check was configured
|
|
82
|
+
if (options.healthCheck) {
|
|
83
|
+
const timeout = options.healthTimeout ?? 30_000;
|
|
84
|
+
await waitForHealthy(containerId, timeout);
|
|
85
|
+
}
|
|
86
|
+
// Resolve host ports
|
|
87
|
+
const ports = {};
|
|
88
|
+
if (options.ports) {
|
|
89
|
+
for (const p of options.ports) {
|
|
90
|
+
const proto = p.protocol ?? "tcp";
|
|
91
|
+
const key = `${p.container}/${proto}`;
|
|
92
|
+
const { stdout: portOut } = await execFile("docker", [
|
|
93
|
+
"port",
|
|
94
|
+
containerId,
|
|
95
|
+
key,
|
|
96
|
+
]);
|
|
97
|
+
// Output format: "0.0.0.0:49152" or "[::]:49152"
|
|
98
|
+
const match = portOut.trim().match(/:(\d+)$/m);
|
|
99
|
+
if (match) {
|
|
100
|
+
ports[key] = parseInt(match[1], 10);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
await stop();
|
|
104
|
+
throw new Error(`Failed to resolve host port for container port ${key} on container ${containerId}. ` +
|
|
105
|
+
`docker port output: ${JSON.stringify(portOut.trim())}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { id: containerId, name: containerName, ports, stop };
|
|
110
|
+
}
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Health check poller
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
async function waitForHealthy(containerId, timeoutMs) {
|
|
115
|
+
const deadline = Date.now() + timeoutMs;
|
|
116
|
+
while (Date.now() < deadline) {
|
|
117
|
+
try {
|
|
118
|
+
// Check if the container has exited or crashed.
|
|
119
|
+
const { stdout: stateOut } = await execFile("docker", [
|
|
120
|
+
"inspect",
|
|
121
|
+
"--format",
|
|
122
|
+
"{{.State.Status}}",
|
|
123
|
+
containerId,
|
|
124
|
+
]);
|
|
125
|
+
const containerState = stateOut.trim();
|
|
126
|
+
if (containerState === "exited" || containerState === "dead") {
|
|
127
|
+
// Grab exit code and logs for a useful error message.
|
|
128
|
+
let exitInfo = "";
|
|
129
|
+
try {
|
|
130
|
+
const { stdout: exitOut } = await execFile("docker", [
|
|
131
|
+
"inspect",
|
|
132
|
+
"--format",
|
|
133
|
+
"{{.State.ExitCode}}",
|
|
134
|
+
containerId,
|
|
135
|
+
]);
|
|
136
|
+
exitInfo = ` (exit code: ${exitOut.trim()})`;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Best effort.
|
|
140
|
+
}
|
|
141
|
+
let logs = "";
|
|
142
|
+
try {
|
|
143
|
+
const { stderr, stdout: logOut } = await execFile("docker", [
|
|
144
|
+
"logs",
|
|
145
|
+
"--tail",
|
|
146
|
+
"20",
|
|
147
|
+
containerId,
|
|
148
|
+
]);
|
|
149
|
+
logs = (logOut + stderr).trim();
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// Best effort.
|
|
153
|
+
}
|
|
154
|
+
throw new Error(`Container ${containerId} ${containerState}${exitInfo} before becoming healthy.` +
|
|
155
|
+
(logs ? `\nLast logs:\n${logs}` : ""));
|
|
156
|
+
}
|
|
157
|
+
// Check health status.
|
|
158
|
+
const { stdout } = await execFile("docker", [
|
|
159
|
+
"inspect",
|
|
160
|
+
"--format",
|
|
161
|
+
"{{.State.Health.Status}}",
|
|
162
|
+
containerId,
|
|
163
|
+
]);
|
|
164
|
+
const status = stdout.trim();
|
|
165
|
+
if (status === "healthy")
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
// Re-throw container crash errors.
|
|
170
|
+
if (err instanceof Error &&
|
|
171
|
+
(err.message.includes("exited") || err.message.includes("dead"))) {
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
// inspect may fail briefly while container is starting
|
|
175
|
+
}
|
|
176
|
+
await sleep(500);
|
|
177
|
+
}
|
|
178
|
+
throw new Error(`Container ${containerId} did not become healthy within ${timeoutMs}ms`);
|
|
179
|
+
}
|
|
180
|
+
function sleep(ms) {
|
|
181
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
182
|
+
}
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
// Docker exec helper
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
async function dockerExec(containerId, command) {
|
|
187
|
+
const { stdout } = await execFile("docker", [
|
|
188
|
+
"exec",
|
|
189
|
+
containerId,
|
|
190
|
+
...command,
|
|
191
|
+
]);
|
|
192
|
+
return stdout;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Start a MinIO container, wait for it to be healthy, and create a test
|
|
196
|
+
* bucket using `mc mb`.
|
|
197
|
+
*/
|
|
198
|
+
export async function startMinioContainer(options) {
|
|
199
|
+
const rootUser = options?.rootUser ?? "minioadmin";
|
|
200
|
+
const rootPassword = options?.rootPassword ?? "minioadmin";
|
|
201
|
+
const bucket = options?.bucket ?? "test-bucket";
|
|
202
|
+
const image = options?.image ?? "minio/minio:latest";
|
|
203
|
+
const container = await startContainer({
|
|
204
|
+
image,
|
|
205
|
+
ports: [{ host: 0, container: 9000 }],
|
|
206
|
+
env: {
|
|
207
|
+
MINIO_ROOT_USER: rootUser,
|
|
208
|
+
MINIO_ROOT_PASSWORD: rootPassword,
|
|
209
|
+
},
|
|
210
|
+
healthCheck: {
|
|
211
|
+
command: "mc ready local",
|
|
212
|
+
interval: "2s",
|
|
213
|
+
startPeriod: "2s",
|
|
214
|
+
timeout: "5s",
|
|
215
|
+
retries: 10,
|
|
216
|
+
},
|
|
217
|
+
command: ["server", "/data"],
|
|
218
|
+
healthTimeout: options?.healthTimeout ?? 30_000,
|
|
219
|
+
});
|
|
220
|
+
const hostPort = container.ports["9000/tcp"];
|
|
221
|
+
const endpoint = `http://127.0.0.1:${hostPort}`;
|
|
222
|
+
// Create the test bucket via docker exec
|
|
223
|
+
await dockerExec(container.id, [
|
|
224
|
+
"mc",
|
|
225
|
+
"alias",
|
|
226
|
+
"set",
|
|
227
|
+
"local",
|
|
228
|
+
"http://localhost:9000",
|
|
229
|
+
rootUser,
|
|
230
|
+
rootPassword,
|
|
231
|
+
]);
|
|
232
|
+
await dockerExec(container.id, ["mc", "mb", `local/${bucket}`]);
|
|
233
|
+
return {
|
|
234
|
+
...container,
|
|
235
|
+
endpoint,
|
|
236
|
+
accessKeyId: rootUser,
|
|
237
|
+
secretAccessKey: rootPassword,
|
|
238
|
+
bucket,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Start a Sandbox Agent container and return a connected SandboxAgent client.
|
|
243
|
+
*/
|
|
244
|
+
export async function startSandboxAgentContainer(options) {
|
|
245
|
+
const image = options?.image ?? "sandbox-agent-test:dev";
|
|
246
|
+
const port = options?.port ?? 2468;
|
|
247
|
+
const container = await startContainer({
|
|
248
|
+
image,
|
|
249
|
+
ports: [{ host: 0, container: port }],
|
|
250
|
+
command: [
|
|
251
|
+
"server",
|
|
252
|
+
"--host",
|
|
253
|
+
"0.0.0.0",
|
|
254
|
+
"--port",
|
|
255
|
+
String(port),
|
|
256
|
+
"--no-token",
|
|
257
|
+
],
|
|
258
|
+
});
|
|
259
|
+
const hostPort = container.ports[`${port}/tcp`];
|
|
260
|
+
const baseUrl = `http://127.0.0.1:${hostPort}`;
|
|
261
|
+
// Poll health from the host since the container may not have curl.
|
|
262
|
+
const timeout = options?.healthTimeout ?? 60_000;
|
|
263
|
+
const deadline = Date.now() + timeout;
|
|
264
|
+
while (Date.now() < deadline) {
|
|
265
|
+
try {
|
|
266
|
+
const resp = await fetch(`${baseUrl}/`);
|
|
267
|
+
if (resp.ok)
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
// Server not ready yet.
|
|
272
|
+
}
|
|
273
|
+
await sleep(500);
|
|
274
|
+
}
|
|
275
|
+
if (Date.now() >= deadline) {
|
|
276
|
+
await container.stop();
|
|
277
|
+
throw new Error(`Sandbox Agent at ${baseUrl} did not become healthy within ${timeout}ms`);
|
|
278
|
+
}
|
|
279
|
+
// Dynamically import sandbox-agent to avoid requiring it at module load time.
|
|
280
|
+
const { SandboxAgent } = await import("sandbox-agent");
|
|
281
|
+
let client;
|
|
282
|
+
try {
|
|
283
|
+
client = await SandboxAgent.connect({ baseUrl });
|
|
284
|
+
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
await container.stop();
|
|
287
|
+
throw new Error(`Failed to connect to Sandbox Agent at ${baseUrl}: ${err instanceof Error ? err.message : err}`);
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
...container,
|
|
291
|
+
baseUrl,
|
|
292
|
+
client,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Start a Postgres container, wait for it to be healthy, and return
|
|
297
|
+
* connection details.
|
|
298
|
+
*/
|
|
299
|
+
export async function startPostgresContainer(options) {
|
|
300
|
+
const user = options?.user ?? "test";
|
|
301
|
+
const password = options?.password ?? "test";
|
|
302
|
+
const database = options?.database ?? "testdb";
|
|
303
|
+
const image = options?.image ?? "postgres:16-alpine";
|
|
304
|
+
const container = await startContainer({
|
|
305
|
+
image,
|
|
306
|
+
ports: [{ host: 0, container: 5432 }],
|
|
307
|
+
env: {
|
|
308
|
+
POSTGRES_USER: user,
|
|
309
|
+
POSTGRES_PASSWORD: password,
|
|
310
|
+
POSTGRES_DB: database,
|
|
311
|
+
// pg_isready reads these env vars natively, avoiding shell interpolation.
|
|
312
|
+
PGUSER: user,
|
|
313
|
+
PGDATABASE: database,
|
|
314
|
+
},
|
|
315
|
+
healthCheck: {
|
|
316
|
+
command: "pg_isready",
|
|
317
|
+
interval: "2s",
|
|
318
|
+
startPeriod: "2s",
|
|
319
|
+
timeout: "5s",
|
|
320
|
+
retries: 15,
|
|
321
|
+
},
|
|
322
|
+
healthTimeout: options?.healthTimeout ?? 30_000,
|
|
323
|
+
});
|
|
324
|
+
const hostPort = container.ports["5432/tcp"];
|
|
325
|
+
const host = "127.0.0.1";
|
|
326
|
+
const connectionString = `postgresql://${user}:${password}@${host}:${hostPort}/${database}`;
|
|
327
|
+
return {
|
|
328
|
+
...container,
|
|
329
|
+
connectionString,
|
|
330
|
+
host,
|
|
331
|
+
port: hostPort,
|
|
332
|
+
user,
|
|
333
|
+
password,
|
|
334
|
+
database,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared filesystem conformance test suite.
|
|
3
|
+
*
|
|
4
|
+
* Driver authors call `defineFsDriverTests(config)` inside a vitest
|
|
5
|
+
* `describe` block. The helper registers core tests that every VFS
|
|
6
|
+
* implementation must pass, plus conditional tests gated on the
|
|
7
|
+
* `capabilities` object.
|
|
8
|
+
*/
|
|
9
|
+
import type { VirtualFileSystem } from "../runtime-compat.js";
|
|
10
|
+
export interface FsDriverTestCapabilities {
|
|
11
|
+
symlinks: boolean;
|
|
12
|
+
hardLinks: boolean;
|
|
13
|
+
permissions: boolean;
|
|
14
|
+
utimes: boolean;
|
|
15
|
+
truncate: boolean;
|
|
16
|
+
pread: boolean;
|
|
17
|
+
mkdir: boolean;
|
|
18
|
+
removeDir: boolean;
|
|
19
|
+
allowMissingFileRemoveNoop: boolean;
|
|
20
|
+
allowMissingDirReadAsEmpty: boolean;
|
|
21
|
+
allowMissingSourceRenameNoop: boolean;
|
|
22
|
+
allowDirectoryRenameUnsupported: boolean;
|
|
23
|
+
allowSymlinkLoopErrnoFallback: boolean;
|
|
24
|
+
allowDirectoryHardLink: boolean;
|
|
25
|
+
allowMkdirWithoutRecursiveParentAutoCreate: boolean;
|
|
26
|
+
allowRemoveDirNonEmptyRecursiveDelete: boolean;
|
|
27
|
+
allowSymlinkOverwrite: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface FsDriverTestConfig {
|
|
30
|
+
/** Human-readable name shown in the describe block. */
|
|
31
|
+
name: string;
|
|
32
|
+
/** Create a fresh VFS instance for each test. */
|
|
33
|
+
createFs: () => Promise<VirtualFileSystem> | VirtualFileSystem;
|
|
34
|
+
/** Optional teardown called after each test. */
|
|
35
|
+
cleanup?: () => Promise<void> | void;
|
|
36
|
+
/** Which optional capabilities the driver supports. */
|
|
37
|
+
capabilities: FsDriverTestCapabilities;
|
|
38
|
+
}
|
|
39
|
+
export declare function defineFsDriverTests(config: FsDriverTestConfig): void;
|