hahma / src /lib /oauth /gitlab.ts
automindy's picture
Upload 1980 files
bc4a7e8 verified
Raw
History Blame Contribute Delete
3.32 kB
type JsonRecord = Record<string, unknown>;
export type GitLabDirectAccessDetails = {
token: string;
baseUrl: string;
expiresAt: string | null;
headers: Record<string, string>;
};
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");
}