Repository navigation
Expand file tree
/
Copy pathdecider.js
More file actions
34 lines (32 loc) · 3.3 KB
/
Copy pathdecider.js
File metadata and controls
34 lines (32 loc) · 3.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// The decider (Captain 2026-10-08): the router/decider that answers our yes-no and pick-one questions (route2.js task?/hard?, the browser
// agent's next action and "is it done?") is Perplexity's decider on OpenRouter, replacing TypeSafe Jev (half the price: $0.02/M in, $0 out).
// Endpoint /api/v1/systemone: { model, provider, state, questions } -> { answers, usage: { cost }, model }. One endpoint, Perplexity's own
// (perplexity/pplx-decider-v1.1-27b-20261006), and it is on OpenRouter's /api/v1/endpoints/zdr list (checked 2026-10-08), as is
// typesafe/jev-1.13. Prompt caching does not apply (no implicit caching on this model, and no chat prefix).
// Kill switch: env DECIDER_MODEL swaps the model id with no code change (e.g. "~typesafe/jev-latest" to go back to Jev). Every
// caller uses deciderModel(env); nothing else names the model.
import { aigOn, aigMeta, aigPayload, OR_MAX_PRICE } from "../model.js";
export const DECIDER_DEFAULT = "perplexity/pplx-decider-v1.1-27b";
export const DECIDER_IN_NANO = 20; // $0.02 per million input tokens; estimate only when usage.cost is missing
export const DECIDER_URL = "https://openrouter.ai/api/v1/systemone";
export const deciderModel = (env) => (env?.MODEL_SETTINGS?.decider && env.MODEL_SETTINGS.decider !== "default" ? env.MODEL_SETTINGS.decider : null) || String(env?.DECIDER_MODEL || "").trim() || DECIDER_DEFAULT;
// Provider rules like every other route: the model's own host only (perplexity/… → "perplexity", ~typesafe/jev-latest → "typesafe"),
// no fallbacks, ZDR only, the price ceiling. If that host ever drops ZDR, OpenRouter refuses and the caller sees the error (no retry without it).
export const deciderHost = (model) => String(model || DECIDER_DEFAULT).replace(/^~/, "").split("/")[0];
export const deciderProvider = (model = DECIDER_DEFAULT) => { const h = deciderHost(model); return { order: [h], only: [h], allow_fallbacks: false, zdr: true, max_price: OR_MAX_PRICE }; };
// POST one decision request. Through the AI Gateway when on (logged and tagged kind "decider"; gateway trouble falls back to OpenRouter
// directly, a gateway 429 is returned as-is, like orFetch). Returns the fetch Response.
export async function deciderFetch(env, body, { signal = null, title = "1Helm decider", meta = {}, fetchImpl = globalThis.fetch } = {}) {
const headers = { authorization: `Bearer ${env.OPENROUTER_API_KEY}`, "content-type": "application/json", "x-title": title };
if (env.APP_HOST) headers["http-referer"] = `https://${env.APP_HOST}`;
if (aigOn(env)) {
try {
const r = await fetchImpl(`https://gateway.ai.cloudflare.com/v1/${env.CF_ACCOUNT_ID}/${env.AIG_ID}/openrouter/v1/systemone`, { method: "POST", body, ...(signal ? { signal } : {}),
headers: { ...headers, "cf-aig-authorization": `Bearer ${env.AIG_TOKEN}`, "cf-aig-collect-log-payload": aigPayload(env), "cf-aig-no-wholesale": "true", "cf-aig-metadata": JSON.stringify(aigMeta(env, { kind: "decider", ...meta })) } });
if (r.ok || r.status === 429 || (r.status < 500 && r.status !== 401 && r.status !== 404)) return r;
const t = await r.clone().text().catch(() => "");
if (!/AiGatewayError/.test(t) && r.status !== 404) return r;
} catch (e) { if (signal?.aborted) throw e; }
}
return fetchImpl(DECIDER_URL, { method: "POST", headers, body, ...(signal ? { signal } : {}) });
}