@@ -22,6 +22,48 @@ const KNOWN_KINDS = new Set([
2222
2323const 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). */
2668function 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.
100141ingestRouter . 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