- Updated the Exa tool factory to emit details using a raw field rather than response. - Updated the MCP wrapped Exa tool to emit details using a raw field rather than response.
60 lines
2.1 KiB
TypeScript
60 lines
2.1 KiB
TypeScript
/**
|
|
* Shared factory for creating Exa tools with consistent error handling and response formatting.
|
|
*/
|
|
import type { TObject, TProperties } from "@sinclair/typebox";
|
|
import type { CustomTool } from "../extensibility/custom-tools/types";
|
|
import { callExaTool, findApiKey, formatGenericResponse, formatSearchResults, isSearchResponse } from "./mcp-client";
|
|
import type { ExaRenderDetails } from "./types";
|
|
|
|
/** Creates an Exa tool with standardized API key handling, error wrapping, and optional search response formatting. */
|
|
export function createExaTool(
|
|
name: string,
|
|
label: string,
|
|
description: string,
|
|
parameters: TObject<TProperties>,
|
|
mcpToolName: string,
|
|
options?: {
|
|
/** When true, checks isSearchResponse and formats with formatSearchResults. Default: true */
|
|
formatResponse?: boolean;
|
|
/** Transform params before passing to callExaTool */
|
|
transformParams?: (params: Record<string, unknown>) => Record<string, unknown>;
|
|
},
|
|
): CustomTool<any, ExaRenderDetails> {
|
|
const formatResponse = options?.formatResponse ?? true;
|
|
const transformParams = options?.transformParams;
|
|
|
|
return {
|
|
name,
|
|
label,
|
|
description,
|
|
parameters,
|
|
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
|
try {
|
|
const apiKey = findApiKey();
|
|
// Exa MCP endpoint is publicly accessible; API key is optional
|
|
const args = transformParams ? transformParams(params as Record<string, unknown>) : params;
|
|
const response = await callExaTool(mcpToolName, args, apiKey);
|
|
|
|
if (formatResponse && isSearchResponse(response)) {
|
|
const formatted = formatSearchResults(response);
|
|
return {
|
|
content: [{ type: "text" as const, text: formatted }],
|
|
details: { response, toolName: name },
|
|
};
|
|
}
|
|
|
|
return {
|
|
content: [{ type: "text" as const, text: formatGenericResponse(response) }],
|
|
details: { raw: response, toolName: name },
|
|
};
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
return {
|
|
content: [{ type: "text" as const, text: `Error: ${message}` }],
|
|
details: { error: message, toolName: name },
|
|
};
|
|
}
|
|
},
|
|
};
|
|
}
|