@oh-my-pi/pi-coding-agent 16.1.23 → 16.2.1
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 +82 -0
- package/dist/cli.js +3785 -3695
- package/dist/types/advisor/__tests__/emission-guard.test.d.ts +1 -0
- package/dist/types/advisor/emission-guard.d.ts +73 -0
- package/dist/types/advisor/index.d.ts +1 -0
- package/dist/types/advisor/runtime.d.ts +7 -0
- package/dist/types/advisor/watchdog.d.ts +13 -0
- package/dist/types/cli/flag-tables.d.ts +1 -0
- package/dist/types/cli/gallery-fixtures/search.d.ts +1 -1
- package/dist/types/collab/display-name.d.ts +3 -0
- package/dist/types/collab/host.d.ts +0 -2
- package/dist/types/commands/worktree.d.ts +0 -3
- package/dist/types/config/inline-tool-descriptors-mode.d.ts +13 -0
- package/dist/types/config/model-discovery.d.ts +6 -1
- package/dist/types/config/model-registry.d.ts +6 -2
- package/dist/types/config/model-resolver.d.ts +12 -0
- package/dist/types/config/models-config-schema.d.ts +29 -2
- package/dist/types/config/models-config.d.ts +22 -1
- package/dist/types/config/settings-schema.d.ts +91 -21
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/dap/config.d.ts +1 -1
- package/dist/types/edit/hashline/filesystem.d.ts +4 -2
- package/dist/types/edit/renderer.d.ts +4 -0
- package/dist/types/extensibility/extensions/types.d.ts +17 -17
- package/dist/types/extensibility/hooks/types.d.ts +10 -10
- package/dist/types/extensibility/plugins/marketplace/types.d.ts +1 -0
- package/dist/types/internal-urls/__tests__/ssh-protocol.test.d.ts +1 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/registry-helpers.d.ts +2 -0
- package/dist/types/internal-urls/router.d.ts +1 -1
- package/dist/types/internal-urls/ssh-protocol.d.ts +10 -0
- package/dist/types/internal-urls/types.d.ts +19 -2
- package/dist/types/irc/bus.d.ts +6 -0
- package/dist/types/modes/acp/acp-event-mapper.d.ts +1 -0
- package/dist/types/modes/components/__tests__/move-overlay.test.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +7 -0
- package/dist/types/modes/components/move-overlay.d.ts +23 -0
- package/dist/types/modes/components/plugin-selector.d.ts +2 -1
- package/dist/types/modes/components/queue-mode-selector.d.ts +2 -1
- package/dist/types/modes/components/select-list-mouse-routing.d.ts +2 -0
- package/dist/types/modes/components/settings-defs.d.ts +4 -1
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/show-images-selector.d.ts +2 -1
- package/dist/types/modes/components/status-line/component.d.ts +2 -0
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/theme-selector.d.ts +2 -1
- package/dist/types/modes/components/thinking-selector.d.ts +2 -1
- package/dist/types/modes/controllers/command-controller.d.ts +10 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +10 -1
- package/dist/types/modes/internal-url-autocomplete.d.ts +1 -1
- package/dist/types/modes/running-subagent-badge.d.ts +6 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +9 -1
- package/dist/types/sdk.d.ts +2 -2
- package/dist/types/session/agent-session.d.ts +8 -1
- package/dist/types/session/blob-store.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +13 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
- package/dist/types/ssh/__tests__/connection-manager-args.test.d.ts +1 -0
- package/dist/types/ssh/__tests__/file-transfer-posix-guard.test.d.ts +1 -0
- package/dist/types/ssh/connection-manager.d.ts +2 -0
- package/dist/types/ssh/file-transfer.d.ts +79 -0
- package/dist/types/ssh/utils.d.ts +6 -0
- package/dist/types/system-prompt.d.ts +3 -0
- package/dist/types/task/executor.d.ts +16 -0
- package/dist/types/tiny/text.d.ts +1 -1
- package/dist/types/tools/builtin-names.d.ts +5 -1
- package/dist/types/tools/{find.d.ts → glob.d.ts} +15 -15
- package/dist/types/tools/{search.d.ts → grep.d.ts} +14 -14
- package/dist/types/tools/index.d.ts +3 -3
- package/dist/types/tools/path-utils.d.ts +29 -0
- package/dist/types/tools/read.d.ts +2 -2
- package/dist/types/tools/render-utils.d.ts +8 -0
- package/dist/types/tools/renderers.d.ts +11 -0
- package/dist/types/tools/ssh.d.ts +2 -0
- package/dist/types/tools/todo.d.ts +0 -16
- package/dist/types/tools/write.d.ts +2 -2
- package/dist/types/utils/active-repo-context.d.ts +8 -0
- package/dist/types/utils/image-resize.d.ts +1 -0
- package/dist/types/utils/markit-cache.d.ts +23 -0
- package/dist/types/utils/markit.d.ts +5 -1
- package/dist/types/utils/prompt-path.d.ts +1 -0
- package/dist/types/web/search/providers/duckduckgo.d.ts +14 -0
- package/dist/types/web/search/providers/firecrawl.d.ts +28 -0
- package/dist/types/web/search/providers/tinyfish.d.ts +29 -0
- package/dist/types/web/search/providers/xai.d.ts +13 -0
- package/dist/types/web/search/types.d.ts +18 -2
- package/package.json +30 -15
- package/scripts/build-binary.ts +9 -9
- package/scripts/bundle-dist.ts +2 -2
- package/src/advisor/__tests__/advisor.test.ts +61 -4
- package/src/advisor/__tests__/emission-guard.test.ts +147 -0
- package/src/advisor/advise-tool.ts +1 -1
- package/src/advisor/emission-guard.ts +172 -0
- package/src/advisor/index.ts +1 -0
- package/src/advisor/runtime.ts +11 -0
- package/src/advisor/watchdog.ts +27 -1
- package/src/cli/args.ts +5 -2
- package/src/cli/auth-broker-cli.ts +17 -0
- package/src/cli/config-cli.ts +4 -0
- package/src/cli/flag-tables.ts +7 -4
- package/src/cli/gallery-cli.ts +14 -2
- package/src/cli/gallery-fixtures/edit.ts +60 -0
- package/src/cli/gallery-fixtures/fs.ts +17 -17
- package/src/cli/gallery-fixtures/search.ts +4 -4
- package/src/cli/usage-cli.ts +20 -1
- package/src/cli/web-search-cli.ts +1 -1
- package/src/collab/display-name.ts +13 -0
- package/src/collab/guest.ts +5 -1
- package/src/collab/host.ts +2 -13
- package/src/commands/worktree.ts +6 -0
- package/src/config/inline-tool-descriptors-mode.ts +27 -0
- package/src/config/model-discovery.ts +77 -8
- package/src/config/model-registry.ts +89 -11
- package/src/config/model-resolver.ts +39 -1
- package/src/config/models-config-schema.ts +41 -5
- package/src/config/models-config.ts +4 -1
- package/src/config/settings-schema.ts +85 -27
- package/src/config/settings.ts +172 -1
- package/src/cursor.ts +1 -1
- package/src/dap/config.ts +152 -8
- package/src/dap/session.ts +1 -1
- package/src/discovery/claude-plugins.ts +3 -2
- package/src/discovery/helpers.ts +3 -1
- package/src/edit/hashline/diff.ts +14 -3
- package/src/edit/hashline/execute.ts +42 -6
- package/src/edit/hashline/filesystem.ts +38 -2
- package/src/edit/index.ts +1 -0
- package/src/edit/modes/patch.ts +11 -1
- package/src/edit/renderer.ts +140 -13
- package/src/eval/__tests__/agent-bridge.test.ts +101 -0
- package/src/eval/agent-bridge.ts +26 -3
- package/src/eval/js/tool-bridge.ts +2 -2
- package/src/export/html/index.ts +1 -1
- package/src/export/html/template.js +3 -1
- package/src/export/html/tool-views.generated.js +20 -20
- package/src/extensibility/extensions/types.ts +22 -22
- package/src/extensibility/hooks/types.ts +11 -11
- package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +8 -3
- package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +25 -10
- package/src/extensibility/plugins/marketplace/manager.ts +22 -0
- package/src/extensibility/plugins/marketplace/types.ts +1 -0
- package/src/internal-urls/__tests__/ssh-protocol.test.ts +331 -0
- package/src/internal-urls/docs-index.generated.txt +2 -2
- package/src/internal-urls/docs-index.ts +2 -3
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/registry-helpers.ts +19 -4
- package/src/internal-urls/router.ts +5 -3
- package/src/internal-urls/ssh-protocol.ts +367 -0
- package/src/internal-urls/types.ts +19 -2
- package/src/irc/bus.ts +11 -3
- package/src/mcp/tool-bridge.ts +32 -2
- package/src/memories/index.ts +1 -1
- package/src/modes/acp/acp-agent.ts +14 -1
- package/src/modes/acp/acp-event-mapper.ts +91 -27
- package/src/modes/components/__tests__/move-overlay.test.ts +166 -0
- package/src/modes/components/agent-hub.ts +3 -0
- package/src/modes/components/agent-transcript-viewer.ts +9 -7
- package/src/modes/components/custom-editor.ts +90 -5
- package/src/modes/components/move-overlay.ts +282 -0
- package/src/modes/components/plan-review-overlay.ts +35 -35
- package/src/modes/components/plugin-selector.ts +6 -1
- package/src/modes/components/queue-mode-selector.ts +6 -1
- package/src/modes/components/select-list-mouse-routing.ts +35 -0
- package/src/modes/components/session-selector.ts +11 -10
- package/src/modes/components/settings-defs.ts +14 -1
- package/src/modes/components/settings-selector.ts +196 -29
- package/src/modes/components/show-images-selector.ts +6 -1
- package/src/modes/components/status-line/component.ts +108 -28
- package/src/modes/components/status-line/segments.ts +5 -1
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/theme-selector.ts +6 -1
- package/src/modes/components/thinking-selector.ts +6 -1
- package/src/modes/components/tool-execution.ts +25 -9
- package/src/modes/components/tree-selector.ts +5 -5
- package/src/modes/controllers/command-controller.ts +140 -47
- package/src/modes/controllers/event-controller.ts +59 -3
- package/src/modes/controllers/input-controller.ts +68 -2
- package/src/modes/controllers/selector-controller.ts +5 -2
- package/src/modes/controllers/streaming-reveal.ts +17 -0
- package/src/modes/controllers/tool-args-reveal.ts +1 -1
- package/src/modes/interactive-mode.ts +116 -50
- package/src/modes/internal-url-autocomplete.ts +17 -2
- package/src/modes/prompt-action-autocomplete.ts +3 -1
- package/src/modes/running-subagent-badge.ts +13 -0
- package/src/modes/setup-wizard/scenes/glyph.ts +2 -13
- package/src/modes/setup-wizard/scenes/providers.ts +2 -1
- package/src/modes/setup-wizard/scenes/theme.ts +6 -12
- package/src/modes/setup-wizard/scenes/web-search.ts +8 -13
- package/src/modes/setup-wizard/wizard-overlay.ts +6 -5
- package/src/modes/theme/theme.ts +9 -1
- package/src/modes/types.ts +9 -1
- package/src/modes/utils/interactive-context-helpers.ts +1 -1
- package/src/priority.json +15 -0
- package/src/prompts/advisor/active-repo-watchdog.md +6 -0
- package/src/prompts/advisor/context-files.md +8 -0
- package/src/prompts/advisor/system.md +21 -7
- package/src/prompts/agents/designer.md +1 -1
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/agents/librarian.md +2 -2
- package/src/prompts/agents/plan.md +2 -2
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/agents/task.md +2 -2
- package/src/prompts/system/active-repo-context.md +4 -0
- package/src/prompts/system/gemini-tool-call-reminder.md +9 -0
- package/src/prompts/system/plan-mode-active.md +3 -3
- package/src/prompts/system/project-prompt.md +2 -2
- package/src/prompts/system/system-prompt.md +6 -6
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/checkpoint.md +1 -1
- package/src/prompts/tools/{find.md → glob.md} +1 -1
- package/src/prompts/tools/{search.md → grep.md} +3 -3
- package/src/prompts/tools/read.md +4 -2
- package/src/sdk.ts +65 -25
- package/src/session/agent-session.ts +519 -132
- package/src/session/blob-store.ts +24 -0
- package/src/session/session-history-format.ts +2 -2
- package/src/session/session-manager.ts +46 -0
- package/src/slash-commands/builtin-registry.ts +138 -20
- package/src/slash-commands/helpers/usage-report.ts +23 -2
- package/src/ssh/__tests__/connection-manager-args.test.ts +69 -0
- package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +68 -0
- package/src/ssh/connection-manager.ts +12 -15
- package/src/ssh/file-transfer.ts +209 -0
- package/src/ssh/utils.ts +24 -0
- package/src/system-prompt.ts +56 -28
- package/src/task/executor.ts +96 -36
- package/src/task/index.ts +3 -3
- package/src/task/render.ts +14 -13
- package/src/task/worktree.ts +38 -1
- package/src/tiny/text.ts +49 -4
- package/src/tiny/worker.ts +3 -3
- package/src/tools/bash.ts +2 -2
- package/src/tools/builtin-names.ts +26 -2
- package/src/tools/{find.ts → glob.ts} +48 -42
- package/src/tools/{search.ts → grep.ts} +311 -129
- package/src/tools/index.ts +13 -14
- package/src/tools/irc.ts +6 -2
- package/src/tools/path-utils.ts +64 -1
- package/src/tools/read.ts +35 -6
- package/src/tools/render-utils.ts +14 -0
- package/src/tools/renderers.ts +15 -4
- package/src/tools/ssh.ts +17 -2
- package/src/tools/todo.ts +4 -26
- package/src/tools/write.ts +25 -9
- package/src/utils/active-repo-context.ts +143 -0
- package/src/utils/image-resize.ts +88 -7
- package/src/utils/lang-from-path.ts +3 -3
- package/src/utils/markit-cache.ts +166 -0
- package/src/utils/markit.ts +86 -18
- package/src/utils/prompt-path.ts +3 -0
- package/src/utils/title-generator.ts +1 -1
- package/src/web/search/provider.ts +54 -34
- package/src/web/search/providers/duckduckgo.ts +140 -0
- package/src/web/search/providers/firecrawl.ts +144 -0
- package/src/web/search/providers/tinyfish.ts +159 -0
- package/src/web/search/providers/xai.ts +292 -0
- package/src/web/search/providers/zai.ts +142 -56
- package/src/web/search/types.ts +6 -2
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as fsPromises from "node:fs/promises";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { type GitRepository, repo } from "./git";
|
|
6
|
+
|
|
7
|
+
export interface ActiveRepoContext {
|
|
8
|
+
cwd: string;
|
|
9
|
+
repoRoot: string;
|
|
10
|
+
relativeRepoRoot: string;
|
|
11
|
+
source: "single-direct-child-repo";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function compareEntryNames(left: fs.Dirent, right: fs.Dirent): number {
|
|
15
|
+
if (left.name < right.name) return -1;
|
|
16
|
+
if (left.name > right.name) return 1;
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function buildContext(cwd: string, repoRoot: string): ActiveRepoContext {
|
|
21
|
+
const resolvedCwd = path.resolve(cwd);
|
|
22
|
+
const resolvedRepoRoot = path.resolve(repoRoot);
|
|
23
|
+
return {
|
|
24
|
+
cwd: resolvedCwd,
|
|
25
|
+
repoRoot: resolvedRepoRoot,
|
|
26
|
+
relativeRepoRoot: path.relative(resolvedCwd, resolvedRepoRoot),
|
|
27
|
+
source: "single-direct-child-repo",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function resolveRepository(cwd: string): Promise<GitRepository | null> {
|
|
32
|
+
try {
|
|
33
|
+
return await repo.resolve(cwd);
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resolveRepositorySync(cwd: string): GitRepository | null {
|
|
40
|
+
try {
|
|
41
|
+
return repo.resolveSync(cwd);
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function readDirectChildren(cwd: string): Promise<fs.Dirent[]> {
|
|
48
|
+
try {
|
|
49
|
+
const entries = await fsPromises.readdir(cwd, { withFileTypes: true });
|
|
50
|
+
entries.sort(compareEntryNames);
|
|
51
|
+
return entries;
|
|
52
|
+
} catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function readDirectChildrenSync(cwd: string): fs.Dirent[] {
|
|
58
|
+
try {
|
|
59
|
+
const entries = fs.readdirSync(cwd, { withFileTypes: true });
|
|
60
|
+
entries.sort(compareEntryNames);
|
|
61
|
+
return entries;
|
|
62
|
+
} catch {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function resolveDirectChildDirectory(cwd: string, entry: fs.Dirent): Promise<string | null> {
|
|
68
|
+
const childPath = path.join(cwd, entry.name);
|
|
69
|
+
if (entry.isDirectory()) return childPath;
|
|
70
|
+
if (!entry.isSymbolicLink()) return null;
|
|
71
|
+
try {
|
|
72
|
+
const stat = await fsPromises.stat(childPath);
|
|
73
|
+
return stat.isDirectory() ? childPath : null;
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function resolveDirectChildDirectorySync(cwd: string, entry: fs.Dirent): string | null {
|
|
80
|
+
const childPath = path.join(cwd, entry.name);
|
|
81
|
+
if (entry.isDirectory()) return childPath;
|
|
82
|
+
if (!entry.isSymbolicLink()) return null;
|
|
83
|
+
try {
|
|
84
|
+
const stat = fs.statSync(childPath);
|
|
85
|
+
return stat.isDirectory() ? childPath : null;
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function hasGitMarker(childPath: string): Promise<boolean> {
|
|
92
|
+
try {
|
|
93
|
+
const stat = await fsPromises.stat(path.join(childPath, ".git"));
|
|
94
|
+
return stat.isDirectory() || stat.isFile();
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function hasGitMarkerSync(childPath: string): boolean {
|
|
101
|
+
try {
|
|
102
|
+
const stat = fs.statSync(path.join(childPath, ".git"));
|
|
103
|
+
return stat.isDirectory() || stat.isFile();
|
|
104
|
+
} catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function findSingleDirectChildRepo(cwd: string): Promise<ActiveRepoContext | null> {
|
|
110
|
+
let context: ActiveRepoContext | null = null;
|
|
111
|
+
for (const entry of await readDirectChildren(cwd)) {
|
|
112
|
+
const childPath = await resolveDirectChildDirectory(cwd, entry);
|
|
113
|
+
if (!childPath) continue;
|
|
114
|
+
if (!(await hasGitMarker(childPath))) continue;
|
|
115
|
+
if (context) return null;
|
|
116
|
+
context = buildContext(cwd, childPath);
|
|
117
|
+
}
|
|
118
|
+
return context;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function findSingleDirectChildRepoSync(cwd: string): ActiveRepoContext | null {
|
|
122
|
+
let context: ActiveRepoContext | null = null;
|
|
123
|
+
for (const entry of readDirectChildrenSync(cwd)) {
|
|
124
|
+
const childPath = resolveDirectChildDirectorySync(cwd, entry);
|
|
125
|
+
if (!childPath) continue;
|
|
126
|
+
if (!hasGitMarkerSync(childPath)) continue;
|
|
127
|
+
if (context) return null;
|
|
128
|
+
context = buildContext(cwd, childPath);
|
|
129
|
+
}
|
|
130
|
+
return context;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function resolveActiveRepoContext(cwd: string): Promise<ActiveRepoContext | null> {
|
|
134
|
+
const resolvedCwd = path.resolve(cwd);
|
|
135
|
+
if (await resolveRepository(resolvedCwd)) return null;
|
|
136
|
+
return findSingleDirectChildRepo(resolvedCwd);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function resolveActiveRepoContextSync(cwd: string): ActiveRepoContext | null {
|
|
140
|
+
const resolvedCwd = path.resolve(cwd);
|
|
141
|
+
if (resolveRepositorySync(resolvedCwd)) return null;
|
|
142
|
+
return findSingleDirectChildRepoSync(resolvedCwd);
|
|
143
|
+
}
|
|
@@ -18,6 +18,7 @@ export interface ResizedImage {
|
|
|
18
18
|
width: number;
|
|
19
19
|
height: number;
|
|
20
20
|
wasResized: boolean;
|
|
21
|
+
decodeFailed?: boolean;
|
|
21
22
|
get data(): string;
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -42,6 +43,83 @@ const DEFAULT_OPTIONS: Required<Omit<ImageResizeOptions, "excludeWebP">> = {
|
|
|
42
43
|
minDimension: DEFAULT_MIN_DIMENSION,
|
|
43
44
|
};
|
|
44
45
|
|
|
46
|
+
interface ImageHeaderDimensions {
|
|
47
|
+
width: number;
|
|
48
|
+
height: number;
|
|
49
|
+
mimeType: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function readUint16BE(buffer: Uint8Array, offset: number): number {
|
|
53
|
+
return (buffer[offset] << 8) | buffer[offset + 1];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function readUint32BE(buffer: Uint8Array, offset: number): number {
|
|
57
|
+
return ((buffer[offset] << 24) | (buffer[offset + 1] << 16) | (buffer[offset + 2] << 8) | buffer[offset + 3]) >>> 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function readPngHeaderDimensions(buffer: Uint8Array): ImageHeaderDimensions | undefined {
|
|
61
|
+
if (buffer.length < 24) return undefined;
|
|
62
|
+
if (
|
|
63
|
+
buffer[0] !== 0x89 ||
|
|
64
|
+
buffer[1] !== 0x50 ||
|
|
65
|
+
buffer[2] !== 0x4e ||
|
|
66
|
+
buffer[3] !== 0x47 ||
|
|
67
|
+
buffer[4] !== 0x0d ||
|
|
68
|
+
buffer[5] !== 0x0a ||
|
|
69
|
+
buffer[6] !== 0x1a ||
|
|
70
|
+
buffer[7] !== 0x0a
|
|
71
|
+
) {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
if (readUint32BE(buffer, 8) !== 13) return undefined;
|
|
75
|
+
if (buffer[12] !== 0x49 || buffer[13] !== 0x48 || buffer[14] !== 0x44 || buffer[15] !== 0x52) return undefined;
|
|
76
|
+
const width = readUint32BE(buffer, 16);
|
|
77
|
+
const height = readUint32BE(buffer, 20);
|
|
78
|
+
if (width === 0 || height === 0) return undefined;
|
|
79
|
+
return { width, height, mimeType: "image/png" };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isJpegStartOfFrame(marker: number): boolean {
|
|
83
|
+
return (
|
|
84
|
+
(marker >= 0xc0 && marker <= 0xc3) ||
|
|
85
|
+
(marker >= 0xc5 && marker <= 0xc7) ||
|
|
86
|
+
(marker >= 0xc9 && marker <= 0xcb) ||
|
|
87
|
+
(marker >= 0xcd && marker <= 0xcf)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function readJpegHeaderDimensions(buffer: Uint8Array): ImageHeaderDimensions | undefined {
|
|
92
|
+
if (buffer.length < 4 || buffer[0] !== 0xff || buffer[1] !== 0xd8) return undefined;
|
|
93
|
+
let offset = 2;
|
|
94
|
+
while (offset + 3 < buffer.length) {
|
|
95
|
+
if (buffer[offset] !== 0xff) {
|
|
96
|
+
offset++;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
while (offset < buffer.length && buffer[offset] === 0xff) offset++;
|
|
100
|
+
if (offset >= buffer.length) return undefined;
|
|
101
|
+
const marker = buffer[offset++];
|
|
102
|
+
if (marker === 0xd9 || marker === 0xda) return undefined;
|
|
103
|
+
if (marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) continue;
|
|
104
|
+
if (offset + 1 >= buffer.length) return undefined;
|
|
105
|
+
const segmentLength = readUint16BE(buffer, offset);
|
|
106
|
+
if (segmentLength < 2) return undefined;
|
|
107
|
+
if (isJpegStartOfFrame(marker)) {
|
|
108
|
+
if (offset + 7 >= buffer.length) return undefined;
|
|
109
|
+
const height = readUint16BE(buffer, offset + 3);
|
|
110
|
+
const width = readUint16BE(buffer, offset + 5);
|
|
111
|
+
if (width === 0 || height === 0) return undefined;
|
|
112
|
+
return { width, height, mimeType: "image/jpeg" };
|
|
113
|
+
}
|
|
114
|
+
offset += segmentLength;
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function readImageHeaderDimensions(buffer: Uint8Array): ImageHeaderDimensions | undefined {
|
|
120
|
+
return readPngHeaderDimensions(buffer) ?? readJpegHeaderDimensions(buffer);
|
|
121
|
+
}
|
|
122
|
+
|
|
45
123
|
/**
|
|
46
124
|
* Read `OMP_NO_WEBP` per-call so runtime toggles take effect.
|
|
47
125
|
* Only `"1"` and `"true"` (case-insensitive) enable exclusion — an empty string
|
|
@@ -298,21 +376,24 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption
|
|
|
298
376
|
},
|
|
299
377
|
};
|
|
300
378
|
} catch {
|
|
379
|
+
const headerDimensions = readImageHeaderDimensions(inputBuffer);
|
|
380
|
+
const fallbackMimeType = img.mimeType ?? headerDimensions?.mimeType ?? "application/octet-stream";
|
|
301
381
|
// Bun.Image rejected the input — we cannot decode/re-encode it.
|
|
302
|
-
// When the caller demanded WebP exclusion AND the
|
|
382
|
+
// When the caller demanded WebP exclusion AND the source might be WebP,
|
|
303
383
|
// returning the original buffer would silently violate that contract,
|
|
304
384
|
// so surface an explicit error instead.
|
|
305
|
-
if (excludeWebP && (
|
|
385
|
+
if (excludeWebP && (fallbackMimeType === "image/webp" || (!img.mimeType && !headerDimensions))) {
|
|
306
386
|
throw new Error("resizeImage: failed to decode image and cannot honor excludeWebP for a WebP source");
|
|
307
387
|
}
|
|
308
388
|
return {
|
|
309
389
|
buffer: inputBuffer,
|
|
310
|
-
mimeType:
|
|
311
|
-
originalWidth: 0,
|
|
312
|
-
originalHeight: 0,
|
|
313
|
-
width: 0,
|
|
314
|
-
height: 0,
|
|
390
|
+
mimeType: fallbackMimeType,
|
|
391
|
+
originalWidth: headerDimensions?.width ?? 0,
|
|
392
|
+
originalHeight: headerDimensions?.height ?? 0,
|
|
393
|
+
width: headerDimensions?.width ?? 0,
|
|
394
|
+
height: headerDimensions?.height ?? 0,
|
|
315
395
|
wasResized: false,
|
|
396
|
+
decodeFailed: true,
|
|
316
397
|
get data() {
|
|
317
398
|
return img.data;
|
|
318
399
|
},
|
|
@@ -202,9 +202,6 @@ function lspExtensionKey(filePath: string): string {
|
|
|
202
202
|
* Language id for syntax highlighting and UI (icons, read tool), or undefined if unknown.
|
|
203
203
|
*/
|
|
204
204
|
export function getLanguageFromPath(filePath: string): string | undefined {
|
|
205
|
-
const pair = EXTENSION_LANG[themeExtensionKey(filePath)];
|
|
206
|
-
if (pair) return pair[0];
|
|
207
|
-
|
|
208
205
|
const baseName = path.basename(filePath).toLowerCase();
|
|
209
206
|
if (baseName.startsWith(".env.")) return "env";
|
|
210
207
|
if (baseName === "dockerfile" || baseName.startsWith("dockerfile.") || baseName === "containerfile") {
|
|
@@ -214,6 +211,9 @@ export function getLanguageFromPath(filePath: string): string | undefined {
|
|
|
214
211
|
if (baseName === "justfile") return "just";
|
|
215
212
|
if (baseName === "cmakelists.txt") return "cmake";
|
|
216
213
|
|
|
214
|
+
const pair = EXTENSION_LANG[themeExtensionKey(filePath)];
|
|
215
|
+
if (pair) return pair[0];
|
|
216
|
+
|
|
217
217
|
return undefined;
|
|
218
218
|
}
|
|
219
219
|
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import type { Stats } from "node:fs";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { getDocumentConversionCacheDir, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
5
|
+
import packageJson from "../../package.json" with { type: "json" };
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Cache schema/format revision. Bumping it changes the on-disk key prefix
|
|
9
|
+
* (`v<N>-...`), so old entries become unreachable and are pruned naturally.
|
|
10
|
+
* Bump when the cache *file* shape changes (entry JSON layout, key scheme).
|
|
11
|
+
*
|
|
12
|
+
* Converter *output* changes are handled separately: the package version is
|
|
13
|
+
* folded into the key (see {@link markitConversionCacheKey}), so any release
|
|
14
|
+
* that ships new markdown from `src/markit/converters/*` auto-invalidates the
|
|
15
|
+
* cache without a manual bump here.
|
|
16
|
+
*/
|
|
17
|
+
export const MARKIT_CONVERSION_CACHE_VERSION = 1;
|
|
18
|
+
export const MAX_MARKIT_CONVERSION_CACHE_BYTES = 256 * 1024 * 1024;
|
|
19
|
+
/** `.tmp` files older than this are treated as orphaned writes and swept. */
|
|
20
|
+
const TMP_ORPHAN_MAX_AGE_MS = 5 * 60 * 1000;
|
|
21
|
+
export type MarkitConversionCacheStatus = "hit" | "miss" | "skipped";
|
|
22
|
+
|
|
23
|
+
export type MarkitConversionCacheReadResult = { status: "hit"; content: string } | { status: "miss" };
|
|
24
|
+
|
|
25
|
+
interface MarkitConversionCacheEntry {
|
|
26
|
+
version: number;
|
|
27
|
+
content: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function markitConversionCacheKey(bytes: Uint8Array, extension: string): string {
|
|
31
|
+
const normalizedExtension = extension.trim().toLowerCase().replace(/^\.+/, "") || "bin";
|
|
32
|
+
const safeExtension = normalizedExtension.replace(/[^a-z0-9]+/g, "_") || "bin";
|
|
33
|
+
const safeVersion = packageJson.version.replace(/[^a-z0-9]+/gi, "_");
|
|
34
|
+
const digest = new Bun.CryptoHasher("sha256").update(bytes).digest("hex");
|
|
35
|
+
return `v${MARKIT_CONVERSION_CACHE_VERSION}-${safeVersion}-${safeExtension}-${digest}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function cacheEntryPath(key: string): string {
|
|
39
|
+
return path.join(getDocumentConversionCacheDir(), `${key}.json`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function errorMessage(error: unknown): string {
|
|
43
|
+
return error instanceof Error ? error.message : String(error);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function parseCacheEntry(raw: string): MarkitConversionCacheEntry | null {
|
|
47
|
+
const parsed: unknown = JSON.parse(raw);
|
|
48
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
49
|
+
if (!("version" in parsed) || parsed.version !== MARKIT_CONVERSION_CACHE_VERSION) return null;
|
|
50
|
+
if (!("content" in parsed) || typeof parsed.content !== "string" || parsed.content.length === 0) return null;
|
|
51
|
+
return { version: MARKIT_CONVERSION_CACHE_VERSION, content: parsed.content };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function readMarkitConversionCache(key: string): Promise<MarkitConversionCacheReadResult> {
|
|
55
|
+
const target = cacheEntryPath(key);
|
|
56
|
+
let raw: string;
|
|
57
|
+
try {
|
|
58
|
+
raw = await Bun.file(target).text();
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (!isEnoent(error)) {
|
|
61
|
+
logger.debug("document conversion cache read failed", { error: errorMessage(error) });
|
|
62
|
+
}
|
|
63
|
+
return { status: "miss" };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let entry: MarkitConversionCacheEntry | null;
|
|
67
|
+
try {
|
|
68
|
+
entry = parseCacheEntry(raw);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
logger.debug("document conversion cache read failed", { error: errorMessage(error) });
|
|
71
|
+
entry = null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!entry) {
|
|
75
|
+
await fs.rm(target, { force: true }).catch(() => undefined);
|
|
76
|
+
return { status: "miss" };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { status: "hit", content: entry.content };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function pruneMarkitConversionCache(cacheDir: string): Promise<void> {
|
|
83
|
+
let names: string[];
|
|
84
|
+
try {
|
|
85
|
+
names = await fs.readdir(cacheDir);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (!isEnoent(error)) {
|
|
88
|
+
logger.debug("document conversion cache prune failed", { error: errorMessage(error) });
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
// Eviction is FIFO by mtime (not LRU): reads do not bump mtime, so a hot
|
|
95
|
+
// entry written long ago is evicted before a cold recent miss. The cap is a
|
|
96
|
+
// coarse disk-footprint safety valve, so the cheaper policy is intentional.
|
|
97
|
+
const entries: { path: string; size: number; mtimeMs: number }[] = [];
|
|
98
|
+
let totalBytes = 0;
|
|
99
|
+
for (const name of names) {
|
|
100
|
+
const entryPath = path.join(cacheDir, name);
|
|
101
|
+
let stat: Stats;
|
|
102
|
+
try {
|
|
103
|
+
stat = await fs.stat(entryPath);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (!isEnoent(error)) {
|
|
106
|
+
logger.debug("document conversion cache prune failed", { error: errorMessage(error) });
|
|
107
|
+
}
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (!stat.isFile()) continue;
|
|
111
|
+
|
|
112
|
+
// Sweep orphaned `.tmp` files left by a crash/SIGKILL between writeFile
|
|
113
|
+
// and rename; they never become `.json` entries, so the size cap would
|
|
114
|
+
// otherwise never see them.
|
|
115
|
+
if (name.endsWith(".tmp")) {
|
|
116
|
+
if (now - stat.mtimeMs > TMP_ORPHAN_MAX_AGE_MS) {
|
|
117
|
+
await fs.rm(entryPath, { force: true }).catch(() => undefined);
|
|
118
|
+
}
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!name.endsWith(".json")) continue;
|
|
123
|
+
entries.push({ path: entryPath, size: stat.size, mtimeMs: stat.mtimeMs });
|
|
124
|
+
totalBytes += stat.size;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (totalBytes <= MAX_MARKIT_CONVERSION_CACHE_BYTES) return;
|
|
128
|
+
|
|
129
|
+
entries.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
130
|
+
for (const entry of entries) {
|
|
131
|
+
if (totalBytes <= MAX_MARKIT_CONVERSION_CACHE_BYTES) break;
|
|
132
|
+
try {
|
|
133
|
+
await fs.rm(entry.path, { force: true });
|
|
134
|
+
totalBytes -= entry.size;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (!isEnoent(error)) {
|
|
137
|
+
logger.debug("document conversion cache prune failed", { error: errorMessage(error) });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function writeMarkitConversionCache(key: string, content: string): Promise<void> {
|
|
144
|
+
const cacheDir = getDocumentConversionCacheDir();
|
|
145
|
+
const target = path.join(cacheDir, `${key}.json`);
|
|
146
|
+
// The random suffix keeps concurrent writers (same pid + same ms) from
|
|
147
|
+
// colliding on one temp path before the atomic rename.
|
|
148
|
+
const tempPath = path.join(cacheDir, `${key}.${process.pid}.${Date.now()}.${crypto.randomUUID()}.tmp`);
|
|
149
|
+
const payload = JSON.stringify({ version: MARKIT_CONVERSION_CACHE_VERSION, content });
|
|
150
|
+
try {
|
|
151
|
+
await fs.mkdir(cacheDir, { recursive: true });
|
|
152
|
+
await Bun.write(tempPath, payload);
|
|
153
|
+
await fs.rename(tempPath, target);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
await fs.rm(tempPath, { force: true }).catch(() => undefined);
|
|
156
|
+
logger.debug("document conversion cache write failed", { error: errorMessage(error) });
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Prune is just GC: the entry is already on disk under its final name, so
|
|
161
|
+
// fire-and-forget rather than make the caller wait on a readdir + N×stat
|
|
162
|
+
// sweep on every miss (the slow path the cache exists to amortise).
|
|
163
|
+
void pruneMarkitConversionCache(cacheDir).catch(error => {
|
|
164
|
+
logger.debug("document conversion cache prune failed", { error: errorMessage(error) });
|
|
165
|
+
});
|
|
166
|
+
}
|
package/src/utils/markit.ts
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
1
2
|
import { logger, untilAborted } from "@oh-my-pi/pi-utils";
|
|
2
|
-
import type { Markit, StreamInfo } from "../markit";
|
|
3
|
+
import type { ConversionResult, Markit, StreamInfo } from "../markit";
|
|
3
4
|
import { ToolAbortError } from "../tools/tool-errors";
|
|
5
|
+
import {
|
|
6
|
+
type MarkitConversionCacheStatus,
|
|
7
|
+
markitConversionCacheKey,
|
|
8
|
+
readMarkitConversionCache,
|
|
9
|
+
writeMarkitConversionCache,
|
|
10
|
+
} from "./markit-cache";
|
|
4
11
|
import { loadEmbeddedMupdfWasm } from "./mupdf-wasm-embed";
|
|
5
12
|
|
|
6
13
|
export interface MarkitConversionResult {
|
|
7
14
|
content: string;
|
|
8
15
|
ok: boolean;
|
|
9
16
|
error?: string;
|
|
17
|
+
cache?: MarkitConversionCacheStatus;
|
|
10
18
|
}
|
|
11
19
|
|
|
12
20
|
export interface MarkitFileConversionOptions {
|
|
@@ -103,41 +111,101 @@ function finalizeConversion(markdown?: string): MarkitConversionResult {
|
|
|
103
111
|
return { content: "", ok: false, error: "Conversion produced no output" };
|
|
104
112
|
}
|
|
105
113
|
|
|
106
|
-
|
|
107
|
-
|
|
114
|
+
function toBuffer(bytes: Uint8Array): Buffer {
|
|
115
|
+
return Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function throwIfAborted(signal?: AbortSignal): void {
|
|
119
|
+
if (signal?.aborted) throw new ToolAbortError();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function runCachedBufferConversion(
|
|
123
|
+
bytes: Uint8Array,
|
|
124
|
+
streamInfo: StreamInfo,
|
|
108
125
|
signal?: AbortSignal,
|
|
109
|
-
|
|
126
|
+
cacheEnabled = true,
|
|
110
127
|
): Promise<MarkitConversionResult> {
|
|
111
|
-
const
|
|
128
|
+
const cacheKey = cacheEnabled
|
|
129
|
+
? markitConversionCacheKey(bytes, streamInfo.extension ?? streamInfo.mimetype ?? ".bin")
|
|
130
|
+
: undefined;
|
|
131
|
+
|
|
132
|
+
if (cacheKey) {
|
|
133
|
+
throwIfAborted(signal);
|
|
134
|
+
const cached = await readMarkitConversionCache(cacheKey);
|
|
135
|
+
throwIfAborted(signal);
|
|
136
|
+
if (cached.status === "hit") {
|
|
137
|
+
return { content: cached.content, ok: true, cache: "hit" };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
throwIfAborted(signal);
|
|
142
|
+
let result: ConversionResult;
|
|
112
143
|
try {
|
|
113
|
-
|
|
114
|
-
return finalizeConversion(result.markdown);
|
|
144
|
+
result = await runMarkitConversion(markit => markit.convert(toBuffer(bytes), streamInfo), signal);
|
|
115
145
|
} catch (error) {
|
|
116
146
|
if (error instanceof ToolAbortError) {
|
|
117
147
|
throw error;
|
|
118
148
|
}
|
|
119
|
-
return { content: "", ok: false, error: normalizeError(error) };
|
|
149
|
+
return { content: "", ok: false, error: normalizeError(error), cache: cacheEnabled ? "miss" : "skipped" };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const finalized = finalizeConversion(result.markdown);
|
|
153
|
+
if (finalized.ok && cacheKey) {
|
|
154
|
+
await writeMarkitConversionCache(cacheKey, finalized.content);
|
|
155
|
+
}
|
|
156
|
+
return { ...finalized, cache: cacheEnabled ? "miss" : "skipped" };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function convertFileWithMarkit(
|
|
160
|
+
filePath: string,
|
|
161
|
+
signal?: AbortSignal,
|
|
162
|
+
options?: MarkitFileConversionOptions,
|
|
163
|
+
): Promise<MarkitConversionResult> {
|
|
164
|
+
if (options?.imageDir) {
|
|
165
|
+
// Image extraction writes files into imageDir as a side effect; a
|
|
166
|
+
// markdown-only cache hit would leave the directory missing members, so
|
|
167
|
+
// this path stays uncached.
|
|
168
|
+
try {
|
|
169
|
+
const result = await runMarkitConversion(
|
|
170
|
+
markit => markit.convertFile(filePath, { imageDir: options.imageDir }),
|
|
171
|
+
signal,
|
|
172
|
+
);
|
|
173
|
+
return { ...finalizeConversion(result.markdown), cache: "skipped" };
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (error instanceof ToolAbortError) {
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
return { content: "", ok: false, error: normalizeError(error), cache: "skipped" };
|
|
179
|
+
}
|
|
120
180
|
}
|
|
181
|
+
|
|
182
|
+
throwIfAborted(signal);
|
|
183
|
+
let bytes: Uint8Array;
|
|
184
|
+
try {
|
|
185
|
+
bytes = await untilAborted(signal, () => Bun.file(filePath).bytes());
|
|
186
|
+
} catch (error) {
|
|
187
|
+
if (error instanceof ToolAbortError) throw error;
|
|
188
|
+
if (error instanceof Error && error.name === "AbortError") throw new ToolAbortError();
|
|
189
|
+
return { content: "", ok: false, error: normalizeError(error), cache: "miss" };
|
|
190
|
+
}
|
|
191
|
+
const streamInfo: StreamInfo = {
|
|
192
|
+
localPath: filePath,
|
|
193
|
+
extension: path.extname(filePath).toLowerCase(),
|
|
194
|
+
filename: path.basename(filePath),
|
|
195
|
+
};
|
|
196
|
+
return runCachedBufferConversion(bytes, streamInfo, signal, true);
|
|
121
197
|
}
|
|
122
198
|
|
|
123
199
|
export async function convertBufferWithMarkit(
|
|
124
200
|
buffer: Uint8Array,
|
|
125
201
|
extension: string,
|
|
126
202
|
signal?: AbortSignal,
|
|
203
|
+
options?: { useCache?: boolean },
|
|
127
204
|
): Promise<MarkitConversionResult> {
|
|
128
205
|
const normalizedExtension = normalizeExtension(extension);
|
|
129
206
|
const streamInfo: StreamInfo = {
|
|
130
207
|
extension: normalizedExtension,
|
|
131
208
|
filename: `input${normalizedExtension}`,
|
|
132
209
|
};
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
const result = await runMarkitConversion(markit => markit.convert(Buffer.from(buffer), streamInfo), signal);
|
|
136
|
-
return finalizeConversion(result.markdown);
|
|
137
|
-
} catch (error) {
|
|
138
|
-
if (error instanceof ToolAbortError) {
|
|
139
|
-
throw error;
|
|
140
|
-
}
|
|
141
|
-
return { content: "", ok: false, error: normalizeError(error) };
|
|
142
|
-
}
|
|
210
|
+
return runCachedBufferConversion(buffer, streamInfo, signal, options?.useCache ?? true);
|
|
143
211
|
}
|
|
@@ -246,7 +246,7 @@ export async function generateTitleOnline(
|
|
|
246
246
|
return null;
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
-
const title = normalizeGeneratedTitle(extractGeneratedTitle(response.content));
|
|
249
|
+
const title = normalizeGeneratedTitle(extractGeneratedTitle(response.content), firstMessage);
|
|
250
250
|
|
|
251
251
|
if (!title) {
|
|
252
252
|
logger.debug("title-generator: no title returned", {
|