chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
# Python → TypeScript Claude Context Bridge
|
||||
|
||||
A simple utility to call TypeScript Claude Context methods from Python.
|
||||
|
||||
## What's This?
|
||||
|
||||
This directory contains a basic bridge that allows you to run Claude Context TypeScript functions from Python scripts. It's not a full SDK - just a simple way to test and use the TypeScript codebase from Python.
|
||||
|
||||
## Files
|
||||
|
||||
- `ts_executor.py` - Executes TypeScript methods from Python
|
||||
- `test_context.ts` - TypeScript test script with Claude Context workflow
|
||||
- `test_endtoend.py` - Python script that calls the TypeScript test
|
||||
|
||||
## Prerequisites
|
||||
|
||||
```bash
|
||||
# Make sure you have Node.js dependencies installed
|
||||
cd .. && pnpm install
|
||||
|
||||
# Set your OpenAI API key (required for actual indexing)
|
||||
export OPENAI_API_KEY="your-openai-api-key"
|
||||
|
||||
# Optional: Set Milvus address (defaults to localhost:19530)
|
||||
export MILVUS_ADDRESS="localhost:19530"
|
||||
```
|
||||
|
||||
## Quick Usage
|
||||
|
||||
```bash
|
||||
# Run the end-to-end test
|
||||
python test_endtoend.py
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Create embeddings using OpenAI
|
||||
2. Connect to Milvus vector database
|
||||
3. Index the `packages/core/src` codebase
|
||||
4. Perform a semantic search
|
||||
5. Show results
|
||||
|
||||
## Manual Usage
|
||||
|
||||
```python
|
||||
from ts_executor import TypeScriptExecutor
|
||||
|
||||
executor = TypeScriptExecutor()
|
||||
result = executor.call_method(
|
||||
'./test_context.ts',
|
||||
'testContextEndToEnd',
|
||||
{
|
||||
'openaiApiKey': 'sk-your-key',
|
||||
'milvusAddress': 'localhost:19530',
|
||||
'codebasePath': '../packages/core/src',
|
||||
'searchQuery': 'vector database configuration'
|
||||
}
|
||||
)
|
||||
|
||||
print(result)
|
||||
```
|
||||
## How It Works
|
||||
|
||||
1. `ts_executor.py` creates temporary TypeScript wrapper files
|
||||
2. Runs them with `ts-node`
|
||||
3. Captures JSON output and returns to Python
|
||||
4. Supports async functions and complex parameters
|
||||
|
||||
That's it! This is just a simple bridge for testing purposes.
|
||||
@@ -0,0 +1,112 @@
|
||||
import { Context } from '../packages/core/src/context';
|
||||
import { OpenAIEmbedding } from '../packages/core/src/embedding/openai-embedding';
|
||||
import { MilvusVectorDatabase } from '../packages/core/src/vectordb/milvus-vectordb';
|
||||
import { AstCodeSplitter } from '../packages/core/src/splitter/ast-splitter';
|
||||
|
||||
/**
|
||||
* Context End-to-End Test - Complete Workflow
|
||||
* Includes: Configure Embedding → Configure Vector Database → Create Context → Index Codebase → Semantic Search
|
||||
*/
|
||||
export async function testContextEndToEnd(config: {
|
||||
openaiApiKey: string;
|
||||
milvusAddress: string;
|
||||
codebasePath: string;
|
||||
searchQuery: string;
|
||||
}) {
|
||||
try {
|
||||
console.log('🚀 Starting Context end-to-end test...');
|
||||
|
||||
// 1. Create embedding instance
|
||||
console.log('📝 Creating OpenAI embedding instance...');
|
||||
const embedding = new OpenAIEmbedding({
|
||||
apiKey: config.openaiApiKey,
|
||||
model: 'text-embedding-3-small'
|
||||
});
|
||||
|
||||
// 2. Create vector database instance
|
||||
console.log('🗄️ Creating Milvus vector database instance...');
|
||||
const vectorDB = new MilvusVectorDatabase({
|
||||
address: config.milvusAddress
|
||||
});
|
||||
|
||||
// 3. Create Context instance
|
||||
console.log('🔧 Creating Context instance...');
|
||||
const codeSplitter = new AstCodeSplitter(1000, 200);
|
||||
const context = new Context({
|
||||
embedding: embedding,
|
||||
vectorDatabase: vectorDB,
|
||||
codeSplitter: codeSplitter
|
||||
});
|
||||
|
||||
// 4. Check if index already exists
|
||||
console.log('🔍 Checking existing index...');
|
||||
const hasIndex = await context.hasIndex(config.codebasePath);
|
||||
console.log(`Existing index status: ${hasIndex}`);
|
||||
|
||||
// 5. Index codebase
|
||||
let indexStats;
|
||||
if (!hasIndex) {
|
||||
console.log('📚 Starting codebase indexing...');
|
||||
indexStats = await context.indexCodebase(config.codebasePath, (progress) => {
|
||||
console.log(`Indexing progress: ${progress.phase} - ${progress.percentage}%`);
|
||||
});
|
||||
console.log('✅ Indexing completed');
|
||||
} else {
|
||||
console.log('📖 Using existing index');
|
||||
indexStats = { indexedFiles: 0, totalChunks: 0, message: "Using existing index" };
|
||||
}
|
||||
|
||||
// 6. Execute semantic search
|
||||
console.log('🔎 Executing semantic search...');
|
||||
const searchResults = await context.semanticSearch(
|
||||
config.codebasePath,
|
||||
config.searchQuery,
|
||||
5, // topK
|
||||
0.5 // threshold
|
||||
);
|
||||
|
||||
// 7. Return complete results
|
||||
const result = {
|
||||
success: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
config: {
|
||||
embeddingProvider: embedding.getProvider(),
|
||||
embeddingModel: 'text-embedding-3-small',
|
||||
embeddingDimension: embedding.getDimension(),
|
||||
vectorDatabase: 'Milvus',
|
||||
chunkSize: 1000,
|
||||
chunkOverlap: 200
|
||||
},
|
||||
indexStats: indexStats,
|
||||
searchQuery: config.searchQuery,
|
||||
searchResults: searchResults.map(result => ({
|
||||
relativePath: result.relativePath,
|
||||
startLine: result.startLine,
|
||||
endLine: result.endLine,
|
||||
language: result.language,
|
||||
score: result.score,
|
||||
contentPreview: result.content.substring(0, 200) + '...'
|
||||
})),
|
||||
summary: {
|
||||
indexedFiles: indexStats.indexedFiles || 0,
|
||||
totalChunks: indexStats.totalChunks || 0,
|
||||
foundResults: searchResults.length,
|
||||
avgScore: searchResults.length > 0 ?
|
||||
searchResults.reduce((sum, r) => sum + r.score, 0) / searchResults.length : 0
|
||||
}
|
||||
};
|
||||
|
||||
console.log('🎉 End-to-end test completed!');
|
||||
return result;
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ End-to-end test failed:', error);
|
||||
return {
|
||||
success: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Claude Context End-to-End Test
|
||||
Use TypeScriptExecutor to call complete Claude Context workflow
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add python directory to path
|
||||
sys.path.append(str(Path(__file__).parent))
|
||||
|
||||
from ts_executor import TypeScriptExecutor
|
||||
|
||||
|
||||
def run_context_endtoend_test():
|
||||
"""Run Claude Context end-to-end test"""
|
||||
|
||||
# Configuration parameters
|
||||
config = {
|
||||
"openaiApiKey": os.environ.get("OPENAI_API_KEY", "your-openai-api-key"),
|
||||
"milvusAddress": os.environ.get("MILVUS_ADDRESS", "localhost:19530"),
|
||||
"codebasePath": str(
|
||||
Path(__file__).parent.parent / "packages" / "core" / "src"
|
||||
), # Index core source code
|
||||
"searchQuery": "embedding creation and vector database configuration",
|
||||
}
|
||||
|
||||
print("🚀 Starting Claude Context end-to-end test")
|
||||
print(f"📊 Configuration:")
|
||||
print(f" - Codebase path: {config['codebasePath']}")
|
||||
print(f" - Vector database: {config['milvusAddress']}")
|
||||
print(f" - Search query: {config['searchQuery']}")
|
||||
print(
|
||||
f" - OpenAI API: {'✅ Configured' if config['openaiApiKey'] != 'your-openai-api-key' else '❌ Need to set OPENAI_API_KEY environment variable'}"
|
||||
)
|
||||
print()
|
||||
|
||||
try:
|
||||
executor = TypeScriptExecutor()
|
||||
|
||||
# Call end-to-end test
|
||||
result = executor.call_method(
|
||||
"./test_context.ts", "testContextEndToEnd", config
|
||||
)
|
||||
|
||||
# Output results
|
||||
if result.get("success"):
|
||||
print("✅ End-to-end test successful!")
|
||||
print(f"📅 Timestamp: {result.get('timestamp')}")
|
||||
|
||||
# Display configuration info
|
||||
config_info = result.get("config", {})
|
||||
print(f"🔧 Configuration:")
|
||||
print(f" - Embedding provider: {config_info.get('embeddingProvider')}")
|
||||
print(f" - Embedding model: {config_info.get('embeddingModel')}")
|
||||
print(f" - Embedding dimension: {config_info.get('embeddingDimension')}")
|
||||
print(f" - Vector database: {config_info.get('vectorDatabase')}")
|
||||
print(f" - Chunk size: {config_info.get('chunkSize')}")
|
||||
print(f" - Chunk overlap: {config_info.get('chunkOverlap')}")
|
||||
|
||||
# Display indexing statistics
|
||||
index_stats = result.get("indexStats", {})
|
||||
print(f"📚 Indexing statistics:")
|
||||
print(f" - Indexed files: {index_stats.get('indexedFiles', 0)}")
|
||||
print(f" - Total chunks: {index_stats.get('totalChunks', 0)}")
|
||||
|
||||
# Display search results
|
||||
summary = result.get("summary", {})
|
||||
search_results = result.get("searchResults", [])
|
||||
print(f"🔍 Search results:")
|
||||
print(f" - Query: '{result.get('searchQuery')}'")
|
||||
print(f" - Results found: {summary.get('foundResults', 0)} items")
|
||||
print(f" - Average relevance: {summary.get('avgScore', 0):.3f}")
|
||||
|
||||
# Display top 3 search results
|
||||
if search_results:
|
||||
print(f"📋 Top {min(3, len(search_results))} most relevant results:")
|
||||
for i, item in enumerate(search_results[:3]):
|
||||
print(
|
||||
f" {i+1}. {item['relativePath']} (lines {item['startLine']}-{item['endLine']})"
|
||||
)
|
||||
print(
|
||||
f" Language: {item['language']}, Relevance: {item['score']:.3f}"
|
||||
)
|
||||
print(f" Preview: {item['contentPreview'][:100]}...")
|
||||
print()
|
||||
|
||||
return True
|
||||
|
||||
else:
|
||||
print("❌ End-to-end test failed")
|
||||
print(f"Error: {result.get('error')}")
|
||||
if result.get("stack"):
|
||||
print(f"Stack trace: {result.get('stack')}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Execution failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function"""
|
||||
print("=" * 60)
|
||||
print("🧪 Claude Context End-to-End Test")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
success = run_context_endtoend_test()
|
||||
|
||||
print()
|
||||
print("=" * 60)
|
||||
if success:
|
||||
print("🎉 Test completed! Claude Context end-to-end workflow runs successfully!")
|
||||
print()
|
||||
print("💡 This proves:")
|
||||
print(" ✅ Can call TypeScript Claude Context from Python")
|
||||
print(" ✅ Supports complete indexing and search workflow")
|
||||
print(" ✅ Supports complex configuration and parameter passing")
|
||||
print(" ✅ Can get detailed execution results and statistics")
|
||||
else:
|
||||
print("❌ Test failed. Please check:")
|
||||
print(" - OPENAI_API_KEY environment variable is correctly set")
|
||||
print(" - Milvus vector database is running properly")
|
||||
print(" - packages/core code is accessible")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,308 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
TypeScript Executor - Execute TypeScript methods from Python
|
||||
Supports calling TypeScript functions with complex parameters and async/await
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
class TypeScriptExecutor:
|
||||
"""TypeScript method executor"""
|
||||
|
||||
def __init__(self, working_dir: Optional[str] = None):
|
||||
"""Initialize TypeScript executor
|
||||
|
||||
Args:
|
||||
working_dir: Working directory, defaults to current directory
|
||||
"""
|
||||
self.working_dir = working_dir or os.getcwd()
|
||||
|
||||
def call_method(self, ts_file_path: str, method_name: str, *args, **kwargs) -> Any:
|
||||
"""Call TypeScript method
|
||||
|
||||
Args:
|
||||
ts_file_path: TypeScript file path
|
||||
method_name: Method name
|
||||
*args: Positional arguments
|
||||
**kwargs: Keyword arguments
|
||||
|
||||
Returns:
|
||||
Execution result
|
||||
"""
|
||||
|
||||
# Convert relative path to absolute path
|
||||
if not os.path.isabs(ts_file_path):
|
||||
ts_file_path = os.path.join(self.working_dir, ts_file_path)
|
||||
|
||||
# Ensure the target file exists
|
||||
if not os.path.exists(ts_file_path):
|
||||
raise FileNotFoundError(f"TypeScript file not found: {ts_file_path}")
|
||||
|
||||
# Get the directory of the target file
|
||||
target_dir = os.path.dirname(ts_file_path)
|
||||
|
||||
# Create wrapper script
|
||||
wrapper_code = self._create_wrapper_script(
|
||||
ts_file_path, method_name, list(args), kwargs
|
||||
)
|
||||
|
||||
# Create temporary file in the same directory as the target file
|
||||
temp_fd, temp_file = tempfile.mkstemp(suffix=".ts", dir=target_dir)
|
||||
|
||||
try:
|
||||
# Write wrapper script
|
||||
with os.fdopen(temp_fd, "w", encoding="utf-8") as f:
|
||||
f.write(wrapper_code)
|
||||
|
||||
# Execute TypeScript code using ts-node
|
||||
# Use subprocess.Popen to capture output in real-time
|
||||
process = subprocess.Popen(
|
||||
["npx", "ts-node", temp_file],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
cwd=self.working_dir,
|
||||
bufsize=1, # Line buffering
|
||||
universal_newlines=True,
|
||||
)
|
||||
|
||||
stdout_lines = []
|
||||
stderr_lines = []
|
||||
|
||||
# Read output line by line and display console.log in real-time
|
||||
while True:
|
||||
output = process.stdout.readline()
|
||||
if output == "" and process.poll() is not None:
|
||||
break
|
||||
if output:
|
||||
line = output.strip()
|
||||
stdout_lines.append(line)
|
||||
|
||||
# Try to parse as JSON to see if it's the final result
|
||||
try:
|
||||
json.loads(line)
|
||||
# If it parses as JSON, it might be the final result, don't print it yet
|
||||
except json.JSONDecodeError:
|
||||
# If it's not JSON, it's likely a console.log, so print it
|
||||
print(line)
|
||||
|
||||
# Get any remaining stderr
|
||||
stderr_output = process.stderr.read()
|
||||
if stderr_output:
|
||||
stderr_lines.append(stderr_output.strip())
|
||||
|
||||
return_code = process.poll()
|
||||
|
||||
if return_code != 0:
|
||||
error_msg = "\n".join(stderr_lines) if stderr_lines else "Unknown error"
|
||||
raise RuntimeError(f"TypeScript execution failed: {error_msg}")
|
||||
|
||||
# Parse results from the last line that looks like JSON
|
||||
for line in reversed(stdout_lines):
|
||||
if line.strip():
|
||||
try:
|
||||
# Try to parse as JSON
|
||||
return json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
# If no JSON found, return the last non-empty line
|
||||
for line in reversed(stdout_lines):
|
||||
if line.strip():
|
||||
return line
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Execution error: {str(e)}")
|
||||
finally:
|
||||
# Clean up temporary file
|
||||
os.unlink(temp_file)
|
||||
|
||||
def _create_wrapper_script(
|
||||
self,
|
||||
ts_file_path: str,
|
||||
method_name: str,
|
||||
args: List[Any],
|
||||
kwargs: Dict[str, Any],
|
||||
) -> str:
|
||||
"""Create wrapper script
|
||||
|
||||
Args:
|
||||
ts_file_path: TypeScript file path
|
||||
method_name: Method name
|
||||
args: Positional arguments
|
||||
kwargs: Keyword arguments
|
||||
|
||||
Returns:
|
||||
Wrapper script code
|
||||
"""
|
||||
|
||||
# Use relative path for import, since temp file is in the same directory
|
||||
ts_filename = os.path.basename(ts_file_path)
|
||||
|
||||
# Remove .ts extension, since import doesn't need it
|
||||
if ts_filename.endswith(".ts"):
|
||||
import_path = "./" + ts_filename[:-3]
|
||||
else:
|
||||
import_path = "./" + ts_filename
|
||||
|
||||
args_json = json.dumps(args)
|
||||
kwargs_json = json.dumps(kwargs)
|
||||
|
||||
wrapper_code = f"""
|
||||
import * as targetModule from '{import_path}';
|
||||
|
||||
async function executeMethod() {{
|
||||
try {{
|
||||
// Prepare arguments
|
||||
const args: any[] = {args_json};
|
||||
const kwargs: any = {kwargs_json};
|
||||
|
||||
// Get method
|
||||
const method = (targetModule as any).{method_name};
|
||||
if (typeof method !== 'function') {{
|
||||
throw new Error(`Method '{method_name}' does not exist or is not a function`);
|
||||
}}
|
||||
|
||||
// Call method
|
||||
let result: any;
|
||||
if (Object.keys(kwargs).length > 0) {{
|
||||
// If there are keyword arguments, pass them as the last parameter
|
||||
result = await method(...args, kwargs);
|
||||
}} else {{
|
||||
// Only positional arguments
|
||||
result = await method(...args);
|
||||
}}
|
||||
|
||||
// Output result
|
||||
console.log(JSON.stringify(result));
|
||||
}} catch (error: any) {{
|
||||
console.error(JSON.stringify({{
|
||||
error: (error as Error).message,
|
||||
stack: (error as Error).stack
|
||||
}}));
|
||||
process.exit(1);
|
||||
}}
|
||||
}}
|
||||
|
||||
executeMethod();
|
||||
"""
|
||||
return wrapper_code
|
||||
|
||||
|
||||
# Convenience function
|
||||
def call_ts_method(
|
||||
ts_file: str, method_name: str, *args, working_dir: Optional[str] = None, **kwargs
|
||||
) -> Any:
|
||||
"""Convenience function: Call TypeScript method
|
||||
|
||||
Args:
|
||||
ts_file: TypeScript file path
|
||||
method_name: Method name
|
||||
*args: Positional arguments
|
||||
working_dir: Working directory
|
||||
**kwargs: Keyword arguments
|
||||
|
||||
Returns:
|
||||
Execution result
|
||||
"""
|
||||
executor = TypeScriptExecutor(working_dir)
|
||||
return executor.call_method(ts_file, method_name, *args, **kwargs)
|
||||
|
||||
|
||||
# Usage example
|
||||
if __name__ == "__main__":
|
||||
|
||||
# Create test TypeScript file
|
||||
test_ts_content = """
|
||||
export function add(a: number, b: number): number {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
export function greet(name: string, options?: { formal?: boolean }): string {
|
||||
const greeting = options?.formal ? "Hello" : "Hi";
|
||||
return `${greeting}, ${name}!`;
|
||||
}
|
||||
|
||||
export async function processData(data: any[]): Promise<{ count: number; items: any[] }> {
|
||||
// Simulate async processing
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
return {
|
||||
count: data.length,
|
||||
items: data.map(item => ({ processed: true, original: item }))
|
||||
};
|
||||
}
|
||||
|
||||
export function complexFunction(
|
||||
numbers: number[],
|
||||
config: { multiplier: number; offset: number }
|
||||
): { result: number[]; sum: number } {
|
||||
const result = numbers.map(n => n * config.multiplier + config.offset);
|
||||
const sum = result.reduce((a, b) => a + b, 0);
|
||||
|
||||
return { result, sum };
|
||||
}
|
||||
"""
|
||||
|
||||
# Write test file
|
||||
with open("test_methods.ts", "w") as f:
|
||||
f.write(test_ts_content)
|
||||
|
||||
try:
|
||||
# Create executor
|
||||
executor = TypeScriptExecutor()
|
||||
|
||||
print("=== TypeScript Method Execution Test ===")
|
||||
|
||||
# Test 1: Simple function
|
||||
print("\n1. Testing simple addition function:")
|
||||
result = executor.call_method("test_methods.ts", "add", 10, 20)
|
||||
print(f" add(10, 20) = {result}")
|
||||
|
||||
# Test 2: Function with optional parameters
|
||||
print("\n2. Testing greeting function:")
|
||||
result1 = executor.call_method("test_methods.ts", "greet", "Alice")
|
||||
print(f" greet('Alice') = {result1}")
|
||||
|
||||
result2 = executor.call_method(
|
||||
"test_methods.ts", "greet", "Bob", {"formal": True}
|
||||
)
|
||||
print(f" greet('Bob', {{formal: true}}) = {result2}")
|
||||
|
||||
# Test 3: Async function
|
||||
print("\n3. Testing async function:")
|
||||
result = executor.call_method(
|
||||
"test_methods.ts", "processData", [1, 2, 3, "hello"]
|
||||
)
|
||||
print(f" processData([1, 2, 3, 'hello']) = {result}")
|
||||
|
||||
# Test 4: Complex function
|
||||
print("\n4. Testing complex function:")
|
||||
result = executor.call_method(
|
||||
"test_methods.ts",
|
||||
"complexFunction",
|
||||
[1, 2, 3, 4, 5],
|
||||
{"multiplier": 2, "offset": 1},
|
||||
)
|
||||
print(f" complexFunction([1,2,3,4,5], {{multiplier:2, offset:1}}) = {result}")
|
||||
|
||||
# Test 5: Using convenience function
|
||||
print("\n5. Testing convenience function:")
|
||||
result = call_ts_method("test_methods.ts", "add", 100, 200)
|
||||
print(f" call_ts_method('test_methods.ts', 'add', 100, 200) = {result}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
finally:
|
||||
# Clean up test file
|
||||
if os.path.exists("test_methods.ts"):
|
||||
os.remove("test_methods.ts")
|
||||
Reference in New Issue
Block a user