128 lines · 3.5 KB · TypeScript
RawDownload
import { Cause, Exit } from "effect"; export type MetricAttributeValue = string;export type MetricAttributes = Readonly<Record<string, MetricAttributeValue>>;export type TraceAttributes = Readonly<Record<string, unknown>>;export type ObservabilityOutcome = "success" | "failure" | "interrupt"; function isPlainObject(value: unknown): value is Record<string, unknown> {  return typeof value === "object" && value !== null && !Array.isArray(value);} function markSeen(value: object, seen: WeakSet<object>): boolean {  if (seen.has(value)) {    return true;  }  seen.add(value);  return false;} function normalizeJsonValue(value: unknown, seen: WeakSet<object> = new WeakSet()): unknown {  if (    value === null ||    value === undefined ||    typeof value === "string" ||    typeof value === "number" ||    typeof value === "boolean"  ) {    return value ?? null;  }  if (typeof value === "bigint") {    return value.toString();  }  if (value instanceof Date) {    return Number.isNaN(value.getTime()) ? "Invalid Date" : value.toISOString();  }  if (value instanceof Error) {    return {      name: value.name,      message: value.message,      ...(value.stack ? { stack: value.stack } : {}),    };  }  if (Array.isArray(value)) {    if (markSeen(value, seen)) {      return "[Circular]";    }    return value.map((entry) => normalizeJsonValue(entry, seen));  }  if (value instanceof Map) {    if (markSeen(value, seen)) {      return "[Circular]";    }    return Object.fromEntries(      Array.from(value.entries(), ([key, entryValue]) => [        String(key),        normalizeJsonValue(entryValue, seen),      ]),    );  }  if (value instanceof Set) {    if (markSeen(value, seen)) {      return "[Circular]";    }    return Array.from(value.values(), (entry) => normalizeJsonValue(entry, seen));  }  if (!isPlainObject(value)) {    return String(value);  }  if (markSeen(value, seen)) {    return "[Circular]";  }  return Object.fromEntries(    Object.entries(value).map(([key, entryValue]) => [key, normalizeJsonValue(entryValue, seen)]),  );} export function compactTraceAttributes(  attributes: Readonly<Record<string, unknown>>,): TraceAttributes {  return Object.fromEntries(    Object.entries(attributes)      .filter(([, value]) => value !== undefined)      .map(([key, value]) => [key, normalizeJsonValue(value)]),  );} export function compactMetricAttributes(  attributes: Readonly<Record<string, unknown>>,): MetricAttributes {  return Object.fromEntries(    Object.entries(attributes).flatMap(([key, value]) => {      if (value === undefined || value === null) {        return [];      }      if (typeof value === "string") {        return [[key, value]];      }      if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {        return [[key, String(value)]];      }      return [];    }),  );} export function outcomeFromExit(exit: Exit.Exit<unknown, unknown>): ObservabilityOutcome {  if (Exit.isSuccess(exit)) {    return "success";  }  return Cause.hasInterruptsOnly(exit.cause) ? "interrupt" : "failure";} export function normalizeModelMetricLabel(model: string | null | undefined): string | undefined {  const normalized = model?.trim().toLowerCase();  if (!normalized) {    return undefined;  }  if (normalized.includes("gpt")) {    return "gpt";  }  if (normalized.includes("claude")) {    return "claude";  }  if (normalized.includes("gemini")) {    return "gemini";  }  return "other";}