Implement improved auto-reconnect logic and fix TypeScript errors
## Features - Add proactive health check monitoring for MCP connections (30s intervals) - Implement automatic reconnection on health check failures - Add proper cleanup of health check intervals on disconnect ## Fixes - Resolve OAuth type compatibility issues with MCP SDK - Fix naming collision between InferenceProvider class and React component - Remove unused React imports across all components (new JSX transform) - Clean up unused type imports and variables - Remove unused handleOAuthAuthentication function ## Technical Details - Health checks use lightweight listTools() calls to verify connection state - Automatic reconnection triggers when health checks fail - Maintains existing exponential backoff retry logic - All TypeScript compilation errors resolved 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
2038ea5957
commit
763742ed30
17 changed files with 122 additions and 71 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { InferenceProvider } from '@/contexts/InferenceContext'
|
import { InferenceContextProvider } from '@/contexts/InferenceContext'
|
||||||
import { MCPProvider } from '@/contexts/MCPContext'
|
import { MCPProvider } from '@/contexts/MCPContext'
|
||||||
import { InferenceTest } from '@/components/InferenceTest'
|
import { InferenceTest } from '@/components/InferenceTest'
|
||||||
import { MCPTest } from '@/components/MCPTest'
|
import { MCPTest } from '@/components/MCPTest'
|
||||||
|
|
@ -23,7 +23,7 @@ function App() {
|
||||||
const [activeTab, setActiveTab] = useState<'conversations' | 'inference' | 'mcp'>('conversations');
|
const [activeTab, setActiveTab] = useState<'conversations' | 'inference' | 'mcp'>('conversations');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InferenceProvider>
|
<InferenceContextProvider>
|
||||||
<MCPProvider>
|
<MCPProvider>
|
||||||
<div className="min-h-screen bg-gray-100 dark:bg-gray-900">
|
<div className="min-h-screen bg-gray-100 dark:bg-gray-900">
|
||||||
{/* Tab Navigation */}
|
{/* Tab Navigation */}
|
||||||
|
|
@ -70,7 +70,7 @@ function App() {
|
||||||
{activeTab === 'mcp' && <MCPTest />}
|
{activeTab === 'mcp' && <MCPTest />}
|
||||||
</div>
|
</div>
|
||||||
</MCPProvider>
|
</MCPProvider>
|
||||||
</InferenceProvider>
|
</InferenceContextProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Main chat interface with message display and input
|
// Main chat interface with message display and input
|
||||||
|
|
||||||
import React, { useState, useRef, useEffect } from 'react';
|
import { useState, useRef, useEffect } from 'react';
|
||||||
import { useConversation } from '@/contexts/ConversationContext';
|
import { useConversation } from '@/contexts/ConversationContext';
|
||||||
import { useInference } from '@/contexts/InferenceContext';
|
import { useInference } from '@/contexts/InferenceContext';
|
||||||
import { MessageList } from './MessageList';
|
import { MessageList } from './MessageList';
|
||||||
|
|
@ -16,7 +16,7 @@ export function ChatInterface() {
|
||||||
getAgentLoopState,
|
getAgentLoopState,
|
||||||
} = useConversation();
|
} = useConversation();
|
||||||
|
|
||||||
const { provider: currentProvider, isAuthenticated } = useInference();
|
const { provider: currentProvider } = useInference();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Main conversation application with sidebar and chat interface
|
// Main conversation application with sidebar and chat interface
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { ConversationProvider } from '@/contexts/ConversationContext';
|
import { ConversationProvider } from '@/contexts/ConversationContext';
|
||||||
import { ConversationSidebar } from './ConversationSidebar';
|
import { ConversationSidebar } from './ConversationSidebar';
|
||||||
import { ChatInterface } from './ChatInterface';
|
import { ChatInterface } from './ChatInterface';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
// Conversation sidebar with conversation list and management
|
// Conversation sidebar with conversation list and management
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { useConversation } from '@/contexts/ConversationContext';
|
import { useConversation } from '@/contexts/ConversationContext';
|
||||||
|
|
||||||
export function ConversationSidebar() {
|
export function ConversationSidebar() {
|
||||||
|
|
@ -91,10 +90,11 @@ export function ConversationSidebar() {
|
||||||
</p>
|
</p>
|
||||||
{conversation.messages.length > 0 && (
|
{conversation.messages.length > 0 && (
|
||||||
<p className="text-xs text-gray-600 dark:text-gray-300 mt-1 truncate">
|
<p className="text-xs text-gray-600 dark:text-gray-300 mt-1 truncate">
|
||||||
{conversation.messages[conversation.messages.length - 1]?.content?.[0]?.type === 'text'
|
{(() => {
|
||||||
? conversation.messages[conversation.messages.length - 1].content[0].text
|
const lastMessage = conversation.messages[conversation.messages.length - 1];
|
||||||
: 'Tool interaction'
|
const firstBlock = lastMessage?.content?.[0];
|
||||||
}
|
return firstBlock?.type === 'text' ? firstBlock.text : 'Tool interaction';
|
||||||
|
})()}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Inference provider login component for inline authentication
|
// Inference provider login component for inline authentication
|
||||||
|
|
||||||
import React, { useState, useMemo } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import { useInference } from '@/contexts/InferenceContext';
|
import { useInference } from '@/contexts/InferenceContext';
|
||||||
import { OpenRouterApiProvider } from '@/providers/openrouter/api-provider';
|
import { OpenRouterApiProvider } from '@/providers/openrouter/api-provider';
|
||||||
import { OpenRouterOAuthProvider } from '@/providers/openrouter/oauth-provider';
|
import { OpenRouterOAuthProvider } from '@/providers/openrouter/oauth-provider';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Test UI for inference provider functionality
|
// Test UI for inference provider functionality
|
||||||
|
|
||||||
import React, { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import { useInference } from '@/contexts/InferenceContext';
|
import { useInference } from '@/contexts/InferenceContext';
|
||||||
import { OpenRouterApiProvider, OpenRouterOAuthProvider } from '@/providers/openrouter';
|
import { OpenRouterApiProvider, OpenRouterOAuthProvider } from '@/providers/openrouter';
|
||||||
import type { ChatMessage, InferenceRequest } from '@/types/inference';
|
import type { ChatMessage, InferenceRequest } from '@/types/inference';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
// MCP status display for the conversation sidebar
|
// MCP status display for the conversation sidebar
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { useMCP } from '@/contexts/MCPContext';
|
import { useMCP } from '@/contexts/MCPContext';
|
||||||
|
|
||||||
export function MCPStatus() {
|
export function MCPStatus() {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Test UI for MCP provider functionality
|
// Test UI for MCP provider functionality
|
||||||
|
|
||||||
import React, { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import { useMCP } from '@/contexts/MCPContext';
|
import { useMCP } from '@/contexts/MCPContext';
|
||||||
import type { MCPServerConfig } from '@/types/mcp';
|
import type { MCPServerConfig } from '@/types/mcp';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Message list component to display conversation messages with tool calls
|
// Message list component to display conversation messages with tool calls
|
||||||
|
|
||||||
import React from 'react';
|
import type { ConversationMessage } from '@/types/conversation';
|
||||||
import type { ConversationMessage, ToolUseBlock, ToolResultBlock } from '@/types/conversation';
|
|
||||||
|
|
||||||
interface MessageListProps {
|
interface MessageListProps {
|
||||||
messages: ConversationMessage[];
|
messages: ConversationMessage[];
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// OAuth callback handler for popup-based OAuth flows
|
// OAuth callback handler for popup-based OAuth flows
|
||||||
|
|
||||||
import React, { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
interface OAuthCallbackProps {
|
interface OAuthCallbackProps {
|
||||||
type: 'inference' | 'mcp';
|
type: 'inference' | 'mcp';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// React context for conversation management and agent loop orchestration
|
// React context for conversation management and agent loop orchestration
|
||||||
|
|
||||||
import React, { createContext, useContext, useState, useCallback, useEffect, ReactNode, useRef } from 'react';
|
import { createContext, useContext, useState, useCallback, useEffect, ReactNode, useRef } from 'react';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
|
@ -8,7 +8,6 @@ import type {
|
||||||
ConversationMessage,
|
ConversationMessage,
|
||||||
ConversationContextValue,
|
ConversationContextValue,
|
||||||
AgentLoopState,
|
AgentLoopState,
|
||||||
ConversationContentBlock,
|
|
||||||
} from '@/types/conversation';
|
} from '@/types/conversation';
|
||||||
import { useAgentLoop } from '@/hooks/useAgentLoop';
|
import { useAgentLoop } from '@/hooks/useAgentLoop';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// React context for inference provider management
|
// React context for inference provider management
|
||||||
|
|
||||||
import React, { createContext, useContext, useState, useCallback, useEffect, ReactNode } from 'react';
|
import { createContext, useContext, useState, useCallback, useEffect, ReactNode } from 'react';
|
||||||
import type {
|
import type {
|
||||||
InferenceProvider,
|
InferenceProvider,
|
||||||
InferenceRequest,
|
InferenceRequest,
|
||||||
|
|
@ -38,12 +38,12 @@ interface InferenceProviderProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InferenceProvider({ children }: InferenceProviderProps) {
|
export function InferenceContextProvider({ children }: InferenceProviderProps) {
|
||||||
const [provider, setProviderState] = useState<InferenceProvider | null>(null);
|
const [provider, setProviderState] = useState<InferenceProvider | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [selectedModelId, setSelectedModelId] = useState<string | undefined>(undefined);
|
const [selectedModelId, setSelectedModelId] = useState<string | undefined>(undefined);
|
||||||
const [authStateVersion, setAuthStateVersion] = useState(0); // Force re-renders on auth changes
|
const [_, setAuthStateVersion] = useState(0); // Force re-renders on auth changes
|
||||||
|
|
||||||
const setProvider = useCallback((newProvider: InferenceProvider) => {
|
const setProvider = useCallback((newProvider: InferenceProvider) => {
|
||||||
setProviderState(newProvider);
|
setProviderState(newProvider);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// React context for MCP server connection management
|
// React context for MCP server connection management
|
||||||
|
|
||||||
import React, { createContext, useContext, useState, useCallback, useEffect, ReactNode, useRef } from 'react';
|
import { createContext, useContext, useState, useCallback, useEffect, ReactNode, useRef } from 'react';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
|
@ -8,7 +8,6 @@ import type {
|
||||||
MCPServerConfig,
|
MCPServerConfig,
|
||||||
MCPResource,
|
MCPResource,
|
||||||
MCPContextValue,
|
MCPContextValue,
|
||||||
MCPError,
|
|
||||||
} from '@/types/mcp';
|
} from '@/types/mcp';
|
||||||
import type { Tool } from '@/types/inference';
|
import type { Tool } from '@/types/inference';
|
||||||
import { MCPConnectionManager } from '@/mcp/connection';
|
import { MCPConnectionManager } from '@/mcp/connection';
|
||||||
|
|
@ -158,18 +157,14 @@ export function MCPProvider({ children }: MCPProviderProps) {
|
||||||
authType: config.authType,
|
authType: config.authType,
|
||||||
errorType: typeof error,
|
errorType: typeof error,
|
||||||
errorConstructor: error?.constructor?.name,
|
errorConstructor: error?.constructor?.name,
|
||||||
errorMessage: error?.message,
|
errorMessage: error instanceof Error ? error.message : 'Unknown error',
|
||||||
errorDetails: error?.details,
|
|
||||||
detailsConstructor: error?.details?.constructor?.name,
|
|
||||||
fullError: error
|
fullError: error
|
||||||
});
|
});
|
||||||
|
|
||||||
const isUnauthorizedError =
|
const isUnauthorizedError =
|
||||||
config.authType === 'oauth' && (
|
config.authType === 'oauth' && (
|
||||||
(error instanceof Error && error.message === 'Unauthorized') ||
|
(error instanceof Error && error.message === 'Unauthorized') ||
|
||||||
(error instanceof Error && error.constructor.name === 'UnauthorizedError') ||
|
(error instanceof Error && error.constructor.name === 'UnauthorizedError')
|
||||||
(error && typeof error === 'object' && error.message === 'Unauthorized' &&
|
|
||||||
error.details && error.details.constructor && error.details.constructor.name === 'UnauthorizedError')
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isUnauthorizedError) {
|
if (isUnauthorizedError) {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import type {
|
||||||
UseAgentLoopReturn,
|
UseAgentLoopReturn,
|
||||||
ConversationMessage,
|
ConversationMessage,
|
||||||
Conversation,
|
Conversation,
|
||||||
ToolUseBlock,
|
|
||||||
ToolResultBlock,
|
ToolResultBlock,
|
||||||
TestTool,
|
TestTool,
|
||||||
} from '@/types/conversation';
|
} from '@/types/conversation';
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,7 @@ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/
|
||||||
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
||||||
import {
|
import {
|
||||||
auth,
|
auth,
|
||||||
discoverOAuthMetadata,
|
type OAuthClientProvider
|
||||||
extractResourceMetadataUrl,
|
|
||||||
type OAuthClientProvider,
|
|
||||||
type OAuthClientMetadata,
|
|
||||||
type OAuthClientInformation,
|
|
||||||
type OAuthTokens,
|
|
||||||
type OAuthClientInformationFull
|
|
||||||
} from '@modelcontextprotocol/sdk/client/auth.js';
|
} from '@modelcontextprotocol/sdk/client/auth.js';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
|
@ -20,7 +14,6 @@ import type {
|
||||||
MCPServerConfig,
|
MCPServerConfig,
|
||||||
MCPResource,
|
MCPResource,
|
||||||
MCPPrompt,
|
MCPPrompt,
|
||||||
MCPConnectionManager,
|
|
||||||
MCPError,
|
MCPError,
|
||||||
} from '@/types/mcp';
|
} from '@/types/mcp';
|
||||||
import type { Tool } from '@/types/inference';
|
import type { Tool } from '@/types/inference';
|
||||||
|
|
@ -31,6 +24,33 @@ interface MCPOAuthState {
|
||||||
expiresAt: number;
|
expiresAt: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OAuth types matching the MCP SDK's internal interfaces
|
||||||
|
interface OAuthClientMetadata {
|
||||||
|
redirect_uris: string[];
|
||||||
|
grant_types?: string[];
|
||||||
|
response_types?: string[];
|
||||||
|
client_name?: string;
|
||||||
|
token_endpoint_auth_method?: string;
|
||||||
|
scope?: string;
|
||||||
|
jwks_uri?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OAuthClientInformation {
|
||||||
|
client_id: string;
|
||||||
|
client_secret?: string;
|
||||||
|
registration_access_token?: string;
|
||||||
|
registration_client_uri?: string;
|
||||||
|
client_secret_expires_at?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OAuthTokens {
|
||||||
|
access_token: string;
|
||||||
|
token_type: string;
|
||||||
|
refresh_token?: string;
|
||||||
|
expires_in?: number;
|
||||||
|
scope?: string;
|
||||||
|
}
|
||||||
|
|
||||||
class MCPOAuthProvider implements OAuthClientProvider {
|
class MCPOAuthProvider implements OAuthClientProvider {
|
||||||
private connectionId: string;
|
private connectionId: string;
|
||||||
private serverName: string;
|
private serverName: string;
|
||||||
|
|
@ -85,7 +105,7 @@ class MCPOAuthProvider implements OAuthClientProvider {
|
||||||
return stored ? JSON.parse(stored) : undefined;
|
return stored ? JSON.parse(stored) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveClientInformation(clientInformation: OAuthClientInformationFull): Promise<void> {
|
async saveClientInformation(clientInformation: OAuthClientInformation): Promise<void> {
|
||||||
console.log('Registered OAuth client for MCP server');
|
console.log('Registered OAuth client for MCP server');
|
||||||
const serverKey = this.getServerKey();
|
const serverKey = this.getServerKey();
|
||||||
localStorage.setItem(`mcp_oauth_client_${serverKey}`, JSON.stringify(clientInformation));
|
localStorage.setItem(`mcp_oauth_client_${serverKey}`, JSON.stringify(clientInformation));
|
||||||
|
|
@ -212,11 +232,12 @@ class MCPOAuthProvider implements OAuthClientProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MCPConnectionManager implements MCPConnectionManager {
|
export class MCPConnectionManager {
|
||||||
private connection: MCPConnection;
|
private connection: MCPConnection;
|
||||||
private client?: Client;
|
private client?: Client;
|
||||||
private transport?: Transport;
|
private transport?: Transport;
|
||||||
private reconnectTimeout?: NodeJS.Timeout;
|
private reconnectTimeout?: NodeJS.Timeout;
|
||||||
|
private healthCheckInterval?: NodeJS.Timeout;
|
||||||
private oauthProvider?: MCPOAuthProvider;
|
private oauthProvider?: MCPOAuthProvider;
|
||||||
private onConnectionUpdate?: () => void;
|
private onConnectionUpdate?: () => void;
|
||||||
|
|
||||||
|
|
@ -309,6 +330,9 @@ export class MCPConnectionManager implements MCPConnectionManager {
|
||||||
this.connection.lastConnected = new Date();
|
this.connection.lastConnected = new Date();
|
||||||
this.connection.connectionAttempts = 0;
|
this.connection.connectionAttempts = 0;
|
||||||
|
|
||||||
|
// Start health check monitoring
|
||||||
|
this.startHealthCheck();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.connection.status = 'failed';
|
this.connection.status = 'failed';
|
||||||
this.connection.error = error instanceof Error ? error.message : 'Unknown connection error';
|
this.connection.error = error instanceof Error ? error.message : 'Unknown connection error';
|
||||||
|
|
@ -339,6 +363,11 @@ export class MCPConnectionManager implements MCPConnectionManager {
|
||||||
this.reconnectTimeout = undefined;
|
this.reconnectTimeout = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.healthCheckInterval) {
|
||||||
|
clearInterval(this.healthCheckInterval);
|
||||||
|
this.healthCheckInterval = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.client) {
|
if (this.client) {
|
||||||
try {
|
try {
|
||||||
await this.client.close();
|
await this.client.close();
|
||||||
|
|
@ -441,30 +470,6 @@ export class MCPConnectionManager implements MCPConnectionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleOAuthAuthentication(): Promise<void> {
|
|
||||||
if (!this.oauthProvider) {
|
|
||||||
throw new Error('OAuth provider not initialized');
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Starting OAuth authentication flow...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await auth(this.oauthProvider, {
|
|
||||||
serverUrl: this.connection.url,
|
|
||||||
scope: this.connection.config.oauthConfig?.scope,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result === 'REDIRECT') {
|
|
||||||
// OAuth flow was initiated via popup, no further action needed here
|
|
||||||
console.log('OAuth flow initiated via popup');
|
|
||||||
} else if (result === 'AUTHORIZED') {
|
|
||||||
console.log('OAuth authentication successful');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('OAuth authentication failed:', error);
|
|
||||||
throw new Error(`OAuth authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async tryStreamableHttp(): Promise<void> {
|
private async tryStreamableHttp(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
|
@ -663,6 +668,60 @@ export class MCPConnectionManager implements MCPConnectionManager {
|
||||||
return Math.min(1000 * Math.pow(2, this.connection.connectionAttempts - 1), 16000);
|
return Math.min(1000 * Math.pow(2, this.connection.connectionAttempts - 1), 16000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private startHealthCheck(): void {
|
||||||
|
// Clear any existing health check
|
||||||
|
if (this.healthCheckInterval) {
|
||||||
|
clearInterval(this.healthCheckInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start health check every 30 seconds
|
||||||
|
this.healthCheckInterval = setInterval(async () => {
|
||||||
|
await this.performHealthCheck();
|
||||||
|
}, 30000);
|
||||||
|
|
||||||
|
console.log(`Started health check monitoring for ${this.connection.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async performHealthCheck(): Promise<void> {
|
||||||
|
// Only check if we're supposed to be connected
|
||||||
|
if (this.connection.status !== 'connected' || !this.client) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try to list tools as a health check - this is a lightweight operation
|
||||||
|
await this.client.listTools();
|
||||||
|
console.log(`Health check passed for ${this.connection.name}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Health check failed for ${this.connection.name}:`, error);
|
||||||
|
await this.handleHealthCheckFailure(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleHealthCheckFailure(error: any): Promise<void> {
|
||||||
|
console.log(`Connection health check failed for ${this.connection.name}, attempting reconnection...`);
|
||||||
|
|
||||||
|
// Stop health check during reconnection attempt
|
||||||
|
if (this.healthCheckInterval) {
|
||||||
|
clearInterval(this.healthCheckInterval);
|
||||||
|
this.healthCheckInterval = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark as disconnected and attempt reconnection
|
||||||
|
this.connection.status = 'connecting';
|
||||||
|
this.connection.error = error instanceof Error ? error.message : 'Health check failed';
|
||||||
|
this.notifyConnectionUpdate();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Attempt reconnection
|
||||||
|
await this.connect();
|
||||||
|
console.log(`Health check reconnection successful for ${this.connection.name}`);
|
||||||
|
} catch (reconnectError) {
|
||||||
|
console.error(`Health check reconnection failed for ${this.connection.name}:`, reconnectError);
|
||||||
|
// The connect method will handle scheduling retry attempts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private createMCPError(type: MCPError['type'], message: string, details?: any): MCPError {
|
private createMCPError(type: MCPError['type'], message: string, details?: any): MCPError {
|
||||||
return {
|
return {
|
||||||
type,
|
type,
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,10 @@ export class OpenRouterClient {
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const url = `${this.baseUrl}${endpoint}`;
|
const url = `${this.baseUrl}${endpoint}`;
|
||||||
|
|
||||||
const headers: HeadersInit = {
|
const headers: Record<string, string> = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': `Bearer ${authToken}`,
|
'Authorization': `Bearer ${authToken}`,
|
||||||
...options.headers,
|
...(options.headers as Record<string, string> || {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.httpReferrer) {
|
if (this.httpReferrer) {
|
||||||
|
|
@ -128,6 +128,7 @@ export class OpenRouterClient {
|
||||||
|
|
||||||
// Check if model supports tools by looking at supported_parameters
|
// Check if model supports tools by looking at supported_parameters
|
||||||
const supportsTools = openRouterModel.supported_parameters?.includes('tools') || false;
|
const supportsTools = openRouterModel.supported_parameters?.includes('tools') || false;
|
||||||
|
console.log(`Model ${openRouterModel.id} supports tools: ${supportsTools}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: openRouterModel.id,
|
id: openRouterModel.id,
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,6 @@
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src", "**/*.ts", "**/*.tsx"],
|
"include": ["src/**/*"],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue