UNPKG

dd-trace

Version:

Datadog APM tracing client for JavaScript

114 lines (95 loc) 3.31 kB
'use strict' // Control-plane HTTP client for LLM Obs Experiments. Uses the global `fetch`, // so this module adds no new dependency; credentials and site come from config. const API_BASE_PATH = '/api/v2/llm-obs/v1' // Control-plane host for a Datadog site, e.g. // datadoghq.com -> api.datadoghq.com // us3.datadoghq.com -> api.us3.datadoghq.com // datad0g.com (staging)-> api.datad0g.com function apiHost (site) { return `api.${site}` } // Web-app host for dashboard URLs. Single-level sites (datadoghq.com, // ddog-gov.com) are served from the `app.` subdomain; regional sites // (us3.datadoghq.com, ap1.datadoghq.com) are used as-is. function appHost (site) { return site.split('.').length === 2 ? `app.${site}` : site } class ExperimentsClient { #apiKey #appKey #site #projectName #timeout #cachedProjectId constructor ({ apiKey, appKey, site, projectName, timeout = 30_000 } = {}) { this.#apiKey = apiKey this.#appKey = appKey this.#site = site this.#projectName = projectName this.#timeout = timeout this.#cachedProjectId = null } // Whether the client has everything it needs to talk to the control plane. get configured () { return Boolean(this.#apiKey && this.#appKey && this.#site) } get site () { return this.#site } // Dashboard URL base for the configured site, e.g. https://app.datadoghq.com get appBase () { return `https://${appHost(this.#site)}` } // Resolve the configured project's id (get-or-create), cached. ensureProjectId () { return this.getOrCreateProject(this.#projectName) } // Low-level request. Builds https://api.<site><path>, attaches both keys, and // returns the parsed JSON body. Throws with status + body on a non-2xx. async request (method, path, body) { const url = `https://${apiHost(this.#site)}${path}` const headers = { 'DD-API-KEY': this.#apiKey, 'DD-APPLICATION-KEY': this.#appKey, } let payload if (body !== undefined) { payload = JSON.stringify(body) headers['Content-Type'] = 'application/json' } let response try { response = await fetch(url, { method, headers, body: payload, signal: AbortSignal.timeout(this.#timeout), }) } catch (err) { throw new Error(`${method} ${path} failed: ${err.message}`) } const text = await response.text() if (!response.ok) { throw new Error(`${method} ${path} failed: HTTP ${response.status} ${text}`) } return text ? JSON.parse(text) : {} } // Resolve the project id for `name`, creating it if absent. The create // endpoint is get-or-create on name, so repeated calls return the same id. // Cached after the first resolution. async getOrCreateProject (name) { if (this.#cachedProjectId) return this.#cachedProjectId let response try { response = await this.request('POST', `${API_BASE_PATH}/projects`, { data: { type: 'projects', attributes: { name } }, }) } catch (err) { throw new Error(`Failed to create or get project '${name}': ${err.message}`) } this.#cachedProjectId = response?.data?.id ?? null return this.#cachedProjectId } } module.exports = { ExperimentsClient, apiHost, appHost, API_BASE_PATH }