mirror of
https://github.com/earendil-works/pi.git
synced 2026-06-18 15:54:04 +08:00
chore(deps): Kill small dependencies (#4467)
This commit is contained in:
committed by
GitHub
Unverified
parent
8da1f3d131
commit
2829146dde
Generated
+5
-186
@@ -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("")}`;
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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
-1
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user