feat: add QVeris tools for dynamic tool discovery and execution
- Introduced `createQverisTools` function to create QVeris search and execute tools. - Updated `TOOL_GROUPS` to include QVeris tools. - Added configuration options for QVeris in schema and types. - Implemented pre-search optimization for short queries in QVeris tools. - Enhanced error handling and response management for QVeris API interactions.
This commit is contained in:
parent
b151b8d196
commit
69b882c6fc
@ -18,6 +18,7 @@ import { createSessionsSendTool } from "./tools/sessions-send-tool.js";
|
|||||||
import { createSessionsSpawnTool } from "./tools/sessions-spawn-tool.js";
|
import { createSessionsSpawnTool } from "./tools/sessions-spawn-tool.js";
|
||||||
import { createWebFetchTool, createWebSearchTool } from "./tools/web-tools.js";
|
import { createWebFetchTool, createWebSearchTool } from "./tools/web-tools.js";
|
||||||
import { createTtsTool } from "./tools/tts-tool.js";
|
import { createTtsTool } from "./tools/tts-tool.js";
|
||||||
|
import { createQverisTools } from "./tools/qveris-tools.js";
|
||||||
|
|
||||||
export function createClawdbotTools(options?: {
|
export function createClawdbotTools(options?: {
|
||||||
browserControlUrl?: string;
|
browserControlUrl?: string;
|
||||||
@ -73,6 +74,11 @@ export function createClawdbotTools(options?: {
|
|||||||
config: options?.config,
|
config: options?.config,
|
||||||
sandboxed: options?.sandboxed,
|
sandboxed: options?.sandboxed,
|
||||||
});
|
});
|
||||||
|
const qverisTools = createQverisTools({
|
||||||
|
config: options?.config,
|
||||||
|
sandboxed: options?.sandboxed,
|
||||||
|
agentSessionKey: options?.agentSessionKey,
|
||||||
|
});
|
||||||
const tools: AnyAgentTool[] = [
|
const tools: AnyAgentTool[] = [
|
||||||
createBrowserTool({
|
createBrowserTool({
|
||||||
defaultControlUrl: options?.browserControlUrl,
|
defaultControlUrl: options?.browserControlUrl,
|
||||||
@ -143,6 +149,7 @@ export function createClawdbotTools(options?: {
|
|||||||
...(webSearchTool ? [webSearchTool] : []),
|
...(webSearchTool ? [webSearchTool] : []),
|
||||||
...(webFetchTool ? [webFetchTool] : []),
|
...(webFetchTool ? [webFetchTool] : []),
|
||||||
...(imageTool ? [imageTool] : []),
|
...(imageTool ? [imageTool] : []),
|
||||||
|
...qverisTools,
|
||||||
];
|
];
|
||||||
|
|
||||||
const pluginTools = resolvePluginTools({
|
const pluginTools = resolvePluginTools({
|
||||||
|
|||||||
@ -14,6 +14,8 @@ export const TOOL_GROUPS: Record<string, string[]> = {
|
|||||||
// NOTE: Keep canonical (lowercase) tool names here.
|
// NOTE: Keep canonical (lowercase) tool names here.
|
||||||
"group:memory": ["memory_search", "memory_get"],
|
"group:memory": ["memory_search", "memory_get"],
|
||||||
"group:web": ["web_search", "web_fetch"],
|
"group:web": ["web_search", "web_fetch"],
|
||||||
|
// QVeris dynamic tool discovery and execution
|
||||||
|
"group:qveris": ["qveris_search", "qveris_execute"],
|
||||||
// Basic workspace/file tools
|
// Basic workspace/file tools
|
||||||
"group:fs": ["read", "write", "edit", "apply_patch"],
|
"group:fs": ["read", "write", "edit", "apply_patch"],
|
||||||
// Host/runtime execution tools
|
// Host/runtime execution tools
|
||||||
@ -53,6 +55,8 @@ export const TOOL_GROUPS: Record<string, string[]> = {
|
|||||||
"web_search",
|
"web_search",
|
||||||
"web_fetch",
|
"web_fetch",
|
||||||
"image",
|
"image",
|
||||||
|
"qveris_search",
|
||||||
|
"qveris_execute",
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -61,7 +65,7 @@ const TOOL_PROFILES: Record<ToolProfileId, ToolProfilePolicy> = {
|
|||||||
allow: ["session_status"],
|
allow: ["session_status"],
|
||||||
},
|
},
|
||||||
coding: {
|
coding: {
|
||||||
allow: ["group:fs", "group:runtime", "group:sessions", "group:memory", "image"],
|
allow: ["group:fs", "group:runtime", "group:sessions", "group:memory", "image", "group:qveris"],
|
||||||
},
|
},
|
||||||
messaging: {
|
messaging: {
|
||||||
allow: [
|
allow: [
|
||||||
|
|||||||
516
src/agents/tools/qveris-tools.ts
Normal file
516
src/agents/tools/qveris-tools.ts
Normal file
@ -0,0 +1,516 @@
|
|||||||
|
import { Type } from "@sinclair/typebox";
|
||||||
|
|
||||||
|
import type { ClawdbotConfig } from "../../config/config.js";
|
||||||
|
import type { AnyAgentTool } from "./common.js";
|
||||||
|
import { jsonResult, readNumberParam, readStringParam } from "./common.js";
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Constants
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
const DEFAULT_QVERIS_BASE_URL = "https://qveris.ai/api/v1";
|
||||||
|
const DEFAULT_TIMEOUT_SECONDS = 60;
|
||||||
|
const DEFAULT_MAX_RESPONSE_SIZE = 20480;
|
||||||
|
const DEFAULT_SEARCH_LIMIT = 10;
|
||||||
|
const DEFAULT_PRE_SEARCH_BYTE_THRESHOLD = 100;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Types
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
type QverisConfig = NonNullable<ClawdbotConfig["tools"]>["qveris"];
|
||||||
|
|
||||||
|
/** Search result parameter from QVeris API */
|
||||||
|
interface QverisSearchResultParam {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
required: boolean;
|
||||||
|
description?: {
|
||||||
|
en?: string;
|
||||||
|
[key: string]: string | undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Example format from QVeris search API */
|
||||||
|
interface QverisSearchResultExamples {
|
||||||
|
sample_parameters?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Search result tool from QVeris API */
|
||||||
|
interface QverisSearchResultTool {
|
||||||
|
tool_id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
params?: QverisSearchResultParam[];
|
||||||
|
provider_description?: string;
|
||||||
|
stats?: {
|
||||||
|
avg_execution_time_ms?: number;
|
||||||
|
success_rate?: number;
|
||||||
|
};
|
||||||
|
examples?: QverisSearchResultExamples;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** QVeris search API response */
|
||||||
|
interface QverisSearchResponse {
|
||||||
|
query: string;
|
||||||
|
total: number;
|
||||||
|
results: QverisSearchResultTool[];
|
||||||
|
search_id: string;
|
||||||
|
elapsed_time_ms?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** QVeris tool execution response */
|
||||||
|
interface QverisExecutionResponse {
|
||||||
|
execution_id: string;
|
||||||
|
result: {
|
||||||
|
data: unknown;
|
||||||
|
};
|
||||||
|
success: boolean;
|
||||||
|
error_message: string | null;
|
||||||
|
elapsed_time_ms: number;
|
||||||
|
cost?: number;
|
||||||
|
credits_used?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Mapping from sanitized function name to original tool info */
|
||||||
|
interface QverisToolMapping {
|
||||||
|
functionName: string;
|
||||||
|
toolId: string;
|
||||||
|
searchId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Config Resolution
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
function resolveQverisConfig(cfg?: ClawdbotConfig): QverisConfig {
|
||||||
|
return cfg?.tools?.qveris;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveQverisEnabled(params: { config?: QverisConfig; sandboxed?: boolean }): boolean {
|
||||||
|
if (typeof params.config?.enabled === "boolean") return params.config.enabled;
|
||||||
|
// Enabled by default if API key is present
|
||||||
|
return Boolean(resolveQverisApiKey(params.config));
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveQverisApiKey(config?: QverisConfig): string | undefined {
|
||||||
|
const fromConfig =
|
||||||
|
config && "apiKey" in config && typeof config.apiKey === "string" ? config.apiKey.trim() : "";
|
||||||
|
const fromEnv = (process.env.QVERIS_API_KEY ?? "").trim();
|
||||||
|
return fromConfig || fromEnv || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveQverisBaseUrl(config?: QverisConfig): string {
|
||||||
|
return config?.baseUrl?.trim() || DEFAULT_QVERIS_BASE_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveTimeoutSeconds(config?: QverisConfig): number {
|
||||||
|
return config?.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveMaxResponseSize(config?: QverisConfig): number {
|
||||||
|
return config?.maxResponseSize ?? DEFAULT_MAX_RESPONSE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveSearchLimit(config?: QverisConfig): number {
|
||||||
|
return config?.searchLimit ?? DEFAULT_SEARCH_LIMIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolvePreSearchEnabled(config?: QverisConfig): boolean {
|
||||||
|
return config?.preSearchEnabled !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolvePreSearchByteThreshold(config?: QverisConfig): number {
|
||||||
|
return config?.preSearchByteThreshold ?? DEFAULT_PRE_SEARCH_BYTE_THRESHOLD;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// API Client
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
async function qverisSearch(params: {
|
||||||
|
query: string;
|
||||||
|
sessionId: string;
|
||||||
|
limit: number;
|
||||||
|
apiKey: string;
|
||||||
|
baseUrl: string;
|
||||||
|
timeoutSeconds: number;
|
||||||
|
}): Promise<QverisSearchResponse> {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), params.timeoutSeconds * 1000);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${params.baseUrl}/search`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${params.apiKey}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
query: params.query,
|
||||||
|
limit: params.limit,
|
||||||
|
session_id: params.sessionId,
|
||||||
|
}),
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const detail = await res.text().catch(() => "");
|
||||||
|
throw new Error(`QVeris search failed (${res.status}): ${detail || res.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await res.json()) as QverisSearchResponse;
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function qverisExecute(params: {
|
||||||
|
toolId: string;
|
||||||
|
searchId: string;
|
||||||
|
sessionId: string;
|
||||||
|
parameters: Record<string, unknown>;
|
||||||
|
maxResponseSize: number;
|
||||||
|
apiKey: string;
|
||||||
|
baseUrl: string;
|
||||||
|
timeoutSeconds: number;
|
||||||
|
}): Promise<QverisExecutionResponse> {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), params.timeoutSeconds * 1000);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`${params.baseUrl}/tools/execute?tool_id=${encodeURIComponent(params.toolId)}`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${params.apiKey}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
parameters: params.parameters,
|
||||||
|
max_response_size: params.maxResponseSize,
|
||||||
|
search_id: params.searchId,
|
||||||
|
session_id: params.sessionId,
|
||||||
|
}),
|
||||||
|
signal: controller.signal,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const detail = await res.text().catch(() => "");
|
||||||
|
throw new Error(`QVeris execute failed (${res.status}): ${detail || res.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await res.json()) as QverisExecutionResponse;
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Tool Schemas
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
const QverisSearchSchema = Type.Object({
|
||||||
|
query: Type.String({
|
||||||
|
description:
|
||||||
|
"Search query describing the capability of the tool you need. Describe what you want to accomplish, not specific params to pass. Example: 'weather forecast API', 'send email', 'stock prices'.",
|
||||||
|
}),
|
||||||
|
limit: Type.Optional(
|
||||||
|
Type.Number({
|
||||||
|
description: "Maximum number of results to return (1-100). Default: 10.",
|
||||||
|
minimum: 1,
|
||||||
|
maximum: 100,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const QverisExecuteSchema = Type.Object({
|
||||||
|
tool_id: Type.String({
|
||||||
|
description: "The ID of the tool to execute. Must come from a previous qveris_search call.",
|
||||||
|
}),
|
||||||
|
search_id: Type.String({
|
||||||
|
description:
|
||||||
|
"The search_id from the qveris_search response that returned this tool. Required for linking execution to the original search.",
|
||||||
|
}),
|
||||||
|
params_to_tool: Type.String({
|
||||||
|
description:
|
||||||
|
'A JSON stringified dictionary of parameters to pass to the tool. Keys are param names and values can be of any type. Example: \'{"city": "London", "units": "metric"}\'.',
|
||||||
|
}),
|
||||||
|
max_response_size: Type.Optional(
|
||||||
|
Type.Number({
|
||||||
|
description:
|
||||||
|
"Maximum size of response data in bytes. If tool generates data longer than this, it will be truncated. Default: 20480 (20KB).",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Pre-Search Handler
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PreSearchToolHandler manages the pre-search flow:
|
||||||
|
* 1. Determines if pre-search should be performed based on first query length
|
||||||
|
* 2. Performs pre-search and stores results
|
||||||
|
* 3. Provides tool mappings for execution
|
||||||
|
*/
|
||||||
|
class PreSearchToolHandler {
|
||||||
|
private searchId: string = "";
|
||||||
|
private toolMappings = new Map<string, QverisToolMapping>();
|
||||||
|
private preSearchedTools: QverisSearchResultTool[] = [];
|
||||||
|
private preSearchPerformed: boolean = false;
|
||||||
|
private preSearchEnabled: boolean = false;
|
||||||
|
private byteThreshold: number;
|
||||||
|
|
||||||
|
constructor(byteThreshold: number = DEFAULT_PRE_SEARCH_BYTE_THRESHOLD) {
|
||||||
|
this.byteThreshold = byteThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if pre-search should be performed
|
||||||
|
* Conditions:
|
||||||
|
* 1. First query length in bytes <= threshold
|
||||||
|
*/
|
||||||
|
shouldPreSearch(firstQuery: string): boolean {
|
||||||
|
const byteLength = new TextEncoder().encode(firstQuery).length;
|
||||||
|
return byteLength <= this.byteThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform pre-search using the query
|
||||||
|
*/
|
||||||
|
async preSearch(params: {
|
||||||
|
query: string;
|
||||||
|
sessionId: string;
|
||||||
|
apiKey: string;
|
||||||
|
baseUrl: string;
|
||||||
|
timeoutSeconds: number;
|
||||||
|
searchLimit: number;
|
||||||
|
}): Promise<QverisSearchResponse | null> {
|
||||||
|
if (this.preSearchPerformed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await qverisSearch({
|
||||||
|
query: params.query,
|
||||||
|
sessionId: params.sessionId,
|
||||||
|
limit: params.searchLimit,
|
||||||
|
apiKey: params.apiKey,
|
||||||
|
baseUrl: params.baseUrl,
|
||||||
|
timeoutSeconds: params.timeoutSeconds,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.results && result.results.length > 0) {
|
||||||
|
this.searchId = result.search_id;
|
||||||
|
this.preSearchedTools = result.results;
|
||||||
|
|
||||||
|
// Store mappings for execution
|
||||||
|
for (const tool of result.results) {
|
||||||
|
const functionName = sanitizeToolName(tool.tool_id);
|
||||||
|
this.toolMappings.set(functionName, {
|
||||||
|
functionName,
|
||||||
|
toolId: tool.tool_id,
|
||||||
|
searchId: result.search_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.preSearchEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.preSearchPerformed = true;
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
this.preSearchPerformed = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isPreSearchEnabled(): boolean {
|
||||||
|
return this.preSearchEnabled && this.preSearchedTools.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPreSearchedTools(): QverisSearchResultTool[] {
|
||||||
|
return this.preSearchedTools;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSearchId(): string {
|
||||||
|
return this.searchId;
|
||||||
|
}
|
||||||
|
|
||||||
|
isPreSearchedTool(toolName: string): boolean {
|
||||||
|
return this.toolMappings.has(toolName);
|
||||||
|
}
|
||||||
|
|
||||||
|
getToolMapping(functionName: string): QverisToolMapping | undefined {
|
||||||
|
return this.toolMappings.get(functionName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize tool ID to create a valid function name
|
||||||
|
*/
|
||||||
|
function sanitizeToolName(toolId: string): string {
|
||||||
|
if (!toolId || typeof toolId !== "string") {
|
||||||
|
toolId = "unknown_tool";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace invalid characters with underscore
|
||||||
|
let sanitized = toolId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
||||||
|
|
||||||
|
// Remove leading hyphens and numbers
|
||||||
|
sanitized = sanitized.replace(/^[-0-9]+/, "");
|
||||||
|
|
||||||
|
// If empty after sanitization, use a default name
|
||||||
|
if (!sanitized || sanitized === "_") {
|
||||||
|
sanitized = "tool_" + Date.now().toString(36);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure starts with letter or underscore
|
||||||
|
if (sanitized.startsWith("-")) {
|
||||||
|
sanitized = "_" + sanitized.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate to 64 characters
|
||||||
|
if (sanitized.length > 64) {
|
||||||
|
sanitized = sanitized.substring(0, 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sanitized;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Tool Creation
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export function createQverisTools(options?: {
|
||||||
|
config?: ClawdbotConfig;
|
||||||
|
sandboxed?: boolean;
|
||||||
|
agentSessionKey?: string;
|
||||||
|
}): AnyAgentTool[] {
|
||||||
|
const config = resolveQverisConfig(options?.config);
|
||||||
|
|
||||||
|
if (!resolveQverisEnabled({ config, sandboxed: options?.sandboxed })) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiKey = resolveQverisApiKey(config);
|
||||||
|
if (!apiKey) {
|
||||||
|
// Return empty array if no API key - tools won't be available
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseUrl = resolveQverisBaseUrl(config);
|
||||||
|
const timeoutSeconds = resolveTimeoutSeconds(config);
|
||||||
|
const maxResponseSize = resolveMaxResponseSize(config);
|
||||||
|
const searchLimit = resolveSearchLimit(config);
|
||||||
|
|
||||||
|
// Generate a session ID tied to clawdbot session key
|
||||||
|
const sessionId = options?.agentSessionKey ?? `clawdbot-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
|
const searchTool: AnyAgentTool = {
|
||||||
|
label: "QVeris Search",
|
||||||
|
name: "qveris_search",
|
||||||
|
description:
|
||||||
|
"Search for available third-party tools using natural language. Returns relevant tools that can help accomplish tasks. Use this to discover tools before executing them with qveris_execute.",
|
||||||
|
parameters: QverisSearchSchema,
|
||||||
|
execute: async (_toolCallId, args) => {
|
||||||
|
const params = args as Record<string, unknown>;
|
||||||
|
const query = readStringParam(params, "query", { required: true });
|
||||||
|
const limit = readNumberParam(params, "limit", { integer: true }) ?? searchLimit;
|
||||||
|
|
||||||
|
const result = await qverisSearch({
|
||||||
|
query,
|
||||||
|
sessionId,
|
||||||
|
limit: Math.min(Math.max(1, limit), 100),
|
||||||
|
apiKey,
|
||||||
|
baseUrl,
|
||||||
|
timeoutSeconds,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return simplified result for the model
|
||||||
|
return jsonResult({
|
||||||
|
query: result.query,
|
||||||
|
total: result.total,
|
||||||
|
search_id: result.search_id,
|
||||||
|
elapsed_time_ms: result.elapsed_time_ms,
|
||||||
|
results: result.results.map((tool) => ({
|
||||||
|
tool_id: tool.tool_id,
|
||||||
|
name: tool.name,
|
||||||
|
description: tool.description,
|
||||||
|
params: tool.params?.map((p) => ({
|
||||||
|
name: p.name,
|
||||||
|
type: p.type,
|
||||||
|
required: p.required,
|
||||||
|
description: p.description?.en ?? Object.values(p.description ?? {})[0],
|
||||||
|
})),
|
||||||
|
examples: tool.examples?.sample_parameters
|
||||||
|
? { sample_parameters: tool.examples.sample_parameters }
|
||||||
|
: undefined,
|
||||||
|
stats: tool.stats,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const executeTool: AnyAgentTool = {
|
||||||
|
label: "QVeris Execute",
|
||||||
|
name: "qveris_execute",
|
||||||
|
description:
|
||||||
|
"Execute a specific third-party tool with provided parameters. The tool_id and search_id must come from a previous qveris_search call. Pass parameters to the tool through params_to_tool as a JSON string.",
|
||||||
|
parameters: QverisExecuteSchema,
|
||||||
|
execute: async (_toolCallId, args) => {
|
||||||
|
const params = args as Record<string, unknown>;
|
||||||
|
const toolId = readStringParam(params, "tool_id", { required: true });
|
||||||
|
const searchId = readStringParam(params, "search_id", { required: true });
|
||||||
|
const paramsToToolRaw = readStringParam(params, "params_to_tool", { required: true });
|
||||||
|
const maxSize =
|
||||||
|
readNumberParam(params, "max_response_size", { integer: true }) ?? maxResponseSize;
|
||||||
|
|
||||||
|
// Parse params_to_tool JSON
|
||||||
|
let toolParams: Record<string, unknown>;
|
||||||
|
try {
|
||||||
|
toolParams = JSON.parse(paramsToToolRaw) as Record<string, unknown>;
|
||||||
|
} catch (parseError) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid JSON in params_to_tool: ${parseError instanceof Error ? parseError.message : "Unknown parse error"}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await qverisExecute({
|
||||||
|
toolId,
|
||||||
|
searchId,
|
||||||
|
sessionId,
|
||||||
|
parameters: toolParams,
|
||||||
|
maxResponseSize: maxSize,
|
||||||
|
apiKey,
|
||||||
|
baseUrl,
|
||||||
|
timeoutSeconds,
|
||||||
|
});
|
||||||
|
|
||||||
|
return jsonResult({
|
||||||
|
execution_id: result.execution_id,
|
||||||
|
success: result.success,
|
||||||
|
elapsed_time_ms: result.elapsed_time_ms,
|
||||||
|
result: result.result?.data,
|
||||||
|
error_message: result.error_message,
|
||||||
|
cost: result.cost ?? result.credits_used,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return [searchTool, executeTool];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if QVeris tools are enabled/available
|
||||||
|
*/
|
||||||
|
export function isQverisEnabled(options?: {
|
||||||
|
config?: ClawdbotConfig;
|
||||||
|
sandboxed?: boolean;
|
||||||
|
}): boolean {
|
||||||
|
const config = resolveQverisConfig(options?.config);
|
||||||
|
return resolveQverisEnabled({ config, sandboxed: options?.sandboxed }) && Boolean(resolveQverisApiKey(config));
|
||||||
|
}
|
||||||
@ -199,6 +199,14 @@ const FIELD_LABELS: Record<string, string> = {
|
|||||||
"tools.web.fetch.cacheTtlMinutes": "Web Fetch Cache TTL (min)",
|
"tools.web.fetch.cacheTtlMinutes": "Web Fetch Cache TTL (min)",
|
||||||
"tools.web.fetch.maxRedirects": "Web Fetch Max Redirects",
|
"tools.web.fetch.maxRedirects": "Web Fetch Max Redirects",
|
||||||
"tools.web.fetch.userAgent": "Web Fetch User-Agent",
|
"tools.web.fetch.userAgent": "Web Fetch User-Agent",
|
||||||
|
"tools.qveris.enabled": "Enable QVeris Tools",
|
||||||
|
"tools.qveris.apiKey": "QVeris API Key",
|
||||||
|
"tools.qveris.baseUrl": "QVeris API Base URL",
|
||||||
|
"tools.qveris.timeoutSeconds": "QVeris Timeout (sec)",
|
||||||
|
"tools.qveris.maxResponseSize": "QVeris Max Response Size",
|
||||||
|
"tools.qveris.searchLimit": "QVeris Search Limit",
|
||||||
|
"tools.qveris.preSearchEnabled": "QVeris Pre-Search",
|
||||||
|
"tools.qveris.preSearchByteThreshold": "QVeris Pre-Search Threshold",
|
||||||
"gateway.controlUi.basePath": "Control UI Base Path",
|
"gateway.controlUi.basePath": "Control UI Base Path",
|
||||||
"gateway.controlUi.allowInsecureAuth": "Allow Insecure Control UI Auth",
|
"gateway.controlUi.allowInsecureAuth": "Allow Insecure Control UI Auth",
|
||||||
"gateway.controlUi.dangerouslyDisableDeviceAuth": "Dangerously Disable Control UI Device Auth",
|
"gateway.controlUi.dangerouslyDisableDeviceAuth": "Dangerously Disable Control UI Device Auth",
|
||||||
@ -462,6 +470,14 @@ const FIELD_HELP: Record<string, string> = {
|
|||||||
"tools.web.fetch.firecrawl.maxAgeMs":
|
"tools.web.fetch.firecrawl.maxAgeMs":
|
||||||
"Firecrawl maxAge (ms) for cached results when supported by the API.",
|
"Firecrawl maxAge (ms) for cached results when supported by the API.",
|
||||||
"tools.web.fetch.firecrawl.timeoutSeconds": "Timeout in seconds for Firecrawl requests.",
|
"tools.web.fetch.firecrawl.timeoutSeconds": "Timeout in seconds for Firecrawl requests.",
|
||||||
|
"tools.qveris.enabled": "Enable QVeris dynamic tool discovery (requires API key).",
|
||||||
|
"tools.qveris.apiKey": "QVeris API key (fallback: QVERIS_API_KEY env var).",
|
||||||
|
"tools.qveris.baseUrl": "QVeris API base URL (default: https://qveris.ai/api/v1).",
|
||||||
|
"tools.qveris.timeoutSeconds": "Timeout in seconds for QVeris requests (default: 60).",
|
||||||
|
"tools.qveris.maxResponseSize": "Max response size in bytes for QVeris tool execution (default: 20480).",
|
||||||
|
"tools.qveris.searchLimit": "Default number of tools to return from search (default: 10).",
|
||||||
|
"tools.qveris.preSearchEnabled": "Enable pre-search optimization for short queries (default: true).",
|
||||||
|
"tools.qveris.preSearchByteThreshold": "Byte threshold for pre-search (queries under this limit trigger pre-search; default: 100).",
|
||||||
"channels.slack.allowBots":
|
"channels.slack.allowBots":
|
||||||
"Allow bot-authored messages to trigger Slack replies (default: false).",
|
"Allow bot-authored messages to trigger Slack replies (default: false).",
|
||||||
"channels.slack.thread.historyScope":
|
"channels.slack.thread.historyScope":
|
||||||
|
|||||||
@ -443,4 +443,23 @@ export type ToolsConfig = {
|
|||||||
deny?: string[];
|
deny?: string[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/** QVeris dynamic tool discovery and execution. */
|
||||||
|
qveris?: {
|
||||||
|
/** Enable QVeris tools (default: true when API key is present). */
|
||||||
|
enabled?: boolean;
|
||||||
|
/** QVeris API key (optional; defaults to QVERIS_API_KEY env var). */
|
||||||
|
apiKey?: string;
|
||||||
|
/** QVeris API base URL (default: https://qveris.ai/api/v1). */
|
||||||
|
baseUrl?: string;
|
||||||
|
/** Timeout in seconds for QVeris requests (default: 60). */
|
||||||
|
timeoutSeconds?: number;
|
||||||
|
/** Max response size in bytes (default: 20480). */
|
||||||
|
maxResponseSize?: number;
|
||||||
|
/** Default search result limit (default: 10). */
|
||||||
|
searchLimit?: number;
|
||||||
|
/** Enable pre-search optimization for short queries (default: true). */
|
||||||
|
preSearchEnabled?: boolean;
|
||||||
|
/** Byte threshold for pre-search (default: 100). */
|
||||||
|
preSearchByteThreshold?: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -536,6 +536,19 @@ export const ToolsSchema = z
|
|||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.optional(),
|
.optional(),
|
||||||
|
qveris: z
|
||||||
|
.object({
|
||||||
|
enabled: z.boolean().optional(),
|
||||||
|
apiKey: z.string().optional(),
|
||||||
|
baseUrl: z.string().optional(),
|
||||||
|
timeoutSeconds: z.number().int().positive().optional(),
|
||||||
|
maxResponseSize: z.number().int().positive().optional(),
|
||||||
|
searchLimit: z.number().int().positive().optional(),
|
||||||
|
preSearchEnabled: z.boolean().optional(),
|
||||||
|
preSearchByteThreshold: z.number().int().positive().optional(),
|
||||||
|
})
|
||||||
|
.strict()
|
||||||
|
.optional(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.superRefine((value, ctx) => {
|
.superRefine((value, ctx) => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user