Merge remote-tracking branch 'origin/main' into pr-586-r2
# Conflicts: # packages/coding-agent/CHANGELOG.md
This commit is contained in:
@@ -189,12 +189,11 @@ function extractFileHeader(diff: string): string {
|
||||
return headerLines.join("\n");
|
||||
}
|
||||
|
||||
function joinPatch(parts: string[]): string {
|
||||
return parts
|
||||
export function joinPatch(parts: string[]): string {
|
||||
return `${parts
|
||||
.map(part => (part.endsWith("\n") ? part : `${part}\n`))
|
||||
.join("\n")
|
||||
.trimEnd()
|
||||
.concat("\n");
|
||||
.replace(/\n+$/, "")}\n`;
|
||||
}
|
||||
|
||||
function selectHunks(file: FileHunks, selector: HunkSelection["hunks"]): FileHunks["hunks"] {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
||||
import { MODEL_ROLE_IDS } from "../config/model-registry";
|
||||
import { parseModelPattern, resolveModelRoleValue } from "../config/model-resolver";
|
||||
import { parseModelPattern, resolveModelRoleValue, resolveRoleSelection } from "../config/model-resolver";
|
||||
import type { Settings } from "../config/settings";
|
||||
import MODEL_PRIO from "../priority.json" with { type: "json" };
|
||||
|
||||
@@ -11,24 +11,6 @@ export interface ResolvedCommitModel {
|
||||
thinkingLevel?: ThinkingLevel;
|
||||
}
|
||||
|
||||
function resolveRoleSelection(
|
||||
roles: readonly string[],
|
||||
settings: Settings,
|
||||
availableModels: Model<Api>[],
|
||||
): { model: Model<Api>; thinkingLevel?: ThinkingLevel } | undefined {
|
||||
const matchPreferences = { usageOrder: settings.getStorage()?.getModelUsageOrder() };
|
||||
for (const role of roles) {
|
||||
const resolved = resolveModelRoleValue(settings.getModelRole(role), availableModels, {
|
||||
settings,
|
||||
matchPreferences,
|
||||
});
|
||||
if (resolved.model) {
|
||||
return { model: resolved.model, thinkingLevel: resolved.thinkingLevel };
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function resolvePrimaryModel(
|
||||
override: string | undefined,
|
||||
settings: Settings,
|
||||
|
||||
@@ -31,7 +31,7 @@ import { type ConfigError, ConfigFile } from "../config";
|
||||
import { parseModelString } from "../config/model-resolver";
|
||||
import { isValidThemeColor, type ThemeColor } from "../modes/theme/theme";
|
||||
import type { AuthStorage, OAuthCredential } from "../session/auth-storage";
|
||||
import type { Settings } from "./settings";
|
||||
import { type Settings, settings } from "./settings";
|
||||
|
||||
export const kNoAuth = "N/A";
|
||||
|
||||
@@ -730,6 +730,14 @@ function normalizeSuppressedSelector(selector: string): string {
|
||||
return `${parsed.provider}/${parsed.id}`;
|
||||
}
|
||||
|
||||
function getDisabledProviderIdsFromSettings(): Set<string> {
|
||||
try {
|
||||
return new Set(settings.get("disabledProviders"));
|
||||
} catch {
|
||||
return new Set();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Model registry - loads and manages models, resolves API keys via AuthStorage.
|
||||
*/
|
||||
@@ -1670,11 +1678,19 @@ export class ModelRegistry {
|
||||
* This is a fast check that doesn't refresh OAuth tokens.
|
||||
*/
|
||||
getAvailable(): Model<Api>[] {
|
||||
return this.#models.filter(m => this.#keylessProviders.has(m.provider) || this.authStorage.hasAuth(m.provider));
|
||||
const disabledProviders = getDisabledProviderIdsFromSettings();
|
||||
return this.#models.filter(
|
||||
m =>
|
||||
!disabledProviders.has(m.provider) &&
|
||||
(this.#keylessProviders.has(m.provider) || this.authStorage.hasAuth(m.provider)),
|
||||
);
|
||||
}
|
||||
|
||||
getDiscoverableProviders(): string[] {
|
||||
return this.#discoverableProviders.map(provider => provider.provider);
|
||||
const disabledProviders = getDisabledProviderIdsFromSettings();
|
||||
return this.#discoverableProviders
|
||||
.filter(provider => !disabledProviders.has(provider.provider))
|
||||
.map(provider => provider.provider);
|
||||
}
|
||||
|
||||
getProviderDiscoveryState(provider: string): ProviderDiscoveryState | undefined {
|
||||
|
||||
@@ -587,6 +587,27 @@ export function resolveModelOverride(
|
||||
return { explicitThinkingLevel: false };
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a list of role patterns to the first matching model.
|
||||
*/
|
||||
export function resolveRoleSelection(
|
||||
roles: readonly string[],
|
||||
settings: Settings,
|
||||
availableModels: Model<Api>[],
|
||||
): { model: Model<Api>; thinkingLevel?: ThinkingLevel } | undefined {
|
||||
const matchPreferences = { usageOrder: settings.getStorage()?.getModelUsageOrder() };
|
||||
for (const role of roles) {
|
||||
const resolved = resolveModelRoleValue(settings.getModelRole(role), availableModels, {
|
||||
settings,
|
||||
matchPreferences,
|
||||
});
|
||||
if (resolved.model) {
|
||||
return { model: resolved.model, thinkingLevel: resolved.thinkingLevel };
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve model patterns to actual Model objects with optional thinking levels
|
||||
* Format: "pattern:level" where :level is optional
|
||||
|
||||
@@ -541,6 +541,7 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
||||
}
|
||||
|
||||
if (parsedArgs.listModels !== undefined) {
|
||||
await logger.timeAsync("settings:init:list-models", () => Settings.init({ cwd: getProjectDir() }));
|
||||
await modelRegistry.refresh("online");
|
||||
const searchPattern = typeof parsedArgs.listModels === "string" ? parsedArgs.listModels : undefined;
|
||||
await listModels(modelRegistry, searchPattern);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
import type { ToolCallContext } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { Text } from "@oh-my-pi/pi-tui";
|
||||
import { Text, visibleWidth, wrapTextWithAnsi } from "@oh-my-pi/pi-tui";
|
||||
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
||||
import type { FileDiagnosticsResult } from "../lsp";
|
||||
import { renderDiff as renderDiffColored } from "../modes/components/diff";
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
shortenPath,
|
||||
truncateDiffByHunk,
|
||||
} from "../tools/render-utils";
|
||||
import { Ellipsis, Hasher, type RenderCache, renderStatusLine, truncateToWidth } from "../tui";
|
||||
import { Hasher, type RenderCache, renderStatusLine, truncateToWidth } from "../tui";
|
||||
import type { HashlineToolEdit } from "./index";
|
||||
import type { DiffError, DiffResult, Operation } from "./types";
|
||||
|
||||
@@ -222,6 +222,31 @@ function renderDiffSection(
|
||||
return text;
|
||||
}
|
||||
|
||||
function wrapEditRendererLine(line: string, width: number): string[] {
|
||||
if (width <= 0) return [line];
|
||||
if (line.length === 0) return [""];
|
||||
|
||||
const startAnsi = line.match(/^((?:\x1b\[[0-9;]*m)*)/)?.[1] ?? "";
|
||||
const bodyWithReset = line.slice(startAnsi.length);
|
||||
const body = bodyWithReset.endsWith("\x1b[39m") ? bodyWithReset.slice(0, -"\x1b[39m".length) : bodyWithReset;
|
||||
const diffMatch = /^([+\-\s])(\s*\d+)\|(.*)$/s.exec(body);
|
||||
|
||||
if (!diffMatch) {
|
||||
return wrapTextWithAnsi(line, width);
|
||||
}
|
||||
|
||||
const [, marker, lineNum, content] = diffMatch;
|
||||
const prefix = `${marker}${lineNum}|`;
|
||||
const prefixWidth = visibleWidth(prefix);
|
||||
const contentWidth = Math.max(1, width - prefixWidth);
|
||||
const continuationPrefix = `${" ".repeat(Math.max(0, prefixWidth - 1))}|`;
|
||||
const wrappedContent = wrapTextWithAnsi(content, contentWidth);
|
||||
|
||||
return wrappedContent.map(
|
||||
(segment, index) => `${startAnsi}${index === 0 ? prefix : continuationPrefix}${segment}\x1b[39m`,
|
||||
);
|
||||
}
|
||||
|
||||
export const editToolRenderer = {
|
||||
mergeCallAndResult: true,
|
||||
|
||||
@@ -357,7 +382,7 @@ export const editToolRenderer = {
|
||||
}
|
||||
|
||||
const lines =
|
||||
width > 0 ? text.split("\n").map(line => truncateToWidth(line, width, Ellipsis.Omit)) : text.split("\n");
|
||||
width > 0 ? text.split("\n").flatMap(line => wrapEditRendererLine(line, width)) : text.split("\n");
|
||||
cached = { key, lines };
|
||||
return lines;
|
||||
},
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
Search code snippets, and technical content.
|
||||
This tool behaves more like grep than natural-language web search.
|
||||
|
||||
<instruction>
|
||||
- Query with exact symbols, identifiers, error strings, CLI flags, filenames, import paths, and short code fragments
|
||||
- Start with the smallest distinctive token; widen or add one nearby token only if the first query is too broad
|
||||
- Prefer exact syntax when punctuation carries meaning, such as `Promise.withResolvers`, `useEffect(`, `--watch`, or `"direnv loading"`
|
||||
- Keep `query` terse; remove filler words, prose, and request framing
|
||||
- Use `code_context` only for a few disambiguating tokens such as language, library, framework, repo, runtime, or API name
|
||||
- If a multi-word literal matters exactly, quote the shortest stable phrase first, then refine
|
||||
- When looking for usage examples of a specific API, search the symbol first; add surrounding call syntax only when needed
|
||||
</instruction>
|
||||
|
||||
<parameters>
|
||||
- query: Grep-style code search query; use exact tokens, short fragments, or short quoted phrases
|
||||
- code_context: Optional disambiguation tokens only, not a sentence
|
||||
</parameters>
|
||||
|
||||
<examples>
|
||||
Good queries:
|
||||
- `Promise.withResolvers`
|
||||
- `DIRENV_LOG_FORMAT`
|
||||
- `"direnv loading"`
|
||||
- `useState` with `code_context: react hooks`
|
||||
- `app.get(` with `code_context: express`
|
||||
- `ERR_REQUIRE_ESM` with `code_context: node`
|
||||
|
||||
Bad queries:
|
||||
- `Need the official or source-backed way to silence direnv loading output`
|
||||
- `How do I use Promise.withResolvers in Bun?`
|
||||
- `find examples of React state hooks in TypeScript projects`
|
||||
- `search GitHub for express routing docs`
|
||||
</examples>
|
||||
|
||||
<avoid>
|
||||
- Do not use this tool for broad conceptual research, comparisons, or authoritative sourcing; use `web_search`, `web_search_deep`, or `fetch` instead
|
||||
- Do not put full-sentence instructions into `query` or `code_context`
|
||||
- Do not pack many weak terms into one query; one strong token plus minimal context usually works better
|
||||
</avoid>
|
||||
|
||||
<critical>
|
||||
- `query` should be grep-style code search, not a natural-language request
|
||||
- `code_context` is optional and should stay short
|
||||
- If you need explanations, best practices, or comprehensive answers, use broader web search tools instead of this one
|
||||
</critical>
|
||||
@@ -1009,7 +1009,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
||||
}
|
||||
|
||||
// Add web search tools
|
||||
customTools.push(...getSearchTools());
|
||||
if (options.toolNames?.includes("web_search")) {
|
||||
customTools.push(...getSearchTools());
|
||||
}
|
||||
|
||||
// Discover and load custom tools from .omp/tools/, .claude/tools/, etc.
|
||||
const builtInToolNames = builtinTools.map(t => t.name);
|
||||
|
||||
@@ -16,7 +16,7 @@ const CHECK_BYTE_COUNT = 1024;
|
||||
const HEADER_LINE_LIMIT = 40;
|
||||
|
||||
const KNOWN_GENERATOR_PATTERN =
|
||||
"(?:protoc(?:-gen-[\\w-]+)?|sqlc|buf|swagger(?:-codegen)?|openapi(?:-generator)?|grpc-gateway|mockery|stringer|easyjson|deepcopy-gen|defaulter-gen|conversion-gen|client-gen|lister-gen|informer-gen|kysely-codegen)";
|
||||
"(?:protoc(?:-gen-[\\w-]+)?|sqlc|buf|swagger(?:-codegen)?|openapi(?:-generator)?|grpc-gateway|mockery|stringer|easyjson|deepcopy-gen|defaulter-gen|conversion-gen|client-gen|lister-gen|informer-gen|kysely-codegen|napi-rs)";
|
||||
|
||||
/**
|
||||
* Strong marker patterns for generated-file headers.
|
||||
|
||||
@@ -382,8 +382,8 @@ export function getDiffStats(diffText: string): DiffStats {
|
||||
|
||||
export function formatDiffStats(added: number, removed: number, hunks: number, theme: Theme): string {
|
||||
const parts: string[] = [];
|
||||
if (added > 0) parts.push(theme.fg("success", `+${added}`));
|
||||
if (removed > 0) parts.push(theme.fg("error", `-${removed}`));
|
||||
if (added > 0) parts.push(theme.fg("toolDiffAdded", `+${added}`));
|
||||
if (removed > 0) parts.push(theme.fg("toolDiffRemoved", `-${removed}`));
|
||||
if (hunks > 0) parts.push(theme.fg("dim", `${hunks} hunk${hunks !== 1 ? "s" : ""}`));
|
||||
return parts.join(theme.fg("dim", " / "));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { Api, Model } from "@oh-my-pi/pi-ai";
|
||||
import { completeSimple } from "@oh-my-pi/pi-ai";
|
||||
import { logger } from "@oh-my-pi/pi-utils";
|
||||
import type { ModelRegistry } from "../config/model-registry";
|
||||
import { resolveModelRoleValue } from "../config/model-resolver";
|
||||
import { resolveRoleSelection } from "../config/model-resolver";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import type { Settings } from "../config/settings";
|
||||
import titleSystemPrompt from "../prompts/system/title-system.md" with { type: "text" };
|
||||
@@ -28,13 +28,9 @@ function getTitleModel(
|
||||
const availableModels = registry.getAvailable();
|
||||
if (availableModels.length === 0) return undefined;
|
||||
|
||||
const matchPreferences = { usageOrder: settings.getStorage()?.getModelUsageOrder() };
|
||||
const configuredSmol = resolveModelRoleValue(settings.getModelRole("smol"), availableModels, {
|
||||
settings,
|
||||
matchPreferences,
|
||||
});
|
||||
if (configuredSmol.model) {
|
||||
return { model: configuredSmol.model, thinkingLevel: configuredSmol.thinkingLevel };
|
||||
const titleModel = resolveRoleSelection(["commit", "smol"], settings, availableModels);
|
||||
if (titleModel) {
|
||||
return { model: titleModel.model, thinkingLevel: titleModel.thinkingLevel };
|
||||
}
|
||||
|
||||
if (currentModel) {
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { Text } from "@oh-my-pi/pi-tui";
|
||||
import { callExaTool, findApiKey as findExaKey, formatSearchResults, isSearchResponse } from "../../exa/mcp-client";
|
||||
import type { CustomToolResult, RenderResultOptions } from "../../extensibility/custom-tools/types";
|
||||
import type { Theme } from "../../modes/theme/theme";
|
||||
import {
|
||||
formatCount,
|
||||
formatExpandHint,
|
||||
formatMoreItems,
|
||||
formatStatusIcon,
|
||||
replaceTabs,
|
||||
truncateToWidth,
|
||||
} from "../../tools/render-utils";
|
||||
import type { CodeSearchProviderId } from "./types";
|
||||
|
||||
export interface CodeSearchToolParams {
|
||||
query: string;
|
||||
code_context?: string;
|
||||
}
|
||||
|
||||
export interface CodeSearchSource {
|
||||
title: string;
|
||||
url: string;
|
||||
repository: string;
|
||||
path: string;
|
||||
branch: string;
|
||||
snippet?: string;
|
||||
totalMatches?: string;
|
||||
}
|
||||
|
||||
export interface CodeSearchResponse {
|
||||
provider: CodeSearchProviderId;
|
||||
query: string;
|
||||
totalResults?: number;
|
||||
sources: CodeSearchSource[];
|
||||
}
|
||||
|
||||
export interface CodeSearchRenderDetails {
|
||||
response?: CodeSearchResponse;
|
||||
error?: string;
|
||||
provider: CodeSearchProviderId;
|
||||
}
|
||||
|
||||
function stringifyExaCodeResponse(payload: unknown): string {
|
||||
if (typeof payload === "string") return payload;
|
||||
if (typeof payload === "number" || typeof payload === "boolean") return String(payload);
|
||||
if (payload === null || payload === undefined) return "";
|
||||
const serialized = JSON.stringify(payload, null, 2);
|
||||
return typeof serialized === "string" ? serialized : "";
|
||||
}
|
||||
|
||||
function normalizeExaCodeSearchResponse(
|
||||
params: CodeSearchToolParams,
|
||||
payload: unknown,
|
||||
formattedSearchResponse?: string,
|
||||
): CodeSearchResponse {
|
||||
const snippet = formattedSearchResponse ?? stringifyExaCodeResponse(payload);
|
||||
return {
|
||||
provider: "exa",
|
||||
query: params.query,
|
||||
sources: [
|
||||
{
|
||||
title: params.query,
|
||||
url: "https://exa.ai/",
|
||||
repository: "exa",
|
||||
path: "code-search",
|
||||
branch: "public-mcp",
|
||||
snippet: snippet.length > 0 ? snippet : undefined,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
async function searchCodeWithExa(params: CodeSearchToolParams): Promise<CodeSearchResponse> {
|
||||
const exaParams = params.code_context
|
||||
? { query: params.query, code_context: params.code_context }
|
||||
: { query: params.query };
|
||||
const response = await callExaTool("get_code_context_exa", exaParams, findExaKey());
|
||||
if (isSearchResponse(response)) {
|
||||
return normalizeExaCodeSearchResponse(params, response, formatSearchResults(response));
|
||||
}
|
||||
|
||||
return normalizeExaCodeSearchResponse(params, response);
|
||||
}
|
||||
|
||||
export function formatCodeSearchForLlm(response: CodeSearchResponse): string {
|
||||
const parts: string[] = [];
|
||||
const summaryParts: string[] = [response.provider];
|
||||
if (response.totalResults !== undefined) {
|
||||
summaryParts.push(`${response.totalResults.toLocaleString()} total matches`);
|
||||
}
|
||||
parts.push(`Code search via ${summaryParts.join(" · ")}`);
|
||||
|
||||
if (response.sources.length === 0) {
|
||||
parts.push("No results found.");
|
||||
return parts.join("\n");
|
||||
}
|
||||
|
||||
for (const [index, source] of response.sources.entries()) {
|
||||
const metadata: string[] = [source.repository, source.path];
|
||||
if (source.totalMatches) metadata.push(`${source.totalMatches} matches`);
|
||||
parts.push(`[${index + 1}] ${metadata.join(" · ")}`);
|
||||
parts.push(` ${source.url}`);
|
||||
if (source.snippet) {
|
||||
for (const line of source.snippet.split("\n").slice(0, 8)) {
|
||||
parts.push(` ${line}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parts.join("\n");
|
||||
}
|
||||
|
||||
export async function executeCodeSearch(
|
||||
params: CodeSearchToolParams,
|
||||
): Promise<CustomToolResult<CodeSearchRenderDetails>> {
|
||||
try {
|
||||
const response = await searchCodeWithExa(params);
|
||||
|
||||
return {
|
||||
content: [{ type: "text", text: formatCodeSearchForLlm(response) }],
|
||||
details: { provider: response.provider, response },
|
||||
};
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
content: [{ type: "text", text: `Error: ${message}` }],
|
||||
details: { provider: "exa", error: message },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function renderCodeSearchCall(
|
||||
args: CodeSearchToolParams,
|
||||
_options: RenderResultOptions,
|
||||
theme: Theme,
|
||||
): Component {
|
||||
let text = `${theme.fg("toolTitle", "Code Search")} ${theme.fg("accent", truncateToWidth(args.query, 80))}`;
|
||||
if (args.code_context) {
|
||||
text += ` ${theme.fg("dim", truncateToWidth(args.code_context, 40))}`;
|
||||
}
|
||||
return new Text(text, 0, 0);
|
||||
}
|
||||
|
||||
export function renderCodeSearchResult(
|
||||
result: { content: Array<{ type: string; text?: string }>; details?: CodeSearchRenderDetails },
|
||||
options: RenderResultOptions,
|
||||
uiTheme: Theme,
|
||||
): Component {
|
||||
const details = result.details;
|
||||
if (details?.error) {
|
||||
return new Text(
|
||||
`${formatStatusIcon("error", uiTheme)} ${uiTheme.fg("error", `Error: ${replaceTabs(details.error)}`)}`,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
const response = details?.response;
|
||||
if (!response) {
|
||||
return new Text(`${formatStatusIcon("warning", uiTheme)} ${uiTheme.fg("muted", "No code search results")}`, 0, 0);
|
||||
}
|
||||
|
||||
const resultCount = response.sources.length;
|
||||
const meta: string[] = [formatCount("result", resultCount), `provider:${response.provider}`];
|
||||
if (response.totalResults !== undefined) {
|
||||
meta.push(`${response.totalResults.toLocaleString()} total`);
|
||||
}
|
||||
const expandHint = formatExpandHint(uiTheme, options.expanded, resultCount > 1);
|
||||
let text = `${formatStatusIcon(resultCount > 0 ? "success" : "warning", uiTheme)} ${uiTheme.fg("dim", meta.join(uiTheme.sep.dot))}${expandHint}`;
|
||||
|
||||
if (resultCount === 0) {
|
||||
text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg("muted", "No results")}`;
|
||||
return new Text(text, 0, 0);
|
||||
}
|
||||
|
||||
const visibleSources = options.expanded ? response.sources : response.sources.slice(0, 1);
|
||||
for (const [index, source] of visibleSources.entries()) {
|
||||
const isLast = index === visibleSources.length - 1;
|
||||
const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
||||
const cont = isLast ? " " : uiTheme.tree.vertical;
|
||||
text += `\n ${uiTheme.fg("dim", branch)} ${uiTheme.fg("accent", truncateToWidth(replaceTabs(source.title), 100))}`;
|
||||
text += `\n ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg("mdLinkUrl", source.url)}`;
|
||||
|
||||
if (source.totalMatches) {
|
||||
text += `\n ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg("muted", `Matches: ${source.totalMatches}`)}`;
|
||||
}
|
||||
|
||||
if (source.snippet) {
|
||||
const snippetLines = source.snippet.split("\n").slice(0, options.expanded ? 6 : 3);
|
||||
for (const line of snippetLines) {
|
||||
text += `\n ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg(
|
||||
"toolOutput",
|
||||
truncateToWidth(replaceTabs(line), 100),
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.expanded && response.sources.length > visibleSources.length) {
|
||||
text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg(
|
||||
"muted",
|
||||
formatMoreItems(response.sources.length - visibleSources.length, "result"),
|
||||
)}`;
|
||||
}
|
||||
|
||||
return new Text(text, 0, 0);
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
* Single tool supporting Anthropic, Perplexity, Exa, Brave, Jina, Kimi, Gemini, Codex, Tavily, Kagi, Z.AI, and Synthetic
|
||||
* providers with provider-specific parameters exposed conditionally.
|
||||
*
|
||||
* Code search is also supported via the code_search tool.
|
||||
*/
|
||||
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import { StringEnum } from "@oh-my-pi/pi-ai";
|
||||
@@ -13,17 +12,9 @@ import { renderPromptTemplate } from "../../config/prompt-templates";
|
||||
import type { CustomTool, CustomToolContext, RenderResultOptions } from "../../extensibility/custom-tools/types";
|
||||
import type { Theme } from "../../modes/theme/theme";
|
||||
import webSearchSystemPrompt from "../../prompts/system/web-search.md" with { type: "text" };
|
||||
import codeSearchDescription from "../../prompts/tools/code-search.md" with { type: "text" };
|
||||
import webSearchDescription from "../../prompts/tools/web-search.md" with { type: "text" };
|
||||
import type { ToolSession } from "../../tools";
|
||||
import { formatAge } from "../../tools/render-utils";
|
||||
import {
|
||||
type CodeSearchRenderDetails,
|
||||
type CodeSearchToolParams,
|
||||
executeCodeSearch,
|
||||
renderCodeSearchCall,
|
||||
renderCodeSearchResult,
|
||||
} from "./code-search";
|
||||
import { getSearchProvider, resolveProviderChain, type SearchProvider } from "./provider";
|
||||
import { renderSearchCall, renderSearchResult, type SearchRenderDetails } from "./render";
|
||||
import type { SearchProviderId, SearchResponse } from "./types";
|
||||
@@ -262,34 +253,8 @@ export const webSearchCustomTool: CustomTool<typeof webSearchSchema, SearchRende
|
||||
},
|
||||
};
|
||||
|
||||
/** Schema for code context search */
|
||||
const codeSearchParameters = Type.Object({
|
||||
query: Type.String({ description: "Grep-style code search query; use exact tokens or short quoted phrases" }),
|
||||
code_context: Type.Optional(Type.String({ description: "Optional disambiguation tokens only, not a sentence" })),
|
||||
});
|
||||
|
||||
/** Code context search - optimized for code snippets and documentation */
|
||||
export const codeSearchTool: CustomTool<typeof codeSearchParameters, CodeSearchRenderDetails> = {
|
||||
name: "code_search",
|
||||
label: "Code Search",
|
||||
description: renderPromptTemplate(codeSearchDescription),
|
||||
parameters: codeSearchParameters,
|
||||
|
||||
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
||||
return executeCodeSearch(params);
|
||||
},
|
||||
|
||||
renderCall(args: CodeSearchToolParams, options: RenderResultOptions, theme: Theme) {
|
||||
return renderCodeSearchCall(args, options, theme);
|
||||
},
|
||||
|
||||
renderResult(result, options, theme) {
|
||||
return renderCodeSearchResult(result, options, theme);
|
||||
},
|
||||
};
|
||||
|
||||
export function getSearchTools(): CustomTool<any, any>[] {
|
||||
return [webSearchCustomTool, codeSearchTool];
|
||||
return [webSearchCustomTool];
|
||||
}
|
||||
|
||||
export { getSearchProvider, setPreferredSearchProvider } from "./provider";
|
||||
|
||||
@@ -20,8 +20,6 @@ export type SearchProviderId =
|
||||
| "kagi"
|
||||
| "synthetic";
|
||||
|
||||
export type CodeSearchProviderId = "exa";
|
||||
|
||||
export function isSearchProviderId(value: string): value is SearchProviderId {
|
||||
return [
|
||||
"exa",
|
||||
|
||||
Reference in New Issue
Block a user