UNPKG

langsmith

Version:

Client library to connect to the LangSmith Observability and Evaluation Platform.

293 lines (292 loc) 10.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ProfileAuth = exports.DEFAULT_API_URL = void 0; exports.hasValue = hasValue; exports.loadProfileClientConfig = loadProfileClientConfig; const env_js_1 = require("./env.cjs"); const fsUtils = __importStar(require("./fs.cjs")); exports.DEFAULT_API_URL = "https://api.smith.langchain.com"; const OAUTH_CLIENT_ID = "langsmith-cli"; const TOKEN_REFRESH_LEEWAY_MS = 60_000; const TOKEN_REFRESH_TIMEOUT_MS = 10_000; function isBrowserLikeRuntime() { const env = (0, env_js_1.getEnv)(); return env === "browser" || env === "webworker"; } function getProfileConfigPath() { const explicitPath = (0, env_js_1.getEnvironmentVariable)("LANGSMITH_CONFIG_FILE"); if (explicitPath) { return explicitPath; } const home = (0, env_js_1.getEnvironmentVariable)("HOME") ?? (0, env_js_1.getEnvironmentVariable)("USERPROFILE"); if (!home) { return undefined; } return fsUtils.path.join(home, ".langsmith", "config.json"); } function resolveProfileName(config) { const envProfile = (0, env_js_1.getEnvironmentVariable)("LANGSMITH_PROFILE"); if (envProfile) { return envProfile; } if (config.current_profile) { return config.current_profile; } if (config.profiles?.default) { return "default"; } return undefined; } function loadProfileState() { if (isBrowserLikeRuntime()) { return undefined; } const configPath = getProfileConfigPath(); if (!configPath || !fsUtils.existsSync(configPath)) { return undefined; } try { const config = JSON.parse(fsUtils.readFileSync(configPath)); const profileName = resolveProfileName(config); const profile = profileName ? config.profiles?.[profileName] : undefined; if (!profileName || !profile) { return undefined; } return { configPath, config, profileName, profile }; } catch { return undefined; } } function hasValue(value) { return value !== undefined && value !== null && value.trim() !== ""; } function trimConfigValue(value) { return value?.trim().replace(/^["']|["']$/g, ""); } function shouldRefreshProfileToken(profile) { const oauth = profile.oauth; if (!oauth?.refresh_token) { return false; } if (!oauth.access_token) { return true; } if (!oauth.expires_at) { return false; } const expiresAt = Date.parse(oauth.expires_at); if (Number.isNaN(expiresAt)) { return false; } return expiresAt <= Date.now() + TOKEN_REFRESH_LEEWAY_MS; } function normalizeConfigUrl(apiUrl) { let normalized = apiUrl; while (normalized.endsWith("/")) { normalized = normalized.slice(0, -1); } const apiV1Suffix = "/api/v1"; return normalized.endsWith(apiV1Suffix) ? normalized.slice(0, -apiV1Suffix.length) : normalized; } function applyTokenResponse(profile, token) { profile.oauth ??= {}; if (token.access_token) { profile.oauth.access_token = token.access_token; } if (token.refresh_token) { profile.oauth.refresh_token = token.refresh_token; } if (typeof token.expires_in === "number" && token.expires_in > 0) { profile.oauth.expires_at = new Date(Date.now() + token.expires_in * 1000).toISOString(); } } function getAbortReason(signal) { return (signal.reason ?? new Error("The operation was aborted.")); } async function waitForAbortSignal(promise, signal) { if (!signal) { return promise; } if (signal.aborted) { throw getAbortReason(signal); } let cleanup; const abortPromise = new Promise((_, reject) => { const onAbort = () => { reject(getAbortReason(signal)); }; signal.addEventListener("abort", onAbort, { once: true }); cleanup = () => { signal.removeEventListener("abort", onAbort); }; }); try { return await Promise.race([promise, abortPromise]); } finally { cleanup?.(); } } function loadProfileClientConfig() { const state = loadProfileState(); const profile = state?.profile; if (!state || !profile) { return {}; } const apiKey = trimConfigValue(profile.api_key); const oauthAccessToken = trimConfigValue(profile.oauth?.access_token); const oauthRefreshToken = trimConfigValue(profile.oauth?.refresh_token); return { apiUrl: profile.api_url, apiKey, workspaceId: profile.workspace_id, oauthAccessToken, oauthRefreshToken, profileAuth: apiKey || oauthAccessToken || oauthRefreshToken ? new ProfileAuth(state) : undefined, }; } class ProfileAuth { constructor(state) { Object.defineProperty(this, "state", { enumerable: true, configurable: true, writable: true, value: state }); Object.defineProperty(this, "refreshPromise", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "managedAuthorizationValue", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.rememberProfileAuthHeader(this.currentAuthHeader()); } currentAuthHeader() { const header = currentAuthHeaderFromProfile(this.state.profile); this.rememberProfileAuthHeader(header); return header; } async getAuthHeader(fetchImplementation, signal) { if (shouldRefreshProfileToken(this.state.profile)) { if (!this.refreshPromise) { this.refreshPromise = this.refreshOAuthToken(fetchImplementation).finally(() => { this.refreshPromise = undefined; }); } await waitForAbortSignal(this.refreshPromise, signal); } const header = authHeaderFromProfile(this.state.profile); this.rememberProfileAuthHeader(header); return header; } isProfileAuthorizationHeader(value) { return value === this.managedAuthorizationValue; } async refreshOAuthToken(fetchImplementation) { const refreshToken = this.state.profile.oauth?.refresh_token; if (!refreshToken) { return; } const refreshApiUrl = trimConfigValue(this.state.profile.api_url) ?? exports.DEFAULT_API_URL; try { const body = new URLSearchParams({ grant_type: "refresh_token", client_id: OAUTH_CLIENT_ID, refresh_token: refreshToken, }); const response = await fetchImplementation(`${normalizeConfigUrl(refreshApiUrl)}/oauth/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: body.toString(), signal: AbortSignal.timeout(TOKEN_REFRESH_TIMEOUT_MS), }); if (!response.ok) { return; } const token = (await response.json()); if (!token.access_token) { return; } applyTokenResponse(this.state.profile, token); this.state.config.profiles ??= {}; this.state.config.profiles[this.state.profileName] = this.state.profile; await fsUtils.writeFileAtomic(this.state.configPath, `${JSON.stringify(this.state.config, null, 2)}\n`); } catch { return; } } rememberProfileAuthHeader(header) { this.managedAuthorizationValue = header?.name === "Authorization" ? header.value : undefined; } } exports.ProfileAuth = ProfileAuth; function currentAuthHeaderFromProfile(profile) { const oauthAccessToken = trimConfigValue(profile.oauth?.access_token); if (oauthAccessToken) { return { name: "Authorization", value: `Bearer ${oauthAccessToken}` }; } if (trimConfigValue(profile.oauth?.refresh_token)) { return undefined; } return authHeaderFromProfile(profile); } function authHeaderFromProfile(profile) { const oauthAccessToken = trimConfigValue(profile.oauth?.access_token); if (oauthAccessToken) { return { name: "Authorization", value: `Bearer ${oauthAccessToken}` }; } const apiKey = trimConfigValue(profile.api_key); if (apiKey) { return { name: "x-api-key", value: apiKey }; } return undefined; }