Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/tools/agentRun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const agentRunInputShape = {
.describe(
"ID returned by an earlier call. Use it to check or continue waiting for the same run. Do not start a duplicate run.",
),
systemPrompt: z.string().optional().describe("Optional system-level guidance for the Agent."),
systemPrompt: z.string().optional().describe("System-level guidance for the Agent."),
outputSchema: recordSchema()
.optional()
.describe(
Expand All @@ -85,7 +85,7 @@ export const agentRunInputShape = {
.array(z.object({ provider: dataSourceProviderSchema }))
.max(5)
.optional()
.describe("Optional Exa Connect providers to enable for this run."),
.describe("Exa Connect providers to enable for this run."),
previousRunId: agentRunIdSchema()
.optional()
.describe("ID of a completed prior run to use as context for a new run."),
Expand All @@ -108,7 +108,7 @@ export const agentRunInputShape = {
})
.optional()
.describe(
"Optional per-run spend and duration limits for the metered auto and ultra efforts. Set it when the user names a cost or time limit.",
"Per-run spend and duration limits for the metered auto and ultra efforts. Set it when the user names a cost or time limit.",
),
};

Expand Down
2 changes: 1 addition & 1 deletion src/tools/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export function createExaClient(config?: Record<string, unknown>, tool?: string)
* double); unset, exa-js keeps its default of https://api.exa.ai.
*/
function newExaClient(apiKey: string): Exa {
const baseUrl = process.env.EXA_API_BASE_URL;
let baseUrl = process.env.EXA_API_BASE_URL;
return baseUrl ? new Exa(apiKey, baseUrl) : new Exa(apiKey);
}

Expand Down
2 changes: 1 addition & 1 deletion src/tools/exaCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function registerExaCodeTool(
): void {
server.tool(
"get_code_context_exa",
`[Deprecated: Use web_search_exa instead] Find code examples, documentation, and programming solutions.
`Find code examples, documentation, and programming solutions.

Best for: Any programming question - API usage, library examples, code snippets, debugging help.
Returns: Relevant code and documentation.
Expand Down
12 changes: 12 additions & 0 deletions src/tools/searchType.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
/**
* The search types the search tools send to the Exa API, shared by every
* place that accepts one: the `type` parameter of `web_search_advanced_exa`
* and a server's configured default for `web_search_exa`
* (`?defaultSearchType=` on HTTP, `DEFAULT_SEARCH_TYPE` on stdio).
*
* `fast` and `instant` are legacy but stay accepted and are forwarded as
* given, never rewritten to `lite`: `lite` serves fewer parameters and only
* cached content, so swapping it in would change what a caller gets.
*/

export const SEARCH_TYPES = ["auto", "lite", "fast", "instant"] as const;

export type SearchType = (typeof SEARCH_TYPES)[number];

/** `value` as a search type, or undefined when it names none (exact, case-sensitive match). */
export function parseSearchType(value: string | null | undefined): SearchType | undefined {
return SEARCH_TYPES.find((type) => type === value);
}
3 changes: 2 additions & 1 deletion src/tools/webSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import { retryWithBackoff, formatToolError, withTimeout } from "../utils/errorHa
import { sanitizeSearchNotices, sanitizeSearchResponse } from "../utils/exaResponseSanitizer.js";
import { lenientString, lenientOptionalNumber } from "./validation.js";
import { logSearchWarnings, withSearchNotices } from "./searchNotes.js";
import type { SearchType } from "./searchType.js";
import { checkpoint } from "agnost";

type WebSearchConfig = {
exaApiKey?: string;
userProvidedApiKey?: boolean;
defaultSearchType?: "auto" | "lite" | "fast" | "instant";
defaultSearchType?: SearchType;
exaSource?: string;
mcpSessionId?: string;
};
Expand Down
34 changes: 30 additions & 4 deletions src/tools/webSearchAdvanced.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
withNoticeMessages,
withStructuredSearchNotices,
} from "./searchNotes.js";
import { SEARCH_TYPES } from "./searchType.js";
import { checkpoint } from "agnost";

/**
Expand Down Expand Up @@ -79,6 +80,30 @@ export function liteUnsupportedParameters(request: ExaAdvancedSearchRequest): st
return [...new Set(fields.map((field) => LITE_REJECTED_FIELD_PARAMETERS[field] ?? field))];
}

/** The parameters that decide which page text a search asks for. */
type TextParameters = {
textMaxCharacters?: number;
enableHighlights?: boolean;
enableSummary?: boolean;
};

/**
* The `contents.text` option for a search: capped at `textMaxCharacters` when
* set, omitted when highlights or a summary stand in for the page, and the
* full page otherwise.
*/
export function requestedText(
params: TextParameters,
): ExaAdvancedSearchRequest["contents"]["text"] {
if (params.textMaxCharacters) {
return { maxCharacters: params.textMaxCharacters };
}
if (params.enableHighlights || params.enableSummary) {
return undefined;
}
return true;
}

/** The error result for a `lite` search that sets parameters `lite` does not serve. */
function liteRefusal(parameters: readonly string[]) {
const pronoun = parameters.length === 1 ? "it" : "them";
Expand All @@ -103,12 +128,12 @@ export function registerWebSearchAdvancedTool(

Best for: When you need specific filters like date ranges, domain restrictions, or category filters.
Not recommended for: Simple searches - use web_search_exa instead.
Returns: Search results with optional highlights, summaries, and subpage content.`,
Returns: Search results with page text, highlights, summaries or subpage content. Page text is the full page by default. When highlights or summaries are enabled, page text is omitted unless textMaxCharacters is set.`,
{
query: lenientString().describe("Search query - can be a question, statement, or keywords"),
numResults: lenientOptionalNumber().describe("Number of results (1-100, default: 10)"),
type: z
.enum(["auto", "lite", "fast", "instant"])
.enum(SEARCH_TYPES)
.optional()
.describe(
"Search type. 'auto' is the default and recommended type. It returns high-quality results and supports every parameter. 'lite' is the low-cost, low-latency type and serves cached content. It supports only query, numResults, category, includeDomains, excludeDomains, startPublishedDate, endPublishedDate, userLocation, moderation, textMaxCharacters and the highlights parameters. 'instant' and 'fast' are legacy types. Use 'lite' instead of 'instant'. Use 'lite' instead of 'fast', or 'auto' when the search needs a parameter 'lite' does not support.",
Expand Down Expand Up @@ -173,7 +198,7 @@ Returns: Search results with optional highlights, summaries, and subpage content
.describe("Additional query variations to expand search coverage"),

textMaxCharacters: lenientOptionalPositiveNumber().describe(
"Max characters for text extraction per result",
"Max characters of page text per result. Set it to get page text alongside highlights or summaries.",
),
contextMaxCharacters: lenientOptionalPositiveNumber().describe(
"Max characters for context string (not included by default)",
Expand Down Expand Up @@ -232,8 +257,9 @@ Returns: Search results with optional highlights, summaries, and subpage content
? {}
: { livecrawl: "fallback" as const };

const text = requestedText(params);
const contents: ExaAdvancedSearchRequest["contents"] = {
text: params.textMaxCharacters ? { maxCharacters: params.textMaxCharacters } : true,
...(text !== undefined && { text }),
...freshness,
...(params.livecrawlTimeout && { livecrawlTimeout: params.livecrawlTimeout }),
};
Expand Down
Loading
Loading