Skip to content

Commit ca8d4ec

Browse files
IlyasShabiBridgeAR
authored andcommitted
refactor(aiguard): slim down sdk file (#9753)
1 parent 0ecdc5f commit ca8d4ec

5 files changed

Lines changed: 632 additions & 279 deletions

File tree

packages/dd-trace/src/aiguard/client.js

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
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+
*/
325
async 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
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use strict'
2+
3+
class AIGuardAbortError extends Error {
4+
/**
5+
* @param {string|undefined} reason
6+
* @param {Array<unknown>} tags
7+
* @param {Record<string, unknown>} tagProbabilities
8+
* @param {Array<unknown>} sds
9+
*/
10+
constructor (reason, tags, tagProbabilities, sds) {
11+
super(reason)
12+
this.name = 'AIGuardAbortError'
13+
this.reason = reason
14+
this.tags = tags
15+
this.tagProbabilities = tagProbabilities
16+
this.sds = sds || []
17+
}
18+
}
19+
20+
class AIGuardClientError extends Error {
21+
/**
22+
* @param {string} message
23+
* @param {{ telemetryType: string, errors?: Array<unknown>, cause?: Error }} opts
24+
*/
25+
constructor (message, opts) {
26+
super(message)
27+
this.name = 'AIGuardClientError'
28+
this.telemetryType = opts.telemetryType
29+
if (opts.errors) {
30+
this.errors = opts.errors
31+
}
32+
if (opts.cause) {
33+
this.cause = opts.cause
34+
}
35+
}
36+
}
37+
38+
module.exports = {
39+
AIGuardAbortError,
40+
AIGuardClientError,
41+
}

0 commit comments

Comments
 (0)