pi-better-openai 0.1.18 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/format.ts CHANGED
@@ -1,30 +1,45 @@
1
+ import {
2
+ truncateToWidth as truncateTerminalText,
3
+ visibleWidth as terminalVisibleWidth,
4
+ } from "@earendil-works/pi-tui";
5
+
1
6
  const ANSI_ESCAPE_PATTERN = String.raw`\u001B\[[0-?]*[ -/]*[@-~]`;
2
7
  const ANSI_ESCAPE_REGEXP = new RegExp(ANSI_ESCAPE_PATTERN, "g");
3
- const LEADING_ANSI_ESCAPE_REGEXP = new RegExp(`^(?:${ANSI_ESCAPE_PATTERN})+`);
4
- const TRAILING_ANSI_ESCAPE_REGEXP = new RegExp(`(?:${ANSI_ESCAPE_PATTERN})+$`);
8
+ const DIAGNOSTIC_MAX_LENGTH = 500;
9
+ const REDACTED = "[REDACTED]";
10
+ const SENSITIVE_DIAGNOSTIC_KEY_SUFFIXES = [
11
+ "token",
12
+ "apikey",
13
+ "accesskey",
14
+ "authorization",
15
+ "password",
16
+ "passwd",
17
+ "secret",
18
+ "privatekey",
19
+ "credential",
20
+ "credentials",
21
+ "accountid",
22
+ ] as const;
23
+
24
+ function replaceControlCharacters(value: string): string {
25
+ let result = "";
26
+ for (const char of value) {
27
+ const code = char.charCodeAt(0);
28
+ result += code <= 31 || (code >= 127 && code <= 159) ? " " : char;
29
+ }
30
+ return result;
31
+ }
5
32
 
6
33
  export function stripAnsi(value: string): string {
7
34
  return value.replace(ANSI_ESCAPE_REGEXP, "");
8
35
  }
9
36
 
10
37
  export function visibleWidth(value: string): number {
11
- return stripAnsi(value).length;
12
- }
13
-
14
- function leadingAnsi(value: string): string {
15
- return value.match(LEADING_ANSI_ESCAPE_REGEXP)?.[0] ?? "";
16
- }
17
-
18
- function trailingAnsi(value: string): string {
19
- return value.match(TRAILING_ANSI_ESCAPE_REGEXP)?.[0] ?? "";
38
+ return terminalVisibleWidth(value);
20
39
  }
21
40
 
22
41
  export function truncateToWidth(value: string, width: number, ellipsis = "..."): string {
23
- if (visibleWidth(value) <= width) return value;
24
- if (width <= 0) return "";
25
- const plain = stripAnsi(value);
26
- if (width <= ellipsis.length) return ellipsis.slice(0, width);
27
- return `${leadingAnsi(value)}${plain.slice(0, Math.max(0, width - ellipsis.length))}${ellipsis}${trailingAnsi(value)}`;
42
+ return truncateTerminalText(value, width, ellipsis);
28
43
  }
29
44
 
30
45
  export function formatTokens(count: number): string {
@@ -36,8 +51,54 @@ export function formatTokens(count: number): string {
36
51
  }
37
52
 
38
53
  export function sanitizeStatusText(text: string): string {
39
- return text
40
- .replace(/[\r\n\t]/g, " ")
41
- .replace(/ +/g, " ")
42
- .trim();
54
+ return text.replace(/[ \r\n\t]+/g, " ").trim();
55
+ }
56
+
57
+ export function maskIdentifier(value: string | undefined): string | undefined {
58
+ const trimmed = value?.trim();
59
+ if (!trimmed) return undefined;
60
+ if (trimmed.length <= 8) return "found";
61
+ return `${trimmed.slice(0, 4)}...${trimmed.slice(-4)}`;
62
+ }
63
+
64
+ export function sanitizeDiagnosticError(
65
+ message: string,
66
+ maxLength = DIAGNOSTIC_MAX_LENGTH,
67
+ ): string {
68
+ const redactedSecrets = stripAnsi(message)
69
+ .replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]")
70
+ .replace(/\bsk-[A-Za-z0-9_-]{8,}\b/g, "sk-[REDACTED]")
71
+ .replace(/\bacct_[A-Za-z0-9_-]{6,}\b/g, "acct_[REDACTED]")
72
+ .replace(
73
+ /(["']?(?:access|access_token|token|api[_-]?key|authorization|accountId|account_id)["']?\s*[:=]\s*["']?)([^"',\s}\]]+)/gi,
74
+ "$1[REDACTED]",
75
+ )
76
+ .replace(/\[REDACTED\](?:\])+/g, "[REDACTED]");
77
+ const redacted = replaceControlCharacters(redactedSecrets).replace(/ +/g, " ").trim();
78
+ const sanitized = redacted || "Unknown error.";
79
+ if (sanitized.length <= maxLength) return sanitized;
80
+ return `${sanitized.slice(0, Math.max(0, maxLength - 1)).trimEnd()}…`;
81
+ }
82
+
83
+ function isSensitiveDiagnosticKey(key: string): boolean {
84
+ const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
85
+ return (
86
+ normalized === "access" ||
87
+ normalized === "auth" ||
88
+ normalized === "refresh" ||
89
+ SENSITIVE_DIAGNOSTIC_KEY_SUFFIXES.some((suffix) => normalized.endsWith(suffix))
90
+ );
91
+ }
92
+
93
+ export function redactDiagnosticValue(value: unknown): unknown {
94
+ if (typeof value === "string") return sanitizeDiagnosticError(value);
95
+ if (Array.isArray(value)) return value.map(redactDiagnosticValue);
96
+ if (typeof value !== "object" || value === null) return value;
97
+
98
+ return Object.fromEntries(
99
+ Object.entries(value).map(([key, entry]) => [
100
+ key,
101
+ isSensitiveDiagnosticKey(key) ? REDACTED : redactDiagnosticValue(entry),
102
+ ]),
103
+ );
43
104
  }