eleusis / worker.js
nph4rd's picture
Add NL-to-Python rule translation (Cloudflare Worker proxy; disabled until URL wired)
46b1fd8 verified
Raw
History Blame Contribute Delete
3.44 kB
/**
* Eleusis NL→Python rule translator — Cloudflare Worker proxy.
*
* Keeps the Prime Inference API key server-side and exposes exactly ONE
* capability: translating a short natural-language card rule into a Python
* predicate, with a pinned model, pinned prompt, and pinned token caps —
* so the endpoint is useless as a general LLM proxy.
*
* Deploy:
* 1. Cloudflare dashboard → Workers & Pages → Create Worker → paste this.
* 2. Settings → Variables → add SECRET PRIME_API_KEY = <your pit_... key>
* 3. Deploy; give the workers.dev URL back so the Space can be wired to it.
* Optional hardening: add a dashboard Rate Limiting rule (e.g. 20 req/min/IP).
*/
const ALLOWED_ORIGINS = [
"https://nph4rd-eleusis.static.hf.space",
"http://localhost:7861",
];
const MODEL = "mistralai/mistral-small-3.2-24b-instruct";
const SYSTEM = `You translate natural-language card-game rules into a single Python predicate.
Available: card.rank (int, A=1,J=11,Q=12,K=13), card.suit ('hearts','diamonds','clubs','spades'),
card.color ('red','black'), card.is_face (rank 11-13), mainline (list of already-accepted cards, oldest to newest; may be empty).
Relational rules compare card to mainline[-1] and must be guarded: not mainline or <condition>.
Reply with ONLY the predicate expression, no backticks, no explanation.`;
function cors(origin) {
const allowed = ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];
return {
"Access-Control-Allow-Origin": allowed,
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "content-type",
"Content-Type": "application/json",
};
}
export default {
async fetch(request, env) {
const origin = request.headers.get("Origin") || "";
const headers = cors(origin);
if (request.method === "OPTIONS") return new Response(null, { headers });
if (request.method !== "POST")
return new Response(JSON.stringify({ error: "POST only" }), { status: 405, headers });
let text;
try {
({ text } = await request.json());
} catch {
return new Response(JSON.stringify({ error: "bad json" }), { status: 400, headers });
}
if (typeof text !== "string" || !text.trim() || text.length > 300)
return new Response(JSON.stringify({ error: "text must be 1-300 chars" }), { status: 400, headers });
const upstream = await fetch("https://api.pinference.ai/api/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${env.PRIME_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: MODEL,
max_tokens: 120,
temperature: 0,
messages: [
{ role: "system", content: SYSTEM },
{ role: "user", content: text.trim() },
],
}),
});
if (!upstream.ok) {
return new Response(
JSON.stringify({ error: `upstream ${upstream.status}` }),
{ status: 502, headers }
);
}
const data = await upstream.json();
let out = (data.choices?.[0]?.message?.content || "").trim();
// strip code fences and lambda wrappers the model sometimes adds
out = out.replace(/^```(python)?|```$/g, "").trim();
const lam = out.match(/^\(?\s*lambda\s+[^:]*:\s*([\s\S]*?)\)?\s*$/);
if (lam) out = lam[1].trim();
return new Response(JSON.stringify({ predicate: out }), { headers });
},
};