Files
OmniRoute/src/lib/conductor/faroProxy.ts
Diego Rodrigues de Sa e Souza 3638adeeae feat(dashboard): Faro chat with push-to-talk voice on the Conductor panel (PRD RF4) (#8222)
* feat(a2a): conductor bridge core — event mapping with canceled->cancelled

* feat(a2a): incremental SSE parser for conductor bridge

* feat(a2a): conductor bridge connection loop with persisted cursor and backoff

* feat(a2a): start conductor bridge at boot behind CONDUCTOR_HUB_URL

* fix(a2a): conductor bridge parses the hub's real SSE wire format (id/type in frame, data={ts,payload})

* docs(reference): document CONDUCTOR_HUB_URL/TOKEN in ENVIRONMENT.md (env-doc-sync gate)

* feat(a2a): fleet skills derived from the Conductor hub for the agent card

* feat(a2a): agent card announces Conductor fleet skills

* feat(dashboard): server-side hub proxy for the Conductor panel (whitelisted shapes, fail-open)

* feat(dashboard): /api/conductor proxy routes (fleet, task detail, cancel) behind management auth

* feat(dashboard): Conductor panel — fleet live view, task detail and cancel over /api/conductor proxy

* feat(dashboard): /api/conductor/ask — server-side proxy to Faro (spokesperson) with whitelisted {text,pending}

* chore(env): CONDUCTOR_SPOKESPERSON_URL declared in schema, .env.example and ENVIRONMENT.md

* feat(dashboard): Faro chat with push-to-talk voice on the Conductor panel

---------

Co-authored-by: backryun <bakryun0718@proton.me>
2026-08-12 02:00:20 -03:00

47 lines
1.5 KiB
TypeScript

/**
* Server-side proxy to Faro, the OmniConductor spokesperson (Conductor PRD RF4).
*
* Faro's `/ask` requires a valid hub credential (Bearer) — that token lives only
* in server env, so the browser talks to our /api/conductor/ask route, never to
* Faro directly. The response is whitelisted to {text, pending}: `pending` set
* means Faro is asking for confirmation (the UI offers Sim/Não); the safety gate
* itself lives in Faro's engine and is never bypassed here.
*/
import { z } from "zod";
const faroResponseSchema = z.object({
text: z.string(),
pending: z.unknown().nullish(),
});
export interface FaroAnswer {
ok: boolean;
text: string;
pending: unknown;
}
export interface FaroProxyOptions {
fetchImpl?: typeof fetch;
}
const DEFAULT_FARO_URL = "http://127.0.0.1:7920";
export async function askFaro(message: string, opts: FaroProxyOptions = {}): Promise<FaroAnswer> {
const base = process.env.CONDUCTOR_SPOKESPERSON_URL?.trim() || DEFAULT_FARO_URL;
const token = process.env.CONDUCTOR_HUB_TOKEN?.trim() ?? "";
try {
const doFetch = opts.fetchImpl ?? fetch;
const res = await doFetch(`${base}/ask`, {
method: "POST",
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
body: JSON.stringify({ message }),
});
if (!res.ok) return { ok: false, text: "", pending: null };
const parsed = faroResponseSchema.parse(await res.json());
return { ok: true, text: parsed.text, pending: parsed.pending ?? null };
} catch {
return { ok: false, text: "", pending: null };
}
}