|
| 1 | +const MAX_BODY_CHARS = 2000; |
| 2 | +// PostHog Logs only facets attribute key/value pairs shorter than 256 chars, |
| 3 | +// so free-text attribute values are capped well below that. |
| 4 | +const MAX_ATTR_CHARS = 200; |
| 5 | +// The SDK default export timeout is 30s; a hanging endpoint must not hold up |
| 6 | +// session cleanup (the sandbox can be torn down right after), so keep it short. |
| 7 | +const EXPORT_TIMEOUT_MS = 5000; |
| 8 | + |
| 9 | +// Batch flush cadence shared by the log and span processors. |
| 10 | +const DEFAULT_FLUSH_INTERVAL_MS = 2000; |
| 11 | + |
| 12 | +export type AttributeValue = string | number | boolean; |
| 13 | +export type Attributes = Record<string, AttributeValue>; |
| 14 | + |
| 15 | +export { |
| 16 | + DEFAULT_FLUSH_INTERVAL_MS, |
| 17 | + EXPORT_TIMEOUT_MS, |
| 18 | + MAX_ATTR_CHARS, |
| 19 | + MAX_BODY_CHARS, |
| 20 | +}; |
| 21 | + |
| 22 | +/** |
| 23 | + * extNotification() can double-prefix custom methods (see matchesExt in |
| 24 | + * acp-extensions.ts); normalize so both spellings map identically. |
| 25 | + */ |
| 26 | +export function normalizeMethod(method: string): string { |
| 27 | + return method.startsWith("__posthog/") ? method.slice(1) : method; |
| 28 | +} |
| 29 | + |
| 30 | +export function asString(value: unknown): string | undefined { |
| 31 | + return typeof value === "string" ? value : undefined; |
| 32 | +} |
| 33 | + |
| 34 | +export function truncate(value: string, max: number): string { |
| 35 | + return value.length <= max ? value : `${value.slice(0, max)}…`; |
| 36 | +} |
| 37 | + |
| 38 | +export function asRecord(value: unknown): Record<string, unknown> | undefined { |
| 39 | + return typeof value === "object" && value !== null && !Array.isArray(value) |
| 40 | + ? (value as Record<string, unknown>) |
| 41 | + : undefined; |
| 42 | +} |
| 43 | + |
| 44 | +export function strAttr( |
| 45 | + attrs: Attributes, |
| 46 | + key: string, |
| 47 | + value: unknown, |
| 48 | + max = MAX_ATTR_CHARS, |
| 49 | +): string | undefined { |
| 50 | + if (typeof value !== "string" || value.length === 0) return undefined; |
| 51 | + const truncated = truncate(value, max); |
| 52 | + attrs[key] = truncated; |
| 53 | + return truncated; |
| 54 | +} |
| 55 | + |
| 56 | +export function numAttr(attrs: Attributes, key: string, value: unknown): void { |
| 57 | + if (typeof value === "number" && Number.isFinite(value)) { |
| 58 | + attrs[key] = value; |
| 59 | + } |
| 60 | +} |
| 61 | + |
| 62 | +export function usageAttributes(params: Record<string, unknown>): Attributes { |
| 63 | + const attrs: Attributes = {}; |
| 64 | + const used = asRecord(params.used); |
| 65 | + if (used) { |
| 66 | + numAttr(attrs, "tokens_input", used.inputTokens); |
| 67 | + numAttr(attrs, "tokens_output", used.outputTokens); |
| 68 | + numAttr(attrs, "tokens_cached_read", used.cachedReadTokens); |
| 69 | + numAttr(attrs, "tokens_cached_write", used.cachedWriteTokens); |
| 70 | + } |
| 71 | + // Claude sends a plain number; other shapes carry { amount }. |
| 72 | + const cost = |
| 73 | + typeof params.cost === "number" |
| 74 | + ? params.cost |
| 75 | + : asRecord(params.cost)?.amount; |
| 76 | + numAttr(attrs, "cost_usd", cost); |
| 77 | + return attrs; |
| 78 | +} |
| 79 | + |
| 80 | +/** Timestamp of a stored entry as a Date, falling back to now when invalid. */ |
| 81 | +export function entryTime(timestamp: string): Date { |
| 82 | + const parsed = new Date(timestamp); |
| 83 | + return Number.isNaN(parsed.getTime()) ? new Date() : parsed; |
| 84 | +} |
0 commit comments