@oh-my-pi/pi-coding-agent 8.4.0 → 8.4.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 +15 -0
- package/package.json +6 -6
- package/scripts/format-prompts.ts +65 -23
- package/src/commit/agentic/prompts/session-user.md +0 -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 +23 -26
- package/src/commit/prompts/analysis-user.md +1 -1
- package/src/commit/prompts/changelog-system.md +1 -2
- package/src/commit/prompts/changelog-user.md +1 -2
- package/src/commit/prompts/file-observer-system.md +1 -3
- package/src/commit/prompts/file-observer-user.md +1 -2
- package/src/commit/prompts/reduce-system.md +16 -16
- package/src/commit/prompts/reduce-user.md +1 -1
- package/src/commit/prompts/summary-retry.md +1 -2
- package/src/commit/prompts/summary-system.md +10 -10
- package/src/commit/prompts/summary-user.md +1 -1
- package/src/commit/prompts/types-description.md +1 -1
- package/src/config/keybindings.ts +3 -0
- package/src/config/settings-manager.ts +5 -0
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/plan-protocol.ts +95 -0
- package/src/modes/components/status-line/presets.ts +7 -7
- package/src/modes/components/status-line/segments.ts +16 -0
- package/src/modes/components/status-line/types.ts +4 -0
- package/src/modes/components/status-line-segment-editor.ts +1 -0
- package/src/modes/components/status-line.ts +16 -2
- package/src/modes/controllers/command-controller.ts +42 -0
- package/src/modes/controllers/event-controller.ts +13 -0
- package/src/modes/controllers/input-controller.ts +16 -0
- package/src/modes/interactive-mode.ts +219 -1
- package/src/modes/theme/theme.ts +7 -0
- package/src/modes/types.ts +7 -0
- package/src/patch/index.ts +9 -3
- package/src/plan-mode/state.ts +6 -0
- 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/plan.md +33 -49
- package/src/prompts/agents/reviewer.md +7 -7
- package/src/prompts/agents/task.md +1 -2
- package/src/prompts/compaction/branch-summary-preamble.md +1 -1
- package/src/prompts/compaction/branch-summary.md +3 -1
- package/src/prompts/compaction/compaction-summary.md +3 -1
- package/src/prompts/compaction/compaction-turn-prefix.md +2 -1
- package/src/prompts/compaction/compaction-update-summary.md +3 -1
- package/src/prompts/review-request.md +4 -1
- package/src/prompts/system/custom-system-prompt.md +8 -8
- package/src/prompts/system/file-operations.md +1 -1
- package/src/prompts/system/plan-mode-active.md +113 -0
- package/src/prompts/system/plan-mode-approved.md +16 -0
- package/src/prompts/system/plan-mode-reference.md +14 -0
- package/src/prompts/system/plan-mode-subagent.md +36 -0
- package/src/prompts/system/summarization-system.md +1 -1
- package/src/prompts/system/system-prompt.md +17 -27
- 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 -3
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/calculator.md +1 -1
- package/src/prompts/tools/enter-plan-mode.md +92 -0
- package/src/prompts/tools/exit-plan-mode.md +38 -0
- 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/grep.md +1 -1
- package/src/prompts/tools/lsp.md +1 -1
- package/src/prompts/tools/patch.md +1 -3
- package/src/prompts/tools/python.md +2 -4
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/replace.md +16 -16
- package/src/prompts/tools/ssh.md +1 -4
- package/src/prompts/tools/task.md +1 -3
- package/src/prompts/tools/todo-write.md +13 -16
- package/src/prompts/tools/web-search.md +1 -1
- package/src/prompts/tools/write.md +1 -1
- package/src/sdk.ts +61 -10
- package/src/session/agent-session.ts +267 -0
- package/src/task/executor.ts +1 -0
- package/src/task/index.ts +18 -4
- package/src/tools/enter-plan-mode.ts +76 -0
- package/src/tools/exit-plan-mode.ts +62 -0
- package/src/tools/find.ts +5 -2
- package/src/tools/grep.ts +13 -12
- package/src/tools/index.ts +19 -1
- package/src/tools/plan-mode-guard.ts +46 -0
- package/src/tools/read.ts +8 -4
- package/src/tools/write.ts +3 -2
- package/src/utils/tools-manager.ts +38 -9
- package/src/web/search/providers/perplexity.ts +3 -1
- package/src/web/search/types.ts +3 -1
package/src/tools/grep.ts
CHANGED
|
@@ -107,21 +107,17 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
107
107
|
private readonly session: ToolSession;
|
|
108
108
|
private readonly ops: GrepOperations;
|
|
109
109
|
|
|
110
|
-
private readonly rgPath: Promise<string | undefined>;
|
|
111
|
-
|
|
112
110
|
constructor(session: ToolSession, options?: GrepToolOptions) {
|
|
113
111
|
this.session = session;
|
|
114
112
|
this.ops = options?.operations ?? defaultGrepOperations;
|
|
115
113
|
this.description = renderPromptTemplate(grepDescription);
|
|
116
|
-
this.rgPath = ensureTool("rg", true);
|
|
117
114
|
}
|
|
118
115
|
|
|
119
116
|
/**
|
|
120
117
|
* Validates a pattern against ripgrep's regex engine.
|
|
121
118
|
* Uses a quick dry-run against /dev/null to check for parse errors.
|
|
122
119
|
*/
|
|
123
|
-
private async validateRegexPattern(pattern: string): Promise<{ valid: boolean; error?: string }> {
|
|
124
|
-
const rgPath = await this.rgPath;
|
|
120
|
+
private async validateRegexPattern(pattern: string, rgPath?: string): Promise<{ valid: boolean; error?: string }> {
|
|
125
121
|
if (!rgPath) {
|
|
126
122
|
return { valid: true }; // Can't validate, assume valid
|
|
127
123
|
}
|
|
@@ -146,7 +142,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
146
142
|
params: GrepParams,
|
|
147
143
|
signal?: AbortSignal,
|
|
148
144
|
_onUpdate?: AgentToolUpdateCallback<GrepToolDetails>,
|
|
149
|
-
|
|
145
|
+
toolContext?: AgentToolContext,
|
|
150
146
|
): Promise<AgentToolResult<GrepToolDetails>> {
|
|
151
147
|
const {
|
|
152
148
|
pattern,
|
|
@@ -167,19 +163,24 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
167
163
|
return untilAborted(signal, async () => {
|
|
168
164
|
// Auto-detect invalid regex patterns and switch to literal mode
|
|
169
165
|
// This handles cases like "abort(" which would cause ripgrep regex parse errors
|
|
166
|
+
const rgPath = await ensureTool("rg", {
|
|
167
|
+
silent: true,
|
|
168
|
+
notify: message => toolContext?.ui?.notify(message, "info"),
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if (!rgPath) {
|
|
172
|
+
throw new ToolError("rg is not available and could not be downloaded");
|
|
173
|
+
}
|
|
174
|
+
|
|
170
175
|
let useLiteral = literal ?? false;
|
|
171
176
|
if (!useLiteral) {
|
|
172
|
-
const validation = await this.validateRegexPattern(pattern);
|
|
177
|
+
const validation = await this.validateRegexPattern(pattern, rgPath);
|
|
173
178
|
if (!validation.valid) {
|
|
174
179
|
useLiteral = true;
|
|
175
180
|
}
|
|
176
181
|
}
|
|
177
182
|
|
|
178
|
-
|
|
179
|
-
if (!rgPath) {
|
|
180
|
-
throw new ToolError("ripgrep (rg) is not available and could not be downloaded");
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
+
// rgPath resolved earlier
|
|
183
184
|
const searchPath = resolveToCwd(searchDir || ".", this.session.cwd);
|
|
184
185
|
const scopePath = (() => {
|
|
185
186
|
const relative = nodePath.relative(this.session.cwd, searchPath).replace(/\\/g, "/");
|
package/src/tools/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { getPreludeDocs, warmPythonEnvironment } from "../ipy/executor";
|
|
|
8
8
|
import { checkPythonKernelAvailability } from "../ipy/kernel";
|
|
9
9
|
import { LspTool } from "../lsp";
|
|
10
10
|
import { EditTool } from "../patch";
|
|
11
|
+
import type { PlanModeState } from "../plan-mode/state";
|
|
11
12
|
import type { ArtifactManager } from "../session/artifacts";
|
|
12
13
|
import { TaskTool } from "../task";
|
|
13
14
|
import type { AgentOutputManager } from "../task/output-manager";
|
|
@@ -18,6 +19,8 @@ import { AskTool } from "./ask";
|
|
|
18
19
|
import { BashTool } from "./bash";
|
|
19
20
|
import { CalculatorTool } from "./calculator";
|
|
20
21
|
import { CompleteTool } from "./complete";
|
|
22
|
+
import { EnterPlanModeTool } from "./enter-plan-mode";
|
|
23
|
+
import { ExitPlanModeTool } from "./exit-plan-mode";
|
|
21
24
|
import { FetchTool } from "./fetch";
|
|
22
25
|
import { FindTool } from "./find";
|
|
23
26
|
import { GrepTool } from "./grep";
|
|
@@ -70,6 +73,8 @@ export { AskTool, type AskToolDetails } from "./ask";
|
|
|
70
73
|
export { BashTool, type BashToolDetails, type BashToolOptions } from "./bash";
|
|
71
74
|
export { CalculatorTool, type CalculatorToolDetails } from "./calculator";
|
|
72
75
|
export { CompleteTool } from "./complete";
|
|
76
|
+
export { type EnterPlanModeDetails, EnterPlanModeTool } from "./enter-plan-mode";
|
|
77
|
+
export { type ExitPlanModeDetails, ExitPlanModeTool } from "./exit-plan-mode";
|
|
73
78
|
export { FetchTool, type FetchToolDetails } from "./fetch";
|
|
74
79
|
export { type FindOperations, FindTool, type FindToolDetails, type FindToolOptions } from "./find";
|
|
75
80
|
export { setPreferredImageProvider } from "./gemini-image";
|
|
@@ -126,6 +131,8 @@ export interface ToolSession {
|
|
|
126
131
|
requireCompleteTool?: boolean;
|
|
127
132
|
/** Get session file */
|
|
128
133
|
getSessionFile: () => string | null;
|
|
134
|
+
/** Get session ID */
|
|
135
|
+
getSessionId?: () => string | null;
|
|
129
136
|
/** Cached artifact manager (allocated per ToolSession) */
|
|
130
137
|
artifactManager?: ArtifactManager;
|
|
131
138
|
/** Get artifacts directory for artifact:// URLs and $ARTIFACTS env var */
|
|
@@ -147,7 +154,10 @@ export interface ToolSession {
|
|
|
147
154
|
/** Agent output manager for unique agent:// IDs across task invocations */
|
|
148
155
|
agentOutputManager?: AgentOutputManager;
|
|
149
156
|
/** Settings manager for passing to subagents (avoids SQLite access in workers) */
|
|
150
|
-
settingsManager?: {
|
|
157
|
+
settingsManager?: {
|
|
158
|
+
serialize: () => import("@oh-my-pi/pi-coding-agent/config/settings-manager").Settings;
|
|
159
|
+
getPlansDirectory: (cwd?: string) => string;
|
|
160
|
+
};
|
|
151
161
|
/** Settings manager (optional) */
|
|
152
162
|
settings?: {
|
|
153
163
|
getImageAutoResize(): boolean;
|
|
@@ -165,6 +175,8 @@ export interface ToolSession {
|
|
|
165
175
|
getPythonKernelMode?(): "session" | "per-call";
|
|
166
176
|
getPythonSharedGateway?(): boolean;
|
|
167
177
|
};
|
|
178
|
+
/** Plan mode state (if active) */
|
|
179
|
+
getPlanModeState?: () => PlanModeState | undefined;
|
|
168
180
|
}
|
|
169
181
|
|
|
170
182
|
type ToolFactory = (session: ToolSession) => Tool | null | Promise<Tool | null>;
|
|
@@ -187,11 +199,13 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
|
187
199
|
fetch: s => new FetchTool(s),
|
|
188
200
|
web_search: s => new WebSearchTool(s),
|
|
189
201
|
write: s => new WriteTool(s),
|
|
202
|
+
enter_plan_mode: s => new EnterPlanModeTool(s),
|
|
190
203
|
};
|
|
191
204
|
|
|
192
205
|
export const HIDDEN_TOOLS: Record<string, ToolFactory> = {
|
|
193
206
|
complete: s => new CompleteTool(s),
|
|
194
207
|
report_finding: () => reportFindingTool,
|
|
208
|
+
exit_plan_mode: s => new ExitPlanModeTool(s),
|
|
195
209
|
};
|
|
196
210
|
|
|
197
211
|
export type ToolName = keyof typeof BUILTIN_TOOLS;
|
|
@@ -234,6 +248,9 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
234
248
|
const includeComplete = session.requireCompleteTool === true;
|
|
235
249
|
const enableLsp = session.enableLsp ?? true;
|
|
236
250
|
const requestedTools = toolNames && toolNames.length > 0 ? [...new Set(toolNames)] : undefined;
|
|
251
|
+
if (requestedTools && !requestedTools.includes("exit_plan_mode")) {
|
|
252
|
+
requestedTools.push("exit_plan_mode");
|
|
253
|
+
}
|
|
237
254
|
const pythonMode = getPythonModeFromEnv() ?? session.settings?.getPythonToolMode?.() ?? "ipy-only";
|
|
238
255
|
const skipPythonPreflight = session.skipPythonPreflight === true;
|
|
239
256
|
let pythonAvailable = true;
|
|
@@ -296,6 +313,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
296
313
|
: [
|
|
297
314
|
...Object.entries(BUILTIN_TOOLS).filter(([name]) => isToolAllowed(name)),
|
|
298
315
|
...(includeComplete ? ([["complete", HIDDEN_TOOLS.complete]] as const) : []),
|
|
316
|
+
...([["exit_plan_mode", HIDDEN_TOOLS.exit_plan_mode]] as const),
|
|
299
317
|
];
|
|
300
318
|
time("createTools:beforeFactories");
|
|
301
319
|
const slowTools: Array<{ name: string; ms: number }> = [];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { resolvePlanUrlToPath } from "@oh-my-pi/pi-coding-agent/internal-urls";
|
|
2
|
+
import type { ToolSession } from ".";
|
|
3
|
+
import { resolveToCwd } from "./path-utils";
|
|
4
|
+
import { ToolError } from "./tool-errors";
|
|
5
|
+
|
|
6
|
+
const PLAN_URL_PREFIX = "plan://";
|
|
7
|
+
|
|
8
|
+
export function resolvePlanPath(session: ToolSession, targetPath: string): string {
|
|
9
|
+
if (!targetPath.startsWith(PLAN_URL_PREFIX)) {
|
|
10
|
+
return resolveToCwd(targetPath, session.cwd);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const settingsManager = session.settingsManager;
|
|
14
|
+
if (!settingsManager) {
|
|
15
|
+
throw new ToolError("Plan mode: settings manager unavailable for plan path resolution.");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return resolvePlanUrlToPath(targetPath, {
|
|
19
|
+
getPlansDirectory: settingsManager.getPlansDirectory.bind(settingsManager),
|
|
20
|
+
cwd: session.cwd,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function enforcePlanModeWrite(
|
|
25
|
+
session: ToolSession,
|
|
26
|
+
targetPath: string,
|
|
27
|
+
options?: { rename?: string; op?: "create" | "update" | "delete" },
|
|
28
|
+
): void {
|
|
29
|
+
const state = session.getPlanModeState?.();
|
|
30
|
+
if (!state?.enabled) return;
|
|
31
|
+
|
|
32
|
+
const resolvedTarget = resolvePlanPath(session, targetPath);
|
|
33
|
+
const resolvedPlan = resolvePlanPath(session, state.planFilePath);
|
|
34
|
+
|
|
35
|
+
if (options?.rename) {
|
|
36
|
+
throw new ToolError("Plan mode: renaming files is not allowed.");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (options?.op === "delete") {
|
|
40
|
+
throw new ToolError("Plan mode: deleting files is not allowed.");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (resolvedTarget !== resolvedPlan) {
|
|
44
|
+
throw new ToolError(`Plan mode: only the plan file may be modified (${state.planFilePath}).`);
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/tools/read.ts
CHANGED
|
@@ -162,10 +162,11 @@ function similarityScore(a: string, b: string): number {
|
|
|
162
162
|
async function listCandidateFiles(
|
|
163
163
|
searchRoot: string,
|
|
164
164
|
signal?: AbortSignal,
|
|
165
|
+
notify?: (message: string) => void,
|
|
165
166
|
): Promise<{ files: string[]; truncated: boolean; error?: string }> {
|
|
166
167
|
let fdPath: string | undefined;
|
|
167
168
|
try {
|
|
168
|
-
fdPath = await ensureTool("fd", true);
|
|
169
|
+
fdPath = await ensureTool("fd", { silent: true, notify });
|
|
169
170
|
} catch {
|
|
170
171
|
return { files: [], truncated: false, error: "fd not available" };
|
|
171
172
|
}
|
|
@@ -248,6 +249,7 @@ async function findReadPathSuggestions(
|
|
|
248
249
|
rawPath: string,
|
|
249
250
|
cwd: string,
|
|
250
251
|
signal?: AbortSignal,
|
|
252
|
+
notify?: (message: string) => void,
|
|
251
253
|
): Promise<{ suggestions: string[]; scopeLabel?: string; truncated?: boolean; error?: string } | null> {
|
|
252
254
|
const resolvedPath = resolveToCwd(rawPath, cwd);
|
|
253
255
|
const searchRoot = await findExistingDirectory(path.dirname(resolvedPath), signal);
|
|
@@ -262,7 +264,7 @@ async function findReadPathSuggestions(
|
|
|
262
264
|
}
|
|
263
265
|
}
|
|
264
266
|
|
|
265
|
-
const { files, truncated, error } = await listCandidateFiles(searchRoot, signal);
|
|
267
|
+
const { files, truncated, error } = await listCandidateFiles(searchRoot, signal, notify);
|
|
266
268
|
const scopeLabel = formatScopeLabel(searchRoot, cwd);
|
|
267
269
|
|
|
268
270
|
if (error && files.length === 0) {
|
|
@@ -418,7 +420,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
418
420
|
params: ReadParams,
|
|
419
421
|
signal?: AbortSignal,
|
|
420
422
|
_onUpdate?: AgentToolUpdateCallback<ReadToolDetails>,
|
|
421
|
-
|
|
423
|
+
toolContext?: AgentToolContext,
|
|
422
424
|
): Promise<AgentToolResult<ReadToolDetails>> {
|
|
423
425
|
const { path: readPath, offset, limit, lines } = params;
|
|
424
426
|
|
|
@@ -442,7 +444,9 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
442
444
|
|
|
443
445
|
// Skip fuzzy matching for remote mounts (sshfs) to avoid hangs
|
|
444
446
|
if (!isRemoteMountPath(absolutePath)) {
|
|
445
|
-
const suggestions = await findReadPathSuggestions(readPath, this.session.cwd, signal
|
|
447
|
+
const suggestions = await findReadPathSuggestions(readPath, this.session.cwd, signal, message =>
|
|
448
|
+
toolContext?.ui?.notify(message, "info"),
|
|
449
|
+
);
|
|
446
450
|
|
|
447
451
|
if (suggestions?.suggestions.length) {
|
|
448
452
|
const scopeLabel = suggestions.scopeLabel ? ` in ${suggestions.scopeLabel}` : "";
|
package/src/tools/write.ts
CHANGED
|
@@ -17,7 +17,7 @@ import writeDescription from "../prompts/tools/write.md" with { type: "text" };
|
|
|
17
17
|
import type { ToolSession } from "../sdk";
|
|
18
18
|
import { renderStatusLine } from "../tui";
|
|
19
19
|
import { type OutputMeta, outputMeta } from "./output-meta";
|
|
20
|
-
import {
|
|
20
|
+
import { enforcePlanModeWrite, resolvePlanPath } from "./plan-mode-guard";
|
|
21
21
|
import {
|
|
22
22
|
formatDiagnostics,
|
|
23
23
|
formatExpandHint,
|
|
@@ -94,7 +94,8 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
94
94
|
context?: AgentToolContext,
|
|
95
95
|
): Promise<AgentToolResult<WriteToolDetails>> {
|
|
96
96
|
return untilAborted(signal, async () => {
|
|
97
|
-
|
|
97
|
+
enforcePlanModeWrite(this.session, path, { op: "create" });
|
|
98
|
+
const absolutePath = resolvePlanPath(this.session, path);
|
|
98
99
|
const batchRequest = getLspBatchRequest(context?.toolCall);
|
|
99
100
|
|
|
100
101
|
const diagnostics = await this.writethrough(absolutePath, content, signal, undefined, batchRequest);
|
|
@@ -6,6 +6,7 @@ import { $ } from "bun";
|
|
|
6
6
|
import { APP_NAME, getBinDir } from "../config";
|
|
7
7
|
|
|
8
8
|
const TOOLS_DIR = getBinDir();
|
|
9
|
+
const TOOL_DOWNLOAD_TIMEOUT_MS = 15000;
|
|
9
10
|
|
|
10
11
|
interface ToolConfig {
|
|
11
12
|
name: string;
|
|
@@ -159,9 +160,18 @@ export async function getToolPath(tool: ToolName): Promise<string | null> {
|
|
|
159
160
|
|
|
160
161
|
// Fetch latest release version from GitHub
|
|
161
162
|
async function getLatestVersion(repo: string): Promise<string> {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
let response: Response;
|
|
164
|
+
try {
|
|
165
|
+
response = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {
|
|
166
|
+
headers: { "User-Agent": `${APP_NAME}-coding-agent` },
|
|
167
|
+
signal: AbortSignal.timeout(TOOL_DOWNLOAD_TIMEOUT_MS),
|
|
168
|
+
});
|
|
169
|
+
} catch (err) {
|
|
170
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
171
|
+
throw new Error("GitHub API request timed out");
|
|
172
|
+
}
|
|
173
|
+
throw err;
|
|
174
|
+
}
|
|
165
175
|
|
|
166
176
|
if (!response.ok) {
|
|
167
177
|
throw new Error(`GitHub API error: ${response.status}`);
|
|
@@ -173,7 +183,17 @@ async function getLatestVersion(repo: string): Promise<string> {
|
|
|
173
183
|
|
|
174
184
|
// Download a file from URL
|
|
175
185
|
async function downloadFile(url: string, dest: string): Promise<void> {
|
|
176
|
-
|
|
186
|
+
let response: Response;
|
|
187
|
+
try {
|
|
188
|
+
response = await fetch(url, {
|
|
189
|
+
signal: AbortSignal.timeout(TOOL_DOWNLOAD_TIMEOUT_MS),
|
|
190
|
+
});
|
|
191
|
+
} catch (err) {
|
|
192
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
193
|
+
throw new Error(`Download timed out: ${url}`);
|
|
194
|
+
}
|
|
195
|
+
throw err;
|
|
196
|
+
}
|
|
177
197
|
if (!response.ok) {
|
|
178
198
|
throw new Error(`Failed to download: ${response.status}`);
|
|
179
199
|
} else if (!response.body) {
|
|
@@ -223,15 +243,12 @@ async function downloadTool(tool: ToolName): Promise<string> {
|
|
|
223
243
|
const tmp = await TempDir.create("@omp-tools-extract-");
|
|
224
244
|
|
|
225
245
|
try {
|
|
226
|
-
if (assetName.endsWith(".tar.gz")) {
|
|
246
|
+
if (assetName.endsWith(".tar.gz") || assetName.endsWith(".zip")) {
|
|
227
247
|
const archive = new Bun.Archive(await Bun.file(archivePath).arrayBuffer());
|
|
228
248
|
const files = await archive.files();
|
|
229
249
|
for (const [filePath, file] of files) {
|
|
230
250
|
await Bun.write(path.join(tmp.path(), filePath), file);
|
|
231
251
|
}
|
|
232
|
-
} else if (assetName.endsWith(".zip")) {
|
|
233
|
-
await fs.mkdir(tmp.path(), { recursive: true });
|
|
234
|
-
await $`unzip -o ${archivePath} -d ${tmp.path()}`.quiet().nothrow();
|
|
235
252
|
}
|
|
236
253
|
|
|
237
254
|
// Find the binary in extracted files
|
|
@@ -284,7 +301,17 @@ async function installPythonPackage(pkg: string): Promise<boolean> {
|
|
|
284
301
|
|
|
285
302
|
// Ensure a tool is available, downloading if necessary
|
|
286
303
|
// Returns the path to the tool, or null if unavailable
|
|
287
|
-
|
|
304
|
+
type EnsureToolOptions = {
|
|
305
|
+
silent?: boolean;
|
|
306
|
+
notify?: (message: string) => void;
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
export async function ensureTool(
|
|
310
|
+
tool: ToolName,
|
|
311
|
+
silentOrOptions: boolean | EnsureToolOptions = false,
|
|
312
|
+
): Promise<string | undefined> {
|
|
313
|
+
const options = typeof silentOrOptions === "object" ? silentOrOptions : { silent: silentOrOptions };
|
|
314
|
+
const silent = options.silent ?? false;
|
|
288
315
|
const existingPath = await getToolPath(tool);
|
|
289
316
|
if (existingPath) {
|
|
290
317
|
return existingPath;
|
|
@@ -296,6 +323,7 @@ export async function ensureTool(tool: ToolName, silent: boolean = false): Promi
|
|
|
296
323
|
if (!silent) {
|
|
297
324
|
logger.debug(`${pythonConfig.name} not found. Installing via uv/pip...`);
|
|
298
325
|
}
|
|
326
|
+
options.notify?.(`Installing ${pythonConfig.name}...`);
|
|
299
327
|
const success = await installPythonPackage(pythonConfig.package);
|
|
300
328
|
if (success) {
|
|
301
329
|
// Re-check for the command after installation
|
|
@@ -320,6 +348,7 @@ export async function ensureTool(tool: ToolName, silent: boolean = false): Promi
|
|
|
320
348
|
if (!silent) {
|
|
321
349
|
logger.debug(`${config.name} not found. Downloading...`);
|
|
322
350
|
}
|
|
351
|
+
options.notify?.(`Downloading ${config.name}...`);
|
|
323
352
|
|
|
324
353
|
try {
|
|
325
354
|
const path = await downloadTool(tool);
|
|
@@ -169,7 +169,9 @@ export async function searchPerplexity(params: PerplexitySearchParams): Promise<
|
|
|
169
169
|
model: "sonar-pro",
|
|
170
170
|
messages,
|
|
171
171
|
return_related_questions: true,
|
|
172
|
-
|
|
172
|
+
web_search_options: {
|
|
173
|
+
search_context_size: "high",
|
|
174
|
+
},
|
|
173
175
|
};
|
|
174
176
|
|
|
175
177
|
if (params.search_recency_filter) {
|
package/src/web/search/types.ts
CHANGED
|
@@ -163,7 +163,9 @@ export interface PerplexityRequest {
|
|
|
163
163
|
search_recency_filter?: "day" | "week" | "month" | "year";
|
|
164
164
|
return_images?: boolean;
|
|
165
165
|
return_related_questions?: boolean;
|
|
166
|
-
|
|
166
|
+
web_search_options?: {
|
|
167
|
+
search_context_size?: "low" | "medium" | "high";
|
|
168
|
+
};
|
|
167
169
|
}
|
|
168
170
|
|
|
169
171
|
export interface PerplexitySearchResult {
|