@oh-my-pi/pi-coding-agent 13.19.0 → 14.0.3
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/CHANGELOG.md +277 -2
- package/package.json +86 -20
- package/scripts/format-prompts.ts +2 -2
- package/src/autoresearch/apply-contract-to-state.ts +24 -0
- package/src/autoresearch/contract.ts +0 -44
- package/src/autoresearch/dashboard.ts +1 -2
- package/src/autoresearch/git.ts +91 -0
- package/src/autoresearch/helpers.ts +49 -0
- package/src/autoresearch/index.ts +28 -187
- package/src/autoresearch/prompt.md +26 -9
- package/src/autoresearch/state.ts +0 -6
- package/src/autoresearch/tools/init-experiment.ts +202 -117
- package/src/autoresearch/tools/log-experiment.ts +83 -125
- package/src/autoresearch/tools/run-experiment.ts +48 -10
- package/src/autoresearch/types.ts +2 -2
- package/src/capability/index.ts +4 -2
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/grep-cli.ts +8 -8
- package/src/cli/grievances-cli.ts +78 -0
- package/src/cli/read-cli.ts +67 -0
- package/src/cli/setup-cli.ts +4 -4
- package/src/cli/update-cli.ts +3 -3
- package/src/cli.ts +2 -0
- package/src/commands/grep.ts +6 -1
- package/src/commands/grievances.ts +20 -0
- package/src/commands/read.ts +33 -0
- package/src/commit/agentic/agent.ts +5 -5
- package/src/commit/agentic/index.ts +3 -4
- package/src/commit/agentic/tools/analyze-file.ts +3 -3
- package/src/commit/agentic/validation.ts +1 -1
- package/src/commit/analysis/conventional.ts +4 -4
- package/src/commit/analysis/summary.ts +3 -3
- package/src/commit/changelog/generate.ts +4 -4
- package/src/commit/map-reduce/map-phase.ts +4 -4
- package/src/commit/map-reduce/reduce-phase.ts +4 -4
- package/src/commit/pipeline.ts +3 -4
- package/src/config/model-registry.ts +17 -3
- package/src/config/prompt-templates.ts +44 -226
- package/src/config/resolve-config-value.ts +4 -2
- package/src/config/settings-schema.ts +54 -2
- package/src/config/settings.ts +25 -26
- package/src/dap/client.ts +674 -0
- package/src/dap/config.ts +150 -0
- package/src/dap/defaults.json +211 -0
- package/src/dap/index.ts +4 -0
- package/src/dap/session.ts +1255 -0
- package/src/dap/types.ts +600 -0
- package/src/debug/log-viewer.ts +3 -2
- package/src/discovery/builtin.ts +1 -2
- package/src/discovery/codex.ts +2 -2
- package/src/discovery/github.ts +2 -1
- package/src/discovery/helpers.ts +2 -2
- package/src/discovery/opencode.ts +2 -2
- package/src/edit/diff.ts +818 -0
- package/src/edit/index.ts +309 -0
- package/src/edit/line-hash.ts +67 -0
- package/src/edit/modes/chunk.ts +454 -0
- package/src/{patch → edit/modes}/hashline.ts +741 -361
- package/src/{patch/applicator.ts → edit/modes/patch.ts} +420 -117
- package/src/{patch/fuzzy.ts → edit/modes/replace.ts} +519 -197
- package/src/{patch → edit}/normalize.ts +97 -76
- package/src/{patch/shared.ts → edit/renderer.ts} +181 -108
- package/src/exec/bash-executor.ts +4 -2
- package/src/exec/idle-timeout-watchdog.ts +126 -0
- package/src/exec/non-interactive-env.ts +5 -0
- package/src/extensibility/custom-commands/bundled/ci-green/index.ts +2 -2
- package/src/extensibility/custom-commands/bundled/review/index.ts +36 -15
- package/src/extensibility/custom-commands/loader.ts +1 -2
- package/src/extensibility/custom-tools/loader.ts +34 -11
- package/src/extensibility/extensions/loader.ts +9 -4
- package/src/extensibility/extensions/runner.ts +24 -1
- package/src/extensibility/extensions/types.ts +1 -1
- package/src/extensibility/hooks/loader.ts +5 -6
- package/src/extensibility/hooks/types.ts +1 -1
- package/src/extensibility/plugins/doctor.ts +2 -1
- package/src/extensibility/slash-commands.ts +3 -7
- package/src/index.ts +2 -1
- package/src/internal-urls/docs-index.generated.ts +11 -11
- package/src/ipy/executor.ts +58 -17
- package/src/ipy/gateway-coordinator.ts +6 -4
- package/src/ipy/kernel.ts +45 -22
- package/src/ipy/runtime.ts +2 -2
- package/src/lsp/client.ts +7 -4
- package/src/lsp/clients/lsp-linter-client.ts +4 -4
- package/src/lsp/config.ts +20 -4
- package/src/lsp/defaults.json +688 -154
- package/src/lsp/index.ts +234 -45
- package/src/lsp/lspmux.ts +2 -2
- package/src/lsp/startup-events.ts +13 -0
- package/src/lsp/types.ts +12 -1
- package/src/lsp/utils.ts +8 -1
- package/src/main.ts +102 -46
- package/src/memories/index.ts +4 -5
- package/src/modes/acp/acp-agent.ts +563 -163
- package/src/modes/acp/acp-event-mapper.ts +9 -1
- package/src/modes/acp/acp-mode.ts +4 -2
- package/src/modes/components/agent-dashboard.ts +3 -4
- package/src/modes/components/diff.ts +6 -7
- package/src/modes/components/read-tool-group.ts +6 -12
- package/src/modes/components/session-observer-overlay.ts +21 -12
- package/src/modes/components/settings-defs.ts +5 -0
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/btw-controller.ts +2 -2
- package/src/modes/controllers/command-controller.ts +3 -2
- package/src/modes/controllers/input-controller.ts +12 -8
- package/src/modes/index.ts +20 -2
- package/src/modes/interactive-mode.ts +94 -37
- package/src/modes/rpc/host-tools.ts +186 -0
- package/src/modes/rpc/rpc-client.ts +178 -13
- package/src/modes/rpc/rpc-mode.ts +73 -3
- package/src/modes/rpc/rpc-types.ts +53 -1
- package/src/modes/theme/theme.ts +80 -8
- package/src/modes/types.ts +2 -2
- package/src/prompts/review-request.md +6 -0
- package/src/prompts/system/system-prompt.md +2 -1
- package/src/prompts/tools/chunk-edit.md +223 -0
- package/src/prompts/tools/debug.md +43 -0
- package/src/prompts/tools/grep.md +3 -0
- package/src/prompts/tools/lsp.md +5 -5
- package/src/prompts/tools/read-chunk.md +17 -0
- package/src/prompts/tools/read.md +19 -5
- package/src/sdk.ts +190 -154
- package/src/secrets/obfuscator.ts +1 -1
- package/src/session/agent-session.ts +306 -256
- package/src/session/agent-storage.ts +12 -12
- package/src/session/compaction/branch-summarization.ts +3 -3
- package/src/session/compaction/compaction.ts +5 -6
- package/src/session/compaction/utils.ts +3 -3
- package/src/session/history-storage.ts +62 -19
- package/src/session/messages.ts +3 -3
- package/src/session/session-dump-format.ts +203 -0
- package/src/session/session-storage.ts +4 -2
- package/src/session/streaming-output.ts +1 -1
- package/src/session/tool-choice-queue.ts +213 -0
- package/src/slash-commands/builtin-registry.ts +56 -8
- package/src/ssh/connection-manager.ts +2 -2
- package/src/ssh/sshfs-mount.ts +5 -5
- package/src/stt/downloader.ts +4 -4
- package/src/stt/recorder.ts +4 -4
- package/src/stt/transcriber.ts +2 -2
- package/src/system-prompt.ts +21 -13
- package/src/task/agents.ts +5 -6
- package/src/task/commands.ts +2 -5
- package/src/task/executor.ts +4 -4
- package/src/task/index.ts +3 -4
- package/src/task/template.ts +2 -2
- package/src/task/worktree.ts +4 -4
- package/src/tools/ask.ts +2 -3
- package/src/tools/ast-edit.ts +7 -7
- package/src/tools/ast-grep.ts +7 -7
- package/src/tools/auto-generated-guard.ts +36 -41
- package/src/tools/await-tool.ts +2 -2
- package/src/tools/bash.ts +5 -23
- package/src/tools/browser.ts +4 -5
- package/src/tools/calculator.ts +2 -3
- package/src/tools/cancel-job.ts +2 -2
- package/src/tools/checkpoint.ts +3 -3
- package/src/tools/debug.ts +1007 -0
- package/src/tools/exit-plan-mode.ts +2 -3
- package/src/tools/fetch.ts +67 -3
- package/src/tools/find.ts +4 -5
- package/src/tools/fs-cache-invalidation.ts +5 -0
- package/src/tools/gemini-image.ts +13 -5
- package/src/tools/gh.ts +10 -11
- package/src/tools/grep.ts +57 -9
- package/src/tools/index.ts +44 -22
- package/src/tools/inspect-image.ts +4 -4
- package/src/tools/output-meta.ts +1 -1
- package/src/tools/python.ts +19 -6
- package/src/tools/read.ts +198 -67
- package/src/tools/render-mermaid.ts +2 -3
- package/src/tools/render-utils.ts +20 -6
- package/src/tools/renderers.ts +3 -1
- package/src/tools/report-tool-issue.ts +80 -0
- package/src/tools/resolve.ts +70 -39
- package/src/tools/search-tool-bm25.ts +2 -2
- package/src/tools/ssh.ts +2 -2
- package/src/tools/todo-write.ts +2 -2
- package/src/tools/tool-timeouts.ts +1 -0
- package/src/tools/write.ts +5 -6
- package/src/tui/tree-list.ts +3 -1
- package/src/utils/clipboard.ts +80 -0
- package/src/utils/commit-message-generator.ts +2 -3
- package/src/utils/edit-mode.ts +49 -0
- package/src/utils/file-display-mode.ts +6 -5
- package/src/utils/file-mentions.ts +8 -7
- package/src/utils/git.ts +4 -4
- package/src/utils/image-loading.ts +98 -0
- package/src/utils/title-generator.ts +2 -3
- package/src/utils/tools-manager.ts +6 -6
- package/src/web/scrapers/choosealicense.ts +1 -1
- package/src/web/search/index.ts +3 -3
- package/src/autoresearch/command-initialize.md +0 -34
- package/src/patch/diff.ts +0 -433
- package/src/patch/index.ts +0 -888
- package/src/patch/parser.ts +0 -532
- package/src/patch/types.ts +0 -292
- package/src/prompts/agents/oracle.md +0 -77
- package/src/tools/pending-action.ts +0 -49
- package/src/utils/child-process.ts +0 -88
- package/src/utils/frontmatter.ts +0 -117
- package/src/utils/image-input.ts +0 -274
- package/src/utils/mime.ts +0 -53
- package/src/utils/prompt-format.ts +0 -170
package/src/utils/image-input.ts
DELETED
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs/promises";
|
|
2
|
-
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
3
|
-
import { formatBytes } from "@oh-my-pi/pi-utils";
|
|
4
|
-
import { resolveReadPath } from "../tools/path-utils";
|
|
5
|
-
import { convertToPng } from "./image-convert";
|
|
6
|
-
import { formatDimensionNote, resizeImage } from "./image-resize";
|
|
7
|
-
import { detectSupportedImageMimeTypeFromFile } from "./mime";
|
|
8
|
-
|
|
9
|
-
export const MAX_IMAGE_INPUT_BYTES = 20 * 1024 * 1024;
|
|
10
|
-
const MAX_IMAGE_METADATA_HEADER_BYTES = 256 * 1024;
|
|
11
|
-
export const SUPPORTED_INPUT_IMAGE_MIME_TYPES = new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
|
|
12
|
-
export interface ImageMetadata {
|
|
13
|
-
mimeType: string;
|
|
14
|
-
bytes: number;
|
|
15
|
-
width?: number;
|
|
16
|
-
height?: number;
|
|
17
|
-
channels?: number;
|
|
18
|
-
hasAlpha?: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface LoadedImageInput {
|
|
22
|
-
resolvedPath: string;
|
|
23
|
-
mimeType: string;
|
|
24
|
-
data: string;
|
|
25
|
-
textNote: string;
|
|
26
|
-
dimensionNote?: string;
|
|
27
|
-
bytes: number;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export async function ensureSupportedImageInput(image: ImageContent): Promise<ImageContent | null> {
|
|
31
|
-
if (SUPPORTED_INPUT_IMAGE_MIME_TYPES.has(image.mimeType)) {
|
|
32
|
-
return image;
|
|
33
|
-
}
|
|
34
|
-
const converted = await convertToPng(image.data, image.mimeType);
|
|
35
|
-
return converted ? { type: "image", data: converted.data, mimeType: converted.mimeType } : null;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface ReadImageMetadataOptions {
|
|
39
|
-
path: string;
|
|
40
|
-
cwd: string;
|
|
41
|
-
resolvedPath?: string;
|
|
42
|
-
detectedMimeType?: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface LoadImageInputOptions extends ReadImageMetadataOptions {
|
|
46
|
-
autoResize: boolean;
|
|
47
|
-
maxBytes?: number;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export class ImageInputTooLargeError extends Error {
|
|
51
|
-
readonly bytes: number;
|
|
52
|
-
readonly maxBytes: number;
|
|
53
|
-
|
|
54
|
-
constructor(bytes: number, maxBytes: number) {
|
|
55
|
-
super(`Image file too large: ${formatBytes(bytes)} exceeds ${formatBytes(maxBytes)} limit.`);
|
|
56
|
-
this.name = "ImageInputTooLargeError";
|
|
57
|
-
this.bytes = bytes;
|
|
58
|
-
this.maxBytes = maxBytes;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
interface ParsedImageHeaderMetadata {
|
|
63
|
-
width?: number;
|
|
64
|
-
height?: number;
|
|
65
|
-
channels?: number;
|
|
66
|
-
hasAlpha?: boolean;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function parsePngMetadata(header: Buffer): ParsedImageHeaderMetadata {
|
|
70
|
-
if (header.length < 26) return {};
|
|
71
|
-
if (
|
|
72
|
-
header[0] !== 0x89 ||
|
|
73
|
-
header[1] !== 0x50 ||
|
|
74
|
-
header[2] !== 0x4e ||
|
|
75
|
-
header[3] !== 0x47 ||
|
|
76
|
-
header[4] !== 0x0d ||
|
|
77
|
-
header[5] !== 0x0a ||
|
|
78
|
-
header[6] !== 0x1a ||
|
|
79
|
-
header[7] !== 0x0a
|
|
80
|
-
) {
|
|
81
|
-
return {};
|
|
82
|
-
}
|
|
83
|
-
if (header.slice(12, 16).toString("ascii") !== "IHDR") return {};
|
|
84
|
-
const width = header.readUInt32BE(16);
|
|
85
|
-
const height = header.readUInt32BE(20);
|
|
86
|
-
const colorType = header[25];
|
|
87
|
-
if (colorType === 0) return { width, height, channels: 1, hasAlpha: false };
|
|
88
|
-
if (colorType === 2) return { width, height, channels: 3, hasAlpha: false };
|
|
89
|
-
if (colorType === 3) return { width, height, channels: 3 };
|
|
90
|
-
if (colorType === 4) return { width, height, channels: 2, hasAlpha: true };
|
|
91
|
-
if (colorType === 6) return { width, height, channels: 4, hasAlpha: true };
|
|
92
|
-
return { width, height };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function parseJpegMetadata(header: Buffer): ParsedImageHeaderMetadata {
|
|
96
|
-
if (header.length < 4) return {};
|
|
97
|
-
if (header[0] !== 0xff || header[1] !== 0xd8) return {};
|
|
98
|
-
|
|
99
|
-
let offset = 2;
|
|
100
|
-
while (offset + 9 < header.length) {
|
|
101
|
-
if (header[offset] !== 0xff) {
|
|
102
|
-
offset += 1;
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
let markerOffset = offset + 1;
|
|
107
|
-
while (markerOffset < header.length && header[markerOffset] === 0xff) {
|
|
108
|
-
markerOffset += 1;
|
|
109
|
-
}
|
|
110
|
-
if (markerOffset >= header.length) break;
|
|
111
|
-
|
|
112
|
-
const marker = header[markerOffset];
|
|
113
|
-
const segmentOffset = markerOffset + 1;
|
|
114
|
-
|
|
115
|
-
if (marker === 0xd8 || marker === 0xd9 || marker === 0x01) {
|
|
116
|
-
offset = segmentOffset;
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
if (marker >= 0xd0 && marker <= 0xd7) {
|
|
120
|
-
offset = segmentOffset;
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
if (segmentOffset + 1 >= header.length) break;
|
|
124
|
-
|
|
125
|
-
const segmentLength = header.readUInt16BE(segmentOffset);
|
|
126
|
-
if (segmentLength < 2) break;
|
|
127
|
-
|
|
128
|
-
const isStartOfFrame = marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc;
|
|
129
|
-
if (isStartOfFrame) {
|
|
130
|
-
if (segmentOffset + 7 >= header.length) break;
|
|
131
|
-
const height = header.readUInt16BE(segmentOffset + 3);
|
|
132
|
-
const width = header.readUInt16BE(segmentOffset + 5);
|
|
133
|
-
const channels = header[segmentOffset + 7];
|
|
134
|
-
return {
|
|
135
|
-
width,
|
|
136
|
-
height,
|
|
137
|
-
channels: Number.isFinite(channels) ? channels : undefined,
|
|
138
|
-
hasAlpha: false,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
offset = segmentOffset + segmentLength;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return {};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function parseGifMetadata(header: Buffer): ParsedImageHeaderMetadata {
|
|
149
|
-
if (header.length < 10) return {};
|
|
150
|
-
const signature = header.slice(0, 6).toString("ascii");
|
|
151
|
-
if (signature !== "GIF87a" && signature !== "GIF89a") return {};
|
|
152
|
-
return {
|
|
153
|
-
width: header.readUInt16LE(6),
|
|
154
|
-
height: header.readUInt16LE(8),
|
|
155
|
-
channels: 3,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function parseWebpMetadata(header: Buffer): ParsedImageHeaderMetadata {
|
|
160
|
-
if (header.length < 30) return {};
|
|
161
|
-
if (header.slice(0, 4).toString("ascii") !== "RIFF") return {};
|
|
162
|
-
if (header.slice(8, 12).toString("ascii") !== "WEBP") return {};
|
|
163
|
-
|
|
164
|
-
const chunkType = header.slice(12, 16).toString("ascii");
|
|
165
|
-
if (chunkType === "VP8X") {
|
|
166
|
-
const hasAlpha = (header[20] & 0x10) !== 0;
|
|
167
|
-
const width = (header[24] | (header[25] << 8) | (header[26] << 16)) + 1;
|
|
168
|
-
const height = (header[27] | (header[28] << 8) | (header[29] << 16)) + 1;
|
|
169
|
-
return { width, height, channels: hasAlpha ? 4 : 3, hasAlpha };
|
|
170
|
-
}
|
|
171
|
-
if (chunkType === "VP8L") {
|
|
172
|
-
if (header.length < 25) return {};
|
|
173
|
-
const bits = header.readUInt32LE(21);
|
|
174
|
-
const width = (bits & 0x3fff) + 1;
|
|
175
|
-
const height = ((bits >> 14) & 0x3fff) + 1;
|
|
176
|
-
const hasAlpha = ((bits >> 28) & 0x1) === 1;
|
|
177
|
-
return { width, height, channels: hasAlpha ? 4 : 3, hasAlpha };
|
|
178
|
-
}
|
|
179
|
-
if (chunkType === "VP8 ") {
|
|
180
|
-
const width = header.readUInt16LE(26) & 0x3fff;
|
|
181
|
-
const height = header.readUInt16LE(28) & 0x3fff;
|
|
182
|
-
return { width, height, channels: 3, hasAlpha: false };
|
|
183
|
-
}
|
|
184
|
-
return {};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function parseImageHeaderMetadata(header: Buffer, mimeType: string): ParsedImageHeaderMetadata {
|
|
188
|
-
if (mimeType === "image/png") return parsePngMetadata(header);
|
|
189
|
-
if (mimeType === "image/jpeg") return parseJpegMetadata(header);
|
|
190
|
-
if (mimeType === "image/gif") return parseGifMetadata(header);
|
|
191
|
-
if (mimeType === "image/webp") return parseWebpMetadata(header);
|
|
192
|
-
return {};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async function readHeader(filePath: string, maxBytes: number): Promise<Buffer> {
|
|
196
|
-
if (maxBytes <= 0) return Buffer.alloc(0);
|
|
197
|
-
const fileHandle = await fs.open(filePath, "r");
|
|
198
|
-
try {
|
|
199
|
-
const buffer = Buffer.allocUnsafe(maxBytes);
|
|
200
|
-
const { bytesRead } = await fileHandle.read(buffer, 0, maxBytes, 0);
|
|
201
|
-
return buffer.subarray(0, bytesRead);
|
|
202
|
-
} finally {
|
|
203
|
-
await fileHandle.close();
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
export async function readImageMetadata(options: ReadImageMetadataOptions): Promise<ImageMetadata | null> {
|
|
208
|
-
const resolvedPath = options.resolvedPath ?? resolveReadPath(options.path, options.cwd);
|
|
209
|
-
const mimeType = options.detectedMimeType ?? (await detectSupportedImageMimeTypeFromFile(resolvedPath));
|
|
210
|
-
if (!mimeType) return null;
|
|
211
|
-
|
|
212
|
-
const stats = await Bun.file(resolvedPath).stat();
|
|
213
|
-
const bytes = stats.size;
|
|
214
|
-
const headerBytes = Math.max(0, Math.min(bytes, MAX_IMAGE_METADATA_HEADER_BYTES));
|
|
215
|
-
const header = await readHeader(resolvedPath, headerBytes);
|
|
216
|
-
const parsed = parseImageHeaderMetadata(header, mimeType);
|
|
217
|
-
|
|
218
|
-
return {
|
|
219
|
-
mimeType,
|
|
220
|
-
bytes,
|
|
221
|
-
width: parsed.width,
|
|
222
|
-
height: parsed.height,
|
|
223
|
-
channels: parsed.channels,
|
|
224
|
-
hasAlpha: parsed.hasAlpha,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
export async function loadImageInput(options: LoadImageInputOptions): Promise<LoadedImageInput | null> {
|
|
229
|
-
const maxBytes = options.maxBytes ?? MAX_IMAGE_INPUT_BYTES;
|
|
230
|
-
const resolvedPath = options.resolvedPath ?? resolveReadPath(options.path, options.cwd);
|
|
231
|
-
const mimeType = options.detectedMimeType ?? (await detectSupportedImageMimeTypeFromFile(resolvedPath));
|
|
232
|
-
if (!mimeType) return null;
|
|
233
|
-
|
|
234
|
-
const stat = await Bun.file(resolvedPath).stat();
|
|
235
|
-
if (stat.size > maxBytes) {
|
|
236
|
-
throw new ImageInputTooLargeError(stat.size, maxBytes);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const inputBuffer = await fs.readFile(resolvedPath);
|
|
240
|
-
if (inputBuffer.byteLength > maxBytes) {
|
|
241
|
-
throw new ImageInputTooLargeError(inputBuffer.byteLength, maxBytes);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
let outputData = Buffer.from(inputBuffer).toBase64();
|
|
245
|
-
let outputMimeType = mimeType;
|
|
246
|
-
let outputBytes = inputBuffer.byteLength;
|
|
247
|
-
let dimensionNote: string | undefined;
|
|
248
|
-
|
|
249
|
-
if (options.autoResize) {
|
|
250
|
-
try {
|
|
251
|
-
const resized = await resizeImage({ type: "image", data: outputData, mimeType });
|
|
252
|
-
outputData = resized.data;
|
|
253
|
-
outputMimeType = resized.mimeType;
|
|
254
|
-
outputBytes = resized.buffer.byteLength;
|
|
255
|
-
dimensionNote = formatDimensionNote(resized);
|
|
256
|
-
} catch {
|
|
257
|
-
// keep original image when resize fails
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
let textNote = `Read image file [${outputMimeType}]`;
|
|
262
|
-
if (dimensionNote) {
|
|
263
|
-
textNote += `\n${dimensionNote}`;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
return {
|
|
267
|
-
resolvedPath,
|
|
268
|
-
mimeType: outputMimeType,
|
|
269
|
-
data: outputData,
|
|
270
|
-
textNote,
|
|
271
|
-
dimensionNote,
|
|
272
|
-
bytes: outputBytes,
|
|
273
|
-
};
|
|
274
|
-
}
|
package/src/utils/mime.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs/promises";
|
|
2
|
-
|
|
3
|
-
const FILE_TYPE_SNIFF_BYTES = 12;
|
|
4
|
-
|
|
5
|
-
function detectMimeFromBytes(buf: Buffer, bytesRead: number): string | null {
|
|
6
|
-
if (bytesRead >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) {
|
|
7
|
-
return "image/jpeg";
|
|
8
|
-
}
|
|
9
|
-
if (
|
|
10
|
-
bytesRead >= 8 &&
|
|
11
|
-
buf[0] === 0x89 &&
|
|
12
|
-
buf[1] === 0x50 &&
|
|
13
|
-
buf[2] === 0x4e &&
|
|
14
|
-
buf[3] === 0x47 &&
|
|
15
|
-
buf[4] === 0x0d &&
|
|
16
|
-
buf[5] === 0x0a &&
|
|
17
|
-
buf[6] === 0x1a &&
|
|
18
|
-
buf[7] === 0x0a
|
|
19
|
-
) {
|
|
20
|
-
return "image/png";
|
|
21
|
-
}
|
|
22
|
-
if (bytesRead >= 4 && buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x38) {
|
|
23
|
-
return "image/gif";
|
|
24
|
-
}
|
|
25
|
-
if (
|
|
26
|
-
bytesRead >= 12 &&
|
|
27
|
-
buf[0] === 0x52 &&
|
|
28
|
-
buf[1] === 0x49 &&
|
|
29
|
-
buf[2] === 0x46 &&
|
|
30
|
-
buf[3] === 0x46 &&
|
|
31
|
-
buf[8] === 0x57 &&
|
|
32
|
-
buf[9] === 0x45 &&
|
|
33
|
-
buf[10] === 0x42 &&
|
|
34
|
-
buf[11] === 0x50
|
|
35
|
-
) {
|
|
36
|
-
return "image/webp";
|
|
37
|
-
}
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export async function detectSupportedImageMimeTypeFromFile(filePath: string): Promise<string | null> {
|
|
42
|
-
const fileHandle = await fs.open(filePath, "r");
|
|
43
|
-
try {
|
|
44
|
-
const buffer = Buffer.allocUnsafe(FILE_TYPE_SNIFF_BYTES);
|
|
45
|
-
const { bytesRead } = await fileHandle.read(buffer, 0, FILE_TYPE_SNIFF_BYTES, 0);
|
|
46
|
-
if (bytesRead === 0) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
return detectMimeFromBytes(buffer, bytesRead);
|
|
50
|
-
} finally {
|
|
51
|
-
await fileHandle.close();
|
|
52
|
-
}
|
|
53
|
-
}
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
export type PromptRenderPhase = "pre-render" | "post-render";
|
|
2
|
-
|
|
3
|
-
export interface PromptFormatOptions {
|
|
4
|
-
renderPhase?: PromptRenderPhase;
|
|
5
|
-
replaceAsciiSymbols?: boolean;
|
|
6
|
-
boldRfc2119Keywords?: boolean;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// Opening XML tag (not self-closing, not closing)
|
|
10
|
-
const OPENING_XML = /^<([a-z_-]+)(?:\s+[^>]*)?>$/;
|
|
11
|
-
// Closing XML tag
|
|
12
|
-
const CLOSING_XML = /^<\/([a-z_-]+)>$/;
|
|
13
|
-
// Handlebars block start: {{#if}}, {{#has}}, {{#list}}, etc.
|
|
14
|
-
const OPENING_HBS = /^\{\{#/;
|
|
15
|
-
// Handlebars block end: {{/if}}, {{/has}}, {{/list}}, etc.
|
|
16
|
-
const CLOSING_HBS = /^\{\{\//;
|
|
17
|
-
// List item (- or * or 1.)
|
|
18
|
-
const LIST_ITEM = /^(?:[-*]\s|\d+\.\s)/;
|
|
19
|
-
// Code fence
|
|
20
|
-
const CODE_FENCE = /^```/;
|
|
21
|
-
// Table row
|
|
22
|
-
const TABLE_ROW = /^\|.*\|$/;
|
|
23
|
-
// Table separator (|---|---|)
|
|
24
|
-
const TABLE_SEP = /^\|[-:\s|]+\|$/;
|
|
25
|
-
|
|
26
|
-
/** RFC 2119 keywords used in prompts. */
|
|
27
|
-
const RFC2119_KEYWORDS = /\b(?:MUST NOT|SHOULD NOT|SHALL NOT|RECOMMENDED|REQUIRED|OPTIONAL|SHOULD|SHALL|MUST|MAY)\b/g;
|
|
28
|
-
|
|
29
|
-
function boldRfc2119Keywords(line: string): string {
|
|
30
|
-
return line.replace(RFC2119_KEYWORDS, (match, offset, source) => {
|
|
31
|
-
const isAlreadyBold =
|
|
32
|
-
source[offset - 2] === "*" &&
|
|
33
|
-
source[offset - 1] === "*" &&
|
|
34
|
-
source[offset + match.length] === "*" &&
|
|
35
|
-
source[offset + match.length + 1] === "*";
|
|
36
|
-
if (isAlreadyBold) {
|
|
37
|
-
return match;
|
|
38
|
-
}
|
|
39
|
-
return `**${match}**`;
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Compact a table row by trimming cell padding */
|
|
44
|
-
function compactTableRow(line: string): string {
|
|
45
|
-
const cells = line.split("|");
|
|
46
|
-
return cells.map(c => c.trim()).join("|");
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Compact a table separator row */
|
|
50
|
-
function compactTableSep(line: string): string {
|
|
51
|
-
const cells = line.split("|").filter(c => c.trim());
|
|
52
|
-
const normalized = cells.map(c => {
|
|
53
|
-
const trimmed = c.trim();
|
|
54
|
-
const left = trimmed.startsWith(":");
|
|
55
|
-
const right = trimmed.endsWith(":");
|
|
56
|
-
if (left && right) return ":---:";
|
|
57
|
-
if (left) return ":---";
|
|
58
|
-
if (right) return "---:";
|
|
59
|
-
return "---";
|
|
60
|
-
});
|
|
61
|
-
return `|${normalized.join("|")}|`;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function replaceCommonAsciiSymbols(line: string): string {
|
|
65
|
-
return line
|
|
66
|
-
.replace(/\.{3}/g, "…")
|
|
67
|
-
.replace(/<->/g, "↔")
|
|
68
|
-
.replace(/->/g, "→")
|
|
69
|
-
.replace(/<-/g, "←")
|
|
70
|
-
.replace(/!=/g, "≠")
|
|
71
|
-
.replace(/<=/g, "≤")
|
|
72
|
-
.replace(/>=/g, "≥");
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function formatPromptContent(content: string, options: PromptFormatOptions = {}): string {
|
|
76
|
-
const {
|
|
77
|
-
renderPhase = "post-render",
|
|
78
|
-
replaceAsciiSymbols = false,
|
|
79
|
-
boldRfc2119Keywords: shouldBoldRfc2119 = false,
|
|
80
|
-
} = options;
|
|
81
|
-
const isPreRender = renderPhase === "pre-render";
|
|
82
|
-
const lines = content.split("\n");
|
|
83
|
-
const result: string[] = [];
|
|
84
|
-
let inCodeBlock = false;
|
|
85
|
-
const topLevelTags: string[] = [];
|
|
86
|
-
|
|
87
|
-
for (let i = 0; i < lines.length; i++) {
|
|
88
|
-
let line = lines[i].trimEnd();
|
|
89
|
-
let trimmedStart = line.trimStart();
|
|
90
|
-
if (CODE_FENCE.test(trimmedStart)) {
|
|
91
|
-
inCodeBlock = !inCodeBlock;
|
|
92
|
-
result.push(line);
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (inCodeBlock) {
|
|
97
|
-
result.push(line);
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (replaceAsciiSymbols) {
|
|
102
|
-
line = replaceCommonAsciiSymbols(line);
|
|
103
|
-
}
|
|
104
|
-
trimmedStart = line.trimStart();
|
|
105
|
-
const trimmed = line.trim();
|
|
106
|
-
|
|
107
|
-
const isOpeningXml = OPENING_XML.test(trimmedStart) && !trimmedStart.endsWith("/>");
|
|
108
|
-
if (isOpeningXml && line.length === trimmedStart.length) {
|
|
109
|
-
const match = OPENING_XML.exec(trimmedStart);
|
|
110
|
-
if (match) topLevelTags.push(match[1]);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const closingMatch = CLOSING_XML.exec(trimmedStart);
|
|
114
|
-
if (closingMatch) {
|
|
115
|
-
const tagName = closingMatch[1];
|
|
116
|
-
if (topLevelTags.length > 0 && topLevelTags[topLevelTags.length - 1] === tagName) {
|
|
117
|
-
topLevelTags.pop();
|
|
118
|
-
}
|
|
119
|
-
} else if (isPreRender && trimmedStart.startsWith("{{")) {
|
|
120
|
-
/* keep indentation as-is in pre-render for Handlebars markers */
|
|
121
|
-
} else if (TABLE_SEP.test(trimmedStart)) {
|
|
122
|
-
const leadingWhitespace = line.slice(0, line.length - trimmedStart.length);
|
|
123
|
-
line = `${leadingWhitespace}${compactTableSep(trimmedStart)}`;
|
|
124
|
-
} else if (TABLE_ROW.test(trimmedStart)) {
|
|
125
|
-
const leadingWhitespace = line.slice(0, line.length - trimmedStart.length);
|
|
126
|
-
line = `${leadingWhitespace}${compactTableRow(trimmedStart)}`;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (shouldBoldRfc2119) {
|
|
130
|
-
line = boldRfc2119Keywords(line);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const isBlank = trimmed === "";
|
|
134
|
-
if (isBlank) {
|
|
135
|
-
const prevLine = result[result.length - 1]?.trim() ?? "";
|
|
136
|
-
const nextLine = lines[i + 1]?.trim() ?? "";
|
|
137
|
-
|
|
138
|
-
if (LIST_ITEM.test(nextLine)) {
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (OPENING_XML.test(prevLine) || (isPreRender && OPENING_HBS.test(prevLine))) {
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (CLOSING_XML.test(nextLine) || (isPreRender && CLOSING_HBS.test(nextLine))) {
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const prevIsBlank = prevLine === "";
|
|
151
|
-
if (prevIsBlank) {
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (CLOSING_XML.test(trimmed) || (isPreRender && CLOSING_HBS.test(trimmed))) {
|
|
157
|
-
while (result.length > 0 && result[result.length - 1].trim() === "") {
|
|
158
|
-
result.pop();
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
result.push(line);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
while (result.length > 0 && result[result.length - 1].trim() === "") {
|
|
166
|
-
result.pop();
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return result.join("\n");
|
|
170
|
-
}
|