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:
Rene Fichtmueller 2026-04-19 22:02:06 +02:00
parent 4d7e251322
commit b943bb1d59
5 changed files with 394 additions and 0 deletions

View 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

View 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"
}

View 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()
})
})
})

View 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

View 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"]
}