code-graph-context 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,26 @@
1
+ import OpenAI from 'openai';
2
+ export class EmbeddingsService {
3
+ openai;
4
+ model;
5
+ constructor(model = 'text-embedding-3-large') {
6
+ const apiKey = process.env.OPENAI_API_KEY;
7
+ if (!apiKey) {
8
+ throw new Error('OPENAI_API_KEY environment variable is required');
9
+ }
10
+ this.openai = new OpenAI({ apiKey });
11
+ this.model = model;
12
+ }
13
+ async embedText(text) {
14
+ try {
15
+ const response = await this.openai.embeddings.create({
16
+ model: this.model,
17
+ input: text,
18
+ });
19
+ return response.data[0].embedding;
20
+ }
21
+ catch (error) {
22
+ console.error('Error creating embedding:', error);
23
+ throw error;
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,148 @@
1
+ import fs from 'fs';
2
+ import OpenAI from 'openai';
3
+ export class NaturalLanguageToCypherService {
4
+ assistantId;
5
+ openai;
6
+ MODEL = 'gpt-4o-mini'; // Using GPT-4 Turbo
7
+ messageInstructions = `
8
+ The schema file (neo4j-apoc-schema.json) contains two sections:
9
+ 1. rawSchema: Complete Neo4j APOC schema with all node labels, properties, and relationships in the graph
10
+ 2. domainContext: Framework-specific semantics including:
11
+ - nodeTypes: Descriptions and example queries for each node type
12
+ - relationships: How nodes connect with context about relationship properties
13
+ - commonQueryPatterns: Pre-built example queries for common use cases
14
+
15
+ Your response must be a valid JSON object with this exact structure:
16
+ {
17
+ "cypher": "MATCH (n:NodeType) WHERE n.property = $param RETURN n",
18
+ "parameters": { "param": "value" } | null,
19
+ "explanation": "Concise explanation of what the query does and why it matches the user's request"
20
+ }
21
+
22
+ Query Generation Process:
23
+ 1. CHECK DOMAIN CONTEXT: Look at domainContext.nodeTypes to understand available node types and their properties
24
+ 2. REVIEW EXAMPLES: Check domainContext.commonQueryPatterns for similar query examples
25
+ 3. CHECK RELATIONSHIPS: Look at domainContext.relationships to understand how nodes connect
26
+ 4. EXAMINE NODE PROPERTIES: Use rawSchema to see exact property names and types
27
+ 5. HANDLE JSON PROPERTIES: If properties or relationship context are stored as JSON strings, use apoc.convert.fromJsonMap() to parse them
28
+ 6. GENERATE QUERY: Write the Cypher query using only node labels, relationships, and properties that exist in the schema
29
+
30
+ Critical Rules:
31
+ - Use the schema information from the file_search tool - do not guess node labels or relationships
32
+ - Use ONLY node labels and properties found in rawSchema
33
+ - For nested JSON data in properties, use: apoc.convert.fromJsonMap(node.propertyName) or apoc.convert.fromJsonMap(relationship.context)
34
+ - Check domainContext for parsing instructions specific to certain node types (e.g., some nodes may store arrays of objects in JSON format)
35
+ - Follow the example queries in commonQueryPatterns for proper syntax patterns
36
+ - Use parameterized queries with $ syntax for any dynamic values
37
+ - Return only the data relevant to the user's request
38
+
39
+ Provide ONLY the JSON response with no additional text, markdown formatting, or explanations outside the JSON structure.
40
+ `;
41
+ constructor() {
42
+ const apiKey = process.env.OPENAI_API_KEY;
43
+ if (!apiKey) {
44
+ throw new Error('OPENAI_API_KEY environment variable is required');
45
+ }
46
+ this.openai = new OpenAI({ apiKey });
47
+ }
48
+ async getOrCreateAssistant(schemaPath) {
49
+ if (process.env.OPENAI_ASSISTANT_ID) {
50
+ this.assistantId = process.env.OPENAI_ASSISTANT_ID;
51
+ console.log(`Using existing assistant with ID: ${this.assistantId} `);
52
+ return this.assistantId;
53
+ }
54
+ const schemaFile = await this.openai.files.create({
55
+ file: fs.createReadStream(schemaPath),
56
+ purpose: 'assistants',
57
+ });
58
+ // Create a vector store for the schema file
59
+ const vectorStore = await this.openai.vectorStores.create({
60
+ name: 'Neo4j APOC Schema Vector Store',
61
+ file_ids: [schemaFile.id],
62
+ metadata: { type: 'neo4j_apoc_schema' },
63
+ });
64
+ const vectorStoreId = vectorStore.id;
65
+ // Create a new assistant
66
+ const assistantConfig = {
67
+ name: 'Neo4j Cypher Query Agent',
68
+ description: 'An agent that helps convert natural language to Neo4j Cypher queries',
69
+ model: this.MODEL,
70
+ instructions: `
71
+ You are a specialized assistant that helps convert natural language requests into Neo4j Cypher queries.
72
+ When users ask questions about their codebase data, you'll analyze their intent and generate appropriate
73
+ Cypher queries based on the Neo4j schema provided in files.
74
+ ${this.messageInstructions}
75
+ `,
76
+ tools: [
77
+ {
78
+ type: 'code_interpreter',
79
+ },
80
+ {
81
+ type: 'file_search',
82
+ },
83
+ ],
84
+ tool_resources: {
85
+ code_interpreter: {
86
+ file_ids: [schemaFile.id],
87
+ },
88
+ file_search: {
89
+ vector_store_ids: [vectorStoreId],
90
+ },
91
+ },
92
+ };
93
+ const assistant = await this.openai.beta.assistants.create(assistantConfig);
94
+ this.assistantId = assistant.id;
95
+ return this.assistantId;
96
+ }
97
+ async promptToQuery(userPrompt) {
98
+ const prompt = `Please convert this request to a valid Neo4j Cypher query: ${userPrompt}.
99
+ Use the Neo4j schema provided and follow the format specified in the instructions.
100
+ `;
101
+ console.log('Prompt:', prompt);
102
+ const run = await this.openai.beta.threads.createAndRunPoll({
103
+ assistant_id: this.assistantId,
104
+ thread: {
105
+ messages: [
106
+ {
107
+ role: 'user',
108
+ content: prompt,
109
+ },
110
+ ],
111
+ },
112
+ });
113
+ const threadId = run.thread_id;
114
+ console.log(`Thread ID: ${threadId}`);
115
+ console.log('Run status:', run.status);
116
+ console.log('Required actions:', run.required_action);
117
+ console.log('Last error:', run.last_error);
118
+ // Validate run completed successfully
119
+ if (run.status !== 'completed') {
120
+ console.error('Full run object:', JSON.stringify(run, null, 2));
121
+ throw new Error(`Assistant run did not complete. Status: ${run.status}. ` +
122
+ `Last error: ${run.last_error ? JSON.stringify(run.last_error) : 'none'}`);
123
+ }
124
+ const messages = await this.openai.beta.threads.messages.list(threadId);
125
+ // Find the first text content in the latest message
126
+ const latestMessage = messages.data[0];
127
+ console.log('Latest message:', JSON.stringify(latestMessage, null, 2));
128
+ const textContent = latestMessage.content.find((content) => content.type === 'text');
129
+ if (!textContent) {
130
+ throw new Error(`No text content found in assistant response. Run status: ${run.status}`);
131
+ }
132
+ // Validate that the text property exists and extract the value safely
133
+ const textValue = textContent.text?.value;
134
+ if (!textValue) {
135
+ throw new Error(`Invalid text content structure in assistant response. Run status: ${run.status}. ` +
136
+ `Text content: ${JSON.stringify(textContent)}`);
137
+ }
138
+ console.log('text value:', textValue);
139
+ return JSON.parse(textValue);
140
+ }
141
+ /**
142
+ * Create a new thread for a user
143
+ */
144
+ async createThread() {
145
+ const thread = await this.openai.beta.threads.create();
146
+ return thread.id;
147
+ }
148
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Parser Factory
3
+ * Creates TypeScript parsers with appropriate framework schemas
4
+ */
5
+ import { FAIRSQUARE_FRAMEWORK_SCHEMA } from '../config/fairsquare-framework-schema.js';
6
+ import { NESTJS_FRAMEWORK_SCHEMA } from '../config/nestjs-framework-schema.js';
7
+ import { CORE_TYPESCRIPT_SCHEMA, CoreNodeType } from '../config/schema.js';
8
+ import { TypeScriptParser } from './typescript-parser.js';
9
+ export var ProjectType;
10
+ (function (ProjectType) {
11
+ ProjectType["NESTJS"] = "nestjs";
12
+ ProjectType["FAIRSQUARE"] = "fairsquare";
13
+ ProjectType["BOTH"] = "both";
14
+ ProjectType["VANILLA"] = "vanilla";
15
+ })(ProjectType || (ProjectType = {}));
16
+ export class ParserFactory {
17
+ /**
18
+ * Create a parser with appropriate framework schemas
19
+ */
20
+ static createParser(options) {
21
+ const { workspacePath, tsConfigPath = 'tsconfig.json', projectType = ProjectType.NESTJS, // Default to NestJS (use auto-detect for best results)
22
+ customFrameworkSchemas = [], excludePatterns = ['node_modules', 'dist', 'build', '.spec.', '.test.'], excludedNodeTypes = [CoreNodeType.PARAMETER_DECLARATION], } = options;
23
+ // Select framework schemas based on project type
24
+ const frameworkSchemas = this.selectFrameworkSchemas(projectType, customFrameworkSchemas);
25
+ console.log(`📦 Creating parser for ${projectType} project`);
26
+ console.log(`📚 Framework schemas: ${frameworkSchemas.map((s) => s.name).join(', ')}`);
27
+ return new TypeScriptParser(workspacePath, tsConfigPath, CORE_TYPESCRIPT_SCHEMA, frameworkSchemas, {
28
+ excludePatterns,
29
+ excludedNodeTypes,
30
+ });
31
+ }
32
+ /**
33
+ * Select framework schemas based on project type
34
+ */
35
+ static selectFrameworkSchemas(projectType, customSchemas) {
36
+ const schemas = [];
37
+ switch (projectType) {
38
+ case ProjectType.NESTJS:
39
+ schemas.push(NESTJS_FRAMEWORK_SCHEMA);
40
+ break;
41
+ case ProjectType.FAIRSQUARE:
42
+ schemas.push(FAIRSQUARE_FRAMEWORK_SCHEMA);
43
+ break;
44
+ case ProjectType.BOTH:
45
+ // Apply FairSquare first (higher priority), then NestJS
46
+ schemas.push(FAIRSQUARE_FRAMEWORK_SCHEMA);
47
+ schemas.push(NESTJS_FRAMEWORK_SCHEMA);
48
+ break;
49
+ case ProjectType.VANILLA:
50
+ // No framework schemas
51
+ break;
52
+ }
53
+ // Add any custom schemas
54
+ schemas.push(...customSchemas);
55
+ return schemas;
56
+ }
57
+ /**
58
+ * Auto-detect project type from workspace
59
+ */
60
+ static async detectProjectType(workspacePath) {
61
+ const fs = await import('fs/promises');
62
+ const path = await import('path');
63
+ try {
64
+ const packageJsonPath = path.join(workspacePath, 'package.json');
65
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
66
+ const deps = {
67
+ ...packageJson.dependencies,
68
+ ...packageJson.devDependencies,
69
+ };
70
+ const hasNestJS = '@nestjs/common' in deps || '@nestjs/core' in deps;
71
+ const hasFairSquare = '@fairsquare/core' in deps || '@fairsquare/server' in deps;
72
+ if (hasFairSquare && hasNestJS) {
73
+ return ProjectType.BOTH;
74
+ }
75
+ else if (hasFairSquare) {
76
+ return ProjectType.FAIRSQUARE;
77
+ }
78
+ else if (hasNestJS) {
79
+ return ProjectType.NESTJS;
80
+ }
81
+ else {
82
+ return ProjectType.VANILLA;
83
+ }
84
+ }
85
+ catch (error) {
86
+ console.warn('Could not detect project type, defaulting to vanilla TypeScript');
87
+ return ProjectType.VANILLA;
88
+ }
89
+ }
90
+ /**
91
+ * Create parser with auto-detection
92
+ */
93
+ static async createParserWithAutoDetection(workspacePath, tsConfigPath) {
94
+ const projectType = await this.detectProjectType(workspacePath);
95
+ console.log(`🔍 Auto-detected project type: ${projectType}`);
96
+ return this.createParser({
97
+ workspacePath,
98
+ tsConfigPath,
99
+ projectType,
100
+ });
101
+ }
102
+ }