mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-11 02:33:51 +08:00
chore(release): v6.3.19 - Dense Reranker, CLI Tools & Issue Workflow
## Documentation Updates - Update all version references to v6.3.19 - Add Dense + Reranker search documentation - Add OpenCode AI CLI tool integration docs - Add Issue workflow (plan → queue → execute) with Codex recommendation - Update CHANGELOG with complete v6.3.19 release notes ## Features - Cross-Encoder reranking for improved search relevance - OpenCode CLI tool support - Issue multi-queue parallel execution - Service architecture improvements (cache-manager, preload-service) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -86,6 +86,7 @@ import {
|
||||
} from '../../config/litellm-api-config-manager.js';
|
||||
import { getContextCacheStore } from '../../tools/context-cache-store.js';
|
||||
import { getLiteLLMClient } from '../../tools/litellm-client.js';
|
||||
import { testApiKeyConnection, getDefaultApiBase } from '../services/api-key-tester.js';
|
||||
|
||||
// Cache for ccw-litellm status check
|
||||
let ccwLitellmStatusCache: {
|
||||
@@ -338,6 +339,201 @@ export async function handleLiteLLMApiRoutes(ctx: RouteContext): Promise<boolean
|
||||
return true;
|
||||
}
|
||||
|
||||
// POST /api/litellm-api/providers/:id/test-key - Test specific API key
|
||||
const providerTestKeyMatch = pathname.match(/^\/api\/litellm-api\/providers\/([^/]+)\/test-key$/);
|
||||
if (providerTestKeyMatch && req.method === 'POST') {
|
||||
const providerId = providerTestKeyMatch[1];
|
||||
|
||||
handlePostRequest(req, res, async (body: unknown) => {
|
||||
const { keyId } = body as { keyId?: string };
|
||||
|
||||
if (!keyId) {
|
||||
return { valid: false, error: 'keyId is required', status: 400 };
|
||||
}
|
||||
|
||||
try {
|
||||
const provider = getProvider(initialPath, providerId);
|
||||
|
||||
if (!provider) {
|
||||
return { valid: false, error: 'Provider not found', status: 404 };
|
||||
}
|
||||
|
||||
// Find the specific API key
|
||||
let apiKeyValue: string | null = null;
|
||||
let keyLabel = 'Default';
|
||||
|
||||
if (keyId === 'default' && provider.apiKey) {
|
||||
// Use the single default apiKey
|
||||
apiKeyValue = provider.apiKey;
|
||||
} else if (provider.apiKeys && provider.apiKeys.length > 0) {
|
||||
const keyEntry = provider.apiKeys.find(k => k.id === keyId);
|
||||
if (keyEntry) {
|
||||
apiKeyValue = keyEntry.key;
|
||||
keyLabel = keyEntry.label || keyEntry.id;
|
||||
}
|
||||
}
|
||||
|
||||
if (!apiKeyValue) {
|
||||
return { valid: false, error: 'API key not found' };
|
||||
}
|
||||
|
||||
// Resolve environment variables
|
||||
const { resolveEnvVar } = await import('../../config/litellm-api-config-manager.js');
|
||||
const resolvedKey = resolveEnvVar(apiKeyValue);
|
||||
|
||||
if (!resolvedKey) {
|
||||
return { valid: false, error: 'API key is empty or environment variable not set' };
|
||||
}
|
||||
|
||||
// Determine API base URL
|
||||
const apiBase = provider.apiBase || getDefaultApiBase(provider.type);
|
||||
|
||||
// Test the API key with appropriate endpoint based on provider type
|
||||
const startTime = Date.now();
|
||||
const testResult = await testApiKeyConnection(provider.type, apiBase, resolvedKey);
|
||||
const latencyMs = Date.now() - startTime;
|
||||
|
||||
// Update key health status in provider config
|
||||
if (provider.apiKeys && provider.apiKeys.length > 0) {
|
||||
const keyEntry = provider.apiKeys.find(k => k.id === keyId);
|
||||
if (keyEntry) {
|
||||
keyEntry.healthStatus = testResult.valid ? 'healthy' : 'unhealthy';
|
||||
keyEntry.lastHealthCheck = new Date().toISOString();
|
||||
if (!testResult.valid) {
|
||||
keyEntry.lastError = testResult.error;
|
||||
} else {
|
||||
delete keyEntry.lastError;
|
||||
}
|
||||
|
||||
// Save updated provider
|
||||
try {
|
||||
updateProvider(initialPath, providerId, { apiKeys: provider.apiKeys });
|
||||
} catch (updateErr) {
|
||||
console.warn('[test-key] Failed to update key health status:', updateErr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: testResult.valid,
|
||||
error: testResult.error,
|
||||
latencyMs: testResult.valid ? latencyMs : undefined,
|
||||
keyLabel,
|
||||
};
|
||||
} catch (err) {
|
||||
return { valid: false, error: (err as Error).message };
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// GET /api/litellm-api/providers/:id/health-status - Get health status for all keys
|
||||
const providerHealthStatusMatch = pathname.match(/^\/api\/litellm-api\/providers\/([^/]+)\/health-status$/);
|
||||
if (providerHealthStatusMatch && req.method === 'GET') {
|
||||
const providerId = providerHealthStatusMatch[1];
|
||||
|
||||
try {
|
||||
const provider = getProvider(initialPath, providerId);
|
||||
|
||||
if (!provider) {
|
||||
res.writeHead(404, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'Provider not found' }));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Import health check service to get runtime state
|
||||
const { getHealthCheckService } = await import('../services/health-check-service.js');
|
||||
const healthService = getHealthCheckService();
|
||||
const healthStatus = healthService.getProviderHealthStatus(providerId);
|
||||
|
||||
// Merge persisted key data with runtime health status
|
||||
const keys = (provider.apiKeys || []).map(key => {
|
||||
const runtimeStatus = healthStatus.find(s => s.keyId === key.id);
|
||||
return {
|
||||
keyId: key.id,
|
||||
label: key.label || key.id,
|
||||
status: runtimeStatus?.status || key.healthStatus || 'unknown',
|
||||
lastCheck: runtimeStatus?.lastCheck || key.lastHealthCheck,
|
||||
lastLatencyMs: key.lastLatencyMs,
|
||||
consecutiveFailures: runtimeStatus?.consecutiveFailures || 0,
|
||||
inCooldown: runtimeStatus?.inCooldown || false,
|
||||
lastError: runtimeStatus?.lastError || key.lastError,
|
||||
};
|
||||
});
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
providerId,
|
||||
providerName: provider.name,
|
||||
keys,
|
||||
}));
|
||||
} catch (err) {
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: (err as Error).message }));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// POST /api/litellm-api/providers/:id/health-check-now - Trigger immediate health check
|
||||
const providerHealthCheckNowMatch = pathname.match(/^\/api\/litellm-api\/providers\/([^/]+)\/health-check-now$/);
|
||||
if (providerHealthCheckNowMatch && req.method === 'POST') {
|
||||
const providerId = providerHealthCheckNowMatch[1];
|
||||
|
||||
try {
|
||||
const provider = getProvider(initialPath, providerId);
|
||||
|
||||
if (!provider) {
|
||||
res.writeHead(404, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'Provider not found' }));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Import health check service and trigger check
|
||||
const { getHealthCheckService } = await import('../services/health-check-service.js');
|
||||
const healthService = getHealthCheckService();
|
||||
|
||||
// Trigger immediate check (async, but we wait for completion)
|
||||
await healthService.checkProviderNow(providerId);
|
||||
|
||||
// Get updated status
|
||||
const healthStatus = healthService.getProviderHealthStatus(providerId);
|
||||
|
||||
// Reload provider to get updated persisted data
|
||||
const updatedProvider = getProvider(initialPath, providerId);
|
||||
const keys = (updatedProvider?.apiKeys || []).map(key => {
|
||||
const runtimeStatus = healthStatus.find(s => s.keyId === key.id);
|
||||
return {
|
||||
keyId: key.id,
|
||||
label: key.label || key.id,
|
||||
status: runtimeStatus?.status || key.healthStatus || 'unknown',
|
||||
lastCheck: runtimeStatus?.lastCheck || key.lastHealthCheck,
|
||||
lastLatencyMs: key.lastLatencyMs,
|
||||
consecutiveFailures: runtimeStatus?.consecutiveFailures || 0,
|
||||
inCooldown: runtimeStatus?.inCooldown || false,
|
||||
lastError: runtimeStatus?.lastError || key.lastError,
|
||||
};
|
||||
});
|
||||
|
||||
broadcastToClients({
|
||||
type: 'PROVIDER_HEALTH_CHECKED',
|
||||
payload: { providerId, keys, timestamp: new Date().toISOString() }
|
||||
});
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
success: true,
|
||||
providerId,
|
||||
providerName: updatedProvider?.name,
|
||||
keys,
|
||||
checkedAt: new Date().toISOString(),
|
||||
}));
|
||||
} catch (err) {
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: false, error: (err as Error).message }));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// Endpoint Management Routes
|
||||
// ===========================
|
||||
|
||||
Reference in New Issue
Block a user