Fix duplicate key warnings and improve inference message details

- Fix duplicate React keys by using unique IDs for request/response/error messages
- Show complete JSON data in Request/Response/Error details sections
- Add Message Metadata section showing all message properties
- Remove debug console.log statements

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jerome 2025-06-17 15:02:22 +01:00
commit 4771d8f1c8
No known key found for this signature in database
8 changed files with 496 additions and 127 deletions

View file

@ -0,0 +1,186 @@
// Component to monitor and display inference messages
import { useState, useRef } from 'react';
import { useInference } from '@/contexts/InferenceContext';
import type { InferenceMessage } from '@/types/inference';
export function InferenceMessageMonitor() {
const { messages, clearMessages } = useInference();
const [maxMessages, setMaxMessages] = useState(50);
const messagesEndRef = useRef<HTMLDivElement>(null);
// Slice messages to show only the latest maxMessages
const displayMessages = messages.slice(-maxMessages);
const getMessageTypeColor = (type: InferenceMessage['type']) => {
switch (type) {
case 'request':
return 'bg-blue-50 dark:bg-blue-900/30 border-blue-200 dark:border-blue-800';
case 'response':
return 'bg-green-50 dark:bg-green-900/30 border-green-200 dark:border-green-800';
case 'error':
return 'bg-red-50 dark:bg-red-900/30 border-red-200 dark:border-red-800';
case 'stream_chunk':
return 'bg-yellow-50 dark:bg-yellow-900/30 border-yellow-200 dark:border-yellow-800';
default:
return 'bg-gray-50 dark:bg-gray-900/30 border-gray-200 dark:border-gray-800';
}
};
const formatDuration = (duration?: number) => {
if (!duration) return '';
if (duration < 1000) return `${duration}ms`;
return `${(duration / 1000).toFixed(2)}s`;
};
const formatMessage = (message: InferenceMessage) => {
switch (message.type) {
case 'request':
return `Request to ${message.model || 'default model'}`;
case 'response':
return `Response from ${message.model || 'default model'}`;
case 'error':
return `Error from ${message.model || 'default model'}`;
case 'stream_chunk':
return `Stream chunk from ${message.model || 'default model'}`;
default:
return 'Unknown message type';
}
};
return (
<div className="flex-1 flex flex-col border-t border-gray-200 dark:border-gray-700 min-h-0">
<div className="flex items-center justify-between p-4 pb-2 flex-shrink-0">
<h3 className="font-medium text-gray-900 dark:text-white">
Inference Monitor ({displayMessages.length})
</h3>
<button
onClick={clearMessages}
className="text-xs px-2 py-1 bg-red-100 dark:bg-red-900/50 text-red-800 dark:text-red-200 rounded hover:bg-red-200 dark:hover:bg-red-800"
>
Clear
</button>
</div>
<div className="flex-1 mx-4 mb-2 overflow-y-auto border border-gray-200 dark:border-gray-600 rounded-lg min-h-0">
{displayMessages.length === 0 ? (
<div className="p-4 text-center text-gray-500 dark:text-gray-400 text-sm">
No messages yet. Messages will appear here as inference requests are made.
</div>
) : (
<div className="p-2 space-y-2">
{displayMessages.map((message) => (
<div
key={message.id}
className={`p-2 rounded border text-xs ${getMessageTypeColor(message.type)}`}
>
<div className="flex items-center justify-between mb-1">
<div className="flex items-center space-x-2">
<span className={
message.type === 'request' ? 'text-blue-600 dark:text-blue-400' :
message.type === 'response' ? 'text-green-600 dark:text-green-400' :
message.type === 'error' ? 'text-red-600 dark:text-red-400' :
'text-yellow-600 dark:text-yellow-400'
}>
{message.type === 'request' ? '→' :
message.type === 'response' ? '←' :
message.type === 'error' ? '✗' : '⋯'}
</span>
<span className="font-medium text-gray-900 dark:text-white">
{formatMessage(message)}
</span>
{message.duration && (
<span className="text-gray-500 dark:text-gray-400">
({formatDuration(message.duration)})
</span>
)}
</div>
<span className="text-gray-500 dark:text-gray-400">
{message.timestamp.toLocaleTimeString()}
</span>
</div>
{/* Message metadata */}
<details className="text-gray-600 dark:text-gray-300 mt-2">
<summary className="cursor-pointer hover:text-gray-800 dark:hover:text-gray-100">
Message Metadata
</summary>
<pre className="mt-1 text-xs bg-gray-100 dark:bg-gray-800 p-2 rounded overflow-x-auto">
{JSON.stringify({
id: message.id,
type: message.type,
providerId: message.providerId,
providerName: message.providerName,
model: message.model,
timestamp: message.timestamp.toISOString(),
duration: message.duration
}, null, 2)}
</pre>
</details>
{/* Request details */}
{message.request && (
<details className="text-gray-600 dark:text-gray-300 mt-2">
<summary className="cursor-pointer hover:text-gray-800 dark:hover:text-gray-100">
Request Details
</summary>
<pre className="mt-1 text-xs bg-gray-100 dark:bg-gray-800 p-2 rounded overflow-x-auto">
{JSON.stringify(message.request, null, 2)}
</pre>
</details>
)}
{/* Response details */}
{message.response && (
<details className="text-gray-600 dark:text-gray-300 mt-2">
<summary className="cursor-pointer hover:text-gray-800 dark:hover:text-gray-100">
Response Details
</summary>
<pre className="mt-1 text-xs bg-gray-100 dark:bg-gray-800 p-2 rounded overflow-x-auto">
{JSON.stringify(message.response, null, 2)}
</pre>
</details>
)}
{/* Error details */}
{message.error && (
<details className="text-gray-600 dark:text-gray-300 mt-2">
<summary className="cursor-pointer hover:text-gray-800 dark:hover:text-gray-100">
Error Details
</summary>
<pre className="mt-1 text-xs bg-gray-100 dark:bg-gray-800 p-2 rounded overflow-x-auto">
{message.error instanceof Error ? message.error.message : JSON.stringify(message.error, null, 2)}
</pre>
</details>
)}
</div>
))}
<div ref={messagesEndRef} />
</div>
)}
</div>
{/* Controls */}
<div className="p-4 pt-2 flex items-center justify-between text-xs border-t border-gray-200 dark:border-gray-700 flex-shrink-0">
<div className="flex items-center space-x-2">
<label className="text-gray-600 dark:text-gray-400">
Max messages:
</label>
<select
value={maxMessages}
onChange={(e) => setMaxMessages(Number(e.target.value))}
className="px-2 py-1 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
>
<option value={25}>25</option>
<option value={50}>50</option>
<option value={100}>100</option>
<option value={200}>200</option>
</select>
</div>
<div className="text-gray-500 dark:text-gray-400">
Live monitoring active
</div>
</div>
</div>
);
}

