Files
OmniRoute/open-sse/executors/tinycms.ts
Praveen K Palaniswamy 65e81158ab fix(ollama): route models by advertised capability (#11088)
Landed with the design call resolved per the owner's pick — **option 1**: the synced store is now endpoint-agnostic (persistDiscoveredModels and managedModelImport no longer drop non-chat models at write time), and chat selectability moved to read time (auto-pool expansion in autoStrategy applies filterChatSelectableModels; the models-route projection already had its chatOnly filter). Your discovery test now passes end-to-end (3/3): /api/show capabilities persist per connection and image/embedding requests route through the advertising host.

Reconciliation notes: conflicted areas merged onto the current tip (adobe discovery import, requestedModel preflight signature, resolvedProvider fast-path coexists with the synced-route override — explicit resolution wins); carried base-red drains (#10055 memoization, #11071 test variants) dropped as already-landed; the managed-model-import exclusion test was propagated to the new contract (image/video models persist; the read filter still hides them from chat pickers — pinned by a new assertion). Full battery: 205/206 focused (the one red is a confirmed periodic-timer timing flake on the loaded devbox — 20/20 isolated), autoCombo vitest 30/30, combo suites 46/46, gates + typecheck clean.

Thank you @yourspraveen — the capability probe + routing design was right; it just needed the store contract opened up. Fixes #11087.
2026-08-23 11:45:01 -03:00

132 lines
4.5 KiB
TypeScript

import { randomUUID } from "node:crypto";
import { BaseExecutor, type ExecuteInput, type ExecutorExecuteResult } from "./base.ts";
import { makeExecutorErrorResult as makeErrorResult } from "../utils/error.ts";
import { initTinyCmsWasm, generateSecurePayload } from "./tinycmsSigner.ts";
const CHAT_URL = "https://gov.freegpt.win/api/openai/oneapi/v1/chat/completions";
const CHALLENGE_URL = "https://gov.freegpt.win/api/challenge";
let publicIp: string | null = null;
let lastIpFetch = 0;
async function getPublicIp(): Promise<string> {
const now = Date.now();
if (publicIp && now - lastIpFetch < 300000) {
return publicIp;
}
try {
const res = await fetch("https://api64.ipify.org?format=json");
const json = (await res.json()) as { ip: string };
publicIp = json.ip;
lastIpFetch = now;
return publicIp;
} catch {
return publicIp || "127.0.0.1";
}
}
async function fetchChallenge(uuid: string): Promise<any> {
const res = await fetch(CHALLENGE_URL, {
method: "GET",
headers: {
uuid: uuid,
"x-origin": "https://gov.freegpt.win",
Accept: "application/json",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
},
});
if (!res.ok) {
throw new Error(`Failed to fetch challenge: ${res.status}`);
}
return await res.json();
}
export class TinyCmsExecutor extends BaseExecutor {
constructor() {
super("tinycms-web", { id: "tinycms-web", baseUrl: CHAT_URL });
}
async execute(input: ExecuteInput): Promise<ExecutorExecuteResult> {
const { body, credentials, signal } = input;
const bodyObj = (body || {}) as Record<string, any>;
// TinyCMS uses 'uuid' header for identification
const uuid = String(credentials?.apiKey ?? "").trim();
if (!uuid || !uuid.startsWith("R")) {
return makeErrorResult(
401,
"TinyCMS: Invalid or missing device UUID (must start with 'R')",
body,
CHAT_URL
);
}
try {
await initTinyCmsWasm();
const ip = await getPublicIp();
const challengeObj = await fetchChallenge(uuid);
const timestamp = Date.now().toString();
// Security context: this nonce is signed into `x-secure-signature` and
// reused as the session id, so it must be unpredictable. `node:crypto`
// randomUUID() is always available on the supported runtime — never fall
// back to a non-CSPRNG source (CodeQL js/insecure-randomness).
const nonceJs = randomUUID();
const securePayload = generateSecurePayload(
uuid,
timestamp,
nonceJs,
challengeObj.challenge,
ip,
challengeObj.difficulty
);
const signedHeaders: Record<string, string> = {
uuid: uuid,
"x-origin": "https://gov.freegpt.win",
referer: "https://gov.freegpt.win/",
"x-secure-challenge-id": challengeObj.challengeId,
"x-secure-challenge-expires-at": String(challengeObj.expiresAt),
"x-secure-challenge-version": challengeObj.version,
"x-secure-signature": securePayload.signature,
"x-secure-fingerprint": securePayload.fingerprint,
"x-secure-client-ip": securePayload.client_ip,
"x-secure-pow-seed-nonce": String(securePayload.pow.seed_nonce),
"x-secure-pow-nonce": String(securePayload.pow.nonce),
"x-secure-pow-hash": securePayload.pow.hash,
"x-secure-pow-difficulty": String(securePayload.pow.difficulty),
"x-secure-timestamp": timestamp,
"x-secure-nonce": nonceJs,
"x-secure-version": securePayload.v,
"x-session-id": nonceJs,
// Use configurable userid from providerSpecificData if present, otherwise generate one
// from the UUID (the server uses it for request attribution, not auth).
userid: String(credentials?.providerSpecificData?.userid ?? "") || uuid.slice(0, 20),
Accept: bodyObj.stream ? "text/event-stream" : "application/json",
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
};
const fetchOptions: RequestInit = {
method: "POST",
headers: signedHeaders,
body: JSON.stringify(bodyObj),
signal,
};
const response = await fetch(CHAT_URL, fetchOptions);
return {
response,
url: CHAT_URL,
headers: Object.fromEntries(response.headers.entries()),
transformedBody: bodyObj,
};
} catch (err: any) {
return makeErrorResult(500, `TinyCMS Error: ${err.message}`, body, CHAT_URL);
}
}
}