@f5-sales-demo/pi-utils 19.51.2

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.
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Mermaid color helpers — terminal color-mode selection, diagram-type detection,
3
+ * and a best-effort per-node tint pass for ASCII/Unicode mermaid output.
4
+ *
5
+ * `beautiful-mermaid` colorizes strictly by global character role (text/border/
6
+ * line/arrow/…), so per-node hues are not reachable through its API. `tintMermaidNodes`
7
+ * adds them as a post-pass: it detects node-box rectangles in the rendered output and
8
+ * overrides the foreground color of each box's border + label cells with a cycled accent.
9
+ * It is intentionally fail-safe — any ambiguity returns the input unchanged so the
10
+ * library's per-role coloring still shows through.
11
+ */
12
+
13
+ /** Color mode subset we drive `beautiful-mermaid` with. */
14
+ export type MermaidColorMode = "none" | "ansi256" | "truecolor";
15
+
16
+ export interface ColorModeInput {
17
+ /** NO_COLOR set, output piped, or color explicitly disabled. */
18
+ noColor: boolean;
19
+ /** Terminal advertises 24-bit color. */
20
+ trueColor: boolean;
21
+ }
22
+
23
+ /** Choose the richest color mode the environment robustly supports. */
24
+ export function pickColorMode({ noColor, trueColor }: ColorModeInput): MermaidColorMode {
25
+ if (noColor) return "none";
26
+ return trueColor ? "truecolor" : "ansi256";
27
+ }
28
+
29
+ /**
30
+ * Complexity ceiling for mermaid sources. beautiful-mermaid's ASCII edge router
31
+ * (A* pathfinder) can allocate unbounded memory and hang for tens of seconds —
32
+ * then throw `RangeError: Out of memory` — on large/dense graphs. Callers should
33
+ * reject sources past this limit BEFORE rendering rather than attempt the layout.
34
+ */
35
+ export const MERMAID_MAX_CHARS = 20000;
36
+ export const MERMAID_MAX_LINES = 400;
37
+
38
+ /** True when a mermaid source is too large to render safely (see limits above). */
39
+ export function mermaidSourceExceedsLimit(source: string): boolean {
40
+ if (source.length > MERMAID_MAX_CHARS) return true;
41
+ let lines = 1;
42
+ for (let i = 0; i < source.length; i++) if (source.charCodeAt(i) === 10 && ++lines > MERMAID_MAX_LINES) return true;
43
+ return false;
44
+ }
45
+
46
+ export type MermaidDiagramType = "flowchart" | "sequence" | "class" | "er" | "state" | "xychart" | "unknown";
47
+
48
+ /** Detect the diagram type from the first meaningful header line of mermaid source. */
49
+ export function detectDiagramType(source: string): MermaidDiagramType {
50
+ for (const raw of source.split("\n")) {
51
+ const line = raw.trim();
52
+ // Skip blanks, YAML frontmatter fences, %%{init}%% directives, and %% comments.
53
+ if (line === "" || line === "---" || line.startsWith("%%")) continue;
54
+ // Skip frontmatter key/value lines (anything before we hit a known header).
55
+ if (/^(graph|flowchart)\b/.test(line)) return "flowchart";
56
+ if (/^sequenceDiagram\b/.test(line)) return "sequence";
57
+ if (/^classDiagram(-v2)?\b/.test(line)) return "class";
58
+ if (/^erDiagram\b/.test(line)) return "er";
59
+ if (/^stateDiagram(-v2)?\b/.test(line)) return "state";
60
+ if (/^xychart(-beta)?\b/.test(line)) return "xychart";
61
+ // A header-shaped token that isn't recognized → give up (avoid matching frontmatter values).
62
+ if (/^[A-Za-z]/.test(line) && !line.includes(":")) return "unknown";
63
+ }
64
+ return "unknown";
65
+ }
66
+
67
+ // ── per-node tint pass ──────────────────────────────────────────────────────
68
+
69
+ interface Cell {
70
+ char: string;
71
+ /** Active foreground SGR sequence ("" = default). */
72
+ fg: string;
73
+ }
74
+
75
+ interface Rect {
76
+ top: number;
77
+ left: number;
78
+ bottom: number;
79
+ right: number;
80
+ }
81
+
82
+ const TL = new Set(["┌", "╭", "+"]);
83
+ const isH = (ch: string): boolean => ch === "─" || ch === "-";
84
+ const isV = (ch: string): boolean => ch === "│" || ch === "|";
85
+ // Borders may carry an edge-attachment junction (a tee/cross) where an edge meets
86
+ // the box; accept those so attached nodes are still detected and tinted.
87
+ const isHBorder = (ch: string): boolean => isH(ch) || ch === "┬" || ch === "┴" || ch === "┼";
88
+ const isVBorder = (ch: string): boolean => isV(ch) || ch === "├" || ch === "┤" || ch === "┼";
89
+
90
+ /** Parse a colored line into per-visible-cell {char, fg}, tracking the active fg color. */
91
+ function parseCells(line: string): Cell[] {
92
+ const cells: Cell[] = [];
93
+ let fg = "";
94
+ const re = /\x1b\[[0-9;]*m/g;
95
+ let last = 0;
96
+ for (let m = re.exec(line); m !== null; m = re.exec(line)) {
97
+ for (const ch of line.slice(last, m.index)) cells.push({ char: ch, fg });
98
+ fg = nextFg(fg, m[0]);
99
+ last = re.lastIndex;
100
+ }
101
+ for (const ch of line.slice(last)) cells.push({ char: ch, fg });
102
+ return cells;
103
+ }
104
+
105
+ /** Compute the new active fg from an SGR sequence (only fg-affecting codes matter). */
106
+ function nextFg(prev: string, seq: string): string {
107
+ const params = seq.slice(2, -1); // strip ESC[ and trailing m
108
+ if (params === "" || params === "0") return "";
109
+ if (params.startsWith("38;2;") || params.startsWith("38;5;")) return seq;
110
+ if (params === "39") return "";
111
+ if (/^(3[0-7]|9[0-7])$/.test(params)) return seq;
112
+ return prev; // bold/underline/bg/etc. — leave fg unchanged
113
+ }
114
+
115
+ /** Locate a single rectangle whose top-left corner is at (r,c), or null. */
116
+ function readRect(grid: string[][], r: number, c: number): Rect | null {
117
+ const tl = grid[r]?.[c];
118
+ if (tl === undefined || !TL.has(tl)) return null;
119
+ const ascii = tl === "+";
120
+ const trCh = ascii ? "+" : tl === "╭" ? "╮" : "┐";
121
+ const blCh = ascii ? "+" : tl === "╭" ? "╰" : "└";
122
+ const brCh = ascii ? "+" : tl === "╭" ? "╯" : "┘";
123
+
124
+ const row = grid[r]!;
125
+ let right = -1;
126
+ for (let x = c + 1; x < row.length; x++) {
127
+ const ch = row[x]!;
128
+ if (ch === trCh) {
129
+ right = x;
130
+ break;
131
+ }
132
+ if (!isHBorder(ch)) break;
133
+ }
134
+ if (right < c + 2) return null; // need interior width
135
+
136
+ let bottom = -1;
137
+ for (let y = r + 1; y < grid.length; y++) {
138
+ const ch = grid[y]?.[c];
139
+ if (ch === undefined) break;
140
+ if (ch === blCh) {
141
+ bottom = y;
142
+ break;
143
+ }
144
+ if (!isVBorder(ch)) break;
145
+ }
146
+ if (bottom < r + 2) return null; // need interior height
147
+
148
+ if (grid[bottom]?.[right] !== brCh) return null;
149
+ // Verify bottom edge and right edge (junctions where edges attach are allowed).
150
+ for (let x = c + 1; x < right; x++) if (!isHBorder(grid[bottom]?.[x] ?? "")) return null;
151
+ for (let y = r + 1; y < bottom; y++) if (!isVBorder(grid[y]?.[right] ?? "")) return null;
152
+
153
+ return { top: r, left: c, bottom, right };
154
+ }
155
+
156
+ function findRects(grid: string[][]): Rect[] {
157
+ const rects: Rect[] = [];
158
+ for (let r = 0; r < grid.length; r++) {
159
+ const row = grid[r]!;
160
+ for (let c = 0; c < row.length; c++) {
161
+ if (TL.has(row[c]!)) {
162
+ const rect = readRect(grid, r, c);
163
+ if (rect) rects.push(rect);
164
+ }
165
+ }
166
+ }
167
+ return rects;
168
+ }
169
+
170
+ function contains(a: Rect, b: Rect): boolean {
171
+ if (a === b) return false;
172
+ return a.top <= b.top && a.left <= b.left && a.bottom >= b.bottom && a.right >= b.right;
173
+ }
174
+
175
+ /**
176
+ * Tint each node box a cycled accent color. `coloredLines` is the role-colored
177
+ * mermaid render; `accentsAnsi` is a list of fg SGR escapes to cycle across nodes.
178
+ * Returns the input unchanged if no boxes are found or no accents are given.
179
+ */
180
+ export function tintMermaidNodes(coloredLines: string[], accentsAnsi: string[]): string[] {
181
+ if (accentsAnsi.length === 0) return coloredLines;
182
+ try {
183
+ const rows = coloredLines.map(parseCells);
184
+ const grid = rows.map(cells => cells.map(cell => cell.char));
185
+ const rects = findRects(grid);
186
+ if (rects.length === 0) return coloredLines;
187
+
188
+ // Nodes are the innermost rectangles (a subgraph frame contains others → skip it).
189
+ const nodes = rects
190
+ .filter(rect => !rects.some(other => contains(rect, other)))
191
+ .sort((p, q) => p.top - q.top || p.left - q.left);
192
+ if (nodes.length === 0) return coloredLines;
193
+
194
+ // overrides: "r,c" → accent fg sequence.
195
+ const overrides = new Map<string, string>();
196
+ nodes.forEach((rect, i) => {
197
+ const accent = accentsAnsi[i % accentsAnsi.length]!;
198
+ for (let x = rect.left; x <= rect.right; x++) {
199
+ overrides.set(`${rect.top},${x}`, accent);
200
+ overrides.set(`${rect.bottom},${x}`, accent);
201
+ }
202
+ for (let y = rect.top; y <= rect.bottom; y++) {
203
+ overrides.set(`${y},${rect.left}`, accent);
204
+ overrides.set(`${y},${rect.right}`, accent);
205
+ for (let x = rect.left + 1; x < rect.right; x++) {
206
+ if ((grid[y]?.[x] ?? " ") !== " ") overrides.set(`${y},${x}`, accent);
207
+ }
208
+ }
209
+ });
210
+
211
+ return rows.map((cells, r) => {
212
+ const hasOverride = cells.some((_, c) => overrides.has(`${r},${c}`));
213
+ if (!hasOverride) return coloredLines[r]!;
214
+ let out = "";
215
+ let lastFg = "";
216
+ for (let c = 0; c < cells.length; c++) {
217
+ const cell = cells[c]!;
218
+ const fg = overrides.get(`${r},${c}`) ?? cell.fg;
219
+ if (fg !== lastFg) {
220
+ out += fg === "" ? "\x1b[39m" : fg;
221
+ lastFg = fg;
222
+ }
223
+ out += cell.char;
224
+ }
225
+ if (lastFg !== "") out += "\x1b[0m";
226
+ return out;
227
+ });
228
+ } catch {
229
+ return coloredLines;
230
+ }
231
+ }
package/src/mime.ts ADDED
@@ -0,0 +1,159 @@
1
+ import { peekFile, peekFileSync } from "./peek-file";
2
+
3
+ const DEFAULT_IMAGE_METADATA_HEADER_BYTES = 256 * 1024;
4
+
5
+ const PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
6
+ const JPEG_MAGIC = Buffer.from([0xff, 0xd8, 0xff]);
7
+ const WEBP_RIFF_MAGIC = Buffer.from([0x52, 0x49, 0x46, 0x46]);
8
+ const WEBP_MAGIC = Buffer.from([0x57, 0x45, 0x42, 0x50]);
9
+ const PNG_IHDR = Buffer.from("IHDR");
10
+ const GIF87A = Buffer.from("GIF87a");
11
+ const GIF89A = Buffer.from("GIF89a");
12
+ const WEBP_VP8X = Buffer.from("VP8X");
13
+ const WEBP_VP8L = Buffer.from("VP8L");
14
+ const WEBP_VP8 = Buffer.from("VP8 ");
15
+
16
+ export const SUPPORTED_IMAGE_MIME_TYPES = new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
17
+
18
+ export type ImageMetadata =
19
+ | { mimeType: "image/png"; width?: number; height?: number; channels?: number; hasAlpha?: boolean }
20
+ | { mimeType: "image/jpeg"; width?: number; height?: number; channels?: number; hasAlpha?: false }
21
+ | { mimeType: "image/gif"; width?: number; height?: number; channels?: 3; hasAlpha?: never }
22
+ | { mimeType: "image/webp"; width?: number; height?: number; channels?: number; hasAlpha?: boolean };
23
+
24
+ function magicEquals(header: Uint8Array, offset: number, magic: Buffer): boolean {
25
+ if (header.length < offset + magic.length) {
26
+ return false;
27
+ }
28
+ return magic.equals(header.subarray(offset, offset + magic.length));
29
+ }
30
+
31
+ function parsePngMetadata(header: Uint8Array): ImageMetadata | null {
32
+ if (!magicEquals(header, 0, PNG_MAGIC)) return null;
33
+ if (!magicEquals(header, 12, PNG_IHDR)) return { mimeType: "image/png" };
34
+ if (header.length < 26) return { mimeType: "image/png" };
35
+
36
+ const view = new DataView(header.buffer, header.byteOffset, header.byteLength);
37
+ const width = view.getUint32(16, false);
38
+ const height = view.getUint32(20, false);
39
+ const colorType = view.getUint8(25);
40
+ if (colorType === 0) return { mimeType: "image/png", width, height, channels: 1, hasAlpha: false };
41
+ if (colorType === 2) return { mimeType: "image/png", width, height, channels: 3, hasAlpha: false };
42
+ if (colorType === 3) return { mimeType: "image/png", width, height, channels: 3 };
43
+ if (colorType === 4) return { mimeType: "image/png", width, height, channels: 2, hasAlpha: true };
44
+ if (colorType === 6) return { mimeType: "image/png", width, height, channels: 4, hasAlpha: true };
45
+ return { mimeType: "image/png", width, height };
46
+ }
47
+
48
+ function parseJpegMetadata(header: Uint8Array): ImageMetadata | null {
49
+ if (!magicEquals(header, 0, JPEG_MAGIC)) return null;
50
+ if (header.length < 4) return { mimeType: "image/jpeg" };
51
+
52
+ const view = new DataView(header.buffer, header.byteOffset, header.byteLength);
53
+ let offset = 2;
54
+ while (offset + 9 < header.length) {
55
+ if (header[offset] !== 0xff) {
56
+ offset += 1;
57
+ continue;
58
+ }
59
+
60
+ let markerOffset = offset + 1;
61
+ while (markerOffset < header.length && header[markerOffset] === 0xff) {
62
+ markerOffset += 1;
63
+ }
64
+ if (markerOffset >= header.length) break;
65
+
66
+ const marker = header[markerOffset];
67
+ const segmentOffset = markerOffset + 1;
68
+ if (marker === 0xd8 || marker === 0xd9 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) {
69
+ offset = segmentOffset;
70
+ continue;
71
+ }
72
+ if (segmentOffset + 1 >= header.length) break;
73
+
74
+ const segmentLength = view.getUint16(segmentOffset, false);
75
+ if (segmentLength < 2) break;
76
+
77
+ const isStartOfFrame = marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc;
78
+ if (isStartOfFrame) {
79
+ if (segmentOffset + 7 >= header.length) break;
80
+ const height = view.getUint16(segmentOffset + 3, false);
81
+ const width = view.getUint16(segmentOffset + 5, false);
82
+ const channels = header[segmentOffset + 7];
83
+ return {
84
+ mimeType: "image/jpeg",
85
+ width,
86
+ height,
87
+ channels: Number.isFinite(channels) ? channels : undefined,
88
+ hasAlpha: false,
89
+ };
90
+ }
91
+
92
+ offset = segmentOffset + segmentLength;
93
+ }
94
+
95
+ return { mimeType: "image/jpeg" };
96
+ }
97
+
98
+ function parseGifMetadata(header: Uint8Array): ImageMetadata | null {
99
+ if (!magicEquals(header, 0, GIF87A) && !magicEquals(header, 0, GIF89A)) return null;
100
+ if (header.length < 10) return { mimeType: "image/gif" };
101
+ const view = new DataView(header.buffer, header.byteOffset, header.byteLength);
102
+ return {
103
+ mimeType: "image/gif",
104
+ width: view.getUint16(6, true),
105
+ height: view.getUint16(8, true),
106
+ channels: 3,
107
+ };
108
+ }
109
+
110
+ function parseWebpMetadata(header: Uint8Array): ImageMetadata | null {
111
+ if (!magicEquals(header, 0, WEBP_RIFF_MAGIC)) return null;
112
+ if (!magicEquals(header, 8, WEBP_MAGIC)) return null;
113
+ if (header.length < 30) return { mimeType: "image/webp" };
114
+
115
+ if (magicEquals(header, 12, WEBP_VP8X)) {
116
+ const hasAlpha = (header[20] & 0x10) !== 0;
117
+ const width = (header[24] | (header[25] << 8) | (header[26] << 16)) + 1;
118
+ const height = (header[27] | (header[28] << 8) | (header[29] << 16)) + 1;
119
+ return { mimeType: "image/webp", width, height, channels: hasAlpha ? 4 : 3, hasAlpha };
120
+ }
121
+
122
+ const view = new DataView(header.buffer, header.byteOffset, header.byteLength);
123
+ if (magicEquals(header, 12, WEBP_VP8L)) {
124
+ if (header.length < 25) return { mimeType: "image/webp" };
125
+ const bits = view.getUint32(21, true);
126
+ const width = (bits & 0x3fff) + 1;
127
+ const height = ((bits >> 14) & 0x3fff) + 1;
128
+ const hasAlpha = ((bits >> 28) & 0x1) === 1;
129
+ return { mimeType: "image/webp", width, height, channels: hasAlpha ? 4 : 3, hasAlpha };
130
+ }
131
+
132
+ if (magicEquals(header, 12, WEBP_VP8)) {
133
+ const width = view.getUint16(26, true) & 0x3fff;
134
+ const height = view.getUint16(28, true) & 0x3fff;
135
+ return { mimeType: "image/webp", width, height, channels: 3, hasAlpha: false };
136
+ }
137
+
138
+ return { mimeType: "image/webp" };
139
+ }
140
+
141
+ export function parseImageMetadata(header: Uint8Array): ImageMetadata | null {
142
+ return (
143
+ parsePngMetadata(header) ?? parseJpegMetadata(header) ?? parseGifMetadata(header) ?? parseWebpMetadata(header)
144
+ );
145
+ }
146
+
147
+ export function readImageMetadataSync(
148
+ filePath: string,
149
+ maxBytes = DEFAULT_IMAGE_METADATA_HEADER_BYTES,
150
+ ): ImageMetadata | null {
151
+ return peekFileSync(filePath, maxBytes, parseImageMetadata);
152
+ }
153
+
154
+ export function readImageMetadata(
155
+ filePath: string,
156
+ maxBytes = DEFAULT_IMAGE_METADATA_HEADER_BYTES,
157
+ ): Promise<ImageMetadata | null> {
158
+ return peekFile(filePath, maxBytes, parseImageMetadata);
159
+ }
@@ -0,0 +1,162 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { getAgentDbPath } from "./dirs";
4
+
5
+ /**
6
+ * Shared reader for `~/.xcsh/agent/models.yml`.
7
+ *
8
+ * Parses only the shape written by the LiteLLM auto-config:
9
+ *
10
+ * providers:
11
+ * <name>:
12
+ * baseUrl: "https://..."
13
+ * apiKey: ENV_VAR | "literal" | !shellSecret
14
+ *
15
+ * Consumers (anthropic-auth, auto-config) interpret the structured result
16
+ * and apply their own fallback / resolution policy.
17
+ */
18
+
19
+ export type ApiKeyValue =
20
+ | { kind: "envVar"; name: string }
21
+ | { kind: "literal"; value: string; wasQuoted: boolean }
22
+ | { kind: "shellSecret"; raw: string };
23
+
24
+ export interface ProviderYmlEntry {
25
+ baseUrl?: string;
26
+ apiKey?: ApiKeyValue;
27
+ }
28
+
29
+ /**
30
+ * Read a named provider block from models.yml.
31
+ * Returns null if the file is unreadable or the named block is absent.
32
+ *
33
+ * The parser pins two indent levels once it enters the `providers:` section:
34
+ *
35
+ * - `providersChildIndent` — the column where provider names live. A line
36
+ * only qualifies as a provider header when its indent matches this level,
37
+ * so a nested `anthropic:` inside another provider's sub-map is ignored.
38
+ * - `fieldIndent` — the column of the first key-value line inside the
39
+ * target block. Only lines at exactly this indent are considered for
40
+ * `baseUrl` / `apiKey`, so deeper nested maps (e.g. `discovery:`) cannot
41
+ * leak values back into the target block.
42
+ */
43
+ export function readProviderFromModelsYml(providerName: string, modelsYmlPath?: string): ProviderYmlEntry | null {
44
+ const resolvedPath = resolveModelsYmlPath(modelsYmlPath);
45
+ if (!resolvedPath) return null;
46
+
47
+ let content: string;
48
+ try {
49
+ content = fs.readFileSync(resolvedPath, "utf-8");
50
+ } catch {
51
+ return null;
52
+ }
53
+
54
+ const lines = content.split("\n");
55
+ const providerHeaderRe = new RegExp(`^\\s*${escapeRegex(providerName)}\\s*:\\s*$`);
56
+
57
+ let inProviders = false;
58
+ let providersChildIndent = -1;
59
+ let _providerIndent = -1;
60
+ let fieldIndent = -1;
61
+ let inTargetBlock = false;
62
+ let baseUrl: string | undefined;
63
+ let apiKey: ApiKeyValue | undefined;
64
+
65
+ for (const rawLine of lines) {
66
+ const line = rawLine.trimEnd();
67
+ if (line === "" || line.trimStart().startsWith("#")) continue;
68
+
69
+ const indent = line.length - line.trimStart().length;
70
+
71
+ if (indent === 0) {
72
+ inProviders = /^providers\s*:/.test(line);
73
+ inTargetBlock = false;
74
+ providersChildIndent = -1;
75
+ _providerIndent = -1;
76
+ fieldIndent = -1;
77
+ continue;
78
+ }
79
+
80
+ if (!inProviders) continue;
81
+
82
+ // First indented child fixes the provider-name indent level.
83
+ if (providersChildIndent === -1) providersChildIndent = indent;
84
+
85
+ // A line at the provider-name indent is always a provider header,
86
+ // and it ends the previous target block (if any).
87
+ if (indent === providersChildIndent) {
88
+ inTargetBlock = providerHeaderRe.test(line);
89
+ _providerIndent = inTargetBlock ? indent : -1;
90
+ fieldIndent = -1;
91
+ continue;
92
+ }
93
+
94
+ if (!inTargetBlock) continue;
95
+
96
+ // Pin field indent on the first in-block line; reject anything deeper.
97
+ if (fieldIndent === -1) fieldIndent = indent;
98
+ if (indent !== fieldIndent) continue;
99
+
100
+ const kvMatch = line.match(/^\s+(baseUrl|apiKey)\s*:\s*(.*)$/);
101
+ if (!kvMatch) continue;
102
+ const [, key, rawValue] = kvMatch;
103
+ const trimmed = rawValue.trim();
104
+
105
+ if (key === "baseUrl") {
106
+ baseUrl = stripQuotes(trimmed);
107
+ } else if (key === "apiKey") {
108
+ apiKey = parseApiKeyValue(trimmed);
109
+ }
110
+ }
111
+
112
+ if (baseUrl === undefined && apiKey === undefined) return null;
113
+ return { baseUrl, apiKey };
114
+ }
115
+
116
+ function resolveModelsYmlPath(explicit?: string): string | null {
117
+ if (explicit) return explicit;
118
+ try {
119
+ return path.join(path.dirname(getAgentDbPath()), "models.yml");
120
+ } catch {
121
+ return null;
122
+ }
123
+ }
124
+
125
+ function escapeRegex(s: string): string {
126
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
127
+ }
128
+
129
+ function stripQuotes(s: string): string {
130
+ if (s.length >= 2) {
131
+ const first = s[0];
132
+ const last = s[s.length - 1];
133
+ if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
134
+ return s.slice(1, -1);
135
+ }
136
+ }
137
+ return s;
138
+ }
139
+
140
+ /**
141
+ * Env-var reference heuristic: all-caps identifier with at least one
142
+ * underscore. The underscore requirement is what distinguishes a reference
143
+ * like `LITELLM_API_KEY` from a hand-edited unquoted literal such as
144
+ * `SK12345`, which would otherwise be silently resolved via `process.env`
145
+ * and almost always come back undefined.
146
+ */
147
+ function looksLikeEnvVarName(s: string): boolean {
148
+ return /^[A-Z][A-Z0-9_]*$/.test(s) && s.includes("_");
149
+ }
150
+
151
+ function parseApiKeyValue(raw: string): ApiKeyValue {
152
+ if (raw.startsWith("!")) return { kind: "shellSecret", raw };
153
+ if (raw.length >= 2 && raw.startsWith('"') && raw.endsWith('"')) {
154
+ const inner = raw.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
155
+ return { kind: "literal", value: inner, wasQuoted: true };
156
+ }
157
+ if (raw.length >= 2 && raw.startsWith("'") && raw.endsWith("'")) {
158
+ return { kind: "literal", value: raw.slice(1, -1), wasQuoted: true };
159
+ }
160
+ if (looksLikeEnvVarName(raw)) return { kind: "envVar", name: raw };
161
+ return { kind: "literal", value: raw, wasQuoted: false };
162
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Read the first `maxBytes` of a file (offset 0) and pass that slice to `op`.
3
+ *
4
+ * Buffers are reused to avoid allocating on every peek: sync uses one growable
5
+ * `Uint8Array`; async uses a small fixed pool of `Buffer`s with a bounded wait
6
+ * queue, falling back to a fresh allocation when the pool and queue are saturated
7
+ * or when `maxBytes` exceeds the pool slot size.
8
+ */
9
+ import * as fs from "node:fs";
10
+
11
+ /** Async pool slot size; larger peeks allocate ad hoc. */
12
+ const POOLED_BUFFER_SIZE = 512;
13
+ const ASYNC_POOL_SIZE = 10;
14
+ /** Cap waiter queue so heavy concurrency does not queue unbounded; overflow uses alloc. */
15
+ const MAX_ASYNC_WAITERS = 4;
16
+ const INITIAL_SYNC_BUFFER_SIZE = 1024;
17
+ const EMPTY_BUFFER = Buffer.alloc(0);
18
+
19
+ const asyncPool = Array.from({ length: ASYNC_POOL_SIZE }, () => Buffer.allocUnsafe(POOLED_BUFFER_SIZE));
20
+ const availableAsyncPoolIndexes = Array.from({ length: ASYNC_POOL_SIZE }, (_, index) => index);
21
+ const asyncPoolWaiters: Array<(index: number) => void> = [];
22
+ let syncPool = new Uint8Array(INITIAL_SYNC_BUFFER_SIZE);
23
+
24
+ /** Returns a pool slot index, or `-1` when the caller should use a standalone buffer. */
25
+ function acquireAsyncPoolIndex(): Promise<number> | number {
26
+ const index = availableAsyncPoolIndexes.pop();
27
+ if (index !== undefined) {
28
+ return index;
29
+ }
30
+ if (asyncPoolWaiters.length >= MAX_ASYNC_WAITERS) {
31
+ return -1;
32
+ }
33
+ const { promise, resolve } = Promise.withResolvers<number>();
34
+ asyncPoolWaiters.push(resolve);
35
+ return promise;
36
+ }
37
+
38
+ function releaseAsyncPoolIndex(index: number): void {
39
+ if (index < 0) {
40
+ return;
41
+ }
42
+ const waiter = asyncPoolWaiters.shift();
43
+ if (waiter) {
44
+ waiter(index);
45
+ return;
46
+ }
47
+ availableAsyncPoolIndexes.push(index);
48
+ }
49
+
50
+ async function withAsyncPoolBuffer<T>(maxBytes: number, op: (buffer: Buffer) => Promise<T>): Promise<T> {
51
+ if (maxBytes <= 0) {
52
+ return op(EMPTY_BUFFER);
53
+ }
54
+ if (maxBytes > POOLED_BUFFER_SIZE) {
55
+ return op(Buffer.allocUnsafe(maxBytes));
56
+ }
57
+
58
+ const poolIndex = await acquireAsyncPoolIndex();
59
+ const buffer = poolIndex >= 0 ? asyncPool[poolIndex] : Buffer.allocUnsafe(maxBytes);
60
+ try {
61
+ return await op(buffer.subarray(0, maxBytes));
62
+ } finally {
63
+ releaseAsyncPoolIndex(poolIndex);
64
+ }
65
+ }
66
+
67
+ function withSyncPoolBuffer<T>(maxBytes: number, op: (buffer: Uint8Array) => T): T {
68
+ if (maxBytes <= 0) {
69
+ return op(EMPTY_BUFFER);
70
+ }
71
+ if (maxBytes > syncPool.byteLength) {
72
+ syncPool = new Uint8Array(maxBytes + (maxBytes >> 1));
73
+ }
74
+ return op(syncPool.subarray(0, maxBytes));
75
+ }
76
+
77
+ /**
78
+ * Synchronously reads up to `maxBytes` from the start of `filePath` and returns `op(header)`.
79
+ * If the file is shorter, `header` is only the bytes actually read.
80
+ */
81
+ export function peekFileSync<T>(filePath: string, maxBytes: number, op: (header: Uint8Array) => T): T {
82
+ if (maxBytes <= 0) {
83
+ return op(EMPTY_BUFFER);
84
+ }
85
+
86
+ const fileHandle = fs.openSync(filePath, "r");
87
+ try {
88
+ return withSyncPoolBuffer(maxBytes, buffer => {
89
+ const bytesRead = fs.readSync(fileHandle, buffer, 0, buffer.byteLength, 0);
90
+ return op(buffer.subarray(0, bytesRead));
91
+ });
92
+ } finally {
93
+ fs.closeSync(fileHandle);
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Like {@link peekFileSync} but uses async I/O.
99
+ */
100
+ export async function peekFile<T>(filePath: string, maxBytes: number, op: (header: Uint8Array) => T): Promise<T> {
101
+ if (maxBytes <= 0) {
102
+ return op(EMPTY_BUFFER);
103
+ }
104
+
105
+ const fileHandle = await fs.promises.open(filePath, "r");
106
+ try {
107
+ return await withAsyncPoolBuffer(maxBytes, async buffer => {
108
+ const { bytesRead } = await fileHandle.read(buffer, 0, buffer.byteLength, 0);
109
+ return op(buffer.subarray(0, bytesRead));
110
+ });
111
+ } finally {
112
+ await fileHandle.close();
113
+ }
114
+ }