Skip to content
Open
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
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export function createGraphQLAgent(
},
});
const service = new GraphQLService(project, true, logger);
const tools = createGraphQLTools(service, project, logger);
const verbose = agentConfig.verbose > 0 ? String(agentConfig.verbose) : undefined;
const tools = createGraphQLTools(service, project, logger, verbose);
const agent = createReactAgent({llm, tools}).withConfig({
recursionLimit: 10,
});
Expand Down
2 changes: 1 addition & 1 deletion src/tools/graphql-schema-info.tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {Logger} from 'pino';
import {z} from 'zod';
import {type GraphQLProjectConfig, GraphqlProvider} from '../types.js';

export function createGraphQLSchemaInfoTool(config: GraphQLProjectConfig, logger?: Logger): DynamicStructuredTool {
export function createGraphQLSchemaInfoTool(config: GraphQLProjectConfig, logger?: Logger, verbose?: string): DynamicStructuredTool {
return new DynamicStructuredTool({
name: 'graphql_schema_info',
description: `Get the raw GraphQL entity schema with automatic node type detection and appropriate query patterns.
Expand Down
93 changes: 82 additions & 11 deletions src/tools/graphql-validate-excute.tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,49 @@ import {z} from 'zod';
import type {GraphQLService} from '../graphql.service.js';
import type {GraphQLProjectConfig} from '../types.js';

// Structured response type for tool output
interface DebugInfo {
graphql_query?: string;
execution_time_ms?: number;
response_size_kb?: string;
validation_errors?: string[];
execution_errors?: string[];
validation_stage?: string;
validation_time_ms?: number;
result_summary?: Record<string, unknown>;
}

interface ToolResponse {
result: string;
_debug?: DebugInfo;
}

export function createGraphQLValidatorAndExecuteTool(
config: GraphQLProjectConfig,
graphQLService: GraphQLService,
logger?: Logger
logger?: Logger,
verbose?: string
) {
const schema = z.object({
query: z.string().describe('The GraphQL query to validate'),
variables: z.record(z.string(), z.any()).optional().describe('Variables for the GraphQL query'),
});

const _execute = async (query: string, variables?: Record<string, any>) => {
// Helper function to format response based on verbose level
const formatResponse = (baseResult: string, debugInfo?: DebugInfo): string => {
// verbose = '0' or undefined: return plain text only
if (!verbose) {
return baseResult;
}
// verbose = '1' or '2': return structured JSON
const response: ToolResponse = { result: baseResult };
if (debugInfo) {
response._debug = debugInfo;
}
return JSON.stringify(response);
};
Comment on lines +39 to +51
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Comment contradicts code behavior for verbose='0'.

The comment on Line 41 states verbose = '0' or undefined: return plain text only, but the condition !verbose only handles undefined (and other falsy values). The string '0' is truthy in JavaScript, so !'0' evaluates to false, causing verbose='0' to fall through to the JSON branch unintentionally.

If the intent is for '0' to mean "no verbose output", the condition should explicitly check for it:

Proposed fix
   // Helper function to format response based on verbose level
   const formatResponse = (baseResult: string, debugInfo?: DebugInfo): string => {
     // verbose = '0' or undefined: return plain text only
-    if (!verbose) {
+    if (!verbose || verbose === '0') {
       return baseResult;
     }
     // verbose = '1' or '2': return structured JSON
🤖 Prompt for AI Agents
In `@src/tools/graphql-validate-excute.tool.ts` around lines 39 - 51, The comment
is wrong because formatResponse treats the string '0' as truthy; update
formatResponse to explicitly treat verbose === '0' as non-verbose so verbose='0'
returns plain text. Change the condition that currently reads if (!verbose) to
explicitly check for undefined/null or the string '0' (e.g., if (verbose ===
undefined || verbose === null || verbose === '0')) so formatResponse(baseResult,
debugInfo) returns baseResult for those cases and only builds the ToolResponse
JSON when verbose indicates structured output.


const _execute = async (query: string, variables?: Record<string, any>): Promise<string> => {
const startTime = Date.now();
logger?.info(
{
Expand Down Expand Up @@ -48,7 +80,13 @@ export function createGraphQLValidatorAndExecuteTool(
if (result.errors && result.errors.length > 0) {
const errorMessages = result.errors.map((error: any) => error.message || String(error));
logger?.error({errors: errorMessages}, 'Query execution failed');
return `❌ Query execution failed:\n${errorMessages.map((msg: string) => `- ${msg}`).join('\n')}`;
const baseResult = `❌ Query execution failed:\n${errorMessages.map((msg: string) => `- ${msg}`).join('\n')}`;
const debugInfo = verbose === '2' ? {
graphql_query: query,
execution_errors: errorMessages,
execution_time_ms: executionTime,
} : undefined;
return formatResponse(baseResult, debugInfo);
}

// Format the response
Expand All @@ -63,11 +101,31 @@ export function createGraphQLValidatorAndExecuteTool(
},
'Query executed successfully'
);
return `✅ Query executed successfully:\n\n${formattedData}`;

const baseResult = `✅ Query executed successfully:\n\n${formattedData}`;

let debugInfo: DebugInfo | undefined;
if (verbose === '1') {
debugInfo = {
graphql_query: query,
};
} else if (verbose === '2') {
debugInfo = {
graphql_query: query,
execution_time_ms: executionTime,
response_size_kb: (dataSize / 1024).toFixed(2),
result_summary: {
has_data: !!result.data,
data_type: typeof result.data,
},
};
}

return formatResponse(baseResult, debugInfo);
}

logger?.warn({result}, 'Unexpected response format');
return `⚠️ Unexpected response format:\n${JSON.stringify(result, null, 2)}`;
return formatResponse(`⚠️ Unexpected response format:\n${JSON.stringify(result, null, 2)}`);
} catch (error) {
const executionTime = Date.now() - startTime;
logger?.error(
Expand All @@ -77,7 +135,7 @@ export function createGraphQLValidatorAndExecuteTool(
},
'Error executing query'
);
return `Error executing query: ${(error as any).message}`;
return formatResponse(`Error executing query: ${(error as any).message}`);
}
};

Expand Down Expand Up @@ -185,7 +243,7 @@ export function createGraphQLValidatorAndExecuteTool(
// Check for basic GraphQL structure
if (!query) {
logger?.warn({}, 'Empty query provided');
return '❌ Validation failed: Empty query';
return formatResponse('❌ Validation failed: Empty query');
}

// Check for balanced braces
Expand Down Expand Up @@ -216,7 +274,13 @@ export function createGraphQLValidatorAndExecuteTool(
// Early return if basic syntax errors found
if (validationErrors.length > 0) {
logger?.error({errors: validationErrors}, 'Basic syntax validation failed');
return `❌ Basic syntax validation failed:\n${validationErrors.map((error: string) => `- ${error}`).join('\n')}`;
const baseResult = `❌ Basic syntax validation failed:\n${validationErrors.map((error: string) => `- ${error}`).join('\n')}`;
const debugInfo = verbose === '2' ? {
graphql_query: query,
validation_errors: validationErrors,
validation_stage: 'basic_syntax',
} : undefined;
return formatResponse(baseResult, debugInfo);
}

// Advanced validation with GraphQL parser
Expand Down Expand Up @@ -253,7 +317,14 @@ export function createGraphQLValidatorAndExecuteTool(
// If schema validation errors found, return them
if (schemaValidationErrors.length > 0) {
logger?.error({errors: schemaValidationErrors}, `Schema validation failed`);
return `❌ Schema validation failed:\n${schemaValidationErrors.map((error: string) => `- ${error}`).join('\n')}`;
const baseResult = `❌ Schema validation failed:\n${schemaValidationErrors.map((error: string) => `- ${error}`).join('\n')}`;
const debugInfo = verbose === '2' ? {
graphql_query: query,
validation_errors: schemaValidationErrors,
validation_stage: 'schema_validation',
validation_time_ms: validationTime,
} : undefined;
return formatResponse(baseResult, debugInfo);
}

// If we have GraphQL service but no cached schema, try to fetch and cache it
Expand All @@ -272,12 +343,12 @@ export function createGraphQLValidatorAndExecuteTool(
} catch (parseError: any) {
const validationTime = Date.now() - startTime;
logger?.error(parseError, `Query parsing failed after ${validationTime}ms`);
return `❌ Query parsing failed: ${parseError.message || String(parseError)}`;
return formatResponse(`❌ Query parsing failed: ${parseError.message || String(parseError)}`);
}
} catch (error: any) {
const validationTime = Date.now() - startTime;
logger?.error(error, `Unexpected error after ${validationTime}ms`);
return `Error validating query: ${error.message || String(error)}`;
return formatResponse(`Error validating query: ${error.message || String(error)}`);
}
},
});
Expand Down
7 changes: 4 additions & 3 deletions src/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import {createGraphQLValidatorAndExecuteTool} from './graphql-validate-excute.to
export function createGraphQLTools(
service: GraphQLService,
config: GraphQLProjectConfig,
logger?: Logger
logger?: Logger,
verbose?: string
): DynamicStructuredTool[] {
return [
createGraphQLSchemaInfoTool(config, logger),
createGraphQLValidatorAndExecuteTool(config, service, logger),
createGraphQLSchemaInfoTool(config, logger, verbose),
createGraphQLValidatorAndExecuteTool(config, service, logger, verbose),
// createGraphQLValidatorTool(config, service),
// createGraphQLExecuteTool(config, service)
];
Expand Down