import { Agent } from "agents";
import type { GeneratedEnv } from "@ayjnt/env";
type Sample = {
fetchedAt: number;
from: string; to: string;
forecast: number;
actual: number | null;
index: string;
};
type State = {
intervalSeconds: number;
scheduleId: string | null;
current: Sample | null;
history: Sample[];
error: string | null;
};
const CARBON_URL = "https://api.carbonintensity.org.uk/intensity";
const MAX_HISTORY = 60;
export default class CarbonAgent extends Agent<GeneratedEnv, State> {
override initialState: State = {
intervalSeconds: 0, scheduleId: null,
current: null, history: [], error: null,
};
/** Recurring callback. Errors are stashed in state, not re-thrown. */
async tick(): Promise<void> {
try {
const res = await fetch(CARBON_URL, { headers: { accept: "application/json" } });
const body = await res.json() as { data: { from: string; to: string;
intensity: { forecast: number; actual: number | null; index: string } }[] };
const row = body.data[0]!;
const sample: Sample = {
fetchedAt: Date.now(),
from: row.from, to: row.to,
forecast: row.intensity.forecast,
actual: row.intensity.actual,
index: row.intensity.index,
};
this.setState({
...this.state,
current: sample,
history: [sample, ...this.state.history].slice(0, MAX_HISTORY),
error: null,
});
} catch (err) {
this.setState({ ...this.state, error: String(err) });
}
}
async startPolling(intervalSeconds: number) {
await this.stopPolling();
await this.tick(); // fire once now
const schedule = await this.scheduleEvery(intervalSeconds, "tick");
this.setState({ ...this.state, intervalSeconds, scheduleId: schedule.id });
}
async stopPolling(): Promise<void> {
if (this.state.scheduleId) {
try { await this.cancelSchedule(this.state.scheduleId); } catch {}
}
this.setState({ ...this.state, intervalSeconds: 0, scheduleId: null });
}
override async onRequest(request: Request): Promise<Response> {
if (request.method === "POST") {
const body = await request.json() as { intervalSeconds?: number; stop?: boolean };
if (body.stop) { await this.stopPolling(); return Response.json({ ok: true }); }
await this.startPolling(body.intervalSeconds ?? 60);
return Response.json({ ok: true, intervalSeconds: body.intervalSeconds ?? 60 });
}
return Response.json({ instance: this.name, ...this.state });
}
}