Add pi-langfuse extension (Langfuse LLM observability for pi) — vendored from npm 1.5.8
This commit is contained in:
160
extensions/pi-langfuse/src/source-metadata.ts
Normal file
160
extensions/pi-langfuse/src/source-metadata.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { basename, dirname, join } from "node:path";
|
||||
import { execFileSync } from "node:child_process";
|
||||
|
||||
export type SourceMetadata = Record<string, string>;
|
||||
|
||||
const OVERRIDE_KEYS = new Set([
|
||||
"repo_identity",
|
||||
"repo_owner",
|
||||
"repo_name",
|
||||
"source_type",
|
||||
"service_name",
|
||||
"project_slug",
|
||||
"environment",
|
||||
"observability_owner",
|
||||
]);
|
||||
|
||||
function nonGitMetadata(): SourceMetadata {
|
||||
return {
|
||||
source_type: "non-git",
|
||||
metadata_source: "non-git",
|
||||
};
|
||||
}
|
||||
|
||||
function runGit(cwd: string, args: string[]): string {
|
||||
return execFileSync("git", args, {
|
||||
cwd,
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
timeout: 2000,
|
||||
}).trim();
|
||||
}
|
||||
|
||||
function firstLine(value: string): string {
|
||||
return value.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "";
|
||||
}
|
||||
|
||||
export function sanitizeGitRemote(remoteUrl: string): Partial<Pick<SourceMetadata, "git_remote_host" | "git_remote_path">> {
|
||||
const raw = firstLine(remoteUrl).replace(/\.git$/i, "");
|
||||
if (!raw) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = new URL(raw);
|
||||
const path = parsed.pathname.replace(/^\/+/, "").replace(/\.git$/i, "");
|
||||
return parsed.hostname && path ? { git_remote_host: parsed.hostname, git_remote_path: path } : {};
|
||||
} catch {
|
||||
// Continue with scp-like SSH syntax, for example git@github.com:owner/repo.git.
|
||||
}
|
||||
|
||||
const scpLike = raw.match(/^(?:[^@\s/:]+@)?([^:\s]+):(.+)$/);
|
||||
if (scpLike) {
|
||||
const host = scpLike[1];
|
||||
const path = scpLike[2].replace(/^\/+/, "").replace(/\.git$/i, "");
|
||||
return host && path && !path.includes("@") ? { git_remote_host: host, git_remote_path: path } : {};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
function deriveIdentity(remotePath: string | undefined): Partial<Pick<SourceMetadata, "repo_identity" | "repo_owner" | "repo_name">> {
|
||||
if (!remotePath) {
|
||||
return {};
|
||||
}
|
||||
const parts = remotePath.split("/").filter(Boolean);
|
||||
if (parts.length < 2) {
|
||||
return {};
|
||||
}
|
||||
const repoName = parts[parts.length - 1];
|
||||
const owner = parts[parts.length - 2];
|
||||
if (!repoName || !owner || repoName.includes("/")) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
repo_identity: `${owner}/${repoName}`,
|
||||
repo_owner: owner,
|
||||
repo_name: repoName,
|
||||
};
|
||||
}
|
||||
|
||||
function findRepoMetadataFile(cwd: string, gitRoot: string): string | undefined {
|
||||
let current = cwd;
|
||||
const root = gitRoot;
|
||||
while (true) {
|
||||
const candidate = join(current, ".pi-langfuse.metadata.json");
|
||||
if (existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
if (current === root) {
|
||||
break;
|
||||
}
|
||||
const parent = dirname(current);
|
||||
if (parent === current) {
|
||||
break;
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function readWhitelistedOverrides(cwd: string, gitRoot: string): SourceMetadata {
|
||||
const path = findRepoMetadataFile(cwd, gitRoot);
|
||||
if (!path) {
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(readFileSync(path, "utf8")) as Record<string, unknown>;
|
||||
const output: SourceMetadata = {};
|
||||
for (const [key, value] of Object.entries(parsed)) {
|
||||
if (OVERRIDE_KEYS.has(key) && typeof value === "string" && value.trim()) {
|
||||
output[key] = value.trim();
|
||||
}
|
||||
}
|
||||
if (output.repo_name?.includes("/")) {
|
||||
delete output.repo_name;
|
||||
}
|
||||
return output;
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export function collectSourceMetadata(cwd: string): SourceMetadata {
|
||||
try {
|
||||
const inside = runGit(cwd, ["rev-parse", "--is-inside-work-tree"]);
|
||||
if (inside !== "true") {
|
||||
return nonGitMetadata();
|
||||
}
|
||||
|
||||
const gitRoot = runGit(cwd, ["rev-parse", "--show-toplevel"]);
|
||||
const commit = runGit(cwd, ["rev-parse", "HEAD"]);
|
||||
const branch = runGit(cwd, ["branch", "--show-current"]);
|
||||
const remote = runGit(cwd, ["config", "--get", "remote.origin.url"]);
|
||||
const remoteMetadata = sanitizeGitRemote(remote);
|
||||
const derivedIdentity = deriveIdentity(remoteMetadata.git_remote_path);
|
||||
const overrides = readWhitelistedOverrides(cwd, gitRoot);
|
||||
|
||||
const metadata: SourceMetadata = {
|
||||
source_type: "git-repo",
|
||||
repo_root_name: basename(gitRoot),
|
||||
...(branch ? { git_branch: branch } : {}),
|
||||
git_commit: commit,
|
||||
...remoteMetadata,
|
||||
...derivedIdentity,
|
||||
...overrides,
|
||||
metadata_source: Object.keys(overrides).length > 0 ? "repo-file" : "git-detection",
|
||||
};
|
||||
|
||||
if (metadata.repo_name?.includes("/")) {
|
||||
delete metadata.repo_name;
|
||||
}
|
||||
if (!metadata.repo_identity && metadata.repo_owner && metadata.repo_name) {
|
||||
metadata.repo_identity = `${metadata.repo_owner}/${metadata.repo_name}`;
|
||||
}
|
||||
return metadata;
|
||||
} catch {
|
||||
return nonGitMetadata();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user