Files
codex/codex-rs/wasm-harness/examples/browser/index.html
T

552 lines
16 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Codex WASM Harness Prototype</title>
<style>
:root {
color-scheme: light;
font-family:
ui-serif, "Iowan Old Style", "Palatino Linotype", Georgia, serif;
background: #f2eddf;
color: #1f2a24;
}
body {
margin: 0;
min-height: 100vh;
background:
radial-gradient(circle at 20% 10%, #ffc96b 0 9rem, transparent 9rem),
linear-gradient(135deg, #f8f2e3 0%, #dfecdc 48%, #c7dde7 100%);
}
main {
box-sizing: border-box;
max-width: 960px;
margin: 0 auto;
padding: 48px 20px;
}
h1 {
max-width: 760px;
margin: 0 0 12px;
font-size: clamp(2.4rem, 7vw, 5.5rem);
line-height: 0.92;
letter-spacing: -0.06em;
}
p {
max-width: 720px;
font-size: 1.1rem;
}
form {
display: grid;
grid-template-columns: 1fr auto;
gap: 12px;
margin: 32px 0;
}
input,
button {
border: 2px solid #1f2a24;
border-radius: 999px;
font: inherit;
font-size: 1.05rem;
padding: 14px 18px;
}
input {
background: #fffaf0;
}
button {
background: #1f2a24;
color: #fffaf0;
cursor: pointer;
}
label {
display: grid;
gap: 6px;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.82rem;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.api-key-panel {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 14px;
align-items: end;
margin: 28px 0 0;
border: 2px solid rgb(31 42 36 / 0.26);
border-radius: 28px;
background: rgb(255 250 240 / 0.68);
padding: 18px;
}
.api-key-panel h2 {
margin: 0 0 8px;
font-size: 1.1rem;
letter-spacing: -0.03em;
}
.api-key-panel p {
margin: 0;
font-size: 0.95rem;
}
.api-key-panel label {
grid-column: 1 / -1;
}
.api-key-actions {
display: flex;
flex-wrap: wrap;
grid-column: 1 / -1;
gap: 10px;
align-items: center;
}
.secondary-button {
background: transparent;
color: #1f2a24;
}
.api-key-status {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.85rem;
}
#events {
display: grid;
gap: 12px;
}
.event {
border: 2px solid rgb(31 42 36 / 0.22);
border-radius: 20px;
background: rgb(255 250 240 / 0.75);
padding: 14px 16px;
box-shadow: 0 16px 40px rgb(31 42 36 / 0.08);
}
.event strong {
display: block;
margin-bottom: 6px;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.82rem;
letter-spacing: 0.08em;
text-transform: uppercase;
}
pre {
margin: 0;
overflow: auto;
white-space: pre-wrap;
}
@media (max-width: 640px) {
form,
.api-key-panel {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<main>
<h1>Codex WASM harness prototype</h1>
<p>
This page exercises the real Codex browser boundary: submit a prompt,
stream Codex protocol events from WASM, and resolve when the turn
completes.
</p>
<section class="api-key-panel" aria-labelledby="api-key-title">
<div>
<h2 id="api-key-title">Responses API key</h2>
<p>
Local prototype only. The key is stored in this browser's
localStorage and is sent directly from this page to the Responses
API when you run a turn.
</p>
</div>
<label>
API key
<input
id="api-key"
type="password"
placeholder="sk-..."
autocomplete="off"
spellcheck="false"
/>
</label>
<div class="api-key-actions">
<button id="clear-api-key" class="secondary-button" type="button">
Forget key
</button>
<output id="api-key-status" class="api-key-status"></output>
</div>
</section>
<form id="prompt-form">
<input
id="prompt"
value="write hello world in JavaScript, run it with exec, and tell me the result"
autocomplete="off"
/>
<button type="submit">Run turn</button>
</form>
<section id="events" aria-live="polite"></section>
</main>
<script type="module">
import init, {
BrowserAppServer,
} from "../pkg/codex_wasm_harness.js?v=20260416a";
await init("../pkg/codex_wasm_harness_bg.wasm?v=20260416a");
const form = document.querySelector("#prompt-form");
const input = document.querySelector("#prompt");
const events = document.querySelector("#events");
const apiKeyInput = document.querySelector("#api-key");
const apiKeyStatus = document.querySelector("#api-key-status");
const clearApiKeyButton = document.querySelector("#clear-api-key");
const API_KEY_STORAGE_KEY = "codexWasmHarness.apiKey";
const app = new BrowserAppServer(readStoredApiKey());
const sandbox = createSandboxKernel();
let nextRequestId = 1;
let threadId = null;
const browserSessionOptions = {
cwd: "/workspace",
instructions: {
developer: [
"Code mode runs inside AppKernel, exposed in this demo through a browser iframe sandbox.",
"Browser network APIs and OPFS are available to code running in AppKernel.",
"Approved OPFS path layout:",
"- /workspace: active workspace files and entrypoints",
"- /workspace/cache: fetched artifacts and caches",
"- /workspace/repos: cloned or mirrored source trees",
"Approved GitHub URL prefixes:",
"- https://github.com/openai/",
"- https://raw.githubusercontent.com/openai/",
"Fetch, cache, and search source code yourself using the available browser APIs instead of assuming the harness preloads repository contents.",
].join("\n"),
},
};
apiKeyInput.value = readStoredApiKey();
updateApiKeyStatus();
app.setSessionOptions(browserSessionOptions);
app.setEventHandler(handleServerEvent);
app.set_code_executor(async (requestJson) =>
sandbox.execute(requestJson),
);
await app.notify({ method: "initialized" });
renderEvent({ type: "page_ready" });
apiKeyInput.addEventListener("input", () => {
const apiKey = apiKeyInput.value.trim();
writeStoredApiKey(apiKey);
threadId = null;
app.set_api_key(apiKey);
updateApiKeyStatus();
});
clearApiKeyButton.addEventListener("click", () => {
apiKeyInput.value = "";
writeStoredApiKey("");
threadId = null;
app.set_api_key("");
updateApiKeyStatus();
});
form.addEventListener("submit", async (event) => {
event.preventDefault();
events.replaceChildren();
renderEvent({ type: "submit_clicked" });
try {
const currentThreadId = await ensureThread();
await request("turn/start", {
threadId: currentThreadId,
input: [{ type: "text", text: input.value }],
});
} catch (error) {
renderEvent({
type: "page_error",
message: error instanceof Error ? error.message : String(error),
});
}
});
window.addEventListener("error", (event) => {
renderEvent({
type: "window_error",
message: event.message,
stack:
event.error instanceof Error && event.error.stack
? event.error.stack
: null,
});
});
window.addEventListener("unhandledrejection", (event) => {
renderEvent({
type: "unhandled_rejection",
message:
event.reason instanceof Error
? event.reason.message
: String(event.reason),
stack:
event.reason instanceof Error && event.reason.stack
? event.reason.stack
: null,
});
});
function readStoredApiKey() {
try {
return localStorage.getItem(API_KEY_STORAGE_KEY) ?? "";
} catch {
return "";
}
}
function writeStoredApiKey(apiKey) {
try {
if (apiKey) {
localStorage.setItem(API_KEY_STORAGE_KEY, apiKey);
} else {
localStorage.removeItem(API_KEY_STORAGE_KEY);
}
} catch {
apiKeyStatus.textContent =
"localStorage is unavailable in this browser context.";
}
}
function updateApiKeyStatus() {
apiKeyStatus.textContent = apiKeyInput.value
? "API key stored in localStorage for this browser."
: "No API key stored. Enter a key to run the real Codex harness.";
}
async function ensureThread() {
if (threadId) {
return threadId;
}
const response = await request("thread/start", {});
threadId = response.thread.id;
return threadId;
}
async function request(method, params) {
const payload = { method, id: nextRequestId++, params };
renderEvent({ type: "client_request", payload });
const response = await app.request(payload);
renderEvent({ type: "client_response", method, response });
return response;
}
function handleServerEvent(event) {
if (event?.method === "thread/started") {
threadId = event.params?.thread?.id ?? threadId;
}
renderEvent(event);
}
function renderEvent(event) {
const card = document.createElement("article");
card.className = "event";
const title = document.createElement("strong");
title.textContent = event.method ?? event.type ?? "event";
const body = document.createElement("pre");
body.textContent = JSON.stringify(event, null, 2);
card.append(title, body);
events.append(card);
}
function createSandboxKernel() {
const iframe = document.createElement("iframe");
iframe.setAttribute("sandbox", "allow-scripts allow-same-origin");
iframe.style.display = "none";
iframe.srcdoc = `
<!doctype html>
<html>
<body>
<script>
const EXIT_SIGNAL = Symbol("codexExit");
function formatValue(value) {
if (typeof value === "string") {
return value;
}
if (value === undefined || value === null) {
return "";
}
try {
return JSON.stringify(value);
} catch {
return String(value);
}
}
function cloneJson(value) {
return JSON.parse(JSON.stringify(value ?? null));
}
window.addEventListener("message", async (event) => {
const data = event.data;
if (!data || data.type !== "exec_js") {
return;
}
try {
const request = JSON.parse(data.requestJson);
const outputParts = [];
const storedValues = cloneJson(request.stored_values) ?? {};
const toolsMetadata = Array.isArray(request.enabled_tools)
? request.enabled_tools
: [];
const consoleProxy = {
log: (...args) => outputParts.push(args.map(formatValue).join(" ")),
error: (...args) => outputParts.push(args.map(formatValue).join(" ")),
};
const text = (value) => {
const formatted = formatValue(value);
if (formatted) {
outputParts.push(formatted);
}
};
const notify = text;
const store = (key, value) => {
if (typeof key !== "string" || !key) {
throw new Error("store(key, value) requires a non-empty string key");
}
storedValues[key] = cloneJson(value);
};
const load = (key) => storedValues[key];
const tools = new Proxy(
{},
{
get(_target, prop) {
return async () => {
throw new Error(
"Nested tool " +
String(prop) +
" is unavailable in the browser prototype",
);
};
},
},
);
let errorText = null;
const runner = new Function(
"tools",
"text",
"output_text",
"image",
"store",
"load",
"notify",
"yield_control",
"exit",
"ALL_TOOLS",
"console",
"return (async () => {\\n" + request.source + "\\n})();",
);
try {
const result = await runner(
tools,
text,
text,
text,
store,
load,
notify,
async () => {},
() => {
throw EXIT_SIGNAL;
},
toolsMetadata,
consoleProxy,
);
const formattedResult = formatValue(result);
if (formattedResult) {
outputParts.push(formattedResult);
}
} catch (error) {
if (error !== EXIT_SIGNAL) {
errorText =
error instanceof Error ? error.message : String(error);
}
}
parent.postMessage(
{
type: "exec_js_result",
requestId: data.requestId,
ok: true,
output: outputParts.join("\\n"),
storedValues,
errorText,
},
"*",
);
} catch (error) {
parent.postMessage(
{
type: "exec_js_result",
requestId: data.requestId,
ok: false,
error: error instanceof Error ? error.message : String(error),
},
"*",
);
}
});
<\/script>
</body>
</html>`;
document.body.append(iframe);
let nextRequestId = 0;
const pending = new Map();
window.addEventListener("message", (event) => {
const data = event.data;
if (!data || data.type !== "exec_js_result") {
return;
}
const entry = pending.get(data.requestId);
if (!entry) {
return;
}
pending.delete(data.requestId);
if (data.ok) {
entry.resolve(data);
} else {
entry.reject(new Error(data.error ?? "sandbox execution failed"));
}
});
return {
execute(requestJson) {
return new Promise((resolve, reject) => {
nextRequestId += 1;
pending.set(nextRequestId, { resolve, reject });
iframe.contentWindow.postMessage(
{ type: "exec_js", requestId: nextRequestId, requestJson },
"*",
);
});
},
};
}
</script>
</body>
</html>