View file

@ -3,10 +3,13 @@
import { useState } from 'react';
import { useInference } from '@/contexts/InferenceContext';
import { OpenRouterOAuthProvider } from '@/providers/openrouter/oauth-provider';
import { InferenceMessageMonitor } from './InferenceMessageMonitor';
export function InferenceTab() {
const { provider, models, clearProvider, setProvider } = useInference();
const [isConnecting, setIsConnecting] = useState(false);
const [showConnectionStatus, setShowConnectionStatus] = useState(true);
const [showModels, setShowModels] = useState(false);
const handleConnect = async () => {
setIsConnecting(true);
@ -28,88 +31,112 @@ export function InferenceTab() {
};
return (
<div className="flex-1 overflow-y-auto p-4">
<div className="space-y-4">
{/* Connection Status */}
<div className="bg-gray-50 dark:bg-gray-700 rounded-lg p-3">
<h3 className="font-medium text-gray-900 dark:text-white mb-2">
Inference Provider
</h3>
{provider ? (
<div className="space-y-2">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-700 dark:text-gray-300">
<span className="text-green-600 dark:text-green-400">🟢</span> Connected to OpenRouter
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
{models.length} models available
</p>
</div>
<button
onClick={handleDisconnect}
className="px-3 py-1 text-xs bg-red-100 dark:bg-red-900/50 text-red-800 dark:text-red-200 rounded hover:bg-red-200 dark:hover:bg-red-800"
>
Disconnect
</button>
</div>
</div>
) : (
<div className="space-y-2">
<p className="text-sm text-gray-700 dark:text-gray-300">
<span className="text-gray-400">⚪</span> Not connected
</p>
<button
onClick={handleConnect}
disabled={isConnecting}
className="w-full px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
>
{isConnecting ? 'Connecting...' : 'Connect to OpenRouter'}
</button>
</div>
)}
</div>
{/* Available Models */}
{provider && models.length > 0 && (
<div>
<h3 className="font-medium text-gray-900 dark:text-white mb-3">
Available Models ({models.length})
<div className="h-full flex flex-col overflow-hidden">
{/* Connection Status */}
<div className="border-b border-gray-200 dark:border-gray-700">
<button
onClick={() => setShowConnectionStatus(!showConnectionStatus)}
className="w-full p-4 text-left hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors"
>
<div className="flex items-center justify-between">
<h3 className="font-medium text-gray-900 dark:text-white">
Connection Status
</h3>
<div className="space-y-1 max-h-64 overflow-y-auto">
{models.map((model) => (
<div
key={model.id}
className="text-xs bg-gray-50 dark:bg-gray-700 rounded px-2 py-2"
>
<div className="font-mono text-gray-900 dark:text-white">
{model.id}
</div>
{model.name && model.name !== model.id && (
<div className="text-gray-600 dark:text-gray-300 mt-1">
{model.name}
<span className="text-gray-500 dark:text-gray-400">
{showConnectionStatus ? '−' : '+'}
</span>
</div>
</button>
{showConnectionStatus && (
<div className="px-4 pb-4">
<div className="bg-gray-50 dark:bg-gray-700 rounded-lg p-3">
{provider ? (
<div className="space-y-2">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-700 dark:text-gray-300">
<span className="text-green-600 dark:text-green-400">🟢</span> Connected to {provider.name}
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
{models.length} models available
</p>
</div>
)}
<div className="text-gray-500 dark:text-gray-400 mt-1">
Context: {model.contextLength?.toLocaleString() || 'Unknown'}
<button
onClick={handleDisconnect}
className="px-3 py-1 text-xs bg-red-100 dark:bg-red-900/50 text-red-800 dark:text-red-200 rounded hover:bg-red-200 dark:hover:bg-red-800"
>
Disconnect
</button>
</div>
</div>
))}
) : (
<div className="space-y-2">
<p className="text-sm text-gray-700 dark:text-gray-300">
<span className="text-gray-400">⚪</span> Not connected
</p>
<button
onClick={handleConnect}
disabled={isConnecting}
className="w-full px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
>
{isConnecting ? 'Connecting...' : 'Connect to OpenRouter'}
</button>
<div className="text-xs text-gray-500 dark:text-gray-400 mt-2 space-y-1">
<p>Connect to OpenRouter to access AI models for conversations.</p>
<p>You'll need an OpenRouter account and API key to authenticate.</p>
</div>
</div>
)}
</div>
</div>
)}
{/* Help Text */}
<div className="text-xs text-gray-500 dark:text-gray-400 space-y-1">
<p>
Connect to OpenRouter to access AI models for conversations.
</p>
<p>
You'll need an OpenRouter account and API key to authenticate.
</p>
</div>
</div>
{/* Available Models */}
{provider && models.length > 0 && (
<div className="flex-shrink-0 border-b border-gray-200 dark:border-gray-700">
<button
onClick={() => setShowModels(!showModels)}
className="w-full p-4 text-left hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors"
>
<div className="flex items-center justify-between">
<h3 className="font-medium text-gray-900 dark:text-white">
Available Models ({models.length})
</h3>
<span className="text-gray-500 dark:text-gray-400">
{showModels ? '−' : '+'}
</span>
</div>
</button>
{showModels && (
<div className="px-4 pb-4">
<div className="space-y-1 max-h-60 overflow-y-auto">
{models.map((model) => (
<div
key={model.id}
className="text-xs bg-gray-50 dark:bg-gray-700 rounded px-2 py-2"
>
<div className="font-mono text-gray-900 dark:text-white">
{model.id}
</div>
{model.name && model.name !== model.id && (
<div className="text-gray-600 dark:text-gray-300 mt-1">
{model.name}
</div>
)}
<div className="text-gray-500 dark:text-gray-400 mt-1">
Context: {model.contextLength?.toLocaleString() || 'Unknown'}
</div>
</div>
))}
</div>
</div>
)}
</div>
)}
{/* Inference Message Monitor */}
<InferenceMessageMonitor />
</div>
);
}

View file

@ -1,53 +1,16 @@
// Component to monitor and display MCP messages
import { useState, useEffect, useRef } from 'react';
import { useState, useRef } from 'react';
import { useMCP } from '@/contexts/MCPContext';
import type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
interface MCPMessageEntry {
id: string;
timestamp: Date;
connectionId: string;
connectionName: string;
direction: 'sent' | 'received';
message: JSONRPCMessage;
extra?: any;
}
export function MCPMessageMonitor() {
const { addMessageCallback, removeMessageCallback, connections } = useMCP();
const [messages, setMessages] = useState<MCPMessageEntry[]>([]);
const { messages, clearMessages } = useMCP();
const [maxMessages, setMaxMessages] = useState(50);
const messagesEndRef = useRef<HTMLDivElement>(null);
// Remove auto-scroll - let user control scroll position
// Register message callback
useEffect(() => {
const callbackId = addMessageCallback((connectionId, _client, message, direction, extra) => {
const connection = connections.find(c => c.id === connectionId);
setMessages(prev => {
const newMessage: MCPMessageEntry = {
id: `${Date.now()}-${Math.random()}`,
timestamp: new Date(),
connectionId,
connectionName: connection?.name || 'Unknown',
direction,
message,
extra,
};
// Keep only the latest maxMessages
const newMessages = [...prev, newMessage];
return newMessages.slice(-maxMessages);
});
});
return () => {
removeMessageCallback(callbackId);
};
}, [addMessageCallback, removeMessageCallback, connections, maxMessages]);
// Slice messages to show only the latest maxMessages
const displayMessages = messages.slice(-maxMessages);
const formatMessage = (msg: JSONRPCMessage) => {
if ('method' in msg) {
@ -74,15 +37,12 @@ export function MCPMessageMonitor() {
return baseClass;
};
const clearMessages = () => {
setMessages([]);
};
return (
<div className="flex-1 flex flex-col border-t border-gray-200 dark:border-gray-700 min-h-0">
<div className="flex items-center justify-between p-4 pb-2 flex-shrink-0">
<h3 className="font-medium text-gray-900 dark:text-white">
Message Monitor ({messages.length})
Message Monitor ({displayMessages.length})
</h3>
<button
onClick={clearMessages}
@ -93,13 +53,13 @@ export function MCPMessageMonitor() {
</div>
<div className="flex-1 mx-4 mb-2 overflow-y-auto border border-gray-200 dark:border-gray-600 rounded-lg">
{messages.length === 0 ? (
{displayMessages.length === 0 ? (
<div className="p-4 text-center text-gray-500 dark:text-gray-400 text-sm">
No messages yet. Messages will appear here as they're sent/received.
</div>
) : (
<div className="p-2 space-y-2">
{messages.map((entry) => (
{displayMessages.map((entry) => (
<div
key={entry.id}
className={`p-2 rounded border text-xs ${getMessageTypeColor(entry.message, entry.direction)}`}
@ -113,7 +73,7 @@ export function MCPMessageMonitor() {
{formatMessage(entry.message)}
</span>
<span className="text-gray-500 dark:text-gray-400">
{entry.connectionName}
{entry.serverName}
</span>
</div>
<span className="text-gray-500 dark:text-gray-400">

View file

@ -6,7 +6,10 @@ import type {
InferenceRequest,
InferenceResponse,
Model,
InferenceMessage,
InferenceMessageCallback,
} from '@/types/inference';
import { v4 as uuidv4 } from 'uuid';
import { OpenRouterApiProvider } from '@/providers/openrouter/api-provider';
import { OpenRouterOAuthProvider } from '@/providers/openrouter/oauth-provider';
@ -30,6 +33,12 @@ interface InferenceContextValue {
models: Model[];
selectedModel: Model | undefined;
isAuthenticated: boolean;
// Message monitoring
messages: InferenceMessage[];
addMessageCallback: (callback: InferenceMessageCallback) => string; // Returns callback ID
removeMessageCallback: (callbackId: string) => void;
clearMessages: () => void;
}
const InferenceContext = createContext<InferenceContextValue | null>(null);
@ -47,6 +56,22 @@ export function InferenceContextProvider({ children }: InferenceProviderProps) {
return localStorage.getItem('selected_model_id') || undefined;
});
const [_, setAuthStateVersion] = useState(0); // Force re-renders on auth changes
// Message callback management
const [messageCallbacks, setMessageCallbacks] = useState<Map<string, InferenceMessageCallback>>(new Map());
// Store messages in context for persistence
const [messages, setMessages] = useState<InferenceMessage[]>([]);
const maxMessages = 100; // Keep last 100 messages
// Function to add a message to the stored messages
const addMessage = useCallback((message: InferenceMessage) => {
setMessages(prev => {
const newMessages = [...prev, message];
// Keep only the latest maxMessages
return newMessages.slice(-maxMessages);
});
}, []);
const setProvider = useCallback((newProvider: InferenceProvider) => {
setProviderState(newProvider);
@ -136,18 +161,91 @@ export function InferenceContextProvider({ children }: InferenceProviderProps) {
setIsLoading(true);
setError(null);
const requestId = uuidv4();
const startTime = Date.now();
// Broadcast request message
const requestMessage: InferenceMessage = {
id: `${requestId}-request`,
timestamp: new Date(),
type: 'request',
providerId: provider.id,
providerName: provider.name,
model: request.model,
request,
};
// Store message in context
addMessage(requestMessage);
messageCallbacks.forEach(callback => {
try {
callback(requestMessage);
} catch (error) {
console.error('Error in inference message callback:', error);
}
});
try {
const response = await provider.generateResponse(request);
// Broadcast response message
const responseMessage: InferenceMessage = {
id: `${requestId}-response`,
timestamp: new Date(),
type: 'response',
providerId: provider.id,
providerName: provider.name,
model: request.model,
response,
duration: Date.now() - startTime,
};
// Store message in context
addMessage(responseMessage);
messageCallbacks.forEach(callback => {
try {
callback(responseMessage);
} catch (error) {
console.error('Error in inference message callback:', error);
}
});
return response;
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Inference request failed';
setError(errorMessage);
// Broadcast error message
const errorMessageObj: InferenceMessage = {
id: `${requestId}-error`,
timestamp: new Date(),
type: 'error',
providerId: provider.id,
providerName: provider.name,
model: request.model,
error: err,
duration: Date.now() - startTime,
};
// Store message in context
addMessage(errorMessageObj);
messageCallbacks.forEach(callback => {
try {
callback(errorMessageObj);
} catch (error) {
console.error('Error in inference message callback:', error);
}
});
throw err;
} finally {
setIsLoading(false);
}
}, [provider]);
}, [provider, messageCallbacks, addMessage]);
const selectModel = useCallback((modelId: string) => {
if (!provider) {
@ -209,6 +307,25 @@ export function InferenceContextProvider({ children }: InferenceProviderProps) {
}
}, [provider]);
// Message callback management
const addMessageCallback = useCallback((callback: InferenceMessageCallback): string => {
const callbackId = uuidv4();
setMessageCallbacks(prev => new Map(prev).set(callbackId, callback));
return callbackId;
}, []);
const removeMessageCallback = useCallback((callbackId: string) => {
setMessageCallbacks(prev => {
const newMap = new Map(prev);
newMap.delete(callbackId);
return newMap;
});
}, []);
const clearMessages = useCallback(() => {
setMessages([]);
}, []);
const contextValue: InferenceContextValue = {
provider,
isLoading,
@ -222,6 +339,10 @@ export function InferenceContextProvider({ children }: InferenceProviderProps) {
models: provider?.models || [],
selectedModel: selectedModelId ? provider?.models.find(m => m.id === selectedModelId) : undefined,
isAuthenticated: provider?.isAuthenticated || false,
messages,
addMessageCallback,
removeMessageCallback,
clearMessages,
};

View file

@ -9,6 +9,7 @@ import type {
MCPResource,
MCPContextValue,
MCPMessageCallback,
MCPMonitorMessage,
} from '@/types/mcp';
import type { Tool } from '@/types/inference';
import { MCPConnectionManager } from '@/mcp/connection';
@ -25,15 +26,33 @@ export function MCPProvider({ children }: MCPProviderProps) {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const hasLoadedPersisted = useRef(false);
const connectionsRef = useRef<MCPConnection[]>([]);
// Message callback management
const [messageCallbacks, setMessageCallbacks] = useState<Map<string, MCPMessageCallback>>(new Map());
const messageCallbacksRef = useRef<Map<string, MCPMessageCallback>>(new Map());
// Keep ref in sync with state
// Store messages for persistence
const [messages, setMessages] = useState<MCPMonitorMessage[]>([]);
const maxMessages = 100; // Keep last 100 messages
// Keep refs in sync with state
useEffect(() => {
messageCallbacksRef.current = messageCallbacks;
}, [messageCallbacks]);
useEffect(() => {
connectionsRef.current = connections;
}, [connections]);
// Function to add a message to the stored messages
const addMessage = useCallback((message: MCPMonitorMessage) => {
setMessages(prev => {
const newMessages = [...prev, message];
// Keep only the latest maxMessages
return newMessages.slice(-maxMessages);
});
}, []);
// Function to broadcast messages to all callbacks (stable reference)
const broadcastMessage = useCallback((
@ -43,7 +62,24 @@ export function MCPProvider({ children }: MCPProviderProps) {
direction: 'sent' | 'received',
extra?: any
) => {
console.log(`MCP Message [${direction}] from ${connectionId}:`, message); // Debug log
// console.log(`MCP Message [${direction}] from ${connectionId}:`, message); // Debug log
// Find connection name using ref to get latest state
const connection = connectionsRef.current.find(conn => conn.id === connectionId);
const serverName = connection?.name || 'Unknown Server';
// Store message
const storedMessage: MCPMonitorMessage = {
id: uuidv4(),
timestamp: new Date(),
connectionId,
serverName,
message,
direction,
extra,
};
addMessage(storedMessage);
messageCallbacksRef.current.forEach(callback => {
try {
callback(connectionId, client, message, direction, extra);
@ -51,7 +87,7 @@ export function MCPProvider({ children }: MCPProviderProps) {
console.error('Error in MCP message callback:', error);
}
});
}, []); // No dependencies - uses ref
}, [addMessage]); // Only depends on addMessage, uses ref for connections
// Load persisted connections from localStorage on mount
useEffect(() => {
@ -384,6 +420,10 @@ export function MCPProvider({ children }: MCPProviderProps) {
return newMap;
});
}, []);
const clearMessages = useCallback(() => {
setMessages([]);
}, []);
const contextValue: MCPContextValue = {
connections,
@ -402,8 +442,10 @@ export function MCPProvider({ children }: MCPProviderProps) {
getConnectionById,
updateServerConfig,
handleOAuthCallback,
messages,
addMessageCallback,
removeMessageCallback,
clearMessages,
};
return (

View file

@ -126,7 +126,7 @@ const DEFAULT_CONFIG: AgentLoopConfig = {
export function useAgentLoop(config: Partial<AgentLoopConfig> = {}): UseAgentLoopReturn {
const finalConfig = { ...DEFAULT_CONFIG, ...config };
const { provider: currentProvider, isAuthenticated } = useInference();
const { provider: currentProvider, isAuthenticated, generateResponse } = useInference();
const { getAllTools, callTool: callMCPTool, connections } = useMCP();
// Track running loops
@ -309,7 +309,7 @@ export function useAgentLoop(config: Partial<AgentLoopConfig> = {}): UseAgentLoo
temperature: finalConfig.temperature,
};
const response = await currentProvider.generateResponse(request);
const response = await generateResponse(request);
if (abortController.signal.aborted) {
break;

View file

@ -1,5 +1,22 @@
// Core inference types and interfaces
// Inference message monitoring types
export interface InferenceMessage {
id: string;
timestamp: Date;
type: 'request' | 'response' | 'error' | 'stream_chunk';
providerId: string;
providerName: string;
model?: string;
request?: InferenceRequest;
response?: InferenceResponse;
streamChunk?: any;
error?: any;
duration?: number; // Response time in ms
}
export type InferenceMessageCallback = (message: InferenceMessage) => void;
export interface Model {
id: string;
name: string;
@ -49,6 +66,8 @@ export interface Tool {
export interface InferenceRequest {
messages: ChatMessage[];
model?: string;
systemPrompt?: string;
tools?: Tool[]; // toolChoice defaults to 'auto' when tools provided
maxTokens?: number;
temperature?: number;
@ -59,6 +78,7 @@ export interface InferenceResponse {
message: ChatMessage;
usage: TokenUsage;
stopReason: 'stop' | 'max_tokens' | 'tool_calls' | 'error';
finishReason?: string;
error?: string;
}

View file

@ -41,6 +41,17 @@ export type MCPMessageCallback = (
extra?: { authInfo?: AuthInfo; options?: TransportSendOptions }
) => void;
// MCP message for storage/monitoring
export interface MCPMonitorMessage {
id: string;
timestamp: Date;
connectionId: string;
serverName: string;
message: JSONRPCMessage;
direction: 'sent' | 'received';
extra?: { authInfo?: AuthInfo; options?: TransportSendOptions };
}
export interface MCPResource {
uri: string;
name: string;
@ -143,6 +154,8 @@ export interface MCPContextValue {
handleOAuthCallback: (connectionId: string, authorizationCode: string) => Promise<void>;
// Message monitoring
messages: MCPMonitorMessage[];
addMessageCallback: (callback: MCPMessageCallback) => string; // Returns callback ID
removeMessageCallback: (callbackId: string) => void;
clearMessages: () => void;
}