type JsonRecord = Record; export type GitLabDirectAccessDetails = { token: string; baseUrl: string; expiresAt: string | null; headers: Record; }; export const GITLAB_DUO_DEFAULT_BASE_URL = process.env.GITLAB_DUO_BASE_URL || process.env.GITLAB_BASE_URL || "https://gitlab.com"; function asRecord(value: unknown): JsonRecord { return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {}; } export function normalizeGitLabBaseUrl(baseUrl?: unknown): string { const raw = typeof baseUrl === "string" ? baseUrl.trim() : ""; return (raw || GITLAB_DUO_DEFAULT_BASE_URL).replace(/\/$/, ""); } export function resolveGitLabOAuthBaseUrl(providerSpecificData?: unknown): string { const data = asRecord(providerSpecificData); return normalizeGitLabBaseUrl(data.baseUrl); } export function buildGitLabOAuthEndpoints(baseUrl?: unknown) { const root = normalizeGitLabBaseUrl(baseUrl); return { root, authorizeUrl: `${root}/oauth/authorize`, tokenUrl: `${root}/oauth/token`, userUrl: `${root}/api/v4/user`, directAccessUrl: `${root}/api/v4/code_suggestions/direct_access`, publicCompletionsUrl: `${root}/api/v4/code_suggestions/completions`, }; } export function buildGitLabDirectGatewayUrl(baseUrl: string): string { const normalized = normalizeGitLabBaseUrl(baseUrl); if (normalized.endsWith("/ai/v2/completions")) { return normalized; } if (normalized.endsWith("/ai/v2")) { return `${normalized}/completions`; } return `${normalized}/ai/v2/completions`; } export function parseGitLabDirectAccessDetails(payload: unknown): GitLabDirectAccessDetails | null { const data = asRecord(payload); const token = typeof data.token === "string" ? data.token.trim() : ""; const baseUrl = typeof data.base_url === "string" ? data.base_url.trim() : ""; if (!token || !baseUrl) { return null; } const rawHeaders = asRecord(data.headers); const headers = Object.fromEntries( Object.entries(rawHeaders).filter( (entry): entry is [string, string] => typeof entry[0] === "string" && typeof entry[1] === "string" ) ); const expiresAt = typeof data.expires_at === "number" && Number.isFinite(data.expires_at) ? new Date(data.expires_at * 1000).toISOString() : null; return { token, baseUrl: normalizeGitLabBaseUrl(baseUrl), expiresAt, headers, }; } export function getCachedGitLabDirectAccess( providerSpecificData?: unknown, minValidityMs = 60_000 ): GitLabDirectAccessDetails | null { const data = asRecord(providerSpecificData); const cache = data.gitlabDirectAccess ?? data.directAccessCache; const parsed = parseGitLabDirectAccessDetails(cache); if (!parsed) { return null; } if (!parsed.expiresAt) { return parsed; } const expiresAtMs = new Date(parsed.expiresAt).getTime(); if (!Number.isFinite(expiresAtMs) || expiresAtMs <= Date.now() + minValidityMs) { return null; } return parsed; } export function isGitLabDirectAccessDisabled(status: number, bodyText: string): boolean { return status === 403 && bodyText.toLowerCase().includes("direct connections are disabled"); }