File size: 4,913 Bytes
21c7db9 | 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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | import { API_BASE, ENV_BASE } from "./constants";
import type {
EnvCatalog,
EnvWsMessage,
EnvObservation,
ModelStatus,
ResetEnvOptions,
StepCandidatePayload,
StepResponse,
} from "./types";
async function fetchJson<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(`${API_BASE}${path}`, init);
if (!res.ok) {
const body = await res.text();
throw new Error(`API ${path} failed (${res.status}): ${body.slice(0, 240)}`);
}
return (await res.json()) as T;
}
let envSocket: WebSocket | null = null;
const pendingEnvMessages: Array<{
resolve: (value: unknown) => void;
reject: (reason: unknown) => void;
}> = [];
function envWsUrl(): string {
const base = ENV_BASE.replace(/\/$/, "");
return `${base.replace(/^http/, "ws")}/ws`;
}
async function ensureEnvSocket(): Promise<WebSocket> {
if (envSocket?.readyState === WebSocket.OPEN) return envSocket;
if (envSocket?.readyState === WebSocket.CONNECTING) {
await new Promise((resolve) => setTimeout(resolve, 80));
return ensureEnvSocket();
}
const socket = new WebSocket(envWsUrl());
envSocket = socket;
socket.onmessage = (event) => {
const pending = pendingEnvMessages.shift();
if (!pending) return;
try {
const message = JSON.parse(event.data as string) as EnvWsMessage;
if (message.type === "error") {
const data = message.data;
const messageText =
data && typeof data === "object" && "message" in data
? String((data as Record<string, unknown>).message)
: "Env service returned an error";
pending.reject(new Error(messageText));
return;
}
pending.resolve(message.data);
} catch (err) {
pending.reject(err);
}
};
socket.onerror = () => {
const pending = pendingEnvMessages.shift();
if (pending) pending.reject(new Error(`Unable to connect to env service at ${envWsUrl()}`));
};
socket.onclose = () => {
envSocket = null;
};
await new Promise<void>((resolve, reject) => {
const timeout = window.setTimeout(() => reject(new Error(`Env service timeout at ${envWsUrl()}`)), 2500);
socket.onopen = () => {
window.clearTimeout(timeout);
resolve();
};
});
return socket;
}
export async function envWsSend<T>(type: string, data: unknown): Promise<T> {
const socket = await ensureEnvSocket();
return new Promise<T>((resolve, reject) => {
pendingEnvMessages.push({
resolve: (value) => resolve(value as T),
reject,
});
socket.send(JSON.stringify({ type, data }));
});
}
export function closeEnvSocket(): void {
try {
envSocket?.close();
} catch {
// Ignore close errors during route teardown.
} finally {
envSocket = null;
pendingEnvMessages.splice(0);
}
}
export async function fetchCatalog(): Promise<EnvCatalog> {
return fetchJson<EnvCatalog>("/env/catalog");
}
export async function resetEnv(options: ResetEnvOptions = {}): Promise<EnvObservation> {
return fetchJson<EnvObservation>("/env/reset", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(options),
});
}
export async function orchestrateStep(): Promise<Record<string, unknown>> {
return fetchJson<Record<string, unknown>>("/agents/orchestrate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({}),
});
}
export async function stepCandidate(payload: StepCandidatePayload): Promise<StepResponse> {
return fetchJson<StepResponse>("/env/step_candidate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
}
export async function fetchTrace(): Promise<Array<Record<string, unknown>>> {
return fetchJson<Array<Record<string, unknown>>>("/env/trace");
}
export async function fetchTrainingMetrics(): Promise<Record<string, unknown>> {
return fetchJson<Record<string, unknown>>("/metrics/training");
}
export async function fetchBaselines(): Promise<Record<string, unknown>> {
return fetchJson<Record<string, unknown>>("/eval/run_baselines", { method: "POST" });
}
export async function fetchRewardBreakdown(): Promise<Record<string, unknown>> {
return fetchJson<Record<string, unknown>>("/env/reward_breakdown");
}
export async function fetchModelStatus(): Promise<ModelStatus> {
return fetchJson<ModelStatus>("/policy/model_status");
}
export async function fetchLegalActions(): Promise<Array<Record<string, unknown>>> {
return fetchJson<Array<Record<string, unknown>>>("/env/legal_actions");
}
export async function fetchUncertainty(): Promise<Record<string, unknown>> {
return fetchJson<Record<string, unknown>>("/env/uncertainty");
}
export async function fetchDosingEval(): Promise<Record<string, unknown>> {
return fetchJson<Record<string, unknown>>("/eval/run_dosing", { method: "POST" });
}
|