87 lines
3.9 KiB
TypeScript
87 lines
3.9 KiB
TypeScript
/** P4 fixture provider for the real console: all project work uses the installed bash tool. */
|
|
import { fauxProvider, fauxAssistantMessage, fauxToolCall } from '@earendil-works/pi-ai/providers/faux';
|
|
|
|
const main = `fn greeting(name: &str) -> String { format!("hello {name}") }
|
|
fn main() {
|
|
let Some(name) = std::env::args().nth(1) else { std::process::exit(7) };
|
|
println!("{}", serde_json::json!({"greeting": greeting(&name)}));
|
|
}
|
|
#[test]
|
|
fn greeting_test() { assert_eq!(greeting("world"), "hello world", "P4_expected_failure"); }
|
|
`;
|
|
const write = (path: string, text: string) => `cat > ${path} <<'P4_FILE'\n${text}\nP4_FILE\n`;
|
|
const phases = [
|
|
`set -e
|
|
mkdir -p /src/demo-cli/src /src/demo-node /tmp
|
|
cd /src/demo-cli
|
|
if test -d target; then cargo clean; fi
|
|
${write('Cargo.toml', '[package]\nname="demo-cli"\nversion="0.1.0"\nedition="2024"\n[workspace]\n')}
|
|
${write('src/main.rs', main.replace('"hello world", "P4_expected_failure"', '"hello wrong", "P4_expected_failure"'))}
|
|
cargo add serde_json
|
|
cargo fmt
|
|
set +e
|
|
cargo test -- --nocapture > /tmp/p4-fail.stdout 2> /tmp/p4-fail.stderr
|
|
code=$?
|
|
set -e
|
|
cat /tmp/p4-fail.stdout /tmp/p4-fail.stderr
|
|
test "$code" = 101
|
|
case "$(cat /tmp/p4-fail.stderr)" in *P4_expected_failure*) ;; *) exit 1 ;; esac
|
|
printf 'P4_PHASE_0_OK\n'`,
|
|
`set -e
|
|
cd /src/demo-cli
|
|
${write('src/main.rs', main)}
|
|
cargo fmt
|
|
cargo test -- --nocapture
|
|
cargo build --release
|
|
test -f target/wasm32-wasip2/release/demo-cli.wasm
|
|
sh -c '/src/demo-cli/target/wasm32-wasip2/release/demo-cli.wasm world' > /tmp/p4-cli.json
|
|
case "$(cat /tmp/p4-cli.json)" in *'"greeting":"hello world"'*) ;; *) exit 1 ;; esac
|
|
set +e
|
|
sh -c '/src/demo-cli/target/wasm32-wasip2/release/demo-cli.wasm'
|
|
code=$?
|
|
set -e
|
|
test "$code" = 7
|
|
printf 'P4_PHASE_1_OK\n'`,
|
|
`set -e
|
|
${write('/src/demo-node/package.json', '{"scripts":{"test":"node test.cjs"}}')}
|
|
${write('/src/demo-node/test.cjs', 'const fs = require("node:fs");\nrequire("node:assert/strict").equal(2 + 2, 4);\nfs.writeFileSync("result.txt", "P4 npm test passed\\n");')}
|
|
id=$(box create --ttl 600)
|
|
printf '%s\n' "$id" > /tmp/p4-box-id
|
|
trap 'box rm "$id"' EXIT
|
|
box put "$id" /src/demo-node /work
|
|
box exec "$id" --timeout 120 -- sh -c 'cd /work && npm test'
|
|
box get "$id" /work/result.txt /src/demo-node/result.txt
|
|
test "$(cat /src/demo-node/result.txt)" = 'P4 npm test passed'
|
|
box rm "$id"
|
|
trap - EXIT
|
|
printf 'P4_PHASE_2_OK\n'`,
|
|
`set -e
|
|
undo list --conversation 1 > /tmp/p4-undo.json
|
|
box ls > /tmp/p4-boxes
|
|
printf 'P4_PHASE_3_OK\n'`,
|
|
];
|
|
export default function p4(host: any) {
|
|
// Replace the console's transport before resume. No real model request can occur.
|
|
const faux = fauxProvider({ provider: 'openai', models: [{ id: 'gpt-6.1-sol' }], tokensPerSecond: 1000000 });
|
|
const respond = (transcript: any): any => {
|
|
faux.appendResponses([respond]);
|
|
const messages = transcript.messages;
|
|
const input = messages.findLast((message: any) => message.role === 'user');
|
|
const text = (message: any) => typeof message.content === 'string' ? message.content : (message.content ?? []).filter((part: any) => part.type === 'text').map((part: any) => part.text).join('\n');
|
|
const round = /P4 start (\d+)/.exec(text(input))?.[1];
|
|
if (!round) throw new Error(`Unexpected P4 input: ${JSON.stringify(input)}`);
|
|
const last = messages.findLast((message: any) => message.role !== 'system');
|
|
let phase = 0;
|
|
if (last.role !== 'user') {
|
|
const prior = /p4-\d+-(\d+)/.exec(last.toolCallId)?.[1];
|
|
if (last.isError || prior === undefined || !text(last).includes(`P4_PHASE_${prior}_OK`)) throw new Error(`P4 bash failed: ${JSON.stringify(last)}`);
|
|
phase = Number(prior) + 1;
|
|
}
|
|
if (phase === phases.length) return fauxAssistantMessage(`P4_COMPLETE_${round}`);
|
|
return fauxAssistantMessage([fauxToolCall('bash', { command: phases[phase], timeout: 300 }, { id: `p4-${round}-${phase}` })], { stopReason: 'toolUse' });
|
|
};
|
|
faux.setResponses([respond]);
|
|
host.models().setProvider(faux.provider);
|
|
return { name: 'p4' };
|
|
}
|