11'use strict'
22
3+ const tracerVersion = require ( '../../../../package.json' ) . version
4+ const { AIGuardClientError } = require ( './errors' )
5+ const { parseEvaluationResponse } = require ( './evaluation' )
6+ const TAGS = require ( './tags' )
7+
8+ /**
9+ * Resolves the AI Guard host for a Datadog site.
10+ *
11+ * @param {string } site
12+ * @returns {string }
13+ */
14+ function aiGuardHost ( site ) {
15+ return site . split ( '.' ) . length === 2 ? `app.${ site } ` : site
16+ }
17+
18+ /**
19+ * Sends a request to the AI Guard service.
20+ *
21+ * @param {object } body
22+ * @param {{ url: string, headers: Record<string, string>, timeout: number } } opts
23+ * @returns {Promise<{ status: number, body: unknown }> }
24+ */
325async function executeRequest ( body , opts ) {
426 const postData = JSON . stringify ( body )
527 const headers = {
@@ -22,4 +44,81 @@ async function executeRequest (body, opts) {
2244 }
2345}
2446
25- module . exports = executeRequest
47+ class AIGuardClient {
48+ #headers
49+ #evaluateUrl
50+ #timeout
51+
52+ /**
53+ * @param {import('../config/config-base') } config
54+ */
55+ constructor ( config ) {
56+ this . #headers = {
57+ 'DD-API-KEY' : config . DD_API_KEY ,
58+ 'DD-APPLICATION-KEY' : config . DD_APP_KEY ,
59+ 'DD-AI-GUARD-VERSION' : tracerVersion ,
60+ 'DD-AI-GUARD-SOURCE' : 'SDK' ,
61+ 'DD-AI-GUARD-LANGUAGE' : 'nodejs' ,
62+ }
63+ const endpoint = config . experimental . aiguard . endpoint || `https://${ aiGuardHost ( config . site ) } /api/v2/ai-guard`
64+ this . #evaluateUrl = `${ endpoint } /evaluate`
65+ this . #timeout = config . experimental . aiguard . timeout
66+ }
67+
68+ /**
69+ * Evaluates messages and converts the service response to the internal evaluation contract.
70+ *
71+ * @param {import('../../../../index').aiguard.Message[] } messages
72+ * @param {{ service: string, env: string } } meta
73+ * @returns {Promise<NonNullable<ReturnType<typeof parseEvaluationResponse>>> }
74+ */
75+ evaluate ( messages , meta ) {
76+ const payload = {
77+ data : {
78+ attributes : {
79+ messages,
80+ meta,
81+ } ,
82+ } ,
83+ }
84+ return executeRequest (
85+ payload ,
86+ { url : this . #evaluateUrl, headers : this . #headers, timeout : this . #timeout }
87+ )
88+ . then ( response => this . #parseResponse( response ) )
89+ . catch ( cause => {
90+ if ( cause instanceof AIGuardClientError ) throw cause
91+
92+ throw new AIGuardClientError ( `Unexpected error calling AI Guard service: ${ cause . message } ` , {
93+ cause,
94+ telemetryType : TAGS . ERROR_TYPE_CLIENT ,
95+ } )
96+ } )
97+ }
98+
99+ /**
100+ * Validates an AI Guard HTTP response.
101+ *
102+ * @param {{ status: number, body: unknown } } response
103+ * @returns {NonNullable<ReturnType<typeof parseEvaluationResponse>> }
104+ */
105+ #parseResponse ( response ) {
106+ if ( response . status !== 200 ) {
107+ throw new AIGuardClientError ( `AI Guard service call failed, status ${ response . status } ` , {
108+ errors : response . body ?. errors ,
109+ telemetryType : TAGS . ERROR_TYPE_STATUS ,
110+ } )
111+ }
112+
113+ const evaluation = parseEvaluationResponse ( response . body )
114+ if ( ! evaluation ) {
115+ throw new AIGuardClientError ( `AI Guard service returned unexpected response : ${ response . body } ` , {
116+ telemetryType : TAGS . ERROR_TYPE_RESPONSE ,
117+ } )
118+ }
119+
120+ return evaluation
121+ }
122+ }
123+
124+ module . exports = AIGuardClient
0 commit comments