@oh-my-pi/pi-coding-agent 13.17.1 → 13.17.6
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 +93 -3
- package/package.json +10 -8
- package/scripts/format-prompts.ts +3 -3
- package/src/cli/plugin-cli.ts +114 -25
- package/src/commands/plugin.ts +5 -0
- package/src/commit/agentic/prompts/analyze-file.md +1 -1
- package/src/commit/agentic/prompts/session-user.md +1 -1
- package/src/commit/agentic/prompts/split-confirm.md +1 -1
- package/src/commit/agentic/prompts/system.md +1 -1
- package/src/commit/prompts/analysis-system.md +1 -1
- package/src/commit/prompts/analysis-user.md +1 -1
- package/src/commit/prompts/changelog-system.md +1 -1
- package/src/commit/prompts/changelog-user.md +1 -1
- package/src/commit/prompts/file-observer-system.md +1 -1
- package/src/commit/prompts/file-observer-user.md +1 -1
- package/src/commit/prompts/reduce-system.md +1 -1
- package/src/commit/prompts/reduce-user.md +1 -1
- package/src/commit/prompts/summary-retry.md +1 -1
- package/src/commit/prompts/summary-system.md +1 -1
- package/src/commit/prompts/summary-user.md +1 -1
- package/src/commit/prompts/types-description.md +1 -1
- package/src/config/settings-schema.ts +16 -0
- package/src/discovery/claude-plugins.ts +5 -5
- package/src/discovery/helpers.ts +144 -24
- package/src/extensibility/custom-commands/bundled/ci-green/index.ts +37 -0
- package/src/extensibility/custom-commands/loader.ts +7 -0
- package/src/extensibility/plugins/marketplace/fetcher.ts +55 -37
- package/src/extensibility/plugins/marketplace/manager.ts +296 -73
- package/src/extensibility/plugins/marketplace/registry.ts +15 -0
- package/src/extensibility/plugins/marketplace/types.ts +17 -3
- package/src/internal-urls/docs-index.generated.ts +2 -1
- package/src/main.ts +13 -4
- package/src/modes/components/assistant-message.ts +2 -1
- package/src/modes/components/plugin-selector.ts +20 -11
- package/src/modes/components/tool-execution.ts +2 -3
- package/src/modes/controllers/command-controller.ts +19 -7
- package/src/modes/controllers/selector-controller.ts +7 -4
- package/src/modes/interactive-mode.ts +4 -0
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/tools-markdown.ts +27 -0
- package/src/prompts/agents/designer.md +1 -1
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/agents/frontmatter.md +1 -1
- package/src/prompts/agents/init.md +1 -1
- package/src/prompts/agents/librarian.md +1 -1
- package/src/prompts/agents/oracle.md +1 -1
- package/src/prompts/agents/plan.md +1 -1
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/agents/task.md +1 -1
- package/src/prompts/ci-green-request.md +36 -0
- package/src/prompts/compaction/branch-summary-context.md +1 -1
- package/src/prompts/compaction/branch-summary-preamble.md +1 -1
- package/src/prompts/compaction/branch-summary.md +1 -1
- package/src/prompts/compaction/compaction-short-summary.md +1 -1
- package/src/prompts/compaction/compaction-summary-context.md +1 -1
- package/src/prompts/compaction/compaction-summary.md +1 -1
- package/src/prompts/compaction/compaction-turn-prefix.md +1 -1
- package/src/prompts/compaction/compaction-update-summary.md +1 -1
- package/src/prompts/memories/consolidation.md +1 -1
- package/src/prompts/memories/read-path.md +1 -1
- package/src/prompts/memories/stage_one_input.md +1 -1
- package/src/prompts/memories/stage_one_system.md +1 -1
- package/src/prompts/review-request.md +1 -1
- package/src/prompts/system/agent-creation-architect.md +1 -1
- package/src/prompts/system/agent-creation-user.md +1 -1
- package/src/prompts/system/auto-handoff-threshold-focus.md +1 -1
- package/src/prompts/system/btw-user.md +1 -1
- package/src/prompts/system/commit-message-system.md +1 -1
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/eager-todo.md +1 -1
- package/src/prompts/system/file-operations.md +1 -1
- package/src/prompts/system/handoff-document.md +1 -1
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/system/plan-mode-approved.md +1 -1
- package/src/prompts/system/plan-mode-reference.md +1 -1
- package/src/prompts/system/plan-mode-subagent.md +1 -1
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
- package/src/prompts/system/subagent-submit-reminder.md +1 -1
- package/src/prompts/system/subagent-system-prompt.md +1 -1
- package/src/prompts/system/subagent-user-prompt.md +1 -1
- package/src/prompts/system/summarization-system.md +1 -1
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/title-system.md +1 -1
- package/src/prompts/system/ttsr-interrupt.md +1 -1
- package/src/prompts/system/web-search.md +1 -1
- package/src/prompts/tools/ask.md +1 -1
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/async-result.md +1 -1
- package/src/prompts/tools/await.md +1 -1
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/calculator.md +1 -1
- package/src/prompts/tools/cancel-job.md +1 -1
- package/src/prompts/tools/checkpoint.md +1 -1
- package/src/prompts/tools/exit-plan-mode.md +1 -1
- package/src/prompts/tools/fetch.md +1 -1
- package/src/prompts/tools/find.md +1 -1
- package/src/prompts/tools/gemini-image.md +1 -1
- package/src/prompts/tools/gh-issue-view.md +11 -0
- package/src/prompts/tools/gh-pr-checkout.md +12 -0
- package/src/prompts/tools/gh-pr-diff.md +12 -0
- package/src/prompts/tools/gh-pr-push.md +11 -0
- package/src/prompts/tools/gh-pr-view.md +11 -0
- package/src/prompts/tools/gh-repo-view.md +11 -0
- package/src/prompts/tools/gh-run-watch.md +12 -0
- package/src/prompts/tools/gh-search-issues.md +11 -0
- package/src/prompts/tools/gh-search-prs.md +11 -0
- package/src/prompts/tools/grep.md +1 -1
- package/src/prompts/tools/hashline.md +1 -1
- package/src/prompts/tools/inspect-image-system.md +1 -1
- package/src/prompts/tools/inspect-image.md +1 -1
- package/src/prompts/tools/lsp.md +1 -1
- package/src/prompts/tools/patch.md +1 -1
- package/src/prompts/tools/python.md +1 -1
- package/src/prompts/tools/read.md +6 -3
- package/src/prompts/tools/render-mermaid.md +1 -1
- package/src/prompts/tools/replace.md +1 -1
- package/src/prompts/tools/resolve.md +1 -1
- package/src/prompts/tools/rewind.md +1 -1
- package/src/prompts/tools/search-tool-bm25.md +1 -1
- package/src/prompts/tools/ssh.md +1 -1
- package/src/prompts/tools/task-summary.md +1 -1
- package/src/prompts/tools/task.md +1 -1
- package/src/prompts/tools/todo-write.md +1 -1
- package/src/prompts/tools/web-search.md +1 -1
- package/src/prompts/tools/write.md +2 -1
- package/src/sdk.ts +3 -1
- package/src/session/messages.ts +11 -7
- package/src/session/session-manager.ts +13 -3
- package/src/slash-commands/builtin-registry.ts +109 -37
- package/src/slash-commands/marketplace-install-parser.ts +99 -0
- package/src/task/discovery.ts +1 -1
- package/src/tools/archive-reader.ts +315 -0
- package/src/tools/fetch.ts +21 -19
- package/src/tools/gh-cli.ts +125 -0
- package/src/tools/gh-renderer.ts +305 -0
- package/src/tools/gh.ts +2719 -0
- package/src/tools/index.ts +22 -0
- package/src/tools/read.ts +286 -34
- package/src/tools/render-utils.ts +20 -0
- package/src/tools/renderers.ts +2 -0
- package/src/tools/write.ts +175 -4
- package/src/utils/markit.ts +81 -0
- package/src/utils/tools-manager.ts +1 -6
- package/src/web/scrapers/arxiv.ts +3 -3
- package/src/web/scrapers/iacr.ts +3 -3
- package/src/web/scrapers/utils.ts +6 -34
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { unzipSync } from "fflate";
|
|
2
|
+
import { ToolError } from "./tool-errors";
|
|
3
|
+
|
|
4
|
+
export type ArchiveFormat = "zip" | "tar" | "tar.gz";
|
|
5
|
+
|
|
6
|
+
export interface ArchivePathCandidate {
|
|
7
|
+
archivePath: string;
|
|
8
|
+
subPath: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ArchiveNode {
|
|
12
|
+
path: string;
|
|
13
|
+
isDirectory: boolean;
|
|
14
|
+
size: number;
|
|
15
|
+
mtimeMs?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ArchiveDirectoryEntry extends ArchiveNode {
|
|
19
|
+
name: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ExtractedArchiveFile extends ArchiveNode {
|
|
23
|
+
bytes: Uint8Array;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface TarStorage {
|
|
27
|
+
type: "tar";
|
|
28
|
+
file: File;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface ZipStorage {
|
|
32
|
+
type: "zip";
|
|
33
|
+
bytes: Uint8Array;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type EntryStorage = TarStorage | ZipStorage;
|
|
37
|
+
|
|
38
|
+
interface ArchiveIndexEntry extends ArchiveNode {
|
|
39
|
+
storage?: EntryStorage;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function normalizeArchiveLookupPath(rawPath?: string): string | undefined {
|
|
43
|
+
if (!rawPath) return "";
|
|
44
|
+
|
|
45
|
+
const parts = rawPath.replace(/\\/g, "/").split("/");
|
|
46
|
+
const normalizedParts: string[] = [];
|
|
47
|
+
for (const part of parts) {
|
|
48
|
+
if (!part || part === ".") continue;
|
|
49
|
+
if (part === "..") return undefined;
|
|
50
|
+
normalizedParts.push(part);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return normalizedParts.join("/");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function normalizeArchiveEntryPath(rawPath: string): string | undefined {
|
|
57
|
+
const parts = rawPath.replace(/\\/g, "/").split("/");
|
|
58
|
+
const normalizedParts: string[] = [];
|
|
59
|
+
for (const part of parts) {
|
|
60
|
+
if (!part || part === ".") continue;
|
|
61
|
+
if (part === "..") return undefined;
|
|
62
|
+
normalizedParts.push(part);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (normalizedParts.length === 0) return undefined;
|
|
66
|
+
return normalizedParts.join("/");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isArchiveDirectoryName(rawPath: string): boolean {
|
|
70
|
+
return rawPath.endsWith("/") || rawPath.endsWith("\\");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function upsertArchiveEntry(map: Map<string, ArchiveIndexEntry>, entry: ArchiveIndexEntry): void {
|
|
74
|
+
const existing = map.get(entry.path);
|
|
75
|
+
if (!existing) {
|
|
76
|
+
map.set(entry.path, entry);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (existing.isDirectory && !entry.isDirectory) {
|
|
81
|
+
map.set(entry.path, entry);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!existing.isDirectory && entry.isDirectory) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
map.set(entry.path, {
|
|
90
|
+
...existing,
|
|
91
|
+
size: existing.size || entry.size,
|
|
92
|
+
mtimeMs: existing.mtimeMs ?? entry.mtimeMs,
|
|
93
|
+
storage: existing.storage ?? entry.storage,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function ensureParentDirectories(map: Map<string, ArchiveIndexEntry>): void {
|
|
98
|
+
for (const entry of [...map.values()]) {
|
|
99
|
+
const parts = entry.path.split("/");
|
|
100
|
+
const stop = parts.length - 1;
|
|
101
|
+
for (let index = 1; index <= stop; index++) {
|
|
102
|
+
const dirPath = parts.slice(0, index).join("/");
|
|
103
|
+
if (!dirPath || map.has(dirPath)) continue;
|
|
104
|
+
map.set(dirPath, {
|
|
105
|
+
path: dirPath,
|
|
106
|
+
isDirectory: true,
|
|
107
|
+
size: 0,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getArchiveFormatFromPath(filePath: string): ArchiveFormat | undefined {
|
|
114
|
+
const normalized = filePath.toLowerCase();
|
|
115
|
+
if (normalized.endsWith(".tar.gz") || normalized.endsWith(".tgz")) return "tar.gz";
|
|
116
|
+
if (normalized.endsWith(".tar")) return "tar";
|
|
117
|
+
if (normalized.endsWith(".zip")) return "zip";
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function readTarEntries(bytes: Uint8Array): Promise<ArchiveIndexEntry[]> {
|
|
122
|
+
let archive: Bun.Archive;
|
|
123
|
+
try {
|
|
124
|
+
archive = new Bun.Archive(bytes);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
throw new ToolError(error instanceof Error ? error.message : String(error));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let files: Map<string, File>;
|
|
130
|
+
try {
|
|
131
|
+
files = await archive.files();
|
|
132
|
+
} catch (error) {
|
|
133
|
+
throw new ToolError(error instanceof Error ? error.message : String(error));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const entries: ArchiveIndexEntry[] = [];
|
|
137
|
+
for (const [rawPath, file] of files) {
|
|
138
|
+
const normalizedPath = normalizeArchiveEntryPath(rawPath);
|
|
139
|
+
if (!normalizedPath) continue;
|
|
140
|
+
const mtimeMs = file.lastModified > 0 ? file.lastModified : undefined;
|
|
141
|
+
entries.push({
|
|
142
|
+
path: normalizedPath,
|
|
143
|
+
isDirectory: false,
|
|
144
|
+
size: file.size,
|
|
145
|
+
mtimeMs,
|
|
146
|
+
storage: { type: "tar", file },
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return entries;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function readZipEntries(bytes: Uint8Array): ArchiveIndexEntry[] {
|
|
154
|
+
let files: Record<string, Uint8Array>;
|
|
155
|
+
try {
|
|
156
|
+
files = unzipSync(bytes);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
throw new ToolError(error instanceof Error ? error.message : String(error));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const entries: ArchiveIndexEntry[] = [];
|
|
162
|
+
for (const [rawPath, fileBytes] of Object.entries(files)) {
|
|
163
|
+
const normalizedPath = normalizeArchiveEntryPath(rawPath);
|
|
164
|
+
if (!normalizedPath) continue;
|
|
165
|
+
const isDirectory = isArchiveDirectoryName(rawPath);
|
|
166
|
+
entries.push({
|
|
167
|
+
path: normalizedPath,
|
|
168
|
+
isDirectory,
|
|
169
|
+
size: isDirectory ? 0 : fileBytes.byteLength,
|
|
170
|
+
storage: isDirectory ? undefined : { type: "zip", bytes: fileBytes },
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return entries;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function parseArchivePathCandidates(filePath: string): ArchivePathCandidate[] {
|
|
178
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
179
|
+
const pattern = /\.(?:tar\.gz|tgz|zip|tar)(?=(?::|$))/gi;
|
|
180
|
+
const seen = new Set<string>();
|
|
181
|
+
const candidates: ArchivePathCandidate[] = [];
|
|
182
|
+
|
|
183
|
+
let match: RegExpExecArray | null;
|
|
184
|
+
while (true) {
|
|
185
|
+
match = pattern.exec(normalized);
|
|
186
|
+
if (match === null) {
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
const end = match.index + match[0].length;
|
|
190
|
+
const archivePath = filePath.slice(0, end);
|
|
191
|
+
const subPath = normalized.slice(end).replace(/^:+/, "");
|
|
192
|
+
const key = `${archivePath}\0${subPath}`;
|
|
193
|
+
if (seen.has(key)) continue;
|
|
194
|
+
seen.add(key);
|
|
195
|
+
candidates.push({ archivePath, subPath });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return candidates.sort((left, right) => right.archivePath.length - left.archivePath.length);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export class ArchiveReader {
|
|
202
|
+
readonly format: ArchiveFormat;
|
|
203
|
+
#entries = new Map<string, ArchiveIndexEntry>();
|
|
204
|
+
|
|
205
|
+
constructor(format: ArchiveFormat, entries: ArchiveIndexEntry[]) {
|
|
206
|
+
this.format = format;
|
|
207
|
+
for (const entry of entries) {
|
|
208
|
+
upsertArchiveEntry(this.#entries, entry);
|
|
209
|
+
}
|
|
210
|
+
ensureParentDirectories(this.#entries);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
getNode(subPath?: string): ArchiveNode | undefined {
|
|
214
|
+
const normalizedPath = normalizeArchiveLookupPath(subPath);
|
|
215
|
+
if (normalizedPath === undefined) return undefined;
|
|
216
|
+
if (normalizedPath === "") {
|
|
217
|
+
return { path: "", isDirectory: true, size: 0 };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const entry = this.#entries.get(normalizedPath);
|
|
221
|
+
if (!entry) return undefined;
|
|
222
|
+
return {
|
|
223
|
+
path: entry.path,
|
|
224
|
+
isDirectory: entry.isDirectory,
|
|
225
|
+
size: entry.size,
|
|
226
|
+
mtimeMs: entry.mtimeMs,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
listDirectory(subPath?: string): ArchiveDirectoryEntry[] {
|
|
231
|
+
const normalizedPath = normalizeArchiveLookupPath(subPath);
|
|
232
|
+
if (normalizedPath === undefined) {
|
|
233
|
+
throw new ToolError("Archive path cannot contain '..'");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (normalizedPath) {
|
|
237
|
+
const entry = this.#entries.get(normalizedPath);
|
|
238
|
+
if (!entry) {
|
|
239
|
+
throw new ToolError(`Archive path '${normalizedPath}' not found`);
|
|
240
|
+
}
|
|
241
|
+
if (!entry.isDirectory) {
|
|
242
|
+
throw new ToolError(`Archive path '${normalizedPath}' is not a directory`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const prefix = normalizedPath ? `${normalizedPath}/` : "";
|
|
247
|
+
const children = new Map<string, ArchiveDirectoryEntry>();
|
|
248
|
+
|
|
249
|
+
for (const entry of this.#entries.values()) {
|
|
250
|
+
if (normalizedPath) {
|
|
251
|
+
if (!entry.path.startsWith(prefix) || entry.path === normalizedPath) continue;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const relativePath = normalizedPath ? entry.path.slice(prefix.length) : entry.path;
|
|
255
|
+
const nextSegment = relativePath.split("/")[0];
|
|
256
|
+
if (!nextSegment) continue;
|
|
257
|
+
|
|
258
|
+
const childPath = normalizedPath ? `${normalizedPath}/${nextSegment}` : nextSegment;
|
|
259
|
+
if (children.has(childPath)) continue;
|
|
260
|
+
|
|
261
|
+
const childEntry = this.#entries.get(childPath);
|
|
262
|
+
const isDirectory = childEntry?.isDirectory ?? relativePath.includes("/");
|
|
263
|
+
children.set(childPath, {
|
|
264
|
+
name: nextSegment,
|
|
265
|
+
path: childPath,
|
|
266
|
+
isDirectory,
|
|
267
|
+
size: isDirectory ? 0 : (childEntry?.size ?? entry.size),
|
|
268
|
+
mtimeMs: childEntry?.mtimeMs ?? entry.mtimeMs,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return [...children.values()].sort((left, right) =>
|
|
273
|
+
left.name.toLowerCase().localeCompare(right.name.toLowerCase()),
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async readFile(subPath: string): Promise<ExtractedArchiveFile> {
|
|
278
|
+
const normalizedPath = normalizeArchiveLookupPath(subPath);
|
|
279
|
+
if (!normalizedPath) {
|
|
280
|
+
throw new ToolError("Archive file path is required");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const entry = this.#entries.get(normalizedPath);
|
|
284
|
+
if (!entry) {
|
|
285
|
+
throw new ToolError(`Archive file '${normalizedPath}' not found`);
|
|
286
|
+
}
|
|
287
|
+
if (entry.isDirectory) {
|
|
288
|
+
throw new ToolError(`Archive path '${normalizedPath}' is a directory`);
|
|
289
|
+
}
|
|
290
|
+
if (!entry.storage) {
|
|
291
|
+
throw new ToolError(`Archive file '${normalizedPath}' has no readable storage`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const bytes = entry.storage.type === "tar" ? await entry.storage.file.bytes() : entry.storage.bytes;
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
path: entry.path,
|
|
298
|
+
isDirectory: false,
|
|
299
|
+
size: entry.size,
|
|
300
|
+
mtimeMs: entry.mtimeMs,
|
|
301
|
+
bytes,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export async function openArchive(filePath: string): Promise<ArchiveReader> {
|
|
307
|
+
const format = getArchiveFormatFromPath(filePath);
|
|
308
|
+
if (!format) {
|
|
309
|
+
throw new ToolError(`Unsupported archive format: ${filePath}`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const bytes = await Bun.file(filePath).bytes();
|
|
313
|
+
const entries = format === "zip" ? readZipEntries(bytes) : await readTarEntries(bytes);
|
|
314
|
+
return new ArchiveReader(format, entries);
|
|
315
|
+
}
|
package/src/tools/fetch.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { extractWithParallel, findParallelApiKey, getParallelExtractContent } fr
|
|
|
20
20
|
import { specialHandlers } from "../web/scrapers";
|
|
21
21
|
import type { RenderResult } from "../web/scrapers/types";
|
|
22
22
|
import { finalizeOutput, loadPage, MAX_OUTPUT_CHARS } from "../web/scrapers/types";
|
|
23
|
-
import {
|
|
23
|
+
import { convertWithMarkit, fetchBinary } from "../web/scrapers/utils";
|
|
24
24
|
import type { ToolSession } from ".";
|
|
25
25
|
import { applyListLimit } from "./list-limit";
|
|
26
26
|
import { formatStyledArtifactReference, type OutputMeta } from "./output-meta";
|
|
@@ -34,7 +34,7 @@ import { clampTimeout } from "./tool-timeouts";
|
|
|
34
34
|
// =============================================================================
|
|
35
35
|
|
|
36
36
|
const FETCH_DEFAULT_MAX_LINES = 300;
|
|
37
|
-
// Convertible document types
|
|
37
|
+
// Convertible document types handled by markit.
|
|
38
38
|
const CONVERTIBLE_MIMES = new Set([
|
|
39
39
|
"application/pdf",
|
|
40
40
|
"application/msword",
|
|
@@ -45,6 +45,7 @@ const CONVERTIBLE_MIMES = new Set([
|
|
|
45
45
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
46
46
|
"application/rtf",
|
|
47
47
|
"application/epub+zip",
|
|
48
|
+
"application/x-ipynb+json",
|
|
48
49
|
"application/zip",
|
|
49
50
|
"image/png",
|
|
50
51
|
"image/jpeg",
|
|
@@ -65,6 +66,7 @@ const CONVERTIBLE_EXTENSIONS = new Set([
|
|
|
65
66
|
".xlsx",
|
|
66
67
|
".rtf",
|
|
67
68
|
".epub",
|
|
69
|
+
".ipynb",
|
|
68
70
|
".png",
|
|
69
71
|
".jpg",
|
|
70
72
|
".jpeg",
|
|
@@ -164,7 +166,7 @@ function getExtensionHint(url: string, contentDisposition?: string): string {
|
|
|
164
166
|
}
|
|
165
167
|
|
|
166
168
|
/**
|
|
167
|
-
* Check if content type is convertible via
|
|
169
|
+
* Check if content type is convertible via markit.
|
|
168
170
|
*/
|
|
169
171
|
function isConvertible(mime: string, extensionHint: string): boolean {
|
|
170
172
|
if (CONVERTIBLE_MIMES.has(mime)) return true;
|
|
@@ -711,18 +713,18 @@ async function renderUrl(
|
|
|
711
713
|
notes.push("Fetched image binary");
|
|
712
714
|
const conversionExtension = getExtensionHint(finalUrl, binary.contentDisposition) || extHint;
|
|
713
715
|
let convertedText: string | null = null;
|
|
714
|
-
const converted = await
|
|
716
|
+
const converted = await convertWithMarkit(binary.buffer, conversionExtension, timeout, signal);
|
|
715
717
|
if (converted.ok) {
|
|
716
718
|
if (converted.content.trim().length > 50) {
|
|
717
|
-
notes.push("Converted with
|
|
719
|
+
notes.push("Converted with markit");
|
|
718
720
|
convertedText = converted.content;
|
|
719
721
|
} else {
|
|
720
|
-
notes.push("
|
|
722
|
+
notes.push("markit conversion produced no usable output");
|
|
721
723
|
}
|
|
722
724
|
} else if (converted.error) {
|
|
723
|
-
notes.push(`
|
|
725
|
+
notes.push(`markit conversion failed: ${converted.error}`);
|
|
724
726
|
} else {
|
|
725
|
-
notes.push("
|
|
727
|
+
notes.push("markit conversion failed");
|
|
726
728
|
}
|
|
727
729
|
|
|
728
730
|
if (binary.buffer.byteLength > MAX_INLINE_IMAGE_SOURCE_BYTES) {
|
|
@@ -736,7 +738,7 @@ async function renderUrl(
|
|
|
736
738
|
url,
|
|
737
739
|
finalUrl,
|
|
738
740
|
contentType: imageMimeType,
|
|
739
|
-
method: convertedText ? "
|
|
741
|
+
method: convertedText ? "markit" : "image-too-large",
|
|
740
742
|
content: output.content,
|
|
741
743
|
fetchedAt,
|
|
742
744
|
truncated: output.truncated,
|
|
@@ -761,7 +763,7 @@ async function renderUrl(
|
|
|
761
763
|
url,
|
|
762
764
|
finalUrl,
|
|
763
765
|
contentType: imageMimeType,
|
|
764
|
-
method: convertedText ? "
|
|
766
|
+
method: convertedText ? "markit" : "image-invalid",
|
|
765
767
|
content: output.content,
|
|
766
768
|
fetchedAt,
|
|
767
769
|
truncated: output.truncated,
|
|
@@ -779,7 +781,7 @@ async function renderUrl(
|
|
|
779
781
|
url,
|
|
780
782
|
finalUrl,
|
|
781
783
|
contentType: imageMimeType,
|
|
782
|
-
method: convertedText ? "
|
|
784
|
+
method: convertedText ? "markit" : "image-too-large",
|
|
783
785
|
content: output.content,
|
|
784
786
|
fetchedAt,
|
|
785
787
|
truncated: output.truncated,
|
|
@@ -819,27 +821,27 @@ async function renderUrl(
|
|
|
819
821
|
const binary = await fetchBinary(finalUrl, timeout, signal);
|
|
820
822
|
if (binary.ok) {
|
|
821
823
|
const ext = getExtensionHint(finalUrl, binary.contentDisposition) || extHint;
|
|
822
|
-
const converted = await
|
|
824
|
+
const converted = await convertWithMarkit(binary.buffer, ext, timeout, signal);
|
|
823
825
|
if (converted.ok) {
|
|
824
826
|
if (converted.content.trim().length > 50) {
|
|
825
|
-
notes.push("Converted with
|
|
827
|
+
notes.push("Converted with markit");
|
|
826
828
|
const output = finalizeOutput(converted.content);
|
|
827
829
|
return {
|
|
828
830
|
url,
|
|
829
831
|
finalUrl,
|
|
830
832
|
contentType: mime,
|
|
831
|
-
method: "
|
|
833
|
+
method: "markit",
|
|
832
834
|
content: output.content,
|
|
833
835
|
fetchedAt,
|
|
834
836
|
truncated: output.truncated,
|
|
835
837
|
notes,
|
|
836
838
|
};
|
|
837
839
|
}
|
|
838
|
-
notes.push("
|
|
840
|
+
notes.push("markit conversion produced no usable output");
|
|
839
841
|
} else if (converted.error) {
|
|
840
|
-
notes.push(`
|
|
842
|
+
notes.push(`markit conversion failed: ${converted.error}`);
|
|
841
843
|
} else {
|
|
842
|
-
notes.push("
|
|
844
|
+
notes.push("markit conversion failed");
|
|
843
845
|
}
|
|
844
846
|
} else if (binary.error) {
|
|
845
847
|
notes.push(`Binary fetch failed: ${binary.error}`);
|
|
@@ -1007,7 +1009,7 @@ async function renderUrl(
|
|
|
1007
1009
|
const binary = await fetchBinary(docUrl, timeout, signal);
|
|
1008
1010
|
if (binary.ok) {
|
|
1009
1011
|
const ext = getExtensionHint(docUrl, binary.contentDisposition);
|
|
1010
|
-
const converted = await
|
|
1012
|
+
const converted = await convertWithMarkit(binary.buffer, ext, timeout, signal);
|
|
1011
1013
|
if (converted.ok && converted.content.trim().length > htmlResult.content.length) {
|
|
1012
1014
|
notes.push(`Extracted and converted document: ${docUrl}`);
|
|
1013
1015
|
const output = finalizeOutput(converted.content);
|
|
@@ -1023,7 +1025,7 @@ async function renderUrl(
|
|
|
1023
1025
|
};
|
|
1024
1026
|
}
|
|
1025
1027
|
if (!converted.ok && converted.error) {
|
|
1026
|
-
notes.push(`
|
|
1028
|
+
notes.push(`markit conversion failed: ${converted.error}`);
|
|
1027
1029
|
}
|
|
1028
1030
|
} else if (binary.error) {
|
|
1029
1031
|
notes.push(`Binary fetch failed: ${binary.error}`);
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { ToolAbortError, ToolError, throwIfAborted } from "./tool-errors";
|
|
2
|
+
|
|
3
|
+
export interface GhCommandResult {
|
|
4
|
+
exitCode: number;
|
|
5
|
+
stdout: string;
|
|
6
|
+
stderr: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface GhCommandOptions {
|
|
10
|
+
repoProvided?: boolean;
|
|
11
|
+
trimOutput?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isGhAvailable(): boolean {
|
|
15
|
+
return Boolean(Bun.which("gh"));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function formatGhFailure(args: string[], stdout: string, stderr: string, options?: GhCommandOptions): string {
|
|
19
|
+
const output = stderr || stdout;
|
|
20
|
+
const message = output.trim();
|
|
21
|
+
|
|
22
|
+
if (message.includes("gh auth login") || message.includes("not logged into any GitHub hosts")) {
|
|
23
|
+
return "GitHub CLI is not authenticated. Run `gh auth login`.";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (
|
|
27
|
+
!options?.repoProvided &&
|
|
28
|
+
(message.includes("not a git repository") ||
|
|
29
|
+
message.includes("no git remotes found") ||
|
|
30
|
+
message.includes("unable to determine current repository"))
|
|
31
|
+
) {
|
|
32
|
+
return "GitHub repository context is unavailable. Pass `repo` explicitly or run the tool inside a GitHub checkout.";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (message.length > 0) {
|
|
36
|
+
return message;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return `GitHub CLI command failed: gh ${args.join(" ")}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function runGhCommand(
|
|
43
|
+
cwd: string,
|
|
44
|
+
args: string[],
|
|
45
|
+
signal?: AbortSignal,
|
|
46
|
+
options?: GhCommandOptions,
|
|
47
|
+
): Promise<GhCommandResult> {
|
|
48
|
+
throwIfAborted(signal);
|
|
49
|
+
|
|
50
|
+
if (!isGhAvailable()) {
|
|
51
|
+
throw new ToolError("GitHub CLI (gh) is not installed. Install it from https://cli.github.com/.");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const child = Bun.spawn(["gh", ...args], {
|
|
56
|
+
cwd,
|
|
57
|
+
stdin: "ignore",
|
|
58
|
+
stdout: "pipe",
|
|
59
|
+
stderr: "pipe",
|
|
60
|
+
windowsHide: true,
|
|
61
|
+
signal,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (!child.stdout || !child.stderr) {
|
|
65
|
+
throw new ToolError("Failed to capture GitHub CLI output.");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
69
|
+
new Response(child.stdout).text(),
|
|
70
|
+
new Response(child.stderr).text(),
|
|
71
|
+
child.exited,
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
throwIfAborted(signal);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
exitCode: exitCode ?? 0,
|
|
78
|
+
stdout: options?.trimOutput === false ? stdout : stdout.trim(),
|
|
79
|
+
stderr: options?.trimOutput === false ? stderr : stderr.trim(),
|
|
80
|
+
};
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (signal?.aborted) {
|
|
83
|
+
throw new ToolAbortError();
|
|
84
|
+
}
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function runGhJson<T>(
|
|
90
|
+
cwd: string,
|
|
91
|
+
args: string[],
|
|
92
|
+
signal?: AbortSignal,
|
|
93
|
+
options?: GhCommandOptions,
|
|
94
|
+
): Promise<T> {
|
|
95
|
+
const result = await runGhCommand(cwd, args, signal, options);
|
|
96
|
+
|
|
97
|
+
if (result.exitCode !== 0) {
|
|
98
|
+
throw new ToolError(formatGhFailure(args, result.stdout, result.stderr, options));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!result.stdout) {
|
|
102
|
+
throw new ToolError("GitHub CLI returned empty output.");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
return JSON.parse(result.stdout) as T;
|
|
107
|
+
} catch {
|
|
108
|
+
throw new ToolError("GitHub CLI returned invalid JSON output.");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function runGhText(
|
|
113
|
+
cwd: string,
|
|
114
|
+
args: string[],
|
|
115
|
+
signal?: AbortSignal,
|
|
116
|
+
options?: GhCommandOptions,
|
|
117
|
+
): Promise<string> {
|
|
118
|
+
const result = await runGhCommand(cwd, args, signal, options);
|
|
119
|
+
|
|
120
|
+
if (result.exitCode !== 0) {
|
|
121
|
+
throw new ToolError(formatGhFailure(args, result.stdout, result.stderr, options));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return result.stdout;
|
|
125
|
+
}
|