Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200import { spawn } from "node:child_process";import { EventEmitter } from "node:events";import { createInterface } from "node:readline";
import { codexMcpArgs } from "./tools.mjs";import { VERSION } from "./version.mjs";
export class AppServer extends EventEmitter { constructor({ cwd, resumeThreadId = "", command = "codex", args = ["app-server", "--listen", "stdio://"], environment = {}, developerInstructions = "", // Empty means "whatever ~/.codex/config.toml says", which is how this // bridge has always chosen a model. A name here overrides it per thread. model = "", effort = "", }) { super(); this.cwd = cwd; this.command = command; this.args = args.includes("app-server") ? [...args, ...codexMcpArgs(cwd,environment)] : args; this.environment = environment; this.resumeThreadId = resumeThreadId; this.developerInstructions = developerInstructions; this.model = model; this.effort = effort; this.child = null; this.nextId = 1; this.pending = new Map(); this.threadId = null; this.turnId = null; this.closed = false; }
async connect() { this.child = spawn(this.command, this.args, { cwd: this.cwd, env: { ...process.env, ...this.environment, aesel: "1", EASEL_VERSION: VERSION, }, stdio: ["pipe", "pipe", "pipe"], });
this.child.once("error", (error) => this.#failAll(error)); this.child.once("exit", (code, signal) => { const suffix = signal ? ` (${signal})` : code === null ? "" : ` (${code})`; this.#failAll(new Error(`engine bridge closed${suffix}`)); this.emit("exit", { code, signal }); });
createInterface({ input: this.child.stdout }).on("line", (line) => { const trimmed = line.trim(); if (!trimmed) return; try { this.#receive(JSON.parse(trimmed)); } catch (error) { // See claude-server.mjs: a line that was meant to be JSON is a protocol // fault; anything else is the CLI addressing a person and belongs in the // log rather than in someone's transcript as an error. if (trimmed.startsWith("{") || trimmed.startsWith("[")) { this.emit("protocolError", new Error(`invalid engine message: ${error.message}`)); } else { this.emit("log", trimmed); } } });
createInterface({ input: this.child.stderr }).on("line", (line) => { if (line.trim()) this.emit("log", line.trim()); });
await this.request("initialize", { clientInfo: { name: "easel", title: "aesel", version: VERSION, }, capabilities: { experimentalApi: true }, }); this.notify("initialized", {}); return this.resumeThreadId ? this.resumeThread(this.resumeThreadId) : this.newThread(); }
async newThread() { const result = await this.request("thread/start", { cwd: this.cwd, approvalPolicy: "on-request", approvalsReviewer: "user", sandbox: "workspace-write", ephemeral: false, sessionStartSource: this.threadId ? "clear" : "startup", ...(this.model ? { model: this.model } : {}), ...(this.developerInstructions ? { developerInstructions: this.developerInstructions } : {}), }); this.threadId = result.thread.id; this.turnId = null; return result; }
async resumeThread(threadId) { const result = await this.request("thread/resume", { threadId, cwd: this.cwd, approvalPolicy: "on-request", approvalsReviewer: "user", sandbox: "workspace-write", ...(this.model ? { model: this.model } : {}), ...(this.developerInstructions ? { developerInstructions: this.developerInstructions } : {}), }); this.threadId = result.thread.id; this.turnId = null; return result; }
async startTurn(text, {images=[]}={}) { if (!this.threadId) throw new Error("thread is not ready"); const result = await this.request("turn/start", { threadId: this.threadId, input: [{ type: "text", text },...images.map(image=>({type:"image",url:`data:${image.mimeType};base64,${image.data}`}))], ...(this.effort ? { effort: this.effort } : {}), }); this.turnId = result.turn.id; return result; }
async interrupt() { if (!this.threadId || !this.turnId) return; await this.request("turn/interrupt", { threadId: this.threadId, turnId: this.turnId, }); }
request(method, params) { const id = this.nextId++; this.#send({ method, id, params }); return new Promise((resolve, reject) => { this.pending.set(id, { resolve, reject }); }); }
notify(method, params) { this.#send({ method, params }); }
respond(id, result) { this.#send({ id, result }); }
reject(id, code, message) { this.#send({ id, error: { code, message } }); }
close() { if (this.closed) return; this.closed = true; this.child?.kill("SIGTERM"); }
#send(message) { if (!this.child?.stdin.writable) throw new Error("engine bridge is not writable"); this.child.stdin.write(`${JSON.stringify(message)}\n`); }
#receive(message) { if (Object.hasOwn(message, "id") && !message.method) { const waiter = this.pending.get(message.id); if (!waiter) return; this.pending.delete(message.id); if (message.error) { waiter.reject(new Error(message.error.message || "engine request failed")); } else { waiter.resolve(message.result); } return; }
if (Object.hasOwn(message, "id") && message.method) { this.emit("request", message); return; }
if (message.method) this.emit("notification", message); }
#failAll(error) { if (this.closed && this.pending.size === 0) return; for (const { reject } of this.pending.values()) reject(error); this.pending.clear(); this.emit("fatal", error); }}