chore(deps): Kill small dependencies (#4467)

This commit is contained in:
Armin Ronacher
2026-05-13 10:44:56 +02:00
committed by GitHub
Unverified
parent 8da1f3d131
commit 2829146dde
18 changed files with 357 additions and 227 deletions
+5 -186
View File
@@ -929,14 +929,6 @@
"node": ">=14.21.3"
}
},
"node_modules/@borewit/text-codec": {
"version": "0.2.2",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/@earendil-works/pi-agent-core": {
"resolved": "packages/agent",
"link": true
@@ -3645,25 +3637,6 @@
"vite": "^5.2.0 || ^6 || ^7 || ^8"
}
},
"node_modules/@tokenizer/inflate": {
"version": "0.4.1",
"license": "MIT",
"dependencies": {
"debug": "^4.4.3",
"token-types": "^6.1.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/@tokenizer/token": {
"version": "0.3.0",
"license": "MIT"
},
"node_modules/@tootallnate/quickjs-emscripten": {
"version": "0.23.0",
"license": "MIT"
@@ -3739,14 +3712,6 @@
"version": "2.0.7",
"license": "MIT"
},
"node_modules/@types/yauzl": {
"version": "2.10.3",
"license": "MIT",
"optional": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@typescript/native-preview": {
"version": "7.0.0-dev.20260120.1",
"dev": true,
@@ -3989,16 +3954,6 @@
"version": "2.0.8",
"license": "MIT"
},
"node_modules/ansi-regex": {
"version": "6.2.2",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"license": "MIT",
@@ -4131,13 +4086,6 @@
"ieee754": "^1.1.13"
}
},
"node_modules/buffer-crc32": {
"version": "0.2.13",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"license": "BSD-3-Clause"
@@ -4541,6 +4489,7 @@
},
"node_modules/end-of-stream": {
"version": "1.4.5",
"dev": true,
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
@@ -4718,24 +4667,6 @@
"version": "3.0.2",
"license": "MIT"
},
"node_modules/extract-zip": {
"version": "2.0.1",
"license": "BSD-2-Clause",
"dependencies": {
"debug": "^4.1.1",
"get-stream": "^5.1.0",
"yauzl": "^2.10.0"
},
"bin": {
"extract-zip": "cli.js"
},
"engines": {
"node": ">= 10.17.0"
},
"optionalDependencies": {
"@types/yauzl": "^2.9.1"
}
},
"node_modules/fast-glob": {
"version": "3.3.3",
"dev": true,
@@ -4791,13 +4722,6 @@
"reusify": "^1.0.4"
}
},
"node_modules/fd-slicer": {
"version": "1.1.0",
"license": "MIT",
"dependencies": {
"pend": "~1.2.0"
}
},
"node_modules/fetch-blob": {
"version": "3.2.0",
"funding": [
@@ -4819,22 +4743,6 @@
"node": "^12.20 || >= 14.13"
}
},
"node_modules/file-type": {
"version": "21.3.4",
"license": "MIT",
"dependencies": {
"@tokenizer/inflate": "^0.4.1",
"strtok3": "^10.3.4",
"token-types": "^6.1.1",
"uint8array-extras": "^1.4.0"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"dev": true,
@@ -4925,19 +4833,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/get-stream": {
"version": "5.2.0",
"license": "MIT",
"dependencies": {
"pump": "^3.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/get-tsconfig": {
"version": "4.14.0",
"dev": true,
@@ -5102,6 +4997,7 @@
},
"node_modules/ieee754": {
"version": "1.2.1",
"dev": true,
"funding": [
{
"type": "github",
@@ -5863,6 +5759,7 @@
},
"node_modules/once": {
"version": "1.4.0",
"dev": true,
"license": "ISC",
"dependencies": {
"wrappy": "1"
@@ -6024,10 +5921,6 @@
"@napi-rs/canvas": "^0.1.81"
}
},
"node_modules/pend": {
"version": "1.2.0",
"license": "MIT"
},
"node_modules/pi-extension-custom-provider-anthropic": {
"resolved": "packages/coding-agent/examples/extensions/custom-provider-anthropic",
"link": true
@@ -6188,6 +6081,7 @@
},
"node_modules/pump": {
"version": "3.0.4",
"dev": true,
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
@@ -6621,19 +6515,6 @@
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "7.2.0",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.2.2"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/strip-eof": {
"version": "1.0.0",
"dev": true,
@@ -6671,20 +6552,6 @@
],
"license": "MIT"
},
"node_modules/strtok3": {
"version": "10.3.5",
"license": "MIT",
"dependencies": {
"@tokenizer/token": "^0.3.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/supports-color": {
"version": "8.1.1",
"dev": true,
@@ -6881,22 +6748,6 @@
"node": ">=8.0"
}
},
"node_modules/token-types": {
"version": "6.1.2",
"license": "MIT",
"dependencies": {
"@borewit/text-codec": "^0.2.1",
"@tokenizer/token": "^0.3.0",
"ieee754": "^1.2.1"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/tree-kill": {
"version": "1.2.2",
"dev": true,
@@ -6965,16 +6816,6 @@
"@webreflection/alien-signals": "^0.3.2"
}
},
"node_modules/uint8array-extras": {
"version": "1.5.0",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/undici": {
"version": "7.25.0",
"license": "MIT",
@@ -6990,17 +6831,6 @@
"version": "1.0.2",
"license": "MIT"
},
"node_modules/uuid": {
"version": "14.0.0",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist-node/bin/uuid"
}
},
"node_modules/vite": {
"version": "7.3.3",
"dev": true,
@@ -7275,6 +7105,7 @@
},
"node_modules/wrappy": {
"version": "1.0.2",
"dev": true,
"license": "ISC"
},
"node_modules/ws": {
@@ -7351,14 +7182,6 @@
"node": ">=10"
}
},
"node_modules/yauzl": {
"version": "2.10.0",
"license": "MIT",
"dependencies": {
"buffer-crc32": "~0.2.3",
"fd-slicer": "~1.1.0"
}
},
"node_modules/zod": {
"version": "4.4.3",
"license": "MIT",
@@ -7457,18 +7280,14 @@
"chalk": "^5.5.0",
"cli-highlight": "^2.1.11",
"diff": "^8.0.2",
"extract-zip": "^2.0.1",
"file-type": "^21.1.1",
"glob": "^13.0.1",
"hosted-git-info": "^9.0.2",
"ignore": "^7.0.5",
"jiti": "^2.7.0",
"minimatch": "^10.2.3",
"proper-lockfile": "^4.1.2",
"strip-ansi": "^7.1.0",
"typebox": "^1.1.24",
"undici": "^7.19.1",
"uuid": "^14.0.0",
"yaml": "^2.8.2"
},
"bin": {
@@ -1,6 +1,6 @@
import { v7 as uuidv7 } from "uuid";
import type { SessionMetadata, SessionStorage, SessionTreeEntry } from "../../types.js";
import { Session } from "../session.js";
import { uuidv7 } from "../uuid.js";
export function createSessionId(): string {
return uuidv7();
@@ -1,6 +1,6 @@
import { randomUUID } from "node:crypto";
import { v7 as uuidv7 } from "uuid";
import type { SessionMetadata, SessionStorage, SessionTreeEntry } from "../../types.js";
import { uuidv7 } from "../uuid.js";
function updateLabelCache(labelsById: Map<string, string>, entry: SessionTreeEntry): void {
if (entry.type !== "label") return;
@@ -0,0 +1,44 @@
import { randomBytes } from "node:crypto";
let lastTimestamp = -Infinity;
let sequence = 0;
export function uuidv7(): string {
const random = randomBytes(16);
const timestamp = Date.now();
if (timestamp > lastTimestamp) {
sequence = (random[6] << 23) | (random[7] << 16) | (random[8] << 8) | random[9];
lastTimestamp = timestamp;
} else {
sequence = (sequence + 1) | 0;
if (sequence === 0) {
lastTimestamp++;
}
}
const bytes = new Uint8Array(16);
bytes[0] = (lastTimestamp / 0x10000000000) & 0xff;
bytes[1] = (lastTimestamp / 0x100000000) & 0xff;
bytes[2] = (lastTimestamp / 0x1000000) & 0xff;
bytes[3] = (lastTimestamp / 0x10000) & 0xff;
bytes[4] = (lastTimestamp / 0x100) & 0xff;
bytes[5] = lastTimestamp & 0xff;
bytes[6] = 0x70 | ((sequence >>> 28) & 0x0f);
bytes[7] = (sequence >>> 20) & 0xff;
bytes[8] = 0x80 | ((sequence >>> 14) & 0x3f);
bytes[9] = (sequence >>> 6) & 0xff;
bytes[10] = ((sequence << 2) & 0xff) | (random[10] & 0x03);
bytes[11] = random[11];
bytes[12] = random[12];
bytes[13] = random[13];
bytes[14] = random[14];
bytes[15] = random[15];
return formatUuid(bytes);
}
function formatUuid(bytes: Uint8Array): string {
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0"));
return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10, 16).join("")}`;
}
-4
View File
@@ -45,18 +45,14 @@
"chalk": "^5.5.0",
"cli-highlight": "^2.1.11",
"diff": "^8.0.2",
"extract-zip": "^2.0.1",
"file-type": "^21.1.1",
"glob": "^13.0.1",
"hosted-git-info": "^9.0.2",
"ignore": "^7.0.5",
"jiti": "^2.7.0",
"minimatch": "^10.2.3",
"proper-lockfile": "^4.1.2",
"strip-ansi": "^7.1.0",
"typebox": "^1.1.24",
"undici": "^7.19.1",
"uuid": "^14.0.0",
"yaml": "^2.8.2"
},
"overrides": {
@@ -10,7 +10,7 @@ import { randomBytes } from "node:crypto";
import { createWriteStream, type WriteStream } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import stripAnsi from "strip-ansi";
import { stripAnsi } from "../utils/ansi.js";
import { sanitizeBinaryOutput } from "../utils/shell.js";
import type { BashOperations } from "./tools/bash.js";
import { DEFAULT_MAX_BYTES, truncateTail } from "./tools/truncate.js";
@@ -15,8 +15,8 @@ import {
} from "fs";
import { readdir, readFile, stat } from "fs/promises";
import { join, resolve } from "path";
import { v7 as uuidv7 } from "uuid";
import { getAgentDir as getDefaultAgentDir, getSessionsDir } from "../config.js";
import { uuidv7 } from "../utils/uuid.js";
import {
type BashExecutionMessage,
type CustomMessage,
@@ -1,7 +1,7 @@
import * as os from "node:os";
import type { ImageContent, TextContent } from "@earendil-works/pi-ai";
import { getCapabilities, getImageDimensions, imageFallback } from "@earendil-works/pi-tui";
import stripAnsi from "strip-ansi";
import { stripAnsi } from "../../utils/ansi.js";
import { sanitizeBinaryOutput } from "../../utils/shell.js";
export function shortenPath(path: unknown): string {
@@ -3,13 +3,13 @@
*/
import { Container, Loader, Spacer, Text, type TUI } from "@earendil-works/pi-tui";
import stripAnsi from "strip-ansi";
import {
DEFAULT_MAX_BYTES,
DEFAULT_MAX_LINES,
type TruncationResult,
truncateTail,
} from "../../../core/tools/truncate.js";
import { stripAnsi } from "../../../utils/ansi.js";
import { theme } from "../theme/theme.js";
import { DynamicBorder } from "./dynamic-border.js";
import { keyHint, keyText } from "./keybinding-hints.js";
+87
View File
@@ -0,0 +1,87 @@
export function stripAnsi(value: string): string {
let output = "";
let index = 0;
while (index < value.length) {
const code = value.charCodeAt(index);
if (code === 0x1b) {
const next = value[index + 1];
if (next === "[") {
index = skipCsi(value, index + 2);
continue;
}
if (next === "]" || next === "P" || next === "^" || next === "_") {
index = skipStringControl(value, index + 2);
continue;
}
if (next && "()*+-./#".includes(next)) {
index = Math.min(index + 3, value.length);
continue;
}
if (next && isEscFinalByte(next.charCodeAt(0))) {
index += 2;
continue;
}
} else if (code === 0x9b) {
index = skipCsi(value, index + 1);
continue;
} else if (code === 0x9d || code === 0x90 || code === 0x9e || code === 0x9f) {
index = skipStringControl(value, index + 1);
continue;
}
output += value[index];
index++;
}
return output;
}
function isEscFinalByte(code: number): boolean {
return (
(code >= 0x30 && code <= 0x39) ||
(code >= 0x41 && code <= 0x50) ||
(code >= 0x52 && code <= 0x54) ||
code === 0x5a ||
code === 0x63 ||
(code >= 0x66 && code <= 0x6e) ||
(code >= 0x71 && code <= 0x75) ||
code === 0x79 ||
code === 0x3d ||
code === 0x3e ||
code === 0x3c ||
code === 0x7e
);
}
function skipCsi(value: string, start: number): number {
let index = start;
while (index < value.length) {
const code = value.charCodeAt(index);
index++;
if (code >= 0x40 && code <= 0x7e) {
return index;
}
}
return value.length;
}
function skipStringControl(value: string, start: number): number {
let index = start;
while (index < value.length) {
const code = value.charCodeAt(index);
if (code === 0x07 || code === 0x9c) {
return index + 1;
}
if (code === 0x1b && value[index + 1] === "\\") {
return index + 2;
}
index++;
}
return value.length;
}
+63 -19
View File
@@ -1,30 +1,74 @@
import { open } from "node:fs/promises";
import { fileTypeFromBuffer } from "file-type";
const IMAGE_MIME_TYPES = new Set(["image/jpeg", "image/png", "image/gif", "image/webp"]);
const IMAGE_TYPE_SNIFF_BYTES = 4100;
const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
const FILE_TYPE_SNIFF_BYTES = 4100;
export function detectSupportedImageMimeType(buffer: Uint8Array): string | null {
if (startsWith(buffer, [0xff, 0xd8, 0xff])) {
return buffer[3] === 0xf7 ? null : "image/jpeg";
}
if (startsWith(buffer, PNG_SIGNATURE)) {
return isPng(buffer) && !isAnimatedPng(buffer) ? "image/png" : null;
}
if (startsWithAscii(buffer, 0, "GIF")) {
return "image/gif";
}
if (startsWithAscii(buffer, 0, "RIFF") && startsWithAscii(buffer, 8, "WEBP")) {
return "image/webp";
}
return null;
}
export async function detectSupportedImageMimeTypeFromFile(filePath: string): Promise<string | null> {
const fileHandle = await open(filePath, "r");
try {
const buffer = Buffer.alloc(FILE_TYPE_SNIFF_BYTES);
const { bytesRead } = await fileHandle.read(buffer, 0, FILE_TYPE_SNIFF_BYTES, 0);
if (bytesRead === 0) {
return null;
}
const fileType = await fileTypeFromBuffer(buffer.subarray(0, bytesRead));
if (!fileType) {
return null;
}
if (!IMAGE_MIME_TYPES.has(fileType.mime)) {
return null;
}
return fileType.mime;
const buffer = Buffer.alloc(IMAGE_TYPE_SNIFF_BYTES);
const { bytesRead } = await fileHandle.read(buffer, 0, IMAGE_TYPE_SNIFF_BYTES, 0);
return detectSupportedImageMimeType(buffer.subarray(0, bytesRead));
} finally {
await fileHandle.close();
}
}
function isPng(buffer: Uint8Array): boolean {
return (
buffer.length >= 16 && readUint32BE(buffer, PNG_SIGNATURE.length) === 13 && startsWithAscii(buffer, 12, "IHDR")
);
}
function isAnimatedPng(buffer: Uint8Array): boolean {
let offset = PNG_SIGNATURE.length;
while (offset + 8 <= buffer.length) {
const chunkLength = readUint32BE(buffer, offset);
const chunkTypeOffset = offset + 4;
if (startsWithAscii(buffer, chunkTypeOffset, "acTL")) return true;
if (startsWithAscii(buffer, chunkTypeOffset, "IDAT")) return false;
const nextOffset = offset + 8 + chunkLength + 4;
if (nextOffset <= offset || nextOffset > buffer.length) return false;
offset = nextOffset;
}
return false;
}
function readUint32BE(buffer: Uint8Array, offset: number): number {
return (
(buffer[offset] ?? 0) * 0x1000000 +
((buffer[offset + 1] ?? 0) << 16) +
((buffer[offset + 2] ?? 0) << 8) +
(buffer[offset + 3] ?? 0)
);
}
function startsWith(buffer: Uint8Array, bytes: number[]): boolean {
if (buffer.length < bytes.length) return false;
return bytes.every((byte, index) => buffer[index] === byte);
}
function startsWithAscii(buffer: Uint8Array, offset: number, text: string): boolean {
if (buffer.length < offset + text.length) return false;
for (let index = 0; index < text.length; index++) {
if (buffer[offset + index] !== text.charCodeAt(index)) return false;
}
return true;
}
@@ -1,6 +1,5 @@
import chalk from "chalk";
import { spawnSync } from "child_process";
import extractZip from "extract-zip";
import { type SpawnSyncReturns, spawnSync } from "child_process";
import { chmodSync, createWriteStream, existsSync, mkdirSync, readdirSync, renameSync, rmSync } from "fs";
import { arch, platform } from "os";
import { join } from "path";
@@ -159,6 +158,85 @@ function findBinaryRecursively(rootDir: string, binaryFileName: string): string
return null;
}
function formatSpawnFailure(result: SpawnSyncReturns<Buffer>): string {
if (result.error?.message) {
return result.error.message;
}
const stderr = result.stderr?.toString().trim();
if (stderr) {
return stderr;
}
const stdout = result.stdout?.toString().trim();
if (stdout) {
return stdout;
}
return `exit status ${result.status ?? "unknown"}`;
}
function runExtractionCommand(command: string, args: string[]): string | null {
const result = spawnSync(command, args, { stdio: "pipe" });
if (!result.error && result.status === 0) {
return null;
}
return `${command}: ${formatSpawnFailure(result)}`;
}
function extractTarGzArchive(archivePath: string, extractDir: string, assetName: string): void {
const failure = runExtractionCommand("tar", ["xzf", archivePath, "-C", extractDir]);
if (failure) {
throw new Error(`Failed to extract ${assetName}: ${failure}`);
}
}
function getWindowsTarCommand(): string {
const systemRoot = process.env.SystemRoot ?? process.env.WINDIR;
if (systemRoot) {
const systemTar = join(systemRoot, "System32", "tar.exe");
if (existsSync(systemTar)) {
return systemTar;
}
}
return "tar.exe";
}
function extractZipArchive(archivePath: string, extractDir: string, assetName: string): void {
const failures: string[] = [];
if (platform() === "win32") {
// Windows ships bsdtar as tar.exe, which supports zip files. Prefer the
// System32 binary over Git Bash's GNU tar, which does not handle zip archives.
const tarFailure = runExtractionCommand(getWindowsTarCommand(), ["xf", archivePath, "-C", extractDir]);
if (!tarFailure) return;
failures.push(tarFailure);
const script =
"& { param($archive, $destination) $ErrorActionPreference = 'Stop'; Expand-Archive -LiteralPath $archive -DestinationPath $destination -Force }";
const powershellFailure = runExtractionCommand("powershell.exe", [
"-NoLogo",
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy",
"Bypass",
"-Command",
script,
archivePath,
extractDir,
]);
if (!powershellFailure) return;
failures.push(powershellFailure);
} else {
const unzipFailure = runExtractionCommand("unzip", ["-q", archivePath, "-d", extractDir]);
if (!unzipFailure) return;
failures.push(unzipFailure);
const tarFailure = runExtractionCommand("tar", ["xf", archivePath, "-C", extractDir]);
if (!tarFailure) return;
failures.push(tarFailure);
}
throw new Error(`Failed to extract ${assetName}: ${failures.join("; ")}`);
}
// Download and install a tool
async function downloadTool(tool: "fd" | "rg"): Promise<string> {
const config = TOOLS[tool];
@@ -197,13 +275,9 @@ async function downloadTool(tool: "fd" | "rg"): Promise<string> {
try {
if (assetName.endsWith(".tar.gz")) {
const extractResult = spawnSync("tar", ["xzf", archivePath, "-C", extractDir], { stdio: "pipe" });
if (extractResult.error || extractResult.status !== 0) {
const errMsg = extractResult.error?.message ?? extractResult.stderr?.toString().trim() ?? "unknown error";
throw new Error(`Failed to extract ${assetName}: ${errMsg}`);
}
extractTarGzArchive(archivePath, extractDir, assetName);
} else if (assetName.endsWith(".zip")) {
await extractZip(archivePath, { dir: extractDir });
extractZipArchive(archivePath, extractDir, assetName);
} else {
throw new Error(`Unsupported archive format: ${assetName}`);
}
+44
View File
@@ -0,0 +1,44 @@
import { randomBytes } from "node:crypto";
let lastTimestamp = -Infinity;
let sequence = 0;
export function uuidv7(): string {
const random = randomBytes(16);
const timestamp = Date.now();
if (timestamp > lastTimestamp) {
sequence = (random[6] << 23) | (random[7] << 16) | (random[8] << 8) | random[9];
lastTimestamp = timestamp;
} else {
sequence = (sequence + 1) | 0;
if (sequence === 0) {
lastTimestamp++;
}
}
const bytes = new Uint8Array(16);
bytes[0] = (lastTimestamp / 0x10000000000) & 0xff;
bytes[1] = (lastTimestamp / 0x100000000) & 0xff;
bytes[2] = (lastTimestamp / 0x1000000) & 0xff;
bytes[3] = (lastTimestamp / 0x10000) & 0xff;
bytes[4] = (lastTimestamp / 0x100) & 0xff;
bytes[5] = lastTimestamp & 0xff;
bytes[6] = 0x70 | ((sequence >>> 28) & 0x0f);
bytes[7] = (sequence >>> 20) & 0xff;
bytes[8] = 0x80 | ((sequence >>> 14) & 0x3f);
bytes[9] = (sequence >>> 6) & 0xff;
bytes[10] = ((sequence << 2) & 0xff) | (random[10] & 0x03);
bytes[11] = random[11];
bytes[12] = random[12];
bytes[13] = random[13];
bytes[14] = random[14];
bytes[15] = random[15];
return formatUuid(bytes);
}
function formatUuid(bytes: Uint8Array): string {
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0"));
return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10, 16).join("")}`;
}
@@ -0,0 +1,22 @@
import { describe, expect, it } from "vitest";
import { stripAnsi } from "../src/utils/ansi.js";
describe("stripAnsi", () => {
it("strips RIS without leaking the final byte", () => {
expect(stripAnsi("\x1bcdone")).toBe("done");
});
it("strips single-byte ESC sequences without leaking final bytes", () => {
for (let code = "g".charCodeAt(0); code <= "m".charCodeAt(0); code++) {
expect(stripAnsi(`\x1b${String.fromCharCode(code)}ok`)).toBe("ok");
}
for (let code = "r".charCodeAt(0); code <= "t".charCodeAt(0); code++) {
expect(stripAnsi(`\x1b${String.fromCharCode(code)}ok`)).toBe("ok");
}
});
it("strips common ANSI sequences used in tool output", () => {
const input = "a\x1b[31mred\x1b[0m\x1b]8;;https://example.com\x07link\x1b]8;;\x07z";
expect(stripAnsi(input)).toBe("aredlinkz");
});
});
@@ -1,5 +1,4 @@
import { setKeybindings } from "@earendil-works/pi-tui";
import stripAnsi from "strip-ansi";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { AuthStorage } from "../src/core/auth-storage.js";
import { KeybindingsManager } from "../src/core/keybindings.js";
@@ -7,6 +6,7 @@ import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../src/core/provider-display-na
import { OAuthSelectorComponent } from "../src/modes/interactive/components/oauth-selector.js";
import { isApiKeyLoginProvider } from "../src/modes/interactive/interactive-mode.js";
import { initTheme } from "../src/modes/interactive/theme/theme.js";
import { stripAnsi } from "../src/utils/ansi.js";
const originalOpenAiApiKey = process.env.OPENAI_API_KEY;
@@ -1,10 +1,10 @@
import { setKeybindings, type TUI } from "@earendil-works/pi-tui";
import stripAnsi from "strip-ansi";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { KeybindingsManager } from "../../../src/core/keybindings.js";
import { ModelSelectorComponent } from "../../../src/modes/interactive/components/model-selector.js";
import { ScopedModelsSelectorComponent } from "../../../src/modes/interactive/components/scoped-models-selector.js";
import { initTheme } from "../../../src/modes/interactive/theme/theme.js";
import { stripAnsi } from "../../../src/utils/ansi.js";
import { createHarness, type Harness } from "../harness.js";
function createFakeTui(): TUI {
@@ -1,13 +1,13 @@
import type { AgentMessage } from "@earendil-works/pi-agent-core";
import type { AssistantMessage, ToolResultMessage, Usage } from "@earendil-works/pi-ai";
import { Container, Text, type TUI } from "@earendil-works/pi-tui";
import stripAnsi from "strip-ansi";
import { beforeAll, describe, expect, test, vi } from "vitest";
import type { AgentSessionEvent } from "../../../src/core/agent-session.js";
import type { SessionContext } from "../../../src/core/session-manager.js";
import type { ToolExecutionComponent } from "../../../src/modes/interactive/components/tool-execution.js";
import { InteractiveMode } from "../../../src/modes/interactive/interactive-mode.js";
import { initTheme } from "../../../src/modes/interactive/theme/theme.js";
import { stripAnsi } from "../../../src/utils/ansi.js";
const TOOL_CALL_ID = "tool-4167";
const TOOL_NAME = "slow_tool";
@@ -1,6 +1,5 @@
import { join, resolve } from "node:path";
import { Text, type TUI } from "@earendil-works/pi-tui";
import stripAnsi from "strip-ansi";
import { Type } from "typebox";
import { beforeAll, describe, expect, test } from "vitest";
import { getReadmePath } from "../src/config.js";
@@ -10,6 +9,7 @@ import { createReadTool, createReadToolDefinition } from "../src/core/tools/read
import { createWriteToolDefinition } from "../src/core/tools/write.js";
import { ToolExecutionComponent } from "../src/modes/interactive/components/tool-execution.js";
import { initTheme } from "../src/modes/interactive/theme/theme.js";
import { stripAnsi } from "../src/utils/ansi.js";
function createBaseToolDefinition(name = "custom_tool"): ToolDefinition {
return {