Skip to content

Commit 5c8de11

Browse files
committed
feat(agentos-server): implement SRS proxy functions for policy configuration and tool call evaluation
1 parent 3ce41b9 commit 5c8de11

1 file changed

Lines changed: 61 additions & 6 deletions

File tree

‎packages/agentos-server/src/routes/ingest.ts‎

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,48 @@ const KNOWN_KINDS = new Set([
2222

2323
const MAX_BATCH = 500;
2424

25+
// SRS reverse-proxy for library-mode (Python) SDKs. The SDK authenticates with
26+
// the ingest bearer token and never sees the SRS key or endpoint — agentos
27+
// injects them server-side here, mirroring the dashboard proxy in policies.ts.
28+
const SRS_BASE = (process.env["SRS_BASE_URL"] ?? "").replace(/\/+$/, "");
29+
const SRS_KEY = process.env["SRS_API_KEY"] ?? "";
30+
31+
async function forwardToSrs(
32+
res: import("express").Response,
33+
method: string,
34+
path: string,
35+
body?: unknown,
36+
): Promise<void> {
37+
if (!SRS_BASE) {
38+
res.status(503).json({
39+
error: { code: "SRS_NOT_CONFIGURED", message: "Policy service (SRS) is not configured." },
40+
});
41+
return;
42+
}
43+
try {
44+
const r = await fetch(`${SRS_BASE}${path}`, {
45+
method,
46+
headers: {
47+
"x-api-key": SRS_KEY,
48+
...(body !== undefined ? { "content-type": "application/json" } : {}),
49+
},
50+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
51+
});
52+
const text = await r.text();
53+
let parsed: unknown = {};
54+
if (text) {
55+
try {
56+
parsed = JSON.parse(text);
57+
} catch {
58+
parsed = { raw: text };
59+
}
60+
}
61+
res.status(r.status).json(parsed);
62+
} catch (err) {
63+
res.status(502).json({ error: { code: "SRS_UNREACHABLE", message: (err as Error).message } });
64+
}
65+
}
66+
2567
/** Validate + normalize one wire event. Returns null to skip (counted, not fatal). */
2668
function coerce(raw: unknown): IngestEvent | null {
2769
if (!raw || typeof raw !== "object") return null;
@@ -92,16 +134,29 @@ ingestRouter.post("/events", async (req, res, next) => {
92134
}
93135
});
94136

95-
// Policy config for an agent — lets the library-mode Python SDK resolve which
96-
// SRS policy applies to an agent the same way the TS harness does, over the
97-
// same ingest channel (no Mongo creds / no SRS key in the Python process).
98-
// Returns the {kind:"srs", endpoint, apiKey, policyId, principalId} block from
99-
// the agent's agent_policies binding, or null when unbound.
137+
// Policy config for an agent — lets the library-mode Python SDK learn *which*
138+
// policy is bound to an agent, over the ingest channel. Returns ONLY the binding
139+
// (policyId + principalId), never the SRS endpoint/api key: the SDK reaches SRS
140+
// exclusively through the proxy routes below, so the key stays server-side.
100141
ingestRouter.get("/policy-config/:name", async (req, res, next) => {
101142
try {
102143
const policy = await srsPolicyForAgent(req.params["name"]!);
103-
res.json({ policy: policy ?? null });
144+
if (!policy || !policy["policyId"]) {
145+
res.json({ policy: null });
146+
return;
147+
}
148+
res.json({ policy: { policyId: policy["policyId"], principalId: policy["principalId"] } });
104149
} catch (err) {
105150
next(err);
106151
}
107152
});
153+
154+
// SRS proxy (ingest-token auth, key injected server-side). The Python SDK
155+
// fetches a bound RAI policy's guardrail config and evaluates each tool call
156+
// without ever holding the SRS key or a network path to SRS.
157+
ingestRouter.get("/rai/policies/:id", (req, res) =>
158+
forwardToSrs(res, "GET", `/v1/rai/policies/${encodeURIComponent(req.params["id"]!)}`),
159+
);
160+
ingestRouter.post("/guardrails/evaluate-tool-call", (req, res) =>
161+
forwardToSrs(res, "POST", "/v1/guardrails/evaluate-tool-call", req.body ?? {}),
162+
);

0 commit comments

Comments
 (0)