@oh-my-pi/pi-coding-agent 15.10.0 → 15.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +142 -1
- package/dist/types/cli/dry-balance-cli.d.ts +15 -1
- package/dist/types/cli/startup-cwd.d.ts +2 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/commit/analysis/conventional.d.ts +2 -2
- package/dist/types/commit/analysis/summary.d.ts +2 -2
- package/dist/types/commit/changelog/generate.d.ts +2 -2
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/index.d.ts +3 -3
- package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
- package/dist/types/commit/model-selection.d.ts +10 -4
- package/dist/types/config/api-key-resolver.d.ts +34 -0
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-provider-priority.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +17 -1
- package/dist/types/config/model-resolver.d.ts +4 -1
- package/dist/types/config/settings-schema.d.ts +9 -0
- package/dist/types/config/settings.d.ts +7 -2
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/debug/report-bundle.d.ts +3 -0
- package/dist/types/edit/file-snapshot-store.d.ts +18 -10
- package/dist/types/eval/py/__tests__/prelude.test.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +4 -1
- package/dist/types/lsp/client.d.ts +10 -0
- package/dist/types/lsp/utils.d.ts +3 -2
- package/dist/types/main.d.ts +3 -9
- package/dist/types/mcp/tool-bridge.d.ts +2 -0
- package/dist/types/modes/components/chat-block.d.ts +64 -0
- package/dist/types/modes/components/custom-editor.d.ts +4 -1
- package/dist/types/modes/components/overlay-box.d.ts +17 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
- package/dist/types/modes/components/plan-toc.d.ts +41 -0
- package/dist/types/modes/components/read-tool-group.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +2 -0
- package/dist/types/modes/components/transcript-container.d.ts +11 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +17 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
- package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +16 -5
- package/dist/types/modes/magic-keywords.d.ts +1 -1
- package/dist/types/modes/markdown-prose.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +21 -5
- package/dist/types/modes/utils/copy-targets.d.ts +21 -1
- package/dist/types/modes/workflow.d.ts +3 -3
- package/dist/types/plan-mode/approved-plan.d.ts +27 -8
- package/dist/types/plan-mode/plan-protection.d.ts +4 -4
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +21 -0
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/messages.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +8 -3
- package/dist/types/slash-commands/types.d.ts +4 -6
- package/dist/types/task/executor.d.ts +17 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/render.d.ts +3 -2
- package/dist/types/tools/archive-reader.d.ts +5 -0
- package/dist/types/tools/ast-edit.d.ts +3 -0
- package/dist/types/tools/ast-grep.d.ts +3 -0
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/eval.d.ts +8 -0
- package/dist/types/tools/find.d.ts +8 -4
- package/dist/types/tools/gh-cache-invalidation.d.ts +6 -0
- package/dist/types/tools/github-cache.d.ts +12 -0
- package/dist/types/tools/grouped-file-output.d.ts +95 -12
- package/dist/types/tools/memory-render.d.ts +4 -1
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/plan-mode-guard.d.ts +8 -9
- package/dist/types/tools/render-utils.d.ts +5 -9
- package/dist/types/tools/search.d.ts +6 -2
- package/dist/types/tools/sqlite-reader.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +3 -2
- package/dist/types/tools/write.d.ts +3 -0
- package/dist/types/tools/yield.d.ts +8 -0
- package/dist/types/tui/output-block.d.ts +16 -4
- package/dist/types/tui/status-line.d.ts +3 -0
- package/dist/types/utils/enhanced-paste.d.ts +20 -0
- package/dist/types/web/search/providers/kimi.d.ts +1 -1
- package/package.json +9 -9
- package/src/auto-thinking/classifier.ts +5 -1
- package/src/cli/args.ts +3 -1
- package/src/cli/dry-balance-cli.ts +54 -21
- package/src/cli/gallery-cli.ts +4 -1
- package/src/cli/gallery-fixtures/misc.ts +29 -0
- package/src/cli/startup-cwd.ts +68 -0
- package/src/commands/launch.ts +3 -0
- package/src/commit/analysis/conventional.ts +2 -2
- package/src/commit/analysis/summary.ts +2 -2
- package/src/commit/changelog/generate.ts +2 -2
- package/src/commit/changelog/index.ts +2 -2
- package/src/commit/map-reduce/index.ts +3 -3
- package/src/commit/map-reduce/map-phase.ts +2 -2
- package/src/commit/map-reduce/reduce-phase.ts +2 -2
- package/src/commit/model-selection.ts +36 -11
- package/src/commit/pipeline.ts +4 -4
- package/src/config/api-key-resolver.ts +58 -0
- package/src/config/model-provider-priority.ts +55 -0
- package/src/config/model-registry.ts +29 -24
- package/src/config/model-resolver.ts +39 -7
- package/src/config/settings-schema.ts +10 -0
- package/src/config/settings.ts +106 -43
- package/src/dap/config.ts +41 -2
- package/src/dap/defaults.json +1 -0
- package/src/dap/session.ts +1 -0
- package/src/dap/types.ts +10 -0
- package/src/debug/index.ts +47 -53
- package/src/debug/raw-sse-buffer.ts +7 -4
- package/src/debug/report-bundle.ts +9 -0
- package/src/edit/file-snapshot-store.ts +33 -1
- package/src/edit/hashline/filesystem.ts +2 -1
- package/src/edit/renderer.ts +82 -78
- package/src/eval/__tests__/llm-bridge.test.ts +110 -31
- package/src/eval/js/context-manager.ts +32 -15
- package/src/eval/llm-bridge.ts +22 -6
- package/src/eval/py/__tests__/prelude.test.ts +19 -0
- package/src/eval/py/executor.ts +23 -11
- package/src/eval/py/prelude.py +1 -1
- package/src/extensibility/extensions/types.ts +10 -1
- package/src/goals/tools/goal-tool.ts +36 -26
- package/src/internal-urls/docs-index.generated.ts +8 -8
- package/src/lsp/client.ts +23 -11
- package/src/lsp/config.ts +11 -1
- package/src/lsp/index.ts +61 -9
- package/src/lsp/utils.ts +3 -2
- package/src/main.ts +100 -72
- package/src/mcp/tool-bridge.ts +2 -0
- package/src/memories/index.ts +14 -7
- package/src/mnemopi/backend.ts +5 -1
- package/src/modes/acp/acp-agent.ts +33 -26
- package/src/modes/components/assistant-message.ts +2 -9
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/copy-selector.ts +1 -44
- package/src/modes/components/custom-editor.ts +164 -109
- package/src/modes/components/custom-message.ts +1 -3
- package/src/modes/components/execution-shared.ts +1 -2
- package/src/modes/components/hook-message.ts +1 -3
- package/src/modes/components/model-selector.ts +59 -13
- package/src/modes/components/oauth-selector.ts +33 -7
- package/src/modes/components/overlay-box.ts +108 -0
- package/src/modes/components/plan-review-overlay.ts +799 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/read-tool-group.ts +20 -4
- package/src/modes/components/skill-message.ts +0 -1
- package/src/modes/components/status-line.ts +19 -4
- package/src/modes/components/tips.txt +2 -1
- package/src/modes/components/todo-reminder.ts +0 -2
- package/src/modes/components/tool-execution.ts +68 -88
- package/src/modes/components/transcript-container.ts +84 -24
- package/src/modes/components/user-message.ts +2 -3
- package/src/modes/controllers/command-controller-shared.ts +7 -6
- package/src/modes/controllers/command-controller.ts +57 -55
- package/src/modes/controllers/event-controller.ts +67 -40
- package/src/modes/controllers/extension-ui-controller.ts +10 -73
- package/src/modes/controllers/input-controller.ts +170 -126
- package/src/modes/controllers/mcp-command-controller.ts +69 -60
- package/src/modes/controllers/selector-controller.ts +23 -25
- package/src/modes/controllers/streaming-reveal.ts +212 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/interactive-mode.ts +274 -112
- package/src/modes/magic-keywords.ts +1 -1
- package/src/modes/markdown-prose.ts +1 -1
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/shimmer.ts +20 -9
- package/src/modes/theme/theme-schema.json +1 -1
- package/src/modes/theme/theme.ts +8 -4
- package/src/modes/types.ts +21 -7
- package/src/modes/utils/copy-targets.ts +133 -27
- package/src/modes/utils/ui-helpers.ts +44 -46
- package/src/modes/workflow.ts +10 -10
- package/src/plan-mode/approved-plan.ts +66 -43
- package/src/plan-mode/plan-protection.ts +4 -4
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/plan-mode-active.md +67 -58
- package/src/prompts/system/plan-mode-approved.md +1 -1
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/bash.md +9 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/eval.md +2 -1
- package/src/prompts/tools/read.md +2 -2
- package/src/sdk.ts +37 -46
- package/src/session/agent-session.ts +119 -18
- package/src/session/auth-storage.ts +2 -0
- package/src/session/messages.ts +26 -0
- package/src/session/session-manager.ts +109 -28
- package/src/slash-commands/builtin-registry.ts +36 -9
- package/src/slash-commands/types.ts +4 -6
- package/src/task/executor.ts +76 -38
- package/src/task/index.ts +4 -0
- package/src/task/render.ts +211 -147
- package/src/tools/archive-reader.ts +64 -0
- package/src/tools/ask.ts +119 -164
- package/src/tools/ast-edit.ts +98 -71
- package/src/tools/ast-grep.ts +37 -43
- package/src/tools/bash.ts +57 -6
- package/src/tools/browser/tab-supervisor.ts +13 -1
- package/src/tools/browser/tab-worker.ts +33 -4
- package/src/tools/debug.ts +20 -8
- package/src/tools/eval.ts +13 -2
- package/src/tools/fetch.ts +297 -7
- package/src/tools/find.ts +51 -30
- package/src/tools/gh-cache-invalidation.ts +200 -0
- package/src/tools/gh-renderer.ts +81 -42
- package/src/tools/github-cache.ts +25 -0
- package/src/tools/grouped-file-output.ts +272 -48
- package/src/tools/image-gen.ts +150 -103
- package/src/tools/inspect-image-renderer.ts +63 -41
- package/src/tools/inspect-image.ts +10 -3
- package/src/tools/job.ts +3 -4
- package/src/tools/memory-render.ts +4 -1
- package/src/tools/path-utils.ts +28 -2
- package/src/tools/plan-mode-guard.ts +66 -39
- package/src/tools/read.ts +48 -28
- package/src/tools/render-utils.ts +21 -37
- package/src/tools/resolve.ts +14 -0
- package/src/tools/search-tool-bm25.ts +36 -23
- package/src/tools/search.ts +118 -81
- package/src/tools/sqlite-reader.ts +9 -12
- package/src/tools/todo.ts +118 -52
- package/src/tools/write.ts +83 -64
- package/src/tools/yield.ts +10 -1
- package/src/tui/output-block.ts +60 -13
- package/src/tui/status-line.ts +5 -1
- package/src/utils/commit-message-generator.ts +11 -3
- package/src/utils/enhanced-paste.ts +230 -0
- package/src/utils/title-generator.ts +2 -1
- package/src/web/search/providers/anthropic.ts +25 -19
- package/src/web/search/providers/codex.ts +37 -8
- package/src/web/search/providers/exa.ts +11 -3
- package/src/web/search/providers/kimi.ts +28 -17
- package/src/web/search/providers/parallel.ts +35 -24
- package/src/web/search/providers/synthetic.ts +8 -6
- package/src/web/search/providers/tavily.ts +9 -8
- package/src/web/search/providers/zai.ts +8 -6
package/src/tools/fetch.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
1
2
|
import * as fs from "node:fs/promises";
|
|
3
|
+
import * as os from "node:os";
|
|
2
4
|
import * as path from "node:path";
|
|
3
5
|
import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
4
6
|
import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
|
|
@@ -8,6 +10,7 @@ import { $which, ptree, truncate } from "@oh-my-pi/pi-utils";
|
|
|
8
10
|
import { parseHTML } from "linkedom";
|
|
9
11
|
import { LRUCache } from "lru-cache/raw";
|
|
10
12
|
import type { Settings } from "../config/settings";
|
|
13
|
+
import { readEditableNotebookText } from "../edit/notebook";
|
|
11
14
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
12
15
|
import { type Theme, theme } from "../modes/theme/theme";
|
|
13
16
|
import type { ToolSession } from "../sdk";
|
|
@@ -22,10 +25,12 @@ import { specialHandlers } from "../web/scrapers";
|
|
|
22
25
|
import type { RenderResult } from "../web/scrapers/types";
|
|
23
26
|
import { finalizeOutput, loadPage, looksLikeHtml, MAX_OUTPUT_CHARS } from "../web/scrapers/types";
|
|
24
27
|
import { convertWithMarkit, fetchBinary } from "../web/scrapers/utils";
|
|
28
|
+
import { type ArchiveFormat, listArchiveRoot, sniffArchiveFormat } from "./archive-reader";
|
|
25
29
|
import { applyListLimit } from "./list-limit";
|
|
26
30
|
import { formatStyledArtifactReference, type OutputMeta } from "./output-meta";
|
|
27
31
|
import { type LineRange, parseLineRanges } from "./path-utils";
|
|
28
|
-
import { formatExpandHint, getDomain, replaceTabs } from "./render-utils";
|
|
32
|
+
import { formatBytes, formatExpandHint, getDomain, replaceTabs } from "./render-utils";
|
|
33
|
+
import { listTables, looksLikeSqlite, renderTableList } from "./sqlite-reader";
|
|
29
34
|
import { ToolAbortError, ToolError } from "./tool-errors";
|
|
30
35
|
import { toolResult } from "./tool-result";
|
|
31
36
|
import { clampTimeout } from "./tool-timeouts";
|
|
@@ -46,8 +51,6 @@ const CONVERTIBLE_MIMES = new Set([
|
|
|
46
51
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
47
52
|
"application/rtf",
|
|
48
53
|
"application/epub+zip",
|
|
49
|
-
"application/x-ipynb+json",
|
|
50
|
-
"application/zip",
|
|
51
54
|
"image/png",
|
|
52
55
|
"image/jpeg",
|
|
53
56
|
"image/gif",
|
|
@@ -67,7 +70,6 @@ const CONVERTIBLE_EXTENSIONS = new Set([
|
|
|
67
70
|
".xlsx",
|
|
68
71
|
".rtf",
|
|
69
72
|
".epub",
|
|
70
|
-
".ipynb",
|
|
71
73
|
".png",
|
|
72
74
|
".jpg",
|
|
73
75
|
".jpeg",
|
|
@@ -78,6 +80,27 @@ const CONVERTIBLE_EXTENSIONS = new Set([
|
|
|
78
80
|
".ogg",
|
|
79
81
|
]);
|
|
80
82
|
|
|
83
|
+
const NOTEBOOK_MIMES = new Set(["application/x-ipynb+json"]);
|
|
84
|
+
const NOTEBOOK_EXTENSIONS = new Set([".ipynb"]);
|
|
85
|
+
|
|
86
|
+
const SQLITE_MIMES = new Set([
|
|
87
|
+
"application/vnd.sqlite3",
|
|
88
|
+
"application/x-sqlite3",
|
|
89
|
+
"application/sqlite3",
|
|
90
|
+
"application/sqlite",
|
|
91
|
+
]);
|
|
92
|
+
const SQLITE_EXTENSIONS = new Set([".sqlite", ".sqlite3", ".db", ".db3"]);
|
|
93
|
+
|
|
94
|
+
const ARCHIVE_MIMES = new Set([
|
|
95
|
+
"application/zip",
|
|
96
|
+
"application/x-zip-compressed",
|
|
97
|
+
"application/x-tar",
|
|
98
|
+
"application/tar",
|
|
99
|
+
"application/gzip",
|
|
100
|
+
"application/x-gzip",
|
|
101
|
+
]);
|
|
102
|
+
const ARCHIVE_EXTENSIONS = new Set([".zip", ".tar", ".tar.gz", ".tgz", ".gz"]);
|
|
103
|
+
|
|
81
104
|
const IMAGE_MIME_BY_EXTENSION = new Map<string, string>([
|
|
82
105
|
[".png", "image/png"],
|
|
83
106
|
[".jpg", "image/jpeg"],
|
|
@@ -261,6 +284,12 @@ function normalizeMime(contentType: string): string {
|
|
|
261
284
|
return contentType.split(";")[0].trim().toLowerCase();
|
|
262
285
|
}
|
|
263
286
|
|
|
287
|
+
function getFilenameExtensionHint(filename: string): string {
|
|
288
|
+
const lower = filename.toLowerCase();
|
|
289
|
+
if (lower.endsWith(".tar.gz")) return ".tar.gz";
|
|
290
|
+
return path.extname(filename).toLowerCase();
|
|
291
|
+
}
|
|
292
|
+
|
|
264
293
|
/**
|
|
265
294
|
* Get extension from URL or Content-Disposition
|
|
266
295
|
*/
|
|
@@ -269,7 +298,7 @@ function getExtensionHint(url: string, contentDisposition?: string): string {
|
|
|
269
298
|
if (contentDisposition) {
|
|
270
299
|
const match = contentDisposition.match(/filename[*]?=["']?([^"';\n]+)/i);
|
|
271
300
|
if (match) {
|
|
272
|
-
const ext =
|
|
301
|
+
const ext = getFilenameExtensionHint(match[1]);
|
|
273
302
|
if (ext) return ext;
|
|
274
303
|
}
|
|
275
304
|
}
|
|
@@ -277,7 +306,7 @@ function getExtensionHint(url: string, contentDisposition?: string): string {
|
|
|
277
306
|
// Fall back to URL path
|
|
278
307
|
try {
|
|
279
308
|
const pathname = new URL(url).pathname;
|
|
280
|
-
const ext =
|
|
309
|
+
const ext = getFilenameExtensionHint(pathname);
|
|
281
310
|
if (ext) return ext;
|
|
282
311
|
} catch {}
|
|
283
312
|
|
|
@@ -738,6 +767,254 @@ type FetchRenderResult = RenderResult & {
|
|
|
738
767
|
image?: FetchImagePayload;
|
|
739
768
|
};
|
|
740
769
|
|
|
770
|
+
const BINARY_SAMPLE_CHARS = 4096;
|
|
771
|
+
const URL_ARCHIVE_LIST_LIMIT = 500;
|
|
772
|
+
const URL_SQLITE_LIST_LIMIT = 500;
|
|
773
|
+
|
|
774
|
+
function sampleLooksBinary(text: string): boolean {
|
|
775
|
+
const limit = Math.min(text.length, BINARY_SAMPLE_CHARS);
|
|
776
|
+
if (limit === 0) return false;
|
|
777
|
+
|
|
778
|
+
let replacementCount = 0;
|
|
779
|
+
for (let index = 0; index < limit; index++) {
|
|
780
|
+
const code = text.charCodeAt(index);
|
|
781
|
+
if (code === 0) return true;
|
|
782
|
+
if (code === 0xfffd) replacementCount++;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
return replacementCount >= 3 && replacementCount / limit > 0.01;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function isNotebookHint(mime: string, extensionHint: string): boolean {
|
|
789
|
+
return NOTEBOOK_MIMES.has(mime) || NOTEBOOK_EXTENSIONS.has(extensionHint);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function isSqliteHint(mime: string, extensionHint: string): boolean {
|
|
793
|
+
return SQLITE_MIMES.has(mime) || SQLITE_EXTENSIONS.has(extensionHint);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function isArchiveHint(mime: string, extensionHint: string): boolean {
|
|
797
|
+
return ARCHIVE_MIMES.has(mime) || ARCHIVE_EXTENSIONS.has(extensionHint);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function getArchiveFormatHint(mime: string, extensionHint: string): ArchiveFormat | undefined {
|
|
801
|
+
if (extensionHint === ".zip" || mime === "application/zip" || mime === "application/x-zip-compressed") {
|
|
802
|
+
return "zip";
|
|
803
|
+
}
|
|
804
|
+
if (extensionHint === ".tar" || mime === "application/x-tar" || mime === "application/tar") {
|
|
805
|
+
return "tar";
|
|
806
|
+
}
|
|
807
|
+
if (
|
|
808
|
+
extensionHint === ".tar.gz" ||
|
|
809
|
+
extensionHint === ".tgz" ||
|
|
810
|
+
extensionHint === ".gz" ||
|
|
811
|
+
mime === "application/gzip" ||
|
|
812
|
+
mime === "application/x-gzip"
|
|
813
|
+
) {
|
|
814
|
+
return "tar.gz";
|
|
815
|
+
}
|
|
816
|
+
return undefined;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function formatErrorMessage(error: unknown): string {
|
|
820
|
+
return error instanceof Error ? error.message : String(error);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function binaryContentType(mime: string): string {
|
|
824
|
+
return mime || "application/octet-stream";
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
function buildBinaryNotice(finalUrl: string, mime: string, byteLength?: number): string {
|
|
828
|
+
const size = byteLength === undefined ? "unknown size" : formatBytes(byteLength);
|
|
829
|
+
return `[Binary content: ${binaryContentType(mime)}, ${size}] ${finalUrl}`;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function buildBinaryPayloadResult(
|
|
833
|
+
url: string,
|
|
834
|
+
finalUrl: string,
|
|
835
|
+
mime: string,
|
|
836
|
+
method: string,
|
|
837
|
+
content: string,
|
|
838
|
+
fetchedAt: string,
|
|
839
|
+
notes: string[],
|
|
840
|
+
): FetchRenderResult {
|
|
841
|
+
const output = finalizeOutput(content);
|
|
842
|
+
return {
|
|
843
|
+
url,
|
|
844
|
+
finalUrl,
|
|
845
|
+
contentType: binaryContentType(mime),
|
|
846
|
+
method,
|
|
847
|
+
content: output.content,
|
|
848
|
+
fetchedAt,
|
|
849
|
+
truncated: output.truncated,
|
|
850
|
+
notes,
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
async function withTempBinaryFile<T>(
|
|
855
|
+
prefix: string,
|
|
856
|
+
extension: string,
|
|
857
|
+
bytes: Uint8Array,
|
|
858
|
+
readTempFile: (tempPath: string) => Promise<T>,
|
|
859
|
+
): Promise<T> {
|
|
860
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
861
|
+
const tempPath = path.join(tempDir, `payload${extension}`);
|
|
862
|
+
try {
|
|
863
|
+
await Bun.write(tempPath, bytes);
|
|
864
|
+
return await readTempFile(tempPath);
|
|
865
|
+
} finally {
|
|
866
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
async function renderNotebookPayload(bytes: Uint8Array, displayUrl: string): Promise<string> {
|
|
871
|
+
return withTempBinaryFile("omp-url-notebook-", ".ipynb", bytes, tempPath =>
|
|
872
|
+
readEditableNotebookText(tempPath, displayUrl),
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
async function renderSqlitePayload(bytes: Uint8Array): Promise<string> {
|
|
877
|
+
return withTempBinaryFile("omp-url-sqlite-", ".sqlite", bytes, async tempPath => {
|
|
878
|
+
let db: Database | null = null;
|
|
879
|
+
try {
|
|
880
|
+
db = new Database(tempPath, { readonly: true, strict: true });
|
|
881
|
+
db.run("PRAGMA busy_timeout = 3000");
|
|
882
|
+
const listLimit = applyListLimit(listTables(db), { limit: URL_SQLITE_LIST_LIMIT });
|
|
883
|
+
return renderTableList(listLimit.items);
|
|
884
|
+
} finally {
|
|
885
|
+
db?.close();
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
async function tryRenderBinaryPayload(
|
|
891
|
+
url: string,
|
|
892
|
+
finalUrl: string,
|
|
893
|
+
mime: string,
|
|
894
|
+
extHint: string,
|
|
895
|
+
rawContent: string,
|
|
896
|
+
timeout: number,
|
|
897
|
+
signal: AbortSignal | undefined,
|
|
898
|
+
fetchedAt: string,
|
|
899
|
+
notes: readonly string[],
|
|
900
|
+
): Promise<FetchRenderResult | null> {
|
|
901
|
+
const hasNotebookHint = isNotebookHint(mime, extHint);
|
|
902
|
+
const hasSqliteHint = isSqliteHint(mime, extHint);
|
|
903
|
+
const hasArchiveHint = isArchiveHint(mime, extHint);
|
|
904
|
+
const rawLooksBinary = sampleLooksBinary(rawContent);
|
|
905
|
+
if (!hasNotebookHint && !hasSqliteHint && !hasArchiveHint && !rawLooksBinary) {
|
|
906
|
+
return null;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const resultNotes = [...notes];
|
|
910
|
+
const binary = await fetchBinary(finalUrl, timeout, signal);
|
|
911
|
+
if (!binary.ok) {
|
|
912
|
+
resultNotes.push(binary.error ? `Binary fetch failed: ${binary.error}` : "Binary fetch failed");
|
|
913
|
+
return buildBinaryPayloadResult(
|
|
914
|
+
url,
|
|
915
|
+
finalUrl,
|
|
916
|
+
mime,
|
|
917
|
+
"binary",
|
|
918
|
+
buildBinaryNotice(finalUrl, mime),
|
|
919
|
+
fetchedAt,
|
|
920
|
+
resultNotes,
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
const binaryExtHint = getExtensionHint(finalUrl, binary.contentDisposition) || extHint;
|
|
925
|
+
if (isNotebookHint(mime, binaryExtHint)) {
|
|
926
|
+
try {
|
|
927
|
+
return buildBinaryPayloadResult(
|
|
928
|
+
url,
|
|
929
|
+
finalUrl,
|
|
930
|
+
mime,
|
|
931
|
+
"notebook",
|
|
932
|
+
await renderNotebookPayload(binary.buffer, finalUrl),
|
|
933
|
+
fetchedAt,
|
|
934
|
+
resultNotes,
|
|
935
|
+
);
|
|
936
|
+
} catch (error) {
|
|
937
|
+
resultNotes.push(`Notebook rendering failed: ${formatErrorMessage(error)}`);
|
|
938
|
+
return buildBinaryPayloadResult(
|
|
939
|
+
url,
|
|
940
|
+
finalUrl,
|
|
941
|
+
mime,
|
|
942
|
+
"binary",
|
|
943
|
+
buildBinaryNotice(finalUrl, mime, binary.buffer.byteLength),
|
|
944
|
+
fetchedAt,
|
|
945
|
+
resultNotes,
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
if (isSqliteHint(mime, binaryExtHint) || looksLikeSqlite(binary.buffer)) {
|
|
951
|
+
try {
|
|
952
|
+
return buildBinaryPayloadResult(
|
|
953
|
+
url,
|
|
954
|
+
finalUrl,
|
|
955
|
+
mime,
|
|
956
|
+
"sqlite",
|
|
957
|
+
await renderSqlitePayload(binary.buffer),
|
|
958
|
+
fetchedAt,
|
|
959
|
+
resultNotes,
|
|
960
|
+
);
|
|
961
|
+
} catch (error) {
|
|
962
|
+
resultNotes.push(`SQLite rendering failed: ${formatErrorMessage(error)}`);
|
|
963
|
+
return buildBinaryPayloadResult(
|
|
964
|
+
url,
|
|
965
|
+
finalUrl,
|
|
966
|
+
mime,
|
|
967
|
+
"binary",
|
|
968
|
+
buildBinaryNotice(finalUrl, mime, binary.buffer.byteLength),
|
|
969
|
+
fetchedAt,
|
|
970
|
+
resultNotes,
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
const hintedArchiveFormat = getArchiveFormatHint(mime, binaryExtHint);
|
|
976
|
+
const shouldArchiveSniff = hintedArchiveFormat !== undefined || !isConvertible(mime, binaryExtHint);
|
|
977
|
+
const archiveFormat = hintedArchiveFormat ?? (shouldArchiveSniff ? sniffArchiveFormat(binary.buffer) : undefined);
|
|
978
|
+
if (archiveFormat) {
|
|
979
|
+
try {
|
|
980
|
+
return buildBinaryPayloadResult(
|
|
981
|
+
url,
|
|
982
|
+
finalUrl,
|
|
983
|
+
mime,
|
|
984
|
+
"archive",
|
|
985
|
+
await listArchiveRoot(binary.buffer, archiveFormat, { limit: URL_ARCHIVE_LIST_LIMIT }),
|
|
986
|
+
fetchedAt,
|
|
987
|
+
resultNotes,
|
|
988
|
+
);
|
|
989
|
+
} catch (error) {
|
|
990
|
+
resultNotes.push(`Archive rendering failed: ${formatErrorMessage(error)}`);
|
|
991
|
+
return buildBinaryPayloadResult(
|
|
992
|
+
url,
|
|
993
|
+
finalUrl,
|
|
994
|
+
mime,
|
|
995
|
+
"binary",
|
|
996
|
+
buildBinaryNotice(finalUrl, mime, binary.buffer.byteLength),
|
|
997
|
+
fetchedAt,
|
|
998
|
+
resultNotes,
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
if (rawLooksBinary) {
|
|
1004
|
+
return buildBinaryPayloadResult(
|
|
1005
|
+
url,
|
|
1006
|
+
finalUrl,
|
|
1007
|
+
mime,
|
|
1008
|
+
"binary",
|
|
1009
|
+
buildBinaryNotice(finalUrl, mime, binary.buffer.byteLength),
|
|
1010
|
+
fetchedAt,
|
|
1011
|
+
resultNotes,
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
return null;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
741
1018
|
// =============================================================================
|
|
742
1019
|
// Unified Special Handler Dispatch
|
|
743
1020
|
// =============================================================================
|
|
@@ -984,6 +1261,19 @@ async function renderUrl(
|
|
|
984
1261
|
}
|
|
985
1262
|
}
|
|
986
1263
|
|
|
1264
|
+
const binaryPayloadResult = await tryRenderBinaryPayload(
|
|
1265
|
+
url,
|
|
1266
|
+
finalUrl,
|
|
1267
|
+
mime,
|
|
1268
|
+
extHint,
|
|
1269
|
+
rawContent,
|
|
1270
|
+
timeout,
|
|
1271
|
+
signal,
|
|
1272
|
+
fetchedAt,
|
|
1273
|
+
notes,
|
|
1274
|
+
);
|
|
1275
|
+
if (binaryPayloadResult) return binaryPayloadResult;
|
|
1276
|
+
|
|
987
1277
|
// Step 4: Handle non-HTML text content
|
|
988
1278
|
const isHtml = mime.includes("html") || mime.includes("xhtml");
|
|
989
1279
|
const isJson = mime.includes("json");
|
|
@@ -992,7 +1282,7 @@ async function renderUrl(
|
|
|
992
1282
|
const isFeed = mime.includes("rss") || mime.includes("atom") || mime.includes("feed");
|
|
993
1283
|
|
|
994
1284
|
// Raw mode skips every text-shaping branch below (JSON pretty-print, feed-to-markdown,
|
|
995
|
-
// HTML extraction) and returns the response body verbatim.
|
|
1285
|
+
// HTML extraction) and returns the response body verbatim. Binary-oriented branches
|
|
996
1286
|
// above already ran because raw isn't useful for binary payloads.
|
|
997
1287
|
if (raw) {
|
|
998
1288
|
const output = finalizeOutput(rawContent);
|
package/src/tools/find.ts
CHANGED
|
@@ -13,6 +13,7 @@ import findDescription from "../prompts/tools/find.md" with { type: "text" };
|
|
|
13
13
|
import { type TruncationResult, truncateHead } from "../session/streaming-output";
|
|
14
14
|
import { Ellipsis, fileHyperlink, renderFileList, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
|
|
15
15
|
import type { ToolSession } from ".";
|
|
16
|
+
import { buildPathTree, walkPathTree } from "./grouped-file-output";
|
|
16
17
|
import { applyListLimit } from "./list-limit";
|
|
17
18
|
import { formatFullOutputReference, type OutputMeta } from "./output-meta";
|
|
18
19
|
import {
|
|
@@ -54,34 +55,27 @@ const MIN_GLOB_TIMEOUT_MS = 500;
|
|
|
54
55
|
const MAX_GLOB_TIMEOUT_MS = 60_000;
|
|
55
56
|
|
|
56
57
|
/**
|
|
57
|
-
* Group find matches
|
|
58
|
-
* tokens for shared path prefixes.
|
|
59
|
-
*
|
|
60
|
-
*
|
|
58
|
+
* Group find matches into a multi-level directory tree so the model doesn't pay
|
|
59
|
+
* repeated tokens for shared path prefixes. Single-child directory chains fold
|
|
60
|
+
* into one header (`# a/b/c/`), so a common prefix — including an absolute root
|
|
61
|
+
* for out-of-cwd results — collapses to a single line. Each level adds one `#`;
|
|
62
|
+
* files are listed bare under the deepest directory header that owns them.
|
|
63
|
+
*
|
|
64
|
+
* Order follows the input (mtime-desc for native glob): a directory appears when
|
|
65
|
+
* its first member is emitted, and a node's own files precede its subdirectories.
|
|
61
66
|
*/
|
|
62
67
|
export function formatFindGroupedOutput(paths: readonly string[]): string {
|
|
63
68
|
if (paths.length === 0) return "";
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const dir = slash === -1 ? "" : trimmed.slice(0, slash);
|
|
70
|
-
const base = slash === -1 ? trimmed : trimmed.slice(slash + 1);
|
|
71
|
-
const label = hasTrailingSlash ? `${base}/` : base;
|
|
72
|
-
const list = groups.get(dir);
|
|
73
|
-
if (list) list.push(label);
|
|
74
|
-
else groups.set(dir, [label]);
|
|
75
|
-
}
|
|
76
|
-
const sections: string[] = [];
|
|
77
|
-
for (const [dir, entries] of groups) {
|
|
78
|
-
if (dir === "") {
|
|
79
|
-
sections.push(entries.join("\n"));
|
|
69
|
+
const tree = buildPathTree(paths.map(entry => ({ path: entry, isDir: entry.endsWith("/") })));
|
|
70
|
+
const lines: string[] = [];
|
|
71
|
+
for (const event of walkPathTree(tree)) {
|
|
72
|
+
if (event.kind === "dir") {
|
|
73
|
+
lines.push(`${"#".repeat(event.depth + 1)} ${event.name}/`);
|
|
80
74
|
} else {
|
|
81
|
-
|
|
75
|
+
lines.push(event.name);
|
|
82
76
|
}
|
|
83
77
|
}
|
|
84
|
-
return
|
|
78
|
+
return lines.join("\n");
|
|
85
79
|
}
|
|
86
80
|
|
|
87
81
|
export interface FindToolDetails {
|
|
@@ -359,6 +353,13 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
359
353
|
maxResults: effectiveLimit,
|
|
360
354
|
sortByMtime: true,
|
|
361
355
|
gitignore: useGitignore,
|
|
356
|
+
// parseFindPattern explicitly prepends "**/" when the user's
|
|
357
|
+
// pattern begins with a glob (so `*.ts` becomes `**/*.ts`).
|
|
358
|
+
// Anything that arrives here without "**/" was scoped to a
|
|
359
|
+
// single directory by the user (e.g. `dir/*`); disable the
|
|
360
|
+
// native auto-recursion so `dir/*` does not silently match
|
|
361
|
+
// `dir/sub/nested.ts`.
|
|
362
|
+
recursive: false,
|
|
362
363
|
signal: combinedSignal,
|
|
363
364
|
},
|
|
364
365
|
onMatch,
|
|
@@ -434,6 +435,10 @@ function formatFindRenderPaths(paths: FindRenderArgs["paths"]): string | undefin
|
|
|
434
435
|
|
|
435
436
|
const COLLAPSED_LIST_LIMIT = PREVIEW_LIMITS.COLLAPSED_ITEMS;
|
|
436
437
|
|
|
438
|
+
function findStatusIcon(uiTheme: Theme): string {
|
|
439
|
+
return uiTheme.fg("toolTitle", uiTheme.symbol("icon.search"));
|
|
440
|
+
}
|
|
441
|
+
|
|
437
442
|
export const findToolRenderer = {
|
|
438
443
|
inline: true,
|
|
439
444
|
renderCall(args: FindRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
@@ -441,10 +446,16 @@ export const findToolRenderer = {
|
|
|
441
446
|
if (args.limit !== undefined) meta.push(`limit:${args.limit}`);
|
|
442
447
|
|
|
443
448
|
const text = renderStatusLine(
|
|
444
|
-
{
|
|
449
|
+
{
|
|
450
|
+
icon: "pending",
|
|
451
|
+
title: "Find",
|
|
452
|
+
titleColor: "toolTitle",
|
|
453
|
+
description: formatFindRenderPaths(args.paths) || "*",
|
|
454
|
+
meta,
|
|
455
|
+
},
|
|
445
456
|
uiTheme,
|
|
446
457
|
);
|
|
447
|
-
return new Text(text,
|
|
458
|
+
return new Text(text, 1, 0);
|
|
448
459
|
},
|
|
449
460
|
|
|
450
461
|
renderResult(
|
|
@@ -457,7 +468,7 @@ export const findToolRenderer = {
|
|
|
457
468
|
|
|
458
469
|
if (result.isError || details?.error) {
|
|
459
470
|
const errorText = details?.error || result.content?.find(c => c.type === "text")?.text || "Unknown error";
|
|
460
|
-
return new Text(formatErrorMessage(errorText, uiTheme),
|
|
471
|
+
return new Text(formatErrorMessage(errorText, uiTheme), 1, 0);
|
|
461
472
|
}
|
|
462
473
|
|
|
463
474
|
const hasDetailedData = details?.fileCount !== undefined;
|
|
@@ -470,14 +481,15 @@ export const findToolRenderer = {
|
|
|
470
481
|
textContent.includes("No files found") ||
|
|
471
482
|
textContent.trim() === ""
|
|
472
483
|
) {
|
|
473
|
-
return new Text(formatEmptyMessage("No files found", uiTheme),
|
|
484
|
+
return new Text(formatEmptyMessage("No files found", uiTheme), 1, 0);
|
|
474
485
|
}
|
|
475
486
|
|
|
476
487
|
const lines = textContent.split("\n").filter(l => l.trim());
|
|
477
488
|
const header = renderStatusLine(
|
|
478
489
|
{
|
|
479
|
-
|
|
490
|
+
iconOverride: findStatusIcon(uiTheme),
|
|
480
491
|
title: "Find",
|
|
492
|
+
titleColor: "toolTitle",
|
|
481
493
|
description: formatFindRenderPaths(args?.paths),
|
|
482
494
|
meta: [formatCount("file", lines.length)],
|
|
483
495
|
},
|
|
@@ -498,6 +510,7 @@ export const findToolRenderer = {
|
|
|
498
510
|
);
|
|
499
511
|
return [header, ...listLines].map(l => truncateToWidth(l, width, Ellipsis.Omit));
|
|
500
512
|
},
|
|
513
|
+
{ paddingX: 1 },
|
|
501
514
|
);
|
|
502
515
|
}
|
|
503
516
|
|
|
@@ -513,20 +526,27 @@ export const findToolRenderer = {
|
|
|
513
526
|
|
|
514
527
|
if (fileCount === 0) {
|
|
515
528
|
const header = renderStatusLine(
|
|
516
|
-
{
|
|
529
|
+
{
|
|
530
|
+
icon: "warning",
|
|
531
|
+
title: "Find",
|
|
532
|
+
titleColor: "toolTitle",
|
|
533
|
+
description: formatFindRenderPaths(args?.paths),
|
|
534
|
+
meta: ["0 files"],
|
|
535
|
+
},
|
|
517
536
|
uiTheme,
|
|
518
537
|
);
|
|
519
538
|
const lines = [header, formatEmptyMessage("No files found", uiTheme)];
|
|
520
539
|
if (missingNote) lines.push(missingNote);
|
|
521
|
-
return new Text(lines.join("\n"),
|
|
540
|
+
return new Text(lines.join("\n"), 1, 0);
|
|
522
541
|
}
|
|
523
542
|
const meta: string[] = [formatCount("file", fileCount)];
|
|
524
543
|
if (details?.scopePath) meta.push(`in ${details.scopePath}`);
|
|
525
544
|
if (truncated) meta.push(uiTheme.fg("warning", "truncated"));
|
|
526
545
|
const header = renderStatusLine(
|
|
527
546
|
{
|
|
528
|
-
|
|
547
|
+
...(truncated ? { icon: "warning" as const } : { iconOverride: findStatusIcon(uiTheme) }),
|
|
529
548
|
title: "Find",
|
|
549
|
+
titleColor: "toolTitle",
|
|
530
550
|
description: formatFindRenderPaths(args?.paths),
|
|
531
551
|
meta,
|
|
532
552
|
},
|
|
@@ -565,6 +585,7 @@ export const findToolRenderer = {
|
|
|
565
585
|
);
|
|
566
586
|
return [header, ...fileLines, ...extraLines].map(l => truncateToWidth(l, width, Ellipsis.Omit));
|
|
567
587
|
},
|
|
588
|
+
{ paddingX: 1 },
|
|
568
589
|
);
|
|
569
590
|
},
|
|
570
591
|
mergeCallAndResult: true,
|