feat: Implement Phase 2G.1 — Claude Code IDE bridge
- Create @llm-gateway/claude-code-bridge package - Support explain, refactor, test, document, fix commands - Automatic fallback to local Ollama when gateway unavailable - Health monitoring and confidence tracking - Comprehensive test suite covering all completion methods - Follows ADR-0005 agent integration protocol
This commit is contained in:
parent
4d7e251322
commit
b943bb1d59
123
packages/claude-code-bridge/README.md
Normal file
123
packages/claude-code-bridge/README.md
Normal file
@ -0,0 +1,123 @@
|
||||
# Claude Code Bridge
|
||||
|
||||
Integration layer between Claude Code IDE and LLM Gateway.
|
||||
|
||||
## Overview
|
||||
|
||||
Provides a high-level API for Claude Code to leverage the LLM Gateway's multi-model orchestration, confidence gating, and fallback capabilities.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @llm-gateway/claude-code-bridge
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
import { ClaudeCodeBridge } from '@llm-gateway/claude-code-bridge'
|
||||
|
||||
const bridge = new ClaudeCodeBridge({
|
||||
gatewayUrl: 'https://llm-gateway.context-x.org',
|
||||
agentId: 'claude-code-ide',
|
||||
ideVersion: '1.0.0',
|
||||
extensionVersion: '1.0.0',
|
||||
ollamaUrl: '192.168.178.213:11434' // Local fallback
|
||||
})
|
||||
|
||||
// Explain selected code
|
||||
const explanation = await bridge.explain(context, selectedCode)
|
||||
|
||||
// Refactor code
|
||||
const refactored = await bridge.refactor(context, selectedCode)
|
||||
|
||||
// Generate tests
|
||||
const tests = await bridge.test(context, selectedCode)
|
||||
|
||||
// Add documentation
|
||||
const docs = await bridge.document(context, selectedCode)
|
||||
|
||||
// Fix errors
|
||||
const fix = await bridge.fixError(errorMessage, context)
|
||||
|
||||
// Check health
|
||||
const status = await bridge.health()
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- **Code Explanation**: Analyze and explain code snippets
|
||||
- **Refactoring**: Suggest improvements to existing code
|
||||
- **Test Generation**: Automatically generate test cases
|
||||
- **Documentation**: Create JSDoc/TSDoc comments
|
||||
- **Error Fixing**: Debug and fix code errors
|
||||
- **Fallback**: Automatic fallback to local Ollama when gateway unavailable
|
||||
- **Confidence Tracking**: Monitor model confidence in responses
|
||||
- **Token Counting**: Track usage for billing/analytics
|
||||
|
||||
## Architecture
|
||||
|
||||
The bridge implements the three-layer agent integration stack from ADR-0005:
|
||||
|
||||
1. **Transport Layer**: HTTP/WebSocket communication with gateway
|
||||
2. **Adapter Layer**: ClaudeCodeBridge wraps client SDK
|
||||
3. **Protocol Layer**: Standardized request/response format
|
||||
|
||||
## Health Status
|
||||
|
||||
```typescript
|
||||
const health = await bridge.health()
|
||||
// {
|
||||
// healthy: true,
|
||||
// gateway: true,
|
||||
// ollama: 'running',
|
||||
// mode: 'gateway'
|
||||
// }
|
||||
```
|
||||
|
||||
Modes:
|
||||
- `gateway`: Using LLM Gateway (preferred)
|
||||
- `fallback`: Using local Ollama (gateway unavailable)
|
||||
- `offline`: Both gateway and Ollama offline (error)
|
||||
|
||||
## Configuration
|
||||
|
||||
```typescript
|
||||
interface ClaudeCodeBridgeConfig {
|
||||
gatewayUrl: string // LLM Gateway endpoint
|
||||
agentId: string // Agent identifier (default: 'claude-code-ide')
|
||||
ideVersion: string // Claude Code version
|
||||
extensionVersion: string // Bridge extension version
|
||||
ollamaUrl?: string // Local Ollama URL (optional)
|
||||
apiKey?: string // Gateway API key (if required)
|
||||
requestTimeout?: number // Request timeout in ms (default: 30000)
|
||||
}
|
||||
```
|
||||
|
||||
## Response Format
|
||||
|
||||
```typescript
|
||||
interface ClaudeCodeResponse {
|
||||
text: string // Generated response
|
||||
tokens: {
|
||||
input: number // Input tokens
|
||||
output: number // Output tokens
|
||||
}
|
||||
model: string // Model used
|
||||
fallback: boolean // Whether using fallback
|
||||
confidence: number // 0-1 confidence score
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
Tests cover:
|
||||
- Health checks
|
||||
- All completion methods (explain, refactor, test, document, fix)
|
||||
- Fallback behavior
|
||||
- Token limiting
|
||||
- Metadata tracking
|
||||
31
packages/claude-code-bridge/package.json
Normal file
31
packages/claude-code-bridge/package.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "@llm-gateway/claude-code-bridge",
|
||||
"version": "1.0.0",
|
||||
"description": "Claude Code IDE integration with LLM Gateway",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@llm-gateway/client": "workspace:*",
|
||||
"@anthropic-sdk/sdk": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"vitest": "^1.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"claude",
|
||||
"code",
|
||||
"llm",
|
||||
"gateway",
|
||||
"ide"
|
||||
],
|
||||
"license": "MIT",
|
||||
"author": "Rene Fichtmueller"
|
||||
}
|
||||
120
packages/claude-code-bridge/src/index.test.ts
Normal file
120
packages/claude-code-bridge/src/index.test.ts
Normal file
@ -0,0 +1,120 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { ClaudeCodeBridge } from './index'
|
||||
|
||||
describe('ClaudeCodeBridge', () => {
|
||||
let bridge: ClaudeCodeBridge
|
||||
|
||||
beforeEach(() => {
|
||||
bridge = new ClaudeCodeBridge({
|
||||
gatewayUrl: 'http://localhost:3000',
|
||||
agentId: 'claude-code-test',
|
||||
ideVersion: '1.0.0',
|
||||
extensionVersion: '1.0.0'
|
||||
})
|
||||
})
|
||||
|
||||
describe('health check', () => {
|
||||
it('should report health status', async () => {
|
||||
const health = await bridge.health()
|
||||
expect(health).toHaveProperty('healthy')
|
||||
expect(health).toHaveProperty('gateway')
|
||||
expect(health).toHaveProperty('ollama')
|
||||
expect(health).toHaveProperty('mode')
|
||||
})
|
||||
|
||||
it('should handle gateway unavailable gracefully', async () => {
|
||||
const health = await bridge.health()
|
||||
// Should have fallback mode or offline
|
||||
expect(health.mode).toMatch(/fallback|offline|gateway/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('completion methods', () => {
|
||||
it('should support explain command', async () => {
|
||||
const context = 'function add(a, b) { return a + b; }'
|
||||
const selection = 'return a + b'
|
||||
|
||||
const response = await bridge.explain(context, selection)
|
||||
expect(response).toHaveProperty('text')
|
||||
expect(response).toHaveProperty('tokens')
|
||||
expect(response).toHaveProperty('model')
|
||||
expect(response).toHaveProperty('fallback')
|
||||
expect(response).toHaveProperty('confidence')
|
||||
})
|
||||
|
||||
it('should support refactor command', async () => {
|
||||
const context = 'for(let i=0;i<arr.length;i++){}'
|
||||
const selection = 'for(let i=0;i<arr.length;i++)'
|
||||
|
||||
const response = await bridge.refactor(context, selection)
|
||||
expect(response.text).toBeTruthy()
|
||||
expect(typeof response.tokens.input).toBe('number')
|
||||
expect(typeof response.tokens.output).toBe('number')
|
||||
})
|
||||
|
||||
it('should support test command', async () => {
|
||||
const context = 'export function multiply(a, b) { return a * b }'
|
||||
const selection = 'export function multiply(a, b)'
|
||||
|
||||
const response = await bridge.test(context, selection)
|
||||
expect(response.text).toBeTruthy()
|
||||
expect(response.model).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should support document command', async () => {
|
||||
const context = 'const config = { timeout: 5000 }'
|
||||
const selection = '{ timeout: 5000 }'
|
||||
|
||||
const response = await bridge.document(context, selection)
|
||||
expect(response.text).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should support fix command', async () => {
|
||||
const error = 'ReferenceError: x is not defined'
|
||||
const context = 'function test() { console.log(x); }'
|
||||
|
||||
const response = await bridge.fixError(error, context)
|
||||
expect(response.text).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('generic completion', () => {
|
||||
it('should handle custom prompts', async () => {
|
||||
const response = await bridge.completion('custom', 'Write a hello world function')
|
||||
expect(response).toHaveProperty('text')
|
||||
expect(response).toHaveProperty('tokens')
|
||||
expect(response).toHaveProperty('model')
|
||||
})
|
||||
|
||||
it('should respect maxTokens limit', async () => {
|
||||
const response = await bridge.completion('test', 'Short prompt', 100)
|
||||
expect(response.tokens.output).toBeLessThanOrEqual(150) // Small margin for tokenizer variance
|
||||
})
|
||||
})
|
||||
|
||||
describe('fallback behavior', () => {
|
||||
it('should report when using fallback', async () => {
|
||||
const response = await bridge.completion('test', 'Test prompt')
|
||||
expect(response).toHaveProperty('fallback')
|
||||
expect(typeof response.fallback).toBe('boolean')
|
||||
})
|
||||
|
||||
it('should still work during fallback to Ollama', async () => {
|
||||
const response = await bridge.completion('test', 'Generate a simple greeting')
|
||||
expect(response.text).toBeTruthy()
|
||||
expect(response.tokens).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('metadata tracking', () => {
|
||||
it('should track IDE version', async () => {
|
||||
const status = await bridge.status()
|
||||
expect(status).toBeDefined()
|
||||
})
|
||||
|
||||
it('should identify agent as claude-code', async () => {
|
||||
const response = await bridge.completion('test', 'Simple test')
|
||||
expect(response.model).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
108
packages/claude-code-bridge/src/index.ts
Normal file
108
packages/claude-code-bridge/src/index.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import { createTIPClient, type TIPClientConfig } from '@llm-gateway/client'
|
||||
|
||||
export interface ClaudeCodeBridgeConfig extends TIPClientConfig {
|
||||
agentId: string
|
||||
ideVersion: string
|
||||
extensionVersion: string
|
||||
}
|
||||
|
||||
export interface ClaudeCodeRequest {
|
||||
command: string
|
||||
context: string
|
||||
selection?: string
|
||||
temperature?: number
|
||||
maxTokens?: number
|
||||
}
|
||||
|
||||
export interface ClaudeCodeResponse {
|
||||
text: string
|
||||
tokens: { input: number; output: number }
|
||||
model: string
|
||||
fallback: boolean
|
||||
confidence: number
|
||||
}
|
||||
|
||||
export class ClaudeCodeBridge {
|
||||
private client: ReturnType<typeof createTIPClient>
|
||||
private config: ClaudeCodeBridgeConfig
|
||||
|
||||
constructor(config: ClaudeCodeBridgeConfig) {
|
||||
this.config = {
|
||||
agentId: 'claude-code-ide',
|
||||
ideVersion: config.ideVersion,
|
||||
extensionVersion: config.extensionVersion,
|
||||
...config
|
||||
}
|
||||
this.client = createTIPClient(this.config)
|
||||
}
|
||||
|
||||
async explain(context: string, selection: string): Promise<ClaudeCodeResponse> {
|
||||
const prompt = `Explain the following code in detail:\n\n\`\`\`\n${selection}\n\`\`\`\n\nContext:\n${context}`
|
||||
return this.completion('explain', prompt)
|
||||
}
|
||||
|
||||
async refactor(context: string, selection: string): Promise<ClaudeCodeResponse> {
|
||||
const prompt = `Refactor the following code to improve readability, performance, and maintainability:\n\n\`\`\`\n${selection}\n\`\`\`\n\nContext:\n${context}`
|
||||
return this.completion('refactor', prompt)
|
||||
}
|
||||
|
||||
async test(context: string, selection: string): Promise<ClaudeCodeResponse> {
|
||||
const prompt = `Write comprehensive tests for the following code:\n\n\`\`\`\n${selection}\n\`\`\`\n\nContext:\n${context}`
|
||||
return this.completion('test', prompt)
|
||||
}
|
||||
|
||||
async document(context: string, selection: string): Promise<ClaudeCodeResponse> {
|
||||
const prompt = `Write JSDoc/TSDoc documentation for the following code:\n\n\`\`\`\n${selection}\n\`\`\`\n\nContext:\n${context}`
|
||||
return this.completion('document', prompt)
|
||||
}
|
||||
|
||||
async fixError(errorMessage: string, context: string): Promise<ClaudeCodeResponse> {
|
||||
const prompt = `Fix the following error:\n${errorMessage}\n\nContext:\n${context}`
|
||||
return this.completion('fix', prompt)
|
||||
}
|
||||
|
||||
async completion(command: string, prompt: string, maxTokens = 2000): Promise<ClaudeCodeResponse> {
|
||||
const result = await this.client.completion(prompt, {
|
||||
maxTokens,
|
||||
metadata: {
|
||||
source: 'claude-code-ide',
|
||||
command,
|
||||
version: this.config.ideVersion
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
text: result.text,
|
||||
tokens: result.tokens,
|
||||
model: result.model,
|
||||
fallback: result.fallback,
|
||||
confidence: result.confidence ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
async status() {
|
||||
return this.client.getStatus()
|
||||
}
|
||||
|
||||
async health() {
|
||||
try {
|
||||
const status = await this.status()
|
||||
return {
|
||||
healthy: status.gateway === true || status.ollama !== 'offline',
|
||||
gateway: status.gateway,
|
||||
ollama: status.ollama,
|
||||
mode: status.mode
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
healthy: false,
|
||||
gateway: false,
|
||||
ollama: 'offline' as const,
|
||||
mode: 'offline' as const,
|
||||
error: String(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ClaudeCodeBridge
|
||||
12
packages/claude-code-bridge/tsconfig.json
Normal file
12
packages/claude-code-bridge/tsconfig.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user