From c5d667ff69d78227e6c5210a0f20e945a1ab2362 Mon Sep 17 00:00:00 2001 From: itrytoohard Date: Mon, 8 Jun 2026 21:39:28 +0530 Subject: [PATCH 1/2] feat(frontend): typed openapi-fetch client over the generated schema Main already generates frontend/src/api/schema.ts from the backend OpenAPI document (#103) but ships only the types. This adds the thin runtime layer: an openapi-fetch client typed by those generated paths, so the renderer makes fully typed requests against the daemon's loopback /api/v1 surface instead of hand-writing fetch calls. Co-Authored-By: Claude Opus 4.8 --- frontend/package-lock.json | 18 ++++++++++++++++++ frontend/package.json | 3 +++ frontend/src/api/client.ts | 11 +++++++++++ 3 files changed, 32 insertions(+) create mode 100644 frontend/src/api/client.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7eeda0af..d9552168 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -7,6 +7,9 @@ "": { "name": "agent-orchestrator-frontend", "version": "0.0.0", + "dependencies": { + "openapi-fetch": "^0.17.0" + }, "devDependencies": { "electron": "^33.0.0", "typescript": "^5.6.0" @@ -673,6 +676,21 @@ "wrappy": "1" } }, + "node_modules/openapi-fetch": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.17.0.tgz", + "integrity": "sha512-PsbZR1wAPcG91eEthKhN+Zn92FMHxv+/faECIwjXdxfTODGSGegYv0sc1Olz+HYPvKOuoXfp+0pA2XVt2cI0Ig==", + "license": "MIT", + "dependencies": { + "openapi-typescript-helpers": "^0.1.0" + } + }, + "node_modules/openapi-typescript-helpers": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.1.0.tgz", + "integrity": "sha512-OKTGPthhivLw/fHz6c3OPtg72vi86qaMlqbJuVJ23qOvQ+53uw1n7HdmkJFibloF7QEjDrDkzJiOJuockM/ljw==", + "license": "MIT" + }, "node_modules/p-cancelable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index b58407e8..ec5b2ce9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,6 +9,9 @@ "typecheck": "tsc --noEmit", "start": "npm run build && electron ." }, + "dependencies": { + "openapi-fetch": "^0.17.0" + }, "devDependencies": { "electron": "^33.0.0", "typescript": "^5.6.0" diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts new file mode 100644 index 00000000..55ac3eff --- /dev/null +++ b/frontend/src/api/client.ts @@ -0,0 +1,11 @@ +import createClient from "openapi-fetch"; +import type { paths } from "./schema"; + +// Typed client for the daemon's loopback /api/v1 surface. Request and response +// types come from ./schema.ts, which is generated from the backend OpenAPI +// document (`npm run api` at the repo root) — never hand-maintained. +export const api = createClient({ baseUrl: "http://127.0.0.1:3001" }); + +// Re-export the generated component schemas for convenient use in the renderer, +// e.g. components["schemas"]["Project"], "Session", "APIError". +export type { components, paths } from "./schema"; From 93998d705e3262da9cde094bd10910f14e1d6469 Mon Sep 17 00:00:00 2001 From: itrytoohard Date: Mon, 8 Jun 2026 21:47:15 +0530 Subject: [PATCH 2/2] fix(frontend): derive API base URL from daemon port, drop hardcoded 3001 The daemon binds AO_PORT (default 3001) and writes the actual port to running.json. Baking http://127.0.0.1:3001 into the client meant any user overriding AO_PORT would silently hit the wrong address. Replace the pre-built constant-port client with a createApiClient(baseUrl) factory plus a baseURLForPort helper, so the wiring layer passes the port discovered from running.json (via IPC). DEFAULT_PORT remains only as a documented fallback. Co-Authored-By: Claude Opus 4.8 --- frontend/src/api/client.ts | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 55ac3eff..557c8665 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -1,10 +1,25 @@ import createClient from "openapi-fetch"; import type { paths } from "./schema"; -// Typed client for the daemon's loopback /api/v1 surface. Request and response -// types come from ./schema.ts, which is generated from the backend OpenAPI -// document (`npm run api` at the repo root) — never hand-maintained. -export const api = createClient({ baseUrl: "http://127.0.0.1:3001" }); +// The daemon binds DEFAULT_PORT unless AO_PORT overrides it (see config.go), +// and writes the port it actually bound to running.json (runfile.File.Port). +// DEFAULT_PORT only mirrors config.DefaultPort as a last-resort fallback for +// when the runfile hasn't been read yet — clients must NOT assume it. +export const DEFAULT_PORT = 3001; + +// baseURLForPort builds the loopback base URL for a known daemon port. Pass the +// port read from running.json; default only as a fallback. +export const baseURLForPort = (port: number = DEFAULT_PORT): string => + `http://127.0.0.1:${port}`; + +// createApiClient builds a typed openapi-fetch client for the daemon's +// /api/v1 surface. The renderer cannot read running.json directly, so the +// wiring layer must read it (via IPC to the main process) and pass the resolved +// base URL here — hardcoding DEFAULT_PORT would silently break any user who +// overrides AO_PORT. Request/response types come from ./schema.ts, generated +// from the backend OpenAPI document (`npm run api` at the repo root). +export const createApiClient = (baseUrl: string) => + createClient({ baseUrl }); // Re-export the generated component schemas for convenient use in the renderer, // e.g. components["schemas"]["Project"], "Session", "APIError".