@oh-my-pi/pi-coding-agent 13.9.15 → 13.9.16
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 +38 -0
- package/package.json +7 -7
- package/src/cli/web-search-cli.ts +2 -4
- package/src/config/model-registry.ts +258 -122
- package/src/config/settings-schema.ts +13 -0
- package/src/extensibility/extensions/runner.ts +42 -5
- package/src/extensibility/extensions/types.ts +13 -0
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/main.ts +12 -4
- package/src/modes/components/model-selector.ts +65 -5
- package/src/modes/components/tree-selector.ts +7 -3
- package/src/modes/controllers/input-controller.ts +8 -13
- package/src/modes/controllers/selector-controller.ts +1 -0
- package/src/modes/interactive-mode.ts +66 -6
- package/src/modes/types.ts +12 -1
- package/src/prompts/system/system-prompt.md +2 -1
- package/src/prompts/tools/python.md +1 -1
- package/src/prompts/tools/task.md +1 -1
- package/src/sdk.ts +8 -3
- package/src/session/agent-session.ts +2 -2
- package/src/session/compaction/utils.ts +14 -1
- package/src/tools/write.ts +6 -2
- package/src/utils/external-editor.ts +1 -1
- package/src/web/search/index.ts +19 -47
- package/src/web/search/render.ts +2 -4
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
onThemeChange,
|
|
55
55
|
theme,
|
|
56
56
|
} from "./theme/theme";
|
|
57
|
-
import type { CompactionQueuedMessage, InteractiveModeContext, TodoItem, TodoPhase } from "./types";
|
|
57
|
+
import type { CompactionQueuedMessage, InteractiveModeContext, SubmittedUserInput, TodoItem, TodoPhase } from "./types";
|
|
58
58
|
import { UiHelpers } from "./utils/ui-helpers";
|
|
59
59
|
|
|
60
60
|
const EDITOR_MAX_HEIGHT_MIN = 6;
|
|
@@ -121,8 +121,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
121
121
|
autoCompactionEscapeHandler?: () => void;
|
|
122
122
|
retryEscapeHandler?: () => void;
|
|
123
123
|
unsubscribe?: () => void;
|
|
124
|
-
onInputCallback?: (input:
|
|
124
|
+
onInputCallback?: (input: SubmittedUserInput) => void;
|
|
125
125
|
optimisticUserMessageSignature: string | undefined = undefined;
|
|
126
|
+
#pendingSubmittedInput: SubmittedUserInput | undefined;
|
|
126
127
|
lastSigintTime = 0;
|
|
127
128
|
lastEscapeTime = 0;
|
|
128
129
|
shutdownRequested = false;
|
|
@@ -397,8 +398,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
397
398
|
this.session.setSlashCommands(fileCommands);
|
|
398
399
|
}
|
|
399
400
|
|
|
400
|
-
async getUserInput(): Promise<
|
|
401
|
-
const { promise, resolve } = Promise.withResolvers<
|
|
401
|
+
async getUserInput(): Promise<SubmittedUserInput> {
|
|
402
|
+
const { promise, resolve } = Promise.withResolvers<SubmittedUserInput>();
|
|
402
403
|
this.onInputCallback = input => {
|
|
403
404
|
this.onInputCallback = undefined;
|
|
404
405
|
resolve(input);
|
|
@@ -406,6 +407,64 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
406
407
|
return promise;
|
|
407
408
|
}
|
|
408
409
|
|
|
410
|
+
startPendingSubmission(input: { text: string; images?: ImageContent[] }): SubmittedUserInput {
|
|
411
|
+
const submission: SubmittedUserInput = {
|
|
412
|
+
text: input.text,
|
|
413
|
+
images: input.images,
|
|
414
|
+
cancelled: false,
|
|
415
|
+
started: false,
|
|
416
|
+
};
|
|
417
|
+
this.#pendingSubmittedInput = submission;
|
|
418
|
+
this.optimisticUserMessageSignature = `${submission.text}\u0000${submission.images?.length ?? 0}`;
|
|
419
|
+
this.addMessageToChat({
|
|
420
|
+
role: "user",
|
|
421
|
+
content: [{ type: "text", text: submission.text }, ...(submission.images ?? [])],
|
|
422
|
+
attribution: "user",
|
|
423
|
+
timestamp: Date.now(),
|
|
424
|
+
});
|
|
425
|
+
this.editor.setText("");
|
|
426
|
+
this.ensureLoadingAnimation();
|
|
427
|
+
this.ui.requestRender();
|
|
428
|
+
return submission;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
cancelPendingSubmission(): boolean {
|
|
432
|
+
const submission = this.#pendingSubmittedInput;
|
|
433
|
+
if (!submission || submission.started) {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
submission.cancelled = true;
|
|
438
|
+
this.#pendingSubmittedInput = undefined;
|
|
439
|
+
this.optimisticUserMessageSignature = undefined;
|
|
440
|
+
this.#pendingWorkingMessage = undefined;
|
|
441
|
+
if (this.loadingAnimation) {
|
|
442
|
+
this.loadingAnimation.stop();
|
|
443
|
+
this.loadingAnimation = undefined;
|
|
444
|
+
this.statusContainer.clear();
|
|
445
|
+
}
|
|
446
|
+
this.pendingImages = submission.images ? [...submission.images] : [];
|
|
447
|
+
this.rebuildChatFromMessages();
|
|
448
|
+
this.editor.setText(submission.text);
|
|
449
|
+
this.updateEditorBorderColor();
|
|
450
|
+
this.ui.requestRender();
|
|
451
|
+
return true;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
markPendingSubmissionStarted(input: SubmittedUserInput): boolean {
|
|
455
|
+
if (this.#pendingSubmittedInput !== input || input.cancelled) {
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
input.started = true;
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
finishPendingSubmission(input: SubmittedUserInput): void {
|
|
463
|
+
if (this.#pendingSubmittedInput === input) {
|
|
464
|
+
this.#pendingSubmittedInput = undefined;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
409
468
|
#computeEditorMaxHeight(): number {
|
|
410
469
|
const rows = this.ui.terminal.rows;
|
|
411
470
|
const terminalRows = Number.isFinite(rows) && rows > 0 ? rows : EDITOR_FALLBACK_ROWS;
|
|
@@ -713,8 +772,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
713
772
|
return;
|
|
714
773
|
}
|
|
715
774
|
await this.#enterPlanMode();
|
|
716
|
-
if (initialPrompt) {
|
|
717
|
-
this.onInputCallback
|
|
775
|
+
if (initialPrompt && this.onInputCallback) {
|
|
776
|
+
this.onInputCallback(this.startPendingSubmission({ text: initialPrompt }));
|
|
718
777
|
}
|
|
719
778
|
}
|
|
720
779
|
|
|
@@ -855,6 +914,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
855
914
|
}
|
|
856
915
|
|
|
857
916
|
showError(message: string): void {
|
|
917
|
+
this.#pendingSubmittedInput = undefined;
|
|
858
918
|
this.optimisticUserMessageSignature = undefined;
|
|
859
919
|
this.#pendingWorkingMessage = undefined;
|
|
860
920
|
if (this.loadingAnimation) {
|
package/src/modes/types.ts
CHANGED
|
@@ -27,6 +27,13 @@ export type CompactionQueuedMessage = {
|
|
|
27
27
|
mode: "steer" | "followUp";
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
export type SubmittedUserInput = {
|
|
31
|
+
text: string;
|
|
32
|
+
images?: ImageContent[];
|
|
33
|
+
cancelled: boolean;
|
|
34
|
+
started: boolean;
|
|
35
|
+
};
|
|
36
|
+
|
|
30
37
|
export type TodoStatus = "pending" | "in_progress" | "completed" | "abandoned";
|
|
31
38
|
|
|
32
39
|
export type TodoItem = {
|
|
@@ -87,7 +94,7 @@ export interface InteractiveModeContext {
|
|
|
87
94
|
autoCompactionEscapeHandler?: () => void;
|
|
88
95
|
retryEscapeHandler?: () => void;
|
|
89
96
|
unsubscribe?: () => void;
|
|
90
|
-
onInputCallback?: (input:
|
|
97
|
+
onInputCallback?: (input: SubmittedUserInput) => void;
|
|
91
98
|
optimisticUserMessageSignature: string | undefined;
|
|
92
99
|
lastSigintTime: number;
|
|
93
100
|
lastEscapeTime: number;
|
|
@@ -129,6 +136,10 @@ export interface InteractiveModeContext {
|
|
|
129
136
|
setWorkingMessage(message?: string): void;
|
|
130
137
|
applyPendingWorkingMessage(): void;
|
|
131
138
|
ensureLoadingAnimation(): void;
|
|
139
|
+
startPendingSubmission(input: { text: string; images?: ImageContent[] }): SubmittedUserInput;
|
|
140
|
+
cancelPendingSubmission(): boolean;
|
|
141
|
+
markPendingSubmissionStarted(input: SubmittedUserInput): boolean;
|
|
142
|
+
finishPendingSubmission(input: SubmittedUserInput): void;
|
|
132
143
|
isKnownSlashCommand(text: string): boolean;
|
|
133
144
|
addMessageToChat(message: AgentMessage, options?: { populateHistory?: boolean }): void;
|
|
134
145
|
renderSessionContext(
|
|
@@ -79,6 +79,7 @@ You generate code inside-out: starting at the function body, working outward. Th
|
|
|
79
79
|
- **Time:** You do not feel the cost of duplicating a pattern across six files, of a resource operation with no upper bound, of an escape hatch that bypasses the type system. Name these costs before you choose the easy path. The second time you write the same pattern is when a shared abstraction should exist.
|
|
80
80
|
- When writing a function in a pipeline, ask "what does the next consumer need?" — not just "what do I need right now?"
|
|
81
81
|
- **DRY at 2.** When you write the same pattern a second time, stop and extract a shared helper. Two copies is a maintenance fork. Three copies is a bug.
|
|
82
|
+
- Write maintainable code. Add brief comments when they clarify non-obvious intent, invariants, edge cases, or tradeoffs. Prefer explaining why over restating what the code already does.
|
|
82
83
|
- **Earn every line.** A 12-line switch for a 3-way mapping is a lookup table. A one-liner wrapper that exists only for test access is a design smell.
|
|
83
84
|
</code-integrity>
|
|
84
85
|
|
|
@@ -230,7 +231,7 @@ Don't open a file hoping. Hope is not a strategy.
|
|
|
230
231
|
{{#has tools "grep"}}- `grep` to locate target{{/has}}
|
|
231
232
|
{{#has tools "find"}}- `find` to map it{{/has}}
|
|
232
233
|
{{#has tools "read"}}- `read` with offset/limit, not whole file{{/has}}
|
|
233
|
-
{{#has tools "task"}}- `task`
|
|
234
|
+
{{#has tools "task"}}- `task` for investigate+edit in one pass — prefer this over a separate explore→task chain{{/has}}
|
|
234
235
|
{{/ifAny}}
|
|
235
236
|
|
|
236
237
|
{{#if (includes tools "inspect_image")}}
|
|
@@ -6,7 +6,7 @@ Kernel persists across calls and cells; **imports, variables, and functions surv
|
|
|
6
6
|
- You **SHOULD** use one logical step per cell (imports, define function, test it, use it)
|
|
7
7
|
- You **SHOULD** pass multiple small cells in one call
|
|
8
8
|
- You **SHOULD** define small functions you can reuse and debug individually
|
|
9
|
-
- You **MUST** put explanations in assistant message or cell title
|
|
9
|
+
- You **MUST** put workflow explanations in assistant message or cell title
|
|
10
10
|
**When something fails:**
|
|
11
11
|
- Errors tell you which cell failed (e.g., "Cell 3 failed")
|
|
12
12
|
- You **SHOULD** resubmit only the fixed cell (or fixed cell + remaining cells)
|
|
@@ -22,7 +22,7 @@ Subagents lack your conversation history. Every decision, file content, and user
|
|
|
22
22
|
- **MUST NOT** duplicate shared constraints across assignments — put them in `context` once.
|
|
23
23
|
- **MUST NOT** tell tasks to run project-wide build/test/lint. Parallel agents share the working tree; each task edits, stops. Caller verifies after all complete.
|
|
24
24
|
- For large payloads (traces, JSON blobs), write to `local://<path>` and pass the path in context.
|
|
25
|
-
-
|
|
25
|
+
- Prefer `task` agents that investigate **and** edit in one pass. Only launch a dedicated read-only discovery step when the affected files are genuinely unknown and cannot be inferred from the task description.
|
|
26
26
|
</critical>
|
|
27
27
|
|
|
28
28
|
<scope>
|
package/src/sdk.ts
CHANGED
|
@@ -612,9 +612,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
612
612
|
const { authStorage, modelRegistry } = await logger.timeAsync("discoverModels", async () => {
|
|
613
613
|
const authStorage = options.authStorage ?? (await discoverAuthStorage(agentDir));
|
|
614
614
|
const modelRegistry = options.modelRegistry ?? new ModelRegistry(authStorage);
|
|
615
|
-
if (!options.modelRegistry) {
|
|
616
|
-
await modelRegistry.refresh();
|
|
617
|
-
}
|
|
618
615
|
return { authStorage, modelRegistry };
|
|
619
616
|
});
|
|
620
617
|
|
|
@@ -623,6 +620,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
623
620
|
async () => options.settings ?? (await Settings.init({ cwd, agentDir })),
|
|
624
621
|
);
|
|
625
622
|
logger.time("initializeWithSettings", initializeWithSettings, settings);
|
|
623
|
+
if (!options.modelRegistry) {
|
|
624
|
+
modelRegistry.refreshInBackground();
|
|
625
|
+
}
|
|
626
626
|
const skillsSettings = settings.getGroup("skills") as SkillsSettings;
|
|
627
627
|
const discoveredSkillsPromise =
|
|
628
628
|
options.skills === undefined ? discoverSkills(cwd, agentDir, skillsSettings) : undefined;
|
|
@@ -1358,6 +1358,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1358
1358
|
tools: initialTools,
|
|
1359
1359
|
},
|
|
1360
1360
|
convertToLlm: convertToLlmFinal,
|
|
1361
|
+
onPayload: extensionRunner
|
|
1362
|
+
? async (payload, _model) => {
|
|
1363
|
+
return extensionRunner.emitBeforeProviderRequest(payload);
|
|
1364
|
+
}
|
|
1365
|
+
: undefined,
|
|
1361
1366
|
sessionId: sessionManager.getSessionId(),
|
|
1362
1367
|
transformContext: extensionRunner
|
|
1363
1368
|
? async messages => {
|
|
@@ -4113,8 +4113,8 @@ export class AgentSession {
|
|
|
4113
4113
|
}
|
|
4114
4114
|
|
|
4115
4115
|
#isRetryableErrorMessage(errorMessage: string): boolean {
|
|
4116
|
-
// Match: overloaded_error, rate limit, usage limit, 429, 500, 502, 503, 504, service unavailable, connection error, fetch failed, retry delay exceeded
|
|
4117
|
-
return /overloaded|rate.?limit|usage.?limit|too many requests|429|500|502|503|504|service.?unavailable|server error|internal error|connection.?error|unable to connect|fetch failed|retry delay/i.test(
|
|
4116
|
+
// Match: overloaded_error, rate limit, usage limit, 429, 500, 502, 503, 504, service unavailable, connection error, fetch failed, retry delay exceeded, stream stall
|
|
4117
|
+
return /overloaded|rate.?limit|usage.?limit|too many requests|429|500|502|503|504|service.?unavailable|server error|internal error|connection.?error|unable to connect|fetch failed|retry delay|stream stall/i.test(
|
|
4118
4118
|
errorMessage,
|
|
4119
4119
|
);
|
|
4120
4120
|
}
|
|
@@ -104,6 +104,19 @@ export function upsertFileOperations(summary: string, readFiles: string[], modif
|
|
|
104
104
|
// Message Serialization
|
|
105
105
|
// ============================================================================
|
|
106
106
|
|
|
107
|
+
/** Maximum characters for a tool result in serialized summaries. */
|
|
108
|
+
const TOOL_RESULT_MAX_CHARS = 2000;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Truncate text to a maximum character length for summarization.
|
|
112
|
+
* Keeps the beginning and appends a truncation marker.
|
|
113
|
+
*/
|
|
114
|
+
function truncateForSummary(text: string, maxChars: number): string {
|
|
115
|
+
if (text.length <= maxChars) return text;
|
|
116
|
+
const truncatedChars = text.length - maxChars;
|
|
117
|
+
return `${text.slice(0, maxChars)}\n\n[... ${truncatedChars} more characters truncated]`;
|
|
118
|
+
}
|
|
119
|
+
|
|
107
120
|
/**
|
|
108
121
|
* Serialize LLM messages to text for summarization.
|
|
109
122
|
* This prevents the model from treating it as a conversation to continue.
|
|
@@ -156,7 +169,7 @@ export function serializeConversation(messages: Message[]): string {
|
|
|
156
169
|
.map(c => c.text)
|
|
157
170
|
.join("");
|
|
158
171
|
if (content) {
|
|
159
|
-
parts.push(`[Tool result]: ${content}`);
|
|
172
|
+
parts.push(`[Tool result]: ${truncateForSummary(content, TOOL_RESULT_MAX_CHARS)}`);
|
|
160
173
|
}
|
|
161
174
|
}
|
|
162
175
|
}
|
package/src/tools/write.ts
CHANGED
|
@@ -152,9 +152,13 @@ function formatMetadataLine(lineCount: number | null, language: string | undefin
|
|
|
152
152
|
return uiTheme.fg("dim", `${icon}`);
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
function normalizeDisplayText(text: string): string {
|
|
156
|
+
return text.replace(/\r/g, "");
|
|
157
|
+
}
|
|
158
|
+
|
|
155
159
|
function formatStreamingContent(content: string, uiTheme: Theme): string {
|
|
156
160
|
if (!content) return "";
|
|
157
|
-
const lines = content.split("\n");
|
|
161
|
+
const lines = normalizeDisplayText(content).split("\n");
|
|
158
162
|
const displayLines = lines.slice(-WRITE_STREAMING_PREVIEW_LINES);
|
|
159
163
|
const hidden = lines.length - displayLines.length;
|
|
160
164
|
|
|
@@ -171,7 +175,7 @@ function formatStreamingContent(content: string, uiTheme: Theme): string {
|
|
|
171
175
|
|
|
172
176
|
function renderContentPreview(content: string, expanded: boolean, uiTheme: Theme): string {
|
|
173
177
|
if (!content) return "";
|
|
174
|
-
const lines = content.split("\n");
|
|
178
|
+
const lines = normalizeDisplayText(content).split("\n");
|
|
175
179
|
const maxLines = expanded ? lines.length : Math.min(lines.length, WRITE_PREVIEW_LINES);
|
|
176
180
|
const displayLines = expanded ? lines : lines.slice(-maxLines);
|
|
177
181
|
const hidden = lines.length - displayLines.length;
|
|
@@ -39,7 +39,7 @@ export async function openInEditor(
|
|
|
39
39
|
const [editor, ...editorArgs] = editorCmd.split(" ");
|
|
40
40
|
const stdio = options?.stdio ?? ["inherit", "inherit", "inherit"];
|
|
41
41
|
|
|
42
|
-
const child = spawn(editor, [...editorArgs, tmpFile], { stdio });
|
|
42
|
+
const child = spawn(editor, [...editorArgs, tmpFile], { stdio, shell: process.platform === "win32" });
|
|
43
43
|
const exitCode = await new Promise<number>((resolve, reject) => {
|
|
44
44
|
child.once("exit", (code, signal) => resolve(code ?? (signal ? -1 : 0)));
|
|
45
45
|
child.once("error", error => reject(error));
|
package/src/web/search/index.ts
CHANGED
|
@@ -26,34 +26,12 @@ import type { ToolSession } from "../../tools";
|
|
|
26
26
|
import { formatAge } from "../../tools/render-utils";
|
|
27
27
|
import { getSearchProvider, resolveProviderChain, type SearchProvider } from "./provider";
|
|
28
28
|
import { renderSearchCall, renderSearchResult, type SearchRenderDetails } from "./render";
|
|
29
|
-
import type { SearchResponse } from "./types";
|
|
29
|
+
import type { SearchProviderId, SearchResponse } from "./types";
|
|
30
30
|
import { SearchProviderError } from "./types";
|
|
31
31
|
|
|
32
|
-
/** Web search parameters schema */
|
|
32
|
+
/** Web search tool parameters schema */
|
|
33
33
|
export const webSearchSchema = Type.Object({
|
|
34
34
|
query: Type.String({ description: "Search query" }),
|
|
35
|
-
provider: Type.Optional(
|
|
36
|
-
StringEnum(
|
|
37
|
-
[
|
|
38
|
-
"auto",
|
|
39
|
-
"exa",
|
|
40
|
-
"brave",
|
|
41
|
-
"jina",
|
|
42
|
-
"kimi",
|
|
43
|
-
"zai",
|
|
44
|
-
"anthropic",
|
|
45
|
-
"perplexity",
|
|
46
|
-
"gemini",
|
|
47
|
-
"codex",
|
|
48
|
-
"tavily",
|
|
49
|
-
"kagi",
|
|
50
|
-
"synthetic",
|
|
51
|
-
],
|
|
52
|
-
{
|
|
53
|
-
description: "Search provider (default: auto)",
|
|
54
|
-
},
|
|
55
|
-
),
|
|
56
|
-
),
|
|
57
35
|
recency: Type.Optional(
|
|
58
36
|
StringEnum(["day", "week", "month", "year"], {
|
|
59
37
|
description: "Recency filter (Brave, Perplexity)",
|
|
@@ -65,22 +43,8 @@ export const webSearchSchema = Type.Object({
|
|
|
65
43
|
num_search_results: Type.Optional(Type.Number({ description: "Number of search results to retrieve" })),
|
|
66
44
|
});
|
|
67
45
|
|
|
68
|
-
export type
|
|
46
|
+
export type SearchToolParams = {
|
|
69
47
|
query: string;
|
|
70
|
-
provider?:
|
|
71
|
-
| "auto"
|
|
72
|
-
| "exa"
|
|
73
|
-
| "brave"
|
|
74
|
-
| "jina"
|
|
75
|
-
| "kimi"
|
|
76
|
-
| "zai"
|
|
77
|
-
| "anthropic"
|
|
78
|
-
| "perplexity"
|
|
79
|
-
| "gemini"
|
|
80
|
-
| "codex"
|
|
81
|
-
| "tavily"
|
|
82
|
-
| "kagi"
|
|
83
|
-
| "synthetic";
|
|
84
48
|
recency?: "day" | "week" | "month" | "year";
|
|
85
49
|
limit?: number;
|
|
86
50
|
/** Maximum output tokens. Defaults to 4096. */
|
|
@@ -89,10 +53,12 @@ export type SearchParams = {
|
|
|
89
53
|
temperature?: number;
|
|
90
54
|
/** Number of search results to retrieve. Defaults to 10. */
|
|
91
55
|
num_search_results?: number;
|
|
92
|
-
/** Deprecated CLI flag; explicit provider fallback now happens only when provider is unavailable. */
|
|
93
|
-
no_fallback?: boolean;
|
|
94
56
|
};
|
|
95
57
|
|
|
58
|
+
export interface SearchQueryParams extends SearchToolParams {
|
|
59
|
+
provider?: SearchProviderId | "auto";
|
|
60
|
+
}
|
|
61
|
+
|
|
96
62
|
function formatProviderList(providers: SearchProvider[]): string {
|
|
97
63
|
return providers.map(provider => provider.label).join(", ");
|
|
98
64
|
}
|
|
@@ -178,14 +144,14 @@ function formatForLLM(response: SearchResponse): string {
|
|
|
178
144
|
/** Execute web search */
|
|
179
145
|
async function executeSearch(
|
|
180
146
|
_toolCallId: string,
|
|
181
|
-
params:
|
|
147
|
+
params: SearchQueryParams,
|
|
182
148
|
): Promise<{ content: Array<{ type: "text"; text: string }>; details: SearchRenderDetails }> {
|
|
183
149
|
const providers =
|
|
184
150
|
params.provider && params.provider !== "auto"
|
|
185
151
|
? (await getSearchProvider(params.provider).isAvailable())
|
|
186
152
|
? [getSearchProvider(params.provider)]
|
|
187
153
|
: await resolveProviderChain("auto")
|
|
188
|
-
: await resolveProviderChain(
|
|
154
|
+
: await resolveProviderChain();
|
|
189
155
|
if (providers.length === 0) {
|
|
190
156
|
const message = "No web search provider configured.";
|
|
191
157
|
return {
|
|
@@ -237,7 +203,7 @@ async function executeSearch(
|
|
|
237
203
|
* Execute a web search query for CLI/testing workflows.
|
|
238
204
|
*/
|
|
239
205
|
export async function runSearchQuery(
|
|
240
|
-
params:
|
|
206
|
+
params: SearchQueryParams,
|
|
241
207
|
): Promise<{ content: Array<{ type: "text"; text: string }>; details: SearchRenderDetails }> {
|
|
242
208
|
return executeSearch("cli-web-search", params);
|
|
243
209
|
}
|
|
@@ -261,7 +227,7 @@ export class SearchTool implements AgentTool<typeof webSearchSchema, SearchRende
|
|
|
261
227
|
|
|
262
228
|
async execute(
|
|
263
229
|
_toolCallId: string,
|
|
264
|
-
params:
|
|
230
|
+
params: SearchToolParams,
|
|
265
231
|
_signal?: AbortSignal,
|
|
266
232
|
_onUpdate?: AgentToolUpdateCallback<SearchRenderDetails>,
|
|
267
233
|
_context?: AgentToolContext,
|
|
@@ -277,11 +243,17 @@ export const webSearchCustomTool: CustomTool<typeof webSearchSchema, SearchRende
|
|
|
277
243
|
description: renderPromptTemplate(webSearchDescription),
|
|
278
244
|
parameters: webSearchSchema,
|
|
279
245
|
|
|
280
|
-
async execute(
|
|
246
|
+
async execute(
|
|
247
|
+
toolCallId: string,
|
|
248
|
+
params: SearchToolParams,
|
|
249
|
+
_onUpdate,
|
|
250
|
+
_ctx: CustomToolContext,
|
|
251
|
+
_signal?: AbortSignal,
|
|
252
|
+
) {
|
|
281
253
|
return executeSearch(toolCallId, params);
|
|
282
254
|
},
|
|
283
255
|
|
|
284
|
-
renderCall(args:
|
|
256
|
+
renderCall(args: SearchToolParams, options: RenderResultOptions, theme: Theme) {
|
|
285
257
|
return renderSearchCall(args, options, theme);
|
|
286
258
|
},
|
|
287
259
|
|
package/src/web/search/render.ts
CHANGED
|
@@ -75,7 +75,6 @@ export function renderSearchResult(
|
|
|
75
75
|
theme: Theme,
|
|
76
76
|
args?: {
|
|
77
77
|
query?: string;
|
|
78
|
-
provider?: string;
|
|
79
78
|
allowLongAnswer?: boolean;
|
|
80
79
|
maxAnswerLines?: number;
|
|
81
80
|
},
|
|
@@ -282,13 +281,12 @@ export function renderSearchResult(
|
|
|
282
281
|
|
|
283
282
|
/** Render web search call (query preview) */
|
|
284
283
|
export function renderSearchCall(
|
|
285
|
-
args: { query?: string;
|
|
284
|
+
args: { query?: string; [key: string]: unknown },
|
|
286
285
|
_options: RenderResultOptions,
|
|
287
286
|
theme: Theme,
|
|
288
287
|
): Component {
|
|
289
|
-
const provider = args.provider ?? "auto";
|
|
290
288
|
const query = truncateToWidth(args.query ?? "", 80);
|
|
291
|
-
const text = renderStatusLine({ icon: "pending", title: "Web Search", description: query
|
|
289
|
+
const text = renderStatusLine({ icon: "pending", title: "Web Search", description: query }, theme);
|
|
292
290
|
return new Text(text, 0, 0);
|
|
293
291
|
}
|
|
294
292
|
|