@whiteroom-ai/cli 1.1.0 → 2.0.1
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 +79 -0
- package/contract/catalog.json +264 -0
- package/dist/index.js +19 -15
- package/dist/index.js.map +1 -1
- package/dist/legacy-args.d.ts +4 -0
- package/dist/legacy-args.js +17 -0
- package/dist/legacy-args.js.map +1 -0
- package/dist/practice/adapter.d.ts +59 -0
- package/dist/practice/adapter.js +169 -0
- package/dist/practice/adapter.js.map +1 -0
- package/dist/practice/catalog.d.ts +27 -0
- package/dist/practice/catalog.js +6 -0
- package/dist/practice/catalog.js.map +1 -0
- package/dist/practice/engine.d.ts +52 -0
- package/dist/practice/engine.js +151 -0
- package/dist/practice/engine.js.map +1 -0
- package/dist/practice/index.d.ts +2 -0
- package/dist/practice/index.js +79 -0
- package/dist/practice/index.js.map +1 -0
- package/dist/practice/preflight.d.ts +64 -0
- package/dist/practice/preflight.js +204 -0
- package/dist/practice/preflight.js.map +1 -0
- package/dist/practice/run.d.ts +13 -0
- package/dist/practice/run.js +380 -0
- package/dist/practice/run.js.map +1 -0
- package/dist/practice/text.d.ts +107 -0
- package/dist/practice/text.js +185 -0
- package/dist/practice/text.js.map +1 -0
- package/dist/sandbox/index.d.ts +2 -0
- package/dist/sandbox/index.js +62 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/sandbox/listen.d.ts +57 -0
- package/dist/sandbox/listen.js +299 -0
- package/dist/sandbox/listen.js.map +1 -0
- package/package.json +10 -3
- package/starters/wr-adapter.mjs +104 -0
- package/starters/wr_adapter.py +136 -0
- package/src/index.ts +0 -144
- package/tsconfig.json +0 -16
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
// `whiteroom sandbox listen --adapter "<cmd>"`: the connected helper behind
|
|
2
|
+
// the Sandbox page's Run agent button. It claims a one-time helper code, then
|
|
3
|
+
// waits for tasks the owner queues there and runs each through the user's
|
|
4
|
+
// adapter (the practice tests' NDJSON contract), with the agent's calls going
|
|
5
|
+
// to the Sandbox URL. WhiteRoom sends task data only (a prompt); the command
|
|
6
|
+
// that runs is the one typed here, never anything from WhiteRoom.
|
|
7
|
+
import { hostname } from "node:os";
|
|
8
|
+
import { runAdapter } from "../practice/adapter.js";
|
|
9
|
+
import { CATALOG } from "../practice/catalog.js";
|
|
10
|
+
import { Engine, OFFLINE_MS, REDIRECTED, Stopped, Unreachable } from "../practice/engine.js";
|
|
11
|
+
import { adapterProblem, chatCompletions, firstLineOfStdin, hiddenPrompt, Interrupted, isProvider, nodeTooOld, targetOrigin } from "../practice/preflight.js";
|
|
12
|
+
import { clean, hideSecret, say, warn } from "../practice/text.js";
|
|
13
|
+
export const HELPER_CODE = /^wrhc_[A-Za-z0-9_-]{43}$/;
|
|
14
|
+
/** A Sandbox URL as the Sandbox page shows it: WhiteRoom's address, then the Sandbox key. */
|
|
15
|
+
const SANDBOX_URL = /^(https?:\/\/[^/\s?#@]+)\/(sk-wr-[0-9a-f]{40})(?:[/?#]\S*)?$/;
|
|
16
|
+
/** How long one task may run before its adapter is stopped. */
|
|
17
|
+
export const TASK_MS = 10 * 60_000;
|
|
18
|
+
/** While a task runs the helper doesn't poll, so it tells WhiteRoom it's still there. */
|
|
19
|
+
const HEARTBEAT_MS = 20_000;
|
|
20
|
+
/** Tests shorten the task deadline, heartbeat and offline wait (internal, not a user setting; WHITEROOM_* never reaches the adapter). */
|
|
21
|
+
const testMs = (name, fallback) => {
|
|
22
|
+
const v = Number(process.env[name]);
|
|
23
|
+
return Number.isInteger(v) && v > 0 ? v : fallback;
|
|
24
|
+
};
|
|
25
|
+
const SUMMARY_MAX = 2000;
|
|
26
|
+
const SANDBOX_PAGE = "https://app.whiteroom.tech/sandbox";
|
|
27
|
+
const NEW_CODE = `In the Sandbox page (${SANDBOX_PAGE}), click Run agent to get a new code, then start the helper again.`;
|
|
28
|
+
export const sandboxCopy = {
|
|
29
|
+
connected: "Helper connected · waiting for tasks from the Sandbox page (Ctrl+C to stop)",
|
|
30
|
+
needCode: "Paste the helper code from the Sandbox page (it won't show): ",
|
|
31
|
+
badCode: "That isn't a helper code. Copy it again from the Sandbox page (it starts with wrhc_).",
|
|
32
|
+
noTerminal: "No terminal to ask for the helper code. Pass it on stdin with --code-stdin.",
|
|
33
|
+
noSandboxUrl: "Point your agent at your Sandbox first: set its base URL to the Sandbox URL from the Sandbox page (for example export ANTHROPIC_BASE_URL=<Sandbox URL>), then run the command again. Nothing was sent.",
|
|
34
|
+
severalSandboxUrls: (vars) => `More than one Sandbox URL is set (${vars.join(", ")}). Keep the one your agent uses, then run the command again. Nothing was sent.`,
|
|
35
|
+
otherSandboxHost: (urlHost, host) => `Your Sandbox URL is on ${urlHost}, but the helper connects to ${host}. Use the Sandbox URL and command from the same Sandbox page. Nothing was sent.`,
|
|
36
|
+
badUrl: "--url must be a WhiteRoom address (https://….whiteroom.tech) or http://localhost for local testing.",
|
|
37
|
+
adapterMissing: (cmd, why) => `Couldn't start the adapter \`${cmd}\`: ${why}. Nothing was sent, and your helper code still works.`,
|
|
38
|
+
windows: "The helper runs on macOS and Linux for now. Run the command there, or in WSL.",
|
|
39
|
+
codeInvalid: `This helper code doesn't work any more. ${NEW_CODE}`,
|
|
40
|
+
keyMismatch: (left) => left > 0
|
|
41
|
+
? `Your agent's Sandbox URL isn't this Sandbox's current one (was the key rotated?). Copy the Sandbox URL from the Sandbox page, set it again, then run the same command (${left} ${left === 1 ? "try" : "tries"} left on this code).`
|
|
42
|
+
: `Your agent's Sandbox URL isn't this Sandbox's current one, and this code has no tries left. Copy the Sandbox URL from the Sandbox page, set it again, then get a new code.`,
|
|
43
|
+
notConnected: "This Sandbox has no key yet. Connect a provider key in the Sandbox page first.",
|
|
44
|
+
unavailable: "The Sandbox helper isn't available yet. Try again later.",
|
|
45
|
+
unreachable: "Couldn't reach WhiteRoom. Check your connection, then start the helper again.",
|
|
46
|
+
redirected: "WhiteRoom's server tried to send the helper to another address, so it stopped. Nothing was sent anywhere else.",
|
|
47
|
+
outdated: "WhiteRoom sent something this CLI doesn't understand. Update the CLI, then start the helper again.",
|
|
48
|
+
otherHost: "WhiteRoom named another host for your Sandbox, so the helper stopped before running your agent.",
|
|
49
|
+
sessionEnded: {
|
|
50
|
+
session_lost: `This helper's session has ended (WhiteRoom restarted, or it expired). ${NEW_CODE}`,
|
|
51
|
+
replaced: "Another helper connected to this Sandbox, so this one stopped.",
|
|
52
|
+
disconnected: "The helper was disconnected from the Sandbox page.",
|
|
53
|
+
lost_contact: `WhiteRoom didn't hear from this helper for a while, so its session ended. ${NEW_CODE}`,
|
|
54
|
+
},
|
|
55
|
+
stopped: "Helper stopped. The Sandbox page shows it disconnected.",
|
|
56
|
+
cancelled: "Stopped before the helper connected. Your code still works until it expires.",
|
|
57
|
+
task: (n, prompt) => `Task ${n}: ${prompt.replace(/\s+/g, " ").slice(0, 80)}${prompt.length > 80 ? "…" : ""}`,
|
|
58
|
+
taskDone: (n) => `Task ${n} finished. The result is on the Sandbox page.`,
|
|
59
|
+
taskFailed: (n, why) => `Task ${n} didn't finish: ${why}`,
|
|
60
|
+
resultNotSent: (n) => `Task ${n} ran, but its result couldn't be sent to WhiteRoom. If the Sandbox page hands it out again, the helper sends it again.`,
|
|
61
|
+
noTools: "No tools are available in Sandbox tasks.",
|
|
62
|
+
};
|
|
63
|
+
/** The Sandbox URL the user's agent points at, found in its environment: the variable, WhiteRoom's origin and the Sandbox key. */
|
|
64
|
+
export function findSandboxUrl(env) {
|
|
65
|
+
const found = new Map();
|
|
66
|
+
for (const [variable, value] of Object.entries(env)) {
|
|
67
|
+
const m = value?.trim().match(SANDBOX_URL);
|
|
68
|
+
if (m && !found.has(m[2]))
|
|
69
|
+
found.set(m[2], { variable, origin: new URL(m[1]).origin, key: m[2] });
|
|
70
|
+
}
|
|
71
|
+
return [...found.values()];
|
|
72
|
+
}
|
|
73
|
+
/** The adapter's environment: the user's own (their agent's settings and keys), without WhiteRoom's. */
|
|
74
|
+
export function helperEnv(parent) {
|
|
75
|
+
const env = {};
|
|
76
|
+
for (const [k, v] of Object.entries(parent))
|
|
77
|
+
if (!/^WHITEROOM_/i.test(k))
|
|
78
|
+
env[k] = v;
|
|
79
|
+
return env;
|
|
80
|
+
}
|
|
81
|
+
/** This computer's name as the Sandbox page shows it ("Helper connected · <host>"). */
|
|
82
|
+
export function helperHost(name = hostname()) {
|
|
83
|
+
return name.replace(/[^A-Za-z0-9._-]/g, "-").slice(0, 64) || "computer";
|
|
84
|
+
}
|
|
85
|
+
/** How long a deadline is, in plain words ("10 minutes", "1 second"). */
|
|
86
|
+
const duration = (ms) => {
|
|
87
|
+
const [n, unit] = ms >= 60_000 && ms % 60_000 === 0 ? [ms / 60_000, "minute"] : [Math.max(1, Math.round(ms / 1000)), "second"];
|
|
88
|
+
return `${n} ${unit}${n === 1 ? "" : "s"}`;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* What a task's adapter run reports back: ok and a summary of at most 2,000
|
|
92
|
+
* characters. Everything sent to WhiteRoom goes through clean(), so a provider
|
|
93
|
+
* key, the Sandbox key, the helper code or session the adapter echoes is never sent.
|
|
94
|
+
*/
|
|
95
|
+
export function taskResult(out, taskMs = TASK_MS) {
|
|
96
|
+
const cut = (s) => clean(s).slice(0, SUMMARY_MAX);
|
|
97
|
+
switch (out.kind) {
|
|
98
|
+
case "final": return { ok: true, summary: cut(out.output) };
|
|
99
|
+
case "exited": return { ok: false, summary: cut(`Your adapter exited (${out.how}) before finishing.${out.tail.length ? ` Last output: ${out.tail.join(" / ")}` : ""}`) };
|
|
100
|
+
case "broke": return { ok: false, summary: cut(`Your adapter ${out.why}.`) };
|
|
101
|
+
case "deadline": return { ok: false, summary: cut(`Your agent didn't finish within ${duration(taskMs)}, so it was stopped.`) };
|
|
102
|
+
default: return { ok: false, summary: cut("Stopped on the helper's computer.") };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const fail = (code, line) => (warn(line), code);
|
|
106
|
+
/** A session refusal, in plain words. */
|
|
107
|
+
const sessionOver = (r) => fail(3, sandboxCopy.sessionEnded[r.body?.reason] ?? sandboxCopy.sessionEnded.session_lost);
|
|
108
|
+
/** A reply that ends listening: the session refused (401), or a redirect. */
|
|
109
|
+
const ends = (r) => r.status === 401 || r.status === REDIRECTED;
|
|
110
|
+
const endListening = (r) => (r.status === REDIRECTED ? fail(3, sandboxCopy.redirected) : sessionOver(r));
|
|
111
|
+
export async function listen(a) {
|
|
112
|
+
// ── Preflight: nothing is sent until every check passes ──
|
|
113
|
+
const old = nodeTooOld();
|
|
114
|
+
if (old)
|
|
115
|
+
return fail(2, old);
|
|
116
|
+
if (process.platform === "win32")
|
|
117
|
+
return fail(2, sandboxCopy.windows);
|
|
118
|
+
const origin = targetOrigin(a.url);
|
|
119
|
+
if (!origin)
|
|
120
|
+
return fail(2, sandboxCopy.badUrl);
|
|
121
|
+
const why = adapterProblem(a.adapter);
|
|
122
|
+
if (why)
|
|
123
|
+
return fail(2, sandboxCopy.adapterMissing(a.adapter, why));
|
|
124
|
+
// The user's own command runs through their shell as typed; nothing from WhiteRoom is ever put into it.
|
|
125
|
+
const launch = { shell: a.adapter };
|
|
126
|
+
// Provider keys in the environment are never printed, even in the adapter's output.
|
|
127
|
+
for (const p of Object.values(CATALOG.providers)) {
|
|
128
|
+
const v = process.env[p.keyVar];
|
|
129
|
+
if (v)
|
|
130
|
+
hideSecret(v.trim());
|
|
131
|
+
}
|
|
132
|
+
const urls = findSandboxUrl(process.env);
|
|
133
|
+
if (!urls.length)
|
|
134
|
+
return fail(2, sandboxCopy.noSandboxUrl);
|
|
135
|
+
if (urls.length > 1)
|
|
136
|
+
return fail(2, sandboxCopy.severalSandboxUrls(urls.map((u) => u.variable)));
|
|
137
|
+
const sandbox = urls[0];
|
|
138
|
+
hideSecret(sandbox.key);
|
|
139
|
+
if (sandbox.origin !== origin)
|
|
140
|
+
return fail(2, sandboxCopy.otherSandboxHost(new URL(sandbox.origin).host, new URL(origin).host));
|
|
141
|
+
let code = "";
|
|
142
|
+
try {
|
|
143
|
+
if (a.codeStdin)
|
|
144
|
+
code = await firstLineOfStdin();
|
|
145
|
+
else {
|
|
146
|
+
if (!process.stdin.isTTY)
|
|
147
|
+
return fail(2, sandboxCopy.noTerminal);
|
|
148
|
+
for (let i = 0; i < 3 && !HELPER_CODE.test(code); i++) {
|
|
149
|
+
if (i)
|
|
150
|
+
warn(sandboxCopy.badCode);
|
|
151
|
+
code = await hiddenPrompt(sandboxCopy.needCode);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
// Cancelled at the prompt: no session existed, so this is a plain stop (0), not an ended session (3).
|
|
157
|
+
if (err instanceof Interrupted)
|
|
158
|
+
return fail(0, sandboxCopy.cancelled);
|
|
159
|
+
throw err;
|
|
160
|
+
}
|
|
161
|
+
if (!HELPER_CODE.test(code))
|
|
162
|
+
return fail(2, sandboxCopy.badCode);
|
|
163
|
+
hideSecret(code);
|
|
164
|
+
// ── Claim: the code, and which Sandbox key this computer's agent uses (its non-secret prefix, never the key) ──
|
|
165
|
+
const engine = new Engine(origin, testMs("WHITEROOM_TEST_OFFLINE_MS", OFFLINE_MS));
|
|
166
|
+
let claim;
|
|
167
|
+
try {
|
|
168
|
+
claim = await engine.post("/api/sandbox-helper/claim", { code, keyPrefix: sandbox.key.slice(0, 22), host: helperHost() });
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
// No session yet: a setup problem (2); the code is still unused.
|
|
172
|
+
if (err instanceof Unreachable)
|
|
173
|
+
return fail(2, sandboxCopy.unreachable);
|
|
174
|
+
throw err;
|
|
175
|
+
}
|
|
176
|
+
if (claim.status === 401 && claim.body?.reason === "key_mismatch")
|
|
177
|
+
return fail(2, sandboxCopy.keyMismatch(Number(claim.body.triesLeft) || 0));
|
|
178
|
+
if (claim.status === 410)
|
|
179
|
+
return fail(2, sandboxCopy.codeInvalid);
|
|
180
|
+
if (claim.status === 409 && claim.body?.reason === "not_connected")
|
|
181
|
+
return fail(2, sandboxCopy.notConnected);
|
|
182
|
+
if (claim.status === REDIRECTED)
|
|
183
|
+
return fail(2, sandboxCopy.redirected);
|
|
184
|
+
if (claim.status === 404)
|
|
185
|
+
return fail(2, sandboxCopy.unavailable);
|
|
186
|
+
const { session, proxyUrl, provider } = claim.body ?? {};
|
|
187
|
+
if (claim.status !== 200 || typeof session !== "string" || !isProvider(provider))
|
|
188
|
+
return fail(2, sandboxCopy.outdated);
|
|
189
|
+
engine.session = session;
|
|
190
|
+
hideSecret(session);
|
|
191
|
+
// The agent's calls go to this Sandbox's URL, on the host the helper claimed on, never one WhiteRoom names elsewhere.
|
|
192
|
+
if (proxyUrl !== sandbox.origin) {
|
|
193
|
+
await engine.once("/api/sandbox-helper/disconnect", {});
|
|
194
|
+
return fail(3, sandboxCopy.otherHost);
|
|
195
|
+
}
|
|
196
|
+
// As practice tests pass it: Azure OpenAI under /openai; OpenAI SDKs want a base URL that ends in /v1.
|
|
197
|
+
const sandboxBase = `${sandbox.origin}/${sandbox.key}${provider === "azure-openai" ? "/openai" : ""}`;
|
|
198
|
+
const baseUrl = chatCompletions(provider) ? `${sandboxBase}/v1` : sandboxBase;
|
|
199
|
+
say(sandboxCopy.connected);
|
|
200
|
+
// ── Listening: Ctrl+C (or a signal) disconnects; WhiteRoom can end the session ──
|
|
201
|
+
const stop = new AbortController();
|
|
202
|
+
engine.signal = stop.signal;
|
|
203
|
+
const onStop = () => stop.abort();
|
|
204
|
+
process.on("SIGINT", onStop);
|
|
205
|
+
process.on("SIGTERM", onStop);
|
|
206
|
+
process.on("SIGHUP", onStop);
|
|
207
|
+
/** Results by task: a task handed out again gets its result (re)sent, not a second run. */
|
|
208
|
+
const done = new Map();
|
|
209
|
+
const taskMs = testMs("WHITEROOM_TEST_TASK_MS", TASK_MS);
|
|
210
|
+
const heartbeatMs = testMs("WHITEROOM_TEST_HEARTBEAT_MS", HEARTBEAT_MS);
|
|
211
|
+
let n = 0;
|
|
212
|
+
/** Posts a task's result; announces it only once WhiteRoom has it. Returns a reply that ends listening, else null. */
|
|
213
|
+
const sendResult = async (taskId, entry) => {
|
|
214
|
+
const r = await engine.post("/api/sandbox-helper/result", { taskId, ...entry.result }, { retryNetwork: true });
|
|
215
|
+
if (ends(r))
|
|
216
|
+
return r;
|
|
217
|
+
const ok = r.status >= 200 && r.status < 300;
|
|
218
|
+
if (ok && !entry.sent)
|
|
219
|
+
say(entry.result.ok ? sandboxCopy.taskDone(entry.n) : sandboxCopy.taskFailed(entry.n, clean(entry.result.summary)));
|
|
220
|
+
else if (!ok)
|
|
221
|
+
warn(sandboxCopy.resultNotSent(entry.n));
|
|
222
|
+
entry.sent ||= ok;
|
|
223
|
+
return null;
|
|
224
|
+
};
|
|
225
|
+
let ended = null;
|
|
226
|
+
const runTask = async (task) => {
|
|
227
|
+
say(sandboxCopy.task(++n, task.prompt));
|
|
228
|
+
const taskStop = new AbortController();
|
|
229
|
+
const abort = () => taskStop.abort();
|
|
230
|
+
stop.signal.addEventListener("abort", abort, { once: true });
|
|
231
|
+
const beat = setInterval(() => {
|
|
232
|
+
void engine.once("/api/sandbox-helper/heartbeat", {}).then((r) => {
|
|
233
|
+
if (r?.status === 401)
|
|
234
|
+
(ended = r), taskStop.abort();
|
|
235
|
+
});
|
|
236
|
+
}, heartbeatMs);
|
|
237
|
+
beat.unref();
|
|
238
|
+
let out;
|
|
239
|
+
try {
|
|
240
|
+
out = await runAdapter({
|
|
241
|
+
launch, env: helperEnv(process.env), deadline: Date.now() + taskMs, signal: taskStop.signal,
|
|
242
|
+
message: { type: "task", task: task.prompt, tools: [], headers: {}, model: null, base_url: baseUrl },
|
|
243
|
+
tool: async () => ({ result: { error: sandboxCopy.noTools } }),
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
clearInterval(beat);
|
|
248
|
+
stop.signal.removeEventListener("abort", abort);
|
|
249
|
+
}
|
|
250
|
+
// Stopped here: disconnecting closes the task on the Sandbox page.
|
|
251
|
+
if (out.kind === "stopped" || ended)
|
|
252
|
+
return null;
|
|
253
|
+
const entry = { n, result: taskResult(out, taskMs), sent: false };
|
|
254
|
+
done.set(task.taskId, entry);
|
|
255
|
+
return sendResult(task.taskId, entry);
|
|
256
|
+
};
|
|
257
|
+
try {
|
|
258
|
+
for (;;) {
|
|
259
|
+
// Every request to WhiteRoom in this loop ends the same way: Ctrl+C stops (0), unreachable ends (3).
|
|
260
|
+
try {
|
|
261
|
+
const next = await engine.post("/api/sandbox-helper/next", {}, { retryNetwork: true });
|
|
262
|
+
if (ends(next))
|
|
263
|
+
return endListening(next);
|
|
264
|
+
if (next.status !== 200)
|
|
265
|
+
return fail(3, sandboxCopy.outdated);
|
|
266
|
+
if (next.body?.idle)
|
|
267
|
+
continue;
|
|
268
|
+
const task = next.body?.task;
|
|
269
|
+
if (!task || typeof task.taskId !== "string" || task.kind !== "prompt" || typeof task.prompt !== "string")
|
|
270
|
+
return fail(3, sandboxCopy.outdated);
|
|
271
|
+
// A task handed out again (WhiteRoom didn't get the result): send the result again, don't run it twice.
|
|
272
|
+
const sent = done.get(task.taskId);
|
|
273
|
+
const refused = sent ? await sendResult(task.taskId, sent) : await runTask(task);
|
|
274
|
+
if (refused)
|
|
275
|
+
return endListening(refused);
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
if (err instanceof Stopped)
|
|
279
|
+
break;
|
|
280
|
+
if (err instanceof Unreachable)
|
|
281
|
+
return fail(3, sandboxCopy.unreachable);
|
|
282
|
+
throw err;
|
|
283
|
+
}
|
|
284
|
+
if (ended)
|
|
285
|
+
return sessionOver(ended);
|
|
286
|
+
if (stop.signal.aborted)
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
await engine.once("/api/sandbox-helper/disconnect", {});
|
|
290
|
+
say(sandboxCopy.stopped);
|
|
291
|
+
return 0;
|
|
292
|
+
}
|
|
293
|
+
finally {
|
|
294
|
+
process.off("SIGINT", onStop);
|
|
295
|
+
process.off("SIGTERM", onStop);
|
|
296
|
+
process.off("SIGHUP", onStop);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=listen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listen.js","sourceRoot":"","sources":["../../src/sandbox/listen.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@whiteroom-ai/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "CLI for WhiteRoom — manage AI agent fleets from the terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"whiteroom": "dist/index.js"
|
|
8
8
|
},
|
|
9
|
+
"files": ["dist", "starters", "contract/catalog.json"],
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=20"
|
|
12
|
+
},
|
|
9
13
|
"scripts": {
|
|
10
14
|
"build": "tsc",
|
|
11
15
|
"typecheck": "tsc --noEmit",
|
|
12
|
-
"
|
|
16
|
+
"test": "node --import tsx --test test/*.test.ts",
|
|
17
|
+
"dev": "tsx src/index.ts",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
13
19
|
},
|
|
14
20
|
"license": "Apache-2.0",
|
|
15
21
|
"dependencies": {
|
|
16
22
|
"@whiteroom-ai/sdk": "^1.1.0"
|
|
17
23
|
},
|
|
18
24
|
"devDependencies": {
|
|
19
|
-
"
|
|
25
|
+
"@types/node": "^26.6.2",
|
|
26
|
+
"typescript": "^7.0.2",
|
|
20
27
|
"tsx": "^4.19.0"
|
|
21
28
|
}
|
|
22
29
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// WhiteRoom practice test adapter (JavaScript starter, Node 20+, no dependencies).
|
|
2
|
+
// Run it with: whiteroom practice run --adapter "node wr-adapter.mjs"
|
|
3
|
+
// The CLI sends one practice task as a JSON line on stdin. You answer with
|
|
4
|
+
// {"type":"tool_call",...} lines (each gets a {"type":"tool_result",...} line
|
|
5
|
+
// back) and finish with one {"type":"final","output":"..."} line. Print your
|
|
6
|
+
// own logs to stderr: stdout is only for these JSON lines.
|
|
7
|
+
import { createInterface } from "node:readline";
|
|
8
|
+
|
|
9
|
+
const lines = createInterface({ input: process.stdin })[Symbol.asyncIterator]();
|
|
10
|
+
const receive = async () => JSON.parse((await lines.next()).value ?? "null");
|
|
11
|
+
const send = (msg) => process.stdout.write(JSON.stringify(msg) + "\n");
|
|
12
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
13
|
+
let calls = 0;
|
|
14
|
+
async function callTool(name, input) {
|
|
15
|
+
const id = `call-${++calls}`;
|
|
16
|
+
send({ type: "tool_call", id, name, input });
|
|
17
|
+
return (await receive()).result;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// The CLI sets only the variable of the provider the practice test uses.
|
|
21
|
+
// It decides the API: chat completions for OpenAI and Azure OpenAI (where the
|
|
22
|
+
// model is your deployment name), Anthropic's Messages API for Anthropic,
|
|
23
|
+
// Claude on Azure (Foundry) and AWS Bedrock. And the header the key goes in.
|
|
24
|
+
const KEYS = {
|
|
25
|
+
ANTHROPIC_API_KEY: (k) => ({ "x-api-key": k }),
|
|
26
|
+
ANTHROPIC_FOUNDRY_API_KEY: (k) => ({ "x-api-key": k }),
|
|
27
|
+
AWS_BEARER_TOKEN_BEDROCK: (k) => ({ authorization: `Bearer ${k}` }),
|
|
28
|
+
OPENAI_API_KEY: (k) => ({ authorization: `Bearer ${k}` }),
|
|
29
|
+
AZURE_OPENAI_API_KEY: (k) => ({ "api-key": k }),
|
|
30
|
+
};
|
|
31
|
+
const KEY_VAR = Object.keys(KEYS).find((v) => process.env[v]) ?? "ANTHROPIC_API_KEY";
|
|
32
|
+
const anthropic = !["OPENAI_API_KEY", "AZURE_OPENAI_API_KEY"].includes(KEY_VAR);
|
|
33
|
+
// The model to use when the practice task doesn't name one (cloud providers always name one).
|
|
34
|
+
const DEFAULT_MODEL = anthropic ? "claude-haiku-4-5" : "gpt-4o-mini";
|
|
35
|
+
const BLOCKED = "Stopped: one of my WhiteRoom controls blocked the model call.";
|
|
36
|
+
const GONE = "Stopped: WhiteRoom closed this practice task.";
|
|
37
|
+
|
|
38
|
+
// One model call through WhiteRoom; a final answer (text) if WhiteRoom stopped it.
|
|
39
|
+
// Never follows a redirect, so the key only goes to WhiteRoom.
|
|
40
|
+
// Waits and tries again when WhiteRoom or the provider asks it to, saying each reason once per practice task.
|
|
41
|
+
// Each practice task runs in its own adapter process, so this is per task.
|
|
42
|
+
const said = new Set();
|
|
43
|
+
async function model(base_url, headers, body) {
|
|
44
|
+
const url = anthropic ? `${base_url}/v1/messages` : `${base_url}/chat/completions`;
|
|
45
|
+
const auth = { ...KEYS[KEY_VAR](process.env[KEY_VAR] ?? ""), ...(anthropic && { "anthropic-version": "2023-06-01" }) };
|
|
46
|
+
for (let tries = 1; ; tries++) {
|
|
47
|
+
const res = await fetch(url, { method: "POST", headers: { "content-type": "application/json", ...headers, ...auth },
|
|
48
|
+
body: JSON.stringify(body), redirect: "error", signal: AbortSignal.timeout(180_000) });
|
|
49
|
+
if (res.ok) return res.json();
|
|
50
|
+
const wr = (await res.json().catch(() => null))?.whiteroom;
|
|
51
|
+
const wait = res.status === 503 && wr ? ["WhiteRoom is switching servers. Waiting, then trying again.", Number(res.headers.get("retry-after") || 1)]
|
|
52
|
+
: res.status === 429 && wr ? ["WhiteRoom is saving a summary of your agent's memory. Waiting until it's saved.", 2]
|
|
53
|
+
: res.status === 403 && wr?.retryable ? ["One of your WhiteRoom controls is limiting calls. Waiting, then trying again.", 10]
|
|
54
|
+
: [429, 500, 502, 503, 529].includes(res.status) && !wr && tries < 5 ? ["Your model provider is busy. Waiting, then trying again.", 5 * tries] : null;
|
|
55
|
+
if (res.status === 403 && wr && !wait) return BLOCKED; // Blocked by one of the fleet's controls.
|
|
56
|
+
if (res.status === 410) return GONE; // The practice task's fleet is gone: WhiteRoom reran or ended it.
|
|
57
|
+
if (!wait || tries > 150) throw new Error(`The model call failed (HTTP ${res.status}).`);
|
|
58
|
+
if (!said.has(wait[0])) { said.add(wait[0]); console.error(wait[0]); }
|
|
59
|
+
await sleep(wait[1] * 1000);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Replace this function with a call to your own agent. ────────────────
|
|
64
|
+
// It gets the task, the test tools, WhiteRoom's address and the model your
|
|
65
|
+
// dashboard chose (null: use your own); it calls tools with
|
|
66
|
+
// callTool(name, input) and returns its final answer as text.
|
|
67
|
+
async function runAgent({ task, tools, base_url, headers, model: chosen }) {
|
|
68
|
+
const MODEL = chosen || DEFAULT_MODEL;
|
|
69
|
+
const messages = [{ role: "user", content: task }];
|
|
70
|
+
const fns = tools.map((t) => ({ type: "function", function: { name: t.name, description: t.description, parameters: t.input_schema } }));
|
|
71
|
+
for (let turn = 0; turn < 40; turn++) {
|
|
72
|
+
if (anthropic) {
|
|
73
|
+
const r = await model(base_url, headers, { model: MODEL, max_tokens: 1024, tools, messages });
|
|
74
|
+
if (typeof r === "string") return r;
|
|
75
|
+
messages.push({ role: "assistant", content: r.content });
|
|
76
|
+
const uses = r.content.filter((b) => b.type === "tool_use");
|
|
77
|
+
if (!uses.length) return r.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
78
|
+
const results = [];
|
|
79
|
+
for (const u of uses) results.push({ type: "tool_result", tool_use_id: u.id, content: JSON.stringify(await callTool(u.name, u.input)) });
|
|
80
|
+
messages.push({ role: "user", content: results });
|
|
81
|
+
} else {
|
|
82
|
+
const r = await model(base_url, headers, { model: MODEL, tools: fns, messages });
|
|
83
|
+
if (typeof r === "string") return r;
|
|
84
|
+
const m = r.choices[0].message;
|
|
85
|
+
messages.push(m);
|
|
86
|
+
if (!m.tool_calls?.length) return m.content ?? "";
|
|
87
|
+
for (const c of m.tool_calls) {
|
|
88
|
+
const result = await callTool(c.function.name, JSON.parse(c.function.arguments || "{}"));
|
|
89
|
+
messages.push({ role: "tool", tool_call_id: c.id, content: JSON.stringify(result) });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return "Stopped: too many turns.";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const msg = await receive();
|
|
98
|
+
if (msg?.type !== "task") throw new Error("Expected a task from the WhiteRoom CLI.");
|
|
99
|
+
send({ type: "final", output: await runAgent(msg) });
|
|
100
|
+
process.exit(0);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.error(err.message);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# WhiteRoom practice test adapter (Python starter, Python 3.9+, standard library only).
|
|
2
|
+
# Run it with: whiteroom practice run --adapter "python3 wr_adapter.py"
|
|
3
|
+
# The CLI sends one practice task as a JSON line on stdin. You answer with
|
|
4
|
+
# {"type":"tool_call",...} lines (each gets a {"type":"tool_result",...} line
|
|
5
|
+
# back) and finish with one {"type":"final","output":"..."} line. Print your
|
|
6
|
+
# own logs to stderr: stdout is only for these JSON lines.
|
|
7
|
+
import json, os, sys, time, urllib.error, urllib.request
|
|
8
|
+
|
|
9
|
+
# The CLI sets only the variable of the provider the practice test uses.
|
|
10
|
+
# It decides the API: chat completions for OpenAI and Azure OpenAI (where the
|
|
11
|
+
# model is your deployment name), Anthropic's Messages API for Anthropic,
|
|
12
|
+
# Claude on Azure (Foundry) and AWS Bedrock. And the header the key goes in.
|
|
13
|
+
KEYS = {
|
|
14
|
+
"ANTHROPIC_API_KEY": lambda k: {"x-api-key": k},
|
|
15
|
+
"ANTHROPIC_FOUNDRY_API_KEY": lambda k: {"x-api-key": k},
|
|
16
|
+
"AWS_BEARER_TOKEN_BEDROCK": lambda k: {"authorization": "Bearer " + k},
|
|
17
|
+
"OPENAI_API_KEY": lambda k: {"authorization": "Bearer " + k},
|
|
18
|
+
"AZURE_OPENAI_API_KEY": lambda k: {"api-key": k},
|
|
19
|
+
}
|
|
20
|
+
KEY_VAR = next((v for v in KEYS if os.environ.get(v)), "ANTHROPIC_API_KEY")
|
|
21
|
+
ANTHROPIC = KEY_VAR not in ("OPENAI_API_KEY", "AZURE_OPENAI_API_KEY")
|
|
22
|
+
# The model to use when the practice task doesn't name one (cloud providers always name one).
|
|
23
|
+
DEFAULT_MODEL = "claude-haiku-4-5" if ANTHROPIC else "gpt-4o-mini"
|
|
24
|
+
BLOCKED = "Stopped: one of my WhiteRoom controls blocked the model call."
|
|
25
|
+
GONE = "Stopped: WhiteRoom closed this practice task."
|
|
26
|
+
calls = 0
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class NoRedirects(urllib.request.HTTPRedirectHandler):
|
|
30
|
+
# A redirect is an error, never followed, so the key only goes to WhiteRoom.
|
|
31
|
+
def redirect_request(self, *args):
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
open_url = urllib.request.build_opener(NoRedirects).open
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def receive():
|
|
39
|
+
line = sys.stdin.readline()
|
|
40
|
+
return json.loads(line) if line else None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def send(msg):
|
|
44
|
+
sys.stdout.write(json.dumps(msg) + "\n")
|
|
45
|
+
sys.stdout.flush()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def call_tool(name, args):
|
|
49
|
+
global calls
|
|
50
|
+
calls += 1
|
|
51
|
+
send({"type": "tool_call", "id": f"call-{calls}", "name": name, "input": args})
|
|
52
|
+
return receive()["result"]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# Each practice task runs in its own adapter process, so this is per task.
|
|
56
|
+
SAID = set()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def model(base_url, headers, body):
|
|
60
|
+
"""One model call through WhiteRoom; a final answer (text) if WhiteRoom stopped it.
|
|
61
|
+
Waits and tries again when WhiteRoom or the provider asks it to, saying each reason once per practice task."""
|
|
62
|
+
url = f"{base_url}/v1/messages" if ANTHROPIC else f"{base_url}/chat/completions"
|
|
63
|
+
auth = {**KEYS[KEY_VAR](os.environ.get(KEY_VAR, "")), **({"anthropic-version": "2023-06-01"} if ANTHROPIC else {})}
|
|
64
|
+
for tries in range(1, 152):
|
|
65
|
+
req = urllib.request.Request(url, json.dumps(body).encode(), {"content-type": "application/json", **headers, **auth})
|
|
66
|
+
try:
|
|
67
|
+
with open_url(req, timeout=180) as res:
|
|
68
|
+
return json.load(res)
|
|
69
|
+
except urllib.error.HTTPError as err:
|
|
70
|
+
try:
|
|
71
|
+
wr = json.load(err).get("whiteroom")
|
|
72
|
+
except Exception:
|
|
73
|
+
wr = None
|
|
74
|
+
if err.code == 503 and wr:
|
|
75
|
+
wait = ("WhiteRoom is switching servers. Waiting, then trying again.", float(err.headers.get("retry-after") or 1))
|
|
76
|
+
elif err.code == 429 and wr:
|
|
77
|
+
wait = ("WhiteRoom is saving a summary of your agent's memory. Waiting until it's saved.", 2)
|
|
78
|
+
elif err.code == 403 and wr and wr.get("retryable"):
|
|
79
|
+
wait = ("One of your WhiteRoom controls is limiting calls. Waiting, then trying again.", 10)
|
|
80
|
+
elif err.code == 403 and wr:
|
|
81
|
+
return BLOCKED # Blocked by one of the fleet's controls.
|
|
82
|
+
elif err.code == 410:
|
|
83
|
+
return GONE # The practice task's fleet is gone: WhiteRoom reran or ended it.
|
|
84
|
+
elif err.code in (429, 500, 502, 503, 529) and not wr and tries < 5:
|
|
85
|
+
wait = ("Your model provider is busy. Waiting, then trying again.", 5 * tries)
|
|
86
|
+
else:
|
|
87
|
+
raise RuntimeError(f"The model call failed (HTTP {err.code}).") from None
|
|
88
|
+
if wait[0] not in SAID:
|
|
89
|
+
SAID.add(wait[0])
|
|
90
|
+
print(wait[0], file=sys.stderr)
|
|
91
|
+
time.sleep(wait[1])
|
|
92
|
+
raise RuntimeError("The model call kept failing.")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ── Replace this function with a call to your own agent. ────────────────
|
|
96
|
+
# It gets the task, the test tools, WhiteRoom's address and the model your
|
|
97
|
+
# dashboard chose (None: use your own); it calls tools with
|
|
98
|
+
# call_tool(name, input) and returns its final answer as text.
|
|
99
|
+
def run_agent(task, tools, base_url, headers, chosen_model=None):
|
|
100
|
+
MODEL = chosen_model or DEFAULT_MODEL
|
|
101
|
+
messages = [{"role": "user", "content": task}]
|
|
102
|
+
fns = [{"type": "function", "function": {"name": t["name"], "description": t["description"], "parameters": t["input_schema"]}} for t in tools]
|
|
103
|
+
for _ in range(40):
|
|
104
|
+
if ANTHROPIC:
|
|
105
|
+
r = model(base_url, headers, {"model": MODEL, "max_tokens": 1024, "tools": tools, "messages": messages})
|
|
106
|
+
if isinstance(r, str):
|
|
107
|
+
return r
|
|
108
|
+
messages.append({"role": "assistant", "content": r["content"]})
|
|
109
|
+
uses = [b for b in r["content"] if b["type"] == "tool_use"]
|
|
110
|
+
if not uses:
|
|
111
|
+
return "\n".join(b["text"] for b in r["content"] if b["type"] == "text")
|
|
112
|
+
messages.append({"role": "user", "content": [
|
|
113
|
+
{"type": "tool_result", "tool_use_id": u["id"], "content": json.dumps(call_tool(u["name"], u["input"]))} for u in uses]})
|
|
114
|
+
else:
|
|
115
|
+
r = model(base_url, headers, {"model": MODEL, "tools": fns, "messages": messages})
|
|
116
|
+
if isinstance(r, str):
|
|
117
|
+
return r
|
|
118
|
+
m = r["choices"][0]["message"]
|
|
119
|
+
messages.append(m)
|
|
120
|
+
if not m.get("tool_calls"):
|
|
121
|
+
return m.get("content") or ""
|
|
122
|
+
for c in m["tool_calls"]:
|
|
123
|
+
result = call_tool(c["function"]["name"], json.loads(c["function"]["arguments"] or "{}"))
|
|
124
|
+
messages.append({"role": "tool", "tool_call_id": c["id"], "content": json.dumps(result)})
|
|
125
|
+
return "Stopped: too many turns."
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
if __name__ == "__main__":
|
|
129
|
+
try:
|
|
130
|
+
msg = receive()
|
|
131
|
+
if not msg or msg.get("type") != "task":
|
|
132
|
+
raise RuntimeError("Expected a task from the WhiteRoom CLI.")
|
|
133
|
+
send({"type": "final", "output": run_agent(msg["task"], msg["tools"], msg["base_url"], msg["headers"], msg.get("model"))})
|
|
134
|
+
except Exception as err:
|
|
135
|
+
print(err, file=sys.stderr)
|
|
136
|
+
sys.exit(1)
|