mirror of
https://github.com/earendil-works/pi.git
synced 2026-10-02 08:44:38 +08:00
179 lines
5.0 KiB
JavaScript
179 lines
5.0 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { spawn } from "node:child_process";
|
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
import { createServer } from "node:http";
|
|
import { tmpdir } from "node:os";
|
|
import { extname, join, resolve } from "node:path";
|
|
|
|
const WORKER_MARKER = "PI_CODEMODE_WORKER_OK";
|
|
const SUCCESS_MARKER = "PI_CODEMODE_BINARY_SMOKE_OK";
|
|
const FAILURE_MARKER = "PI_CODEMODE_BINARY_SMOKE_FAILED";
|
|
const TIMEOUT_MS = 30_000;
|
|
|
|
function completionChunk(id, delta, finishReason = null, usage) {
|
|
return {
|
|
id,
|
|
object: "chat.completion.chunk",
|
|
created: 0,
|
|
model: "codemode-smoke",
|
|
choices: [{ index: 0, delta, finish_reason: finishReason }],
|
|
...(usage ? { usage } : {}),
|
|
};
|
|
}
|
|
|
|
function sendCompletion(response, chunks) {
|
|
response.writeHead(200, {
|
|
"content-type": "text/event-stream",
|
|
"cache-control": "no-cache",
|
|
connection: "close",
|
|
});
|
|
for (const chunk of chunks) response.write(`data: ${JSON.stringify(chunk)}\n\n`);
|
|
response.end("data: [DONE]\n\n");
|
|
}
|
|
|
|
async function readRequest(request) {
|
|
let body = "";
|
|
for await (const chunk of request) body += chunk.toString();
|
|
return body;
|
|
}
|
|
|
|
function toolResultContainsMarker(body) {
|
|
const payload = JSON.parse(body);
|
|
return (
|
|
Array.isArray(payload.messages) &&
|
|
payload.messages.some(
|
|
(message) => message?.role === "tool" && JSON.stringify(message.content).includes(WORKER_MARKER),
|
|
)
|
|
);
|
|
}
|
|
|
|
async function main() {
|
|
const binaryArg = process.argv[2];
|
|
if (!binaryArg || process.argv.length !== 3) {
|
|
throw new Error("Usage: node scripts/smoke-test-codemode-binary.mjs <pi-binary>");
|
|
}
|
|
const binary = resolve(binaryArg);
|
|
const tempDir = await mkdtemp(join(tmpdir(), "pi-codemode-binary-smoke-"));
|
|
let requestCount = 0;
|
|
const server = createServer(async (request, response) => {
|
|
if (request.method !== "POST" || !request.url?.endsWith("/chat/completions")) {
|
|
response.writeHead(404).end();
|
|
return;
|
|
}
|
|
const body = await readRequest(request);
|
|
requestCount++;
|
|
const id = `chatcmpl-codemode-smoke-${requestCount}`;
|
|
if (requestCount === 1) {
|
|
sendCompletion(response, [
|
|
completionChunk(id, {
|
|
role: "assistant",
|
|
tool_calls: [
|
|
{
|
|
index: 0,
|
|
id: "call_codemode_smoke",
|
|
type: "function",
|
|
function: {
|
|
name: "codemode",
|
|
arguments: JSON.stringify({ code: `text("${WORKER_MARKER}")` }),
|
|
},
|
|
},
|
|
],
|
|
}),
|
|
completionChunk(id, {}, "tool_calls", { prompt_tokens: 1, completion_tokens: 1 }),
|
|
]);
|
|
return;
|
|
}
|
|
const marker = toolResultContainsMarker(body) ? SUCCESS_MARKER : FAILURE_MARKER;
|
|
sendCompletion(response, [
|
|
completionChunk(id, { role: "assistant", content: marker }),
|
|
completionChunk(id, {}, "stop", { prompt_tokens: 1, completion_tokens: 1 }),
|
|
]);
|
|
});
|
|
|
|
try {
|
|
await new Promise((resolveListen, reject) => {
|
|
server.once("error", reject);
|
|
server.listen(0, "127.0.0.1", resolveListen);
|
|
});
|
|
const address = server.address();
|
|
if (!address || typeof address === "string") throw new Error("Smoke-test server did not bind to a TCP port");
|
|
await writeFile(
|
|
join(tempDir, "models.json"),
|
|
JSON.stringify({
|
|
providers: {
|
|
"codemode-smoke": {
|
|
baseUrl: `http://127.0.0.1:${address.port}/v1`,
|
|
api: "openai-completions",
|
|
apiKey: "smoke-test",
|
|
models: [{ id: "codemode-smoke" }],
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
|
|
const piArgs = [
|
|
"--provider",
|
|
"codemode-smoke",
|
|
"--model",
|
|
"codemode-smoke",
|
|
"--tools",
|
|
"codemode",
|
|
"--print",
|
|
"--no-session",
|
|
"--offline",
|
|
"--no-context-files",
|
|
"--no-skills",
|
|
"--no-prompt-templates",
|
|
"--no-themes",
|
|
"Run the codemode binary smoke test.",
|
|
];
|
|
const javaScript = extname(binary) === ".js";
|
|
const child = spawn(javaScript ? process.execPath : binary, javaScript ? [binary, ...piArgs] : piArgs, {
|
|
cwd: tempDir,
|
|
env: {
|
|
...process.env,
|
|
PI_CODING_AGENT_DIR: tempDir,
|
|
PI_OFFLINE: "1",
|
|
},
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
});
|
|
let stdout = "";
|
|
let stderr = "";
|
|
child.stdout.on("data", (chunk) => {
|
|
stdout += chunk.toString();
|
|
});
|
|
child.stderr.on("data", (chunk) => {
|
|
stderr += chunk.toString();
|
|
});
|
|
const exitCode = await new Promise((resolveExit, reject) => {
|
|
const timer = setTimeout(() => {
|
|
child.kill();
|
|
reject(new Error(`Smoke test timed out after ${TIMEOUT_MS} ms`));
|
|
}, TIMEOUT_MS);
|
|
child.once("error", (error) => {
|
|
clearTimeout(timer);
|
|
reject(error);
|
|
});
|
|
child.once("exit", (code) => {
|
|
clearTimeout(timer);
|
|
resolveExit(code);
|
|
});
|
|
});
|
|
if (exitCode !== 0 || !stdout.includes(SUCCESS_MARKER) || requestCount < 2) {
|
|
throw new Error(
|
|
`Codemode binary smoke test failed (exit ${exitCode}, requests ${requestCount})\nstdout:\n${stdout}\nstderr:\n${stderr}`,
|
|
);
|
|
}
|
|
process.stdout.write(stdout);
|
|
} finally {
|
|
await new Promise((resolveClose) => server.close(resolveClose));
|
|
await rm(tempDir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error instanceof Error ? error.message : error);
|
|
process.exitCode = 1;
|
|
});
|