@oh-my-pi/pi-coding-agent 13.3.13 → 13.4.0
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 +97 -7
- package/examples/sdk/README.md +22 -0
- package/package.json +7 -7
- package/src/capability/index.ts +1 -11
- package/src/commit/analysis/index.ts +4 -4
- package/src/config/settings-schema.ts +18 -15
- package/src/config/settings.ts +2 -20
- package/src/discovery/index.ts +1 -11
- package/src/exa/index.ts +1 -10
- package/src/extensibility/custom-commands/index.ts +2 -15
- package/src/extensibility/custom-tools/index.ts +3 -18
- package/src/extensibility/custom-tools/loader.ts +28 -5
- package/src/extensibility/custom-tools/types.ts +18 -1
- package/src/extensibility/extensions/index.ts +9 -130
- package/src/extensibility/extensions/types.ts +2 -1
- package/src/extensibility/hooks/index.ts +3 -14
- package/src/extensibility/plugins/index.ts +6 -31
- package/src/index.ts +28 -220
- package/src/internal-urls/docs-index.generated.ts +3 -2
- package/src/internal-urls/index.ts +11 -16
- package/src/mcp/index.ts +11 -37
- package/src/mcp/tool-bridge.ts +3 -42
- package/src/mcp/transports/index.ts +2 -2
- package/src/modes/components/extensions/index.ts +3 -3
- package/src/modes/components/index.ts +35 -40
- package/src/modes/interactive-mode.ts +4 -1
- package/src/modes/rpc/rpc-mode.ts +1 -7
- package/src/modes/theme/theme.ts +11 -10
- package/src/modes/types.ts +1 -1
- package/src/patch/index.ts +4 -20
- package/src/prompts/system/system-prompt.md +18 -4
- package/src/prompts/tools/ast-edit.md +33 -0
- package/src/prompts/tools/ast-grep.md +34 -0
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/hashline.md +1 -0
- package/src/prompts/tools/resolve.md +8 -0
- package/src/sdk.ts +27 -7
- package/src/session/agent-session.ts +25 -36
- package/src/session/session-manager.ts +0 -30
- package/src/slash-commands/builtin-registry.ts +4 -2
- package/src/stt/index.ts +3 -3
- package/src/task/types.ts +2 -2
- package/src/tools/ast-edit.ts +480 -0
- package/src/tools/ast-grep.ts +435 -0
- package/src/tools/bash.ts +3 -2
- package/src/tools/gemini-image.ts +3 -3
- package/src/tools/grep.ts +26 -8
- package/src/tools/index.ts +55 -57
- package/src/tools/pending-action.ts +33 -0
- package/src/tools/render-utils.ts +10 -0
- package/src/tools/renderers.ts +6 -4
- package/src/tools/resolve.ts +156 -0
- package/src/tools/submit-result.ts +1 -1
- package/src/web/search/index.ts +6 -4
- package/src/web/search/providers/anthropic.ts +2 -2
- package/src/web/search/providers/base.ts +3 -0
- package/src/web/search/providers/exa.ts +11 -5
- package/src/web/search/providers/gemini.ts +112 -24
- package/src/patch/normative.ts +0 -72
- package/src/prompts/tools/ast-find.md +0 -20
- package/src/prompts/tools/ast-replace.md +0 -21
- package/src/tools/ast-find.ts +0 -316
- package/src/tools/ast-replace.ts +0 -294
package/src/tools/index.ts
CHANGED
|
@@ -15,8 +15,8 @@ import type { AgentOutputManager } from "../task/output-manager";
|
|
|
15
15
|
import type { EventBus } from "../utils/event-bus";
|
|
16
16
|
import { SearchTool } from "../web/search";
|
|
17
17
|
import { AskTool } from "./ask";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
18
|
+
import { AstEditTool } from "./ast-edit";
|
|
19
|
+
import { AstGrepTool } from "./ast-grep";
|
|
20
20
|
import { AwaitTool } from "./await-tool";
|
|
21
21
|
import { BashTool } from "./bash";
|
|
22
22
|
import { BrowserTool } from "./browser";
|
|
@@ -30,6 +30,7 @@ import { NotebookTool } from "./notebook";
|
|
|
30
30
|
import { wrapToolWithMetaNotice } from "./output-meta";
|
|
31
31
|
import { PythonTool } from "./python";
|
|
32
32
|
import { ReadTool } from "./read";
|
|
33
|
+
import { ResolveTool } from "./resolve";
|
|
33
34
|
import { reportFindingTool } from "./review";
|
|
34
35
|
import { loadSshTool } from "./ssh";
|
|
35
36
|
import { SubmitResultTool } from "./submit-result";
|
|
@@ -38,50 +39,36 @@ import { WriteTool } from "./write";
|
|
|
38
39
|
|
|
39
40
|
// Exa MCP tools (22 tools)
|
|
40
41
|
|
|
41
|
-
export
|
|
42
|
-
export type
|
|
43
|
-
export
|
|
44
|
-
|
|
45
|
-
type FileFormatResult,
|
|
46
|
-
getLspStatus,
|
|
47
|
-
type LspServerStatus,
|
|
48
|
-
LspTool,
|
|
49
|
-
type LspToolDetails,
|
|
50
|
-
type LspWarmupOptions,
|
|
51
|
-
type LspWarmupResult,
|
|
52
|
-
warmupLspServers,
|
|
53
|
-
} from "../lsp";
|
|
54
|
-
export { EditTool, type EditToolDetails } from "../patch";
|
|
42
|
+
export * from "../exa";
|
|
43
|
+
export type * from "../exa/types";
|
|
44
|
+
export * from "../lsp";
|
|
45
|
+
export * from "../patch";
|
|
55
46
|
export * from "../session/streaming-output";
|
|
56
|
-
export
|
|
47
|
+
export * from "../task";
|
|
57
48
|
export * from "../web/search";
|
|
58
|
-
export
|
|
59
|
-
export
|
|
60
|
-
export
|
|
61
|
-
export
|
|
62
|
-
export
|
|
63
|
-
export
|
|
64
|
-
export
|
|
65
|
-
export
|
|
66
|
-
export
|
|
67
|
-
export
|
|
68
|
-
export
|
|
69
|
-
export
|
|
70
|
-
export
|
|
71
|
-
export
|
|
72
|
-
export
|
|
73
|
-
export
|
|
74
|
-
export
|
|
75
|
-
export
|
|
76
|
-
export
|
|
77
|
-
export
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
TodoWriteTool,
|
|
82
|
-
type TodoWriteToolDetails,
|
|
83
|
-
} from "./todo-write";
|
|
84
|
-
export { WriteTool, type WriteToolDetails, type WriteToolInput } from "./write";
|
|
49
|
+
export * from "./ask";
|
|
50
|
+
export * from "./ast-edit";
|
|
51
|
+
export * from "./ast-grep";
|
|
52
|
+
export * from "./await-tool";
|
|
53
|
+
export * from "./bash";
|
|
54
|
+
export * from "./browser";
|
|
55
|
+
export * from "./calculator";
|
|
56
|
+
export * from "./cancel-job";
|
|
57
|
+
export * from "./exit-plan-mode";
|
|
58
|
+
export * from "./fetch";
|
|
59
|
+
export * from "./find";
|
|
60
|
+
export * from "./gemini-image";
|
|
61
|
+
export * from "./grep";
|
|
62
|
+
export * from "./notebook";
|
|
63
|
+
export * from "./pending-action";
|
|
64
|
+
export * from "./python";
|
|
65
|
+
export * from "./read";
|
|
66
|
+
export * from "./resolve";
|
|
67
|
+
export * from "./review";
|
|
68
|
+
export * from "./ssh";
|
|
69
|
+
export * from "./submit-result";
|
|
70
|
+
export * from "./todo-write";
|
|
71
|
+
export * from "./write";
|
|
85
72
|
|
|
86
73
|
/** Tool type (AgentTool from pi-ai) */
|
|
87
74
|
export type Tool = AgentTool<any, any, any>;
|
|
@@ -154,13 +141,15 @@ export interface ToolSession {
|
|
|
154
141
|
getTodoPhases?: () => TodoPhase[];
|
|
155
142
|
/** Replace cached todo phases for this session. */
|
|
156
143
|
setTodoPhases?: (phases: TodoPhase[]) => void;
|
|
144
|
+
/** Pending action store for preview/apply workflows */
|
|
145
|
+
pendingActionStore?: import("./pending-action").PendingActionStore;
|
|
157
146
|
}
|
|
158
147
|
|
|
159
148
|
type ToolFactory = (session: ToolSession) => Tool | null | Promise<Tool | null>;
|
|
160
149
|
|
|
161
150
|
export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
162
|
-
|
|
163
|
-
|
|
151
|
+
ast_grep: s => new AstGrepTool(s),
|
|
152
|
+
ast_edit: s => new AstEditTool(s),
|
|
164
153
|
ask: AskTool.createIf,
|
|
165
154
|
bash: s => new BashTool(s),
|
|
166
155
|
python: s => new PythonTool(s),
|
|
@@ -186,6 +175,7 @@ export const HIDDEN_TOOLS: Record<string, ToolFactory> = {
|
|
|
186
175
|
submit_result: s => new SubmitResultTool(s),
|
|
187
176
|
report_finding: () => reportFindingTool,
|
|
188
177
|
exit_plan_mode: s => new ExitPlanModeTool(s),
|
|
178
|
+
resolve: s => new ResolveTool(s),
|
|
189
179
|
};
|
|
190
180
|
|
|
191
181
|
export type ToolName = keyof typeof BUILTIN_TOOLS;
|
|
@@ -289,8 +279,8 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
289
279
|
if (name === "todo_write") return !includeSubmitResult && session.settings.get("todo.enabled");
|
|
290
280
|
if (name === "find") return session.settings.get("find.enabled");
|
|
291
281
|
if (name === "grep") return session.settings.get("grep.enabled");
|
|
292
|
-
if (name === "
|
|
293
|
-
if (name === "
|
|
282
|
+
if (name === "ast_grep") return session.settings.get("astGrep.enabled");
|
|
283
|
+
if (name === "ast_edit") return session.settings.get("astEdit.enabled");
|
|
294
284
|
if (name === "notebook") return session.settings.get("notebook.enabled");
|
|
295
285
|
if (name === "fetch") return session.settings.get("fetch.enabled");
|
|
296
286
|
if (name === "web_search") return session.settings.get("web_search.enabled");
|
|
@@ -309,24 +299,32 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
309
299
|
}
|
|
310
300
|
|
|
311
301
|
const filteredRequestedTools = requestedTools?.filter(name => name in allTools && isToolAllowed(name));
|
|
312
|
-
|
|
313
|
-
const entries =
|
|
302
|
+
const baseEntries =
|
|
314
303
|
filteredRequestedTools !== undefined
|
|
315
|
-
? filteredRequestedTools.map(name => [name, allTools[name]] as const)
|
|
304
|
+
? filteredRequestedTools.filter(name => name !== "resolve").map(name => [name, allTools[name]] as const)
|
|
316
305
|
: [
|
|
317
306
|
...Object.entries(BUILTIN_TOOLS).filter(([name]) => isToolAllowed(name)),
|
|
318
307
|
...(includeSubmitResult ? ([["submit_result", HIDDEN_TOOLS.submit_result]] as const) : []),
|
|
319
308
|
...([["exit_plan_mode", HIDDEN_TOOLS.exit_plan_mode]] as const),
|
|
320
309
|
];
|
|
321
310
|
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
if (filteredRequestedTools && !filteredRequestedTools.includes(name)) {
|
|
325
|
-
return null;
|
|
326
|
-
}
|
|
311
|
+
const baseResults = await Promise.all(
|
|
312
|
+
baseEntries.map(async ([name, factory]) => {
|
|
327
313
|
const tool = await logger.timeAsync(`createTools:${name}`, factory, session);
|
|
328
314
|
return tool ? wrapToolWithMetaNotice(tool) : null;
|
|
329
315
|
}),
|
|
330
316
|
);
|
|
331
|
-
|
|
317
|
+
const tools = baseResults.filter((r): r is Tool => r !== null);
|
|
318
|
+
const hasDeferrableTools = tools.some(tool => tool.deferrable === true);
|
|
319
|
+
if (!hasDeferrableTools) {
|
|
320
|
+
return tools;
|
|
321
|
+
}
|
|
322
|
+
if (tools.some(tool => tool.name === "resolve")) {
|
|
323
|
+
return tools;
|
|
324
|
+
}
|
|
325
|
+
const resolveTool = await logger.timeAsync("createTools:resolve", HIDDEN_TOOLS.resolve, session);
|
|
326
|
+
if (resolveTool) {
|
|
327
|
+
tools.push(wrapToolWithMetaNotice(resolveTool));
|
|
328
|
+
}
|
|
329
|
+
return tools;
|
|
332
330
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
|
|
3
|
+
export interface PendingAction {
|
|
4
|
+
label: string;
|
|
5
|
+
sourceToolName: string;
|
|
6
|
+
apply(reason: string): Promise<AgentToolResult<unknown>>;
|
|
7
|
+
reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>;
|
|
8
|
+
details?: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class PendingActionStore {
|
|
12
|
+
#actions: PendingAction[] = [];
|
|
13
|
+
|
|
14
|
+
push(action: PendingAction): void {
|
|
15
|
+
this.#actions.push(action);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
peek(): PendingAction | null {
|
|
19
|
+
return this.#actions.at(-1) ?? null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
pop(): PendingAction | null {
|
|
23
|
+
return this.#actions.pop() ?? null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
clear(): void {
|
|
27
|
+
this.#actions = [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get hasPending(): boolean {
|
|
31
|
+
return this.#actions.length > 0;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -530,3 +530,13 @@ export function shortenPath(filePath: string, homeDir?: string): string {
|
|
|
530
530
|
export function wrapBrackets(text: string, theme: Theme): string {
|
|
531
531
|
return `${theme.format.bracketLeft}${text}${theme.format.bracketRight}`;
|
|
532
532
|
}
|
|
533
|
+
|
|
534
|
+
export const PARSE_ERRORS_LIMIT = 20;
|
|
535
|
+
|
|
536
|
+
export function formatParseErrors(errors: string[]): string[] {
|
|
537
|
+
if (errors.length === 0) return [];
|
|
538
|
+
const capped = errors.slice(0, PARSE_ERRORS_LIMIT);
|
|
539
|
+
const header =
|
|
540
|
+
errors.length > PARSE_ERRORS_LIMIT ? `Parse issues (${PARSE_ERRORS_LIMIT} / ${errors.length}):` : "Parse issues:";
|
|
541
|
+
return [header, ...capped.map(err => `- ${err}`)];
|
|
542
|
+
}
|
package/src/tools/renderers.ts
CHANGED
|
@@ -11,8 +11,8 @@ import { editToolRenderer } from "../patch";
|
|
|
11
11
|
import { taskToolRenderer } from "../task/render";
|
|
12
12
|
import { webSearchToolRenderer } from "../web/search/render";
|
|
13
13
|
import { askToolRenderer } from "./ask";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
14
|
+
import { astEditToolRenderer } from "./ast-edit";
|
|
15
|
+
import { astGrepToolRenderer } from "./ast-grep";
|
|
16
16
|
import { bashToolRenderer } from "./bash";
|
|
17
17
|
import { calculatorToolRenderer } from "./calculator";
|
|
18
18
|
import { fetchToolRenderer } from "./fetch";
|
|
@@ -21,6 +21,7 @@ import { grepToolRenderer } from "./grep";
|
|
|
21
21
|
import { notebookToolRenderer } from "./notebook";
|
|
22
22
|
import { pythonToolRenderer } from "./python";
|
|
23
23
|
import { readToolRenderer } from "./read";
|
|
24
|
+
import { resolveToolRenderer } from "./resolve";
|
|
24
25
|
import { sshToolRenderer } from "./ssh";
|
|
25
26
|
import { todoWriteToolRenderer } from "./todo-write";
|
|
26
27
|
import { writeToolRenderer } from "./write";
|
|
@@ -40,8 +41,8 @@ type ToolRenderer = {
|
|
|
40
41
|
|
|
41
42
|
export const toolRenderers: Record<string, ToolRenderer> = {
|
|
42
43
|
ask: askToolRenderer as ToolRenderer,
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
ast_grep: astGrepToolRenderer as ToolRenderer,
|
|
45
|
+
ast_edit: astEditToolRenderer as ToolRenderer,
|
|
45
46
|
bash: bashToolRenderer as ToolRenderer,
|
|
46
47
|
python: pythonToolRenderer as ToolRenderer,
|
|
47
48
|
calc: calculatorToolRenderer as ToolRenderer,
|
|
@@ -51,6 +52,7 @@ export const toolRenderers: Record<string, ToolRenderer> = {
|
|
|
51
52
|
lsp: lspToolRenderer as ToolRenderer,
|
|
52
53
|
notebook: notebookToolRenderer as ToolRenderer,
|
|
53
54
|
read: readToolRenderer as ToolRenderer,
|
|
55
|
+
resolve: resolveToolRenderer as ToolRenderer,
|
|
54
56
|
ssh: sshToolRenderer as ToolRenderer,
|
|
55
57
|
task: taskToolRenderer as ToolRenderer,
|
|
56
58
|
todo_write: todoWriteToolRenderer as ToolRenderer,
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import type { Component } from "@oh-my-pi/pi-tui";
|
|
3
|
+
import { Text } from "@oh-my-pi/pi-tui";
|
|
4
|
+
import { untilAborted } from "@oh-my-pi/pi-utils";
|
|
5
|
+
import { type Static, Type } from "@sinclair/typebox";
|
|
6
|
+
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
7
|
+
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
8
|
+
import type { Theme } from "../modes/theme/theme";
|
|
9
|
+
import resolveDescription from "../prompts/tools/resolve.md" with { type: "text" };
|
|
10
|
+
import { Ellipsis, padToWidth, renderStatusLine, truncateToWidth } from "../tui";
|
|
11
|
+
import type { ToolSession } from ".";
|
|
12
|
+
import { replaceTabs } from "./render-utils";
|
|
13
|
+
import { ToolError } from "./tool-errors";
|
|
14
|
+
import { toolResult } from "./tool-result";
|
|
15
|
+
|
|
16
|
+
const resolveSchema = Type.Object({
|
|
17
|
+
action: Type.Union([Type.Literal("apply"), Type.Literal("discard")]),
|
|
18
|
+
reason: Type.String({ description: "Why you're applying or discarding" }),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
type ResolveParams = Static<typeof resolveSchema>;
|
|
22
|
+
|
|
23
|
+
export interface ResolveToolDetails {
|
|
24
|
+
action: "apply" | "discard";
|
|
25
|
+
reason: string;
|
|
26
|
+
sourceToolName?: string;
|
|
27
|
+
label?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function resolveReasonPreview(reason?: string): string | undefined {
|
|
31
|
+
const trimmed = reason?.trim();
|
|
32
|
+
if (!trimmed) return undefined;
|
|
33
|
+
return truncateToWidth(trimmed, 72, Ellipsis.Omit);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class ResolveTool implements AgentTool<typeof resolveSchema, ResolveToolDetails> {
|
|
37
|
+
readonly name = "resolve";
|
|
38
|
+
readonly label = "Resolve";
|
|
39
|
+
readonly hidden = true;
|
|
40
|
+
readonly description: string;
|
|
41
|
+
readonly parameters = resolveSchema;
|
|
42
|
+
readonly strict = true;
|
|
43
|
+
|
|
44
|
+
constructor(private readonly session: ToolSession) {
|
|
45
|
+
this.description = renderPromptTemplate(resolveDescription);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async execute(
|
|
49
|
+
_toolCallId: string,
|
|
50
|
+
params: ResolveParams,
|
|
51
|
+
signal?: AbortSignal,
|
|
52
|
+
_onUpdate?: AgentToolUpdateCallback<ResolveToolDetails>,
|
|
53
|
+
_context?: AgentToolContext,
|
|
54
|
+
): Promise<AgentToolResult<ResolveToolDetails>> {
|
|
55
|
+
return untilAborted(signal, async () => {
|
|
56
|
+
const store = this.session.pendingActionStore;
|
|
57
|
+
if (!store || !store.hasPending) {
|
|
58
|
+
throw new ToolError("No pending action to resolve. Nothing to apply or discard.");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const pendingAction = store.pop();
|
|
62
|
+
if (!pendingAction) {
|
|
63
|
+
throw new ToolError("No pending action to resolve. Nothing to apply or discard.");
|
|
64
|
+
}
|
|
65
|
+
const resolveDetails: ResolveToolDetails = {
|
|
66
|
+
action: params.action,
|
|
67
|
+
reason: params.reason,
|
|
68
|
+
sourceToolName: pendingAction.sourceToolName,
|
|
69
|
+
label: pendingAction.label,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
if (params.action === "apply") {
|
|
73
|
+
const applyResult = await pendingAction.apply(params.reason);
|
|
74
|
+
const appliedText = applyResult.content
|
|
75
|
+
.filter(part => part.type === "text")
|
|
76
|
+
.map(part => part.text)
|
|
77
|
+
.filter(text => text != null && text.length > 0)
|
|
78
|
+
.join("\n");
|
|
79
|
+
const baseResult = toolResult()
|
|
80
|
+
.text(appliedText || `Applied: ${pendingAction.label}.`)
|
|
81
|
+
.done();
|
|
82
|
+
return { ...baseResult, details: resolveDetails };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (params.action === "discard" && pendingAction.reject != null) {
|
|
86
|
+
const discardResult = await pendingAction.reject(params.reason);
|
|
87
|
+
if (discardResult != null) {
|
|
88
|
+
return { ...discardResult, details: resolveDetails };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const discardResult = toolResult().text(`Discarded: ${pendingAction.label}. Reason: ${params.reason}`).done();
|
|
92
|
+
return { ...discardResult, details: resolveDetails };
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const resolveToolRenderer = {
|
|
98
|
+
renderCall(args: ResolveParams, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
99
|
+
const reason = resolveReasonPreview(args.reason);
|
|
100
|
+
const text = renderStatusLine(
|
|
101
|
+
{
|
|
102
|
+
icon: "pending",
|
|
103
|
+
title: "Resolve",
|
|
104
|
+
description: args.action,
|
|
105
|
+
badge: {
|
|
106
|
+
label: args.action === "apply" ? "proposed -> resolved" : "proposed -> rejected",
|
|
107
|
+
color: args.action === "apply" ? "success" : "warning",
|
|
108
|
+
},
|
|
109
|
+
meta: reason ? [uiTheme.fg("muted", reason)] : undefined,
|
|
110
|
+
},
|
|
111
|
+
uiTheme,
|
|
112
|
+
);
|
|
113
|
+
return new Text(text, 0, 0);
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
renderResult(
|
|
117
|
+
result: { content: Array<{ type: string; text?: string }>; details?: ResolveToolDetails; isError?: boolean },
|
|
118
|
+
_options: RenderResultOptions,
|
|
119
|
+
uiTheme: Theme,
|
|
120
|
+
): Component {
|
|
121
|
+
const details = result.details;
|
|
122
|
+
const label = replaceTabs(details?.label ?? "pending action");
|
|
123
|
+
const reason = replaceTabs(details?.reason?.trim() || "No reason provided");
|
|
124
|
+
const action = details?.action ?? "apply";
|
|
125
|
+
const isApply = action === "apply" && !result.isError;
|
|
126
|
+
const bgColor = result.isError ? "error" : isApply ? "success" : "warning";
|
|
127
|
+
const icon = isApply ? uiTheme.status.success : uiTheme.status.error;
|
|
128
|
+
const verb = isApply ? "Accept" : "Discard";
|
|
129
|
+
const separator = ": ";
|
|
130
|
+
const separatorIndex = label.indexOf(separator);
|
|
131
|
+
const sourceLabel = separatorIndex > 0 ? label.slice(0, separatorIndex).trim() : undefined;
|
|
132
|
+
const summaryLabel = separatorIndex > 0 ? label.slice(separatorIndex + separator.length).trim() : label;
|
|
133
|
+
const sourceBadge = sourceLabel
|
|
134
|
+
? uiTheme.bold(`${uiTheme.format.bracketLeft}${sourceLabel}${uiTheme.format.bracketRight}`)
|
|
135
|
+
: undefined;
|
|
136
|
+
const headerLine = `${icon} ${uiTheme.bold(`${verb}:`)} ${summaryLabel}${sourceBadge ? ` ${sourceBadge}` : ""}`;
|
|
137
|
+
const lines = ["", headerLine, "", uiTheme.italic(reason), ""];
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
render(width: number) {
|
|
141
|
+
const lineWidth = Math.max(3, width);
|
|
142
|
+
const innerWidth = Math.max(1, lineWidth - 2);
|
|
143
|
+
return lines.map(line => {
|
|
144
|
+
const truncated = truncateToWidth(line, innerWidth, Ellipsis.Omit);
|
|
145
|
+
const framed = ` ${padToWidth(truncated, innerWidth)} `;
|
|
146
|
+
const padded = padToWidth(framed, lineWidth);
|
|
147
|
+
return uiTheme.inverse(uiTheme.fg(bgColor, padded));
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
invalidate() {},
|
|
151
|
+
};
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
inline: true,
|
|
155
|
+
mergeCallAndResult: true,
|
|
156
|
+
};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Subagents must call this tool to finish and return structured JSON output.
|
|
5
5
|
*/
|
|
6
6
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
7
|
-
import { sanitizeSchemaForStrictMode } from "@oh-my-pi/pi-ai/utils/
|
|
7
|
+
import { sanitizeSchemaForStrictMode } from "@oh-my-pi/pi-ai/utils/schema";
|
|
8
8
|
import type { Static, TSchema } from "@sinclair/typebox";
|
|
9
9
|
import { Type } from "@sinclair/typebox";
|
|
10
10
|
import Ajv, { type ErrorObject, type ValidateFunction } from "ajv";
|
package/src/web/search/index.ts
CHANGED
|
@@ -46,6 +46,9 @@ export const webSearchSchema = Type.Object({
|
|
|
46
46
|
}),
|
|
47
47
|
),
|
|
48
48
|
limit: Type.Optional(Type.Number({ description: "Max results to return" })),
|
|
49
|
+
max_tokens: Type.Optional(Type.Number({ description: "Maximum output tokens" })),
|
|
50
|
+
temperature: Type.Optional(Type.Number({ description: "Sampling temperature" })),
|
|
51
|
+
num_search_results: Type.Optional(Type.Number({ description: "Number of search results to retrieve" })),
|
|
49
52
|
});
|
|
50
53
|
|
|
51
54
|
export type SearchParams = {
|
|
@@ -70,7 +73,7 @@ export type SearchParams = {
|
|
|
70
73
|
temperature?: number;
|
|
71
74
|
/** Number of search results to retrieve. Defaults to 10. */
|
|
72
75
|
num_search_results?: number;
|
|
73
|
-
/**
|
|
76
|
+
/** Deprecated CLI flag; explicit provider fallback now happens only when provider is unavailable. */
|
|
74
77
|
no_fallback?: boolean;
|
|
75
78
|
};
|
|
76
79
|
|
|
@@ -185,12 +188,11 @@ async function executeSearch(
|
|
|
185
188
|
params: SearchParams,
|
|
186
189
|
): Promise<{ content: Array<{ type: "text"; text: string }>; details: SearchRenderDetails }> {
|
|
187
190
|
const providers =
|
|
188
|
-
params.provider && params.provider !== "auto"
|
|
191
|
+
params.provider && params.provider !== "auto"
|
|
189
192
|
? (await getSearchProvider(params.provider).isAvailable())
|
|
190
193
|
? [getSearchProvider(params.provider)]
|
|
191
|
-
:
|
|
194
|
+
: await resolveProviderChain("auto")
|
|
192
195
|
: await resolveProviderChain(params.provider);
|
|
193
|
-
|
|
194
196
|
if (providers.length === 0) {
|
|
195
197
|
const message = "No web search provider configured.";
|
|
196
198
|
return {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import {
|
|
8
8
|
type AnthropicAuthConfig,
|
|
9
9
|
type AnthropicSystemBlock,
|
|
10
|
-
|
|
10
|
+
buildAnthropicSearchHeaders,
|
|
11
11
|
buildAnthropicSystemBlocks,
|
|
12
12
|
buildAnthropicUrl,
|
|
13
13
|
findAnthropicAuth,
|
|
@@ -87,7 +87,7 @@ async function callSearch(
|
|
|
87
87
|
temperature?: number,
|
|
88
88
|
): Promise<AnthropicApiResponse> {
|
|
89
89
|
const url = buildAnthropicUrl(auth);
|
|
90
|
-
const headers =
|
|
90
|
+
const headers = buildAnthropicSearchHeaders(auth);
|
|
91
91
|
|
|
92
92
|
const systemBlocks = buildSystemBlocks(auth, model, systemPrompt);
|
|
93
93
|
|
|
@@ -10,6 +10,9 @@ export interface SearchParams {
|
|
|
10
10
|
maxOutputTokens?: number;
|
|
11
11
|
numSearchResults?: number;
|
|
12
12
|
temperature?: number;
|
|
13
|
+
googleSearch?: Record<string, unknown>;
|
|
14
|
+
codeExecution?: Record<string, unknown>;
|
|
15
|
+
urlContext?: Record<string, unknown>;
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
/** Base class for web search providers. */
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* them into a combined `answer` string on the SearchResponse.
|
|
8
8
|
*/
|
|
9
9
|
import { getEnvApiKey } from "@oh-my-pi/pi-ai";
|
|
10
|
+
import { callExaTool, findApiKey, isSearchResponse } from "../../../exa/mcp-client";
|
|
10
11
|
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
|
11
12
|
import { SearchProviderError } from "../../../web/search/types";
|
|
12
13
|
import { dateToAgeSeconds } from "../utils";
|
|
@@ -121,14 +122,19 @@ async function callExaSearch(apiKey: string, params: ExaSearchParams): Promise<E
|
|
|
121
122
|
return response.json() as Promise<ExaSearchResponse>;
|
|
122
123
|
}
|
|
123
124
|
|
|
125
|
+
async function callExaMcpSearch(params: ExaSearchParams): Promise<ExaSearchResponse> {
|
|
126
|
+
const response = await callExaTool("web_search_exa", { ...params }, findApiKey());
|
|
127
|
+
if (!isSearchResponse(response)) {
|
|
128
|
+
throw new Error("Exa MCP search returned unexpected response shape.");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return response as ExaSearchResponse;
|
|
132
|
+
}
|
|
133
|
+
|
|
124
134
|
/** Execute Exa web search */
|
|
125
135
|
export async function searchExa(params: ExaSearchParams): Promise<SearchResponse> {
|
|
126
136
|
const apiKey = getEnvApiKey("exa");
|
|
127
|
-
|
|
128
|
-
throw new Error("EXA_API_KEY not found. Set it in environment or .env file.");
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const response = await callExaSearch(apiKey, params);
|
|
137
|
+
const response = apiKey ? await callExaSearch(apiKey, params) : await callExaMcpSearch(params);
|
|
132
138
|
|
|
133
139
|
// Convert to unified SearchResponse
|
|
134
140
|
const sources: SearchSource[] = [];
|