Skip to main content
Glama
messages.py6.11 kB
import json import logging from typing import Any from ..handlers import ( MAX_BASH_CHARS, MAX_GLOB_CHARS, MAX_GREP_SEARCH_CHARS, MAX_VIEW_DIRECTORY_CHARS, MAX_VIEW_FILE_CHARS, truncate_for_context, ) logger = logging.getLogger(__name__) class MessageHistoryMixin: def _repair_tool_call_integrity(self, messages: list[dict[str, Any]], trace_id: str) -> None: """Check and repair tool_calls and tool results pairing integrity. Injects error tool result if a tool_call has no corresponding result. This prevents OpenAI-compatible providers from returning 400 due to protocol violation. """ # Collect all tool_call ids expected_ids: set[str] = set() for msg in messages: if msg.get("role") == "assistant": tool_calls = msg.get("tool_calls", []) for tc in tool_calls: tc_id = tc.get("id", "") if tc_id: expected_ids.add(tc_id) # Collect all existing tool result ids existing_ids: set[str] = set() for msg in messages: if msg.get("role") == "tool": tc_id = msg.get("tool_call_id", "") if tc_id: existing_ids.add(tc_id) # Find missing tool results missing_ids = expected_ids - existing_ids if missing_ids: logger.warning( "[%s] Found %d missing tool results, injecting error responses", trace_id, len(missing_ids), ) # Inject error tool results for tc_id in missing_ids: messages.append( { "role": "tool", "tool_call_id": tc_id, "content": "Error: Tool execution was interrupted or result was truncated.", } ) def _truncate_messages(self, messages: list[dict[str, Any]]) -> list[dict[str, Any]]: """Truncate overly long message history, keep system + user + recent turn blocks. Turn block definition: one assistant(tool_calls) + all its corresponding tool results. Truncates by complete blocks to avoid orphan tool messages. """ if len(messages) <= 8: return messages # Keep system (0) + user (1) system_and_user = messages[:2] conversation = messages[2:] # Identify turn blocks blocks: list[list[dict[str, Any]]] = [] current_block: list[dict[str, Any]] = [] for msg in conversation: role = msg.get("role", "") if role == "assistant": # If current block has content, save it first if current_block: blocks.append(current_block) # Start new block current_block = [msg] elif role == "tool": # tool message must follow assistant if current_block: current_block.append(msg) # If current_block is empty (orphan tool message), discard else: # Other types (like user), treat as independent message if current_block: blocks.append(current_block) current_block = [] blocks.append([msg]) # Last block if current_block: blocks.append(current_block) # Keep blocks from newest, target ~6-8 messages target_msg_count = 6 kept_blocks: list[list[dict[str, Any]]] = [] total_msgs = 0 for block in reversed(blocks): block_size = len(block) if total_msgs + block_size <= target_msg_count * 1.5: # Allow slight overflow kept_blocks.insert(0, block) total_msgs += block_size elif total_msgs == 0: # Keep at least the last block (even if exceeds limit) kept_blocks.insert(0, block) break else: break # Combine result result = system_and_user[:] for block in kept_blocks: result.extend(block) return result def _append_tool_results_to_messages( self, messages: list[dict[str, Any]], tool_results: list[tuple[str, str, str | dict[str, Any]]], ) -> None: """Format tool results and add to messages. Args: messages: Messages list to update. tool_results: Tool results list. """ # Tool type to truncation limit and hint mapping tool_limits = { "view_file": ( MAX_VIEW_FILE_CHARS, "For more content, narrow view_range or query in segments.", ), "grep_search": ( MAX_GREP_SEARCH_CHARS, "For more matches, use more specific query or include_pattern.", ), "glob": ( MAX_GLOB_CHARS, "To limit output, narrow the pattern, provide a narrower path, or reduce max_results.", ), "bash": ( MAX_BASH_CHARS, "To limit output, use head -n / tail -n / --max-count params.", ), "view_directory": ( MAX_VIEW_DIRECTORY_CHARS, "To see more entries, use a more specific path.", ), } for tc_id, func_name, result in tool_results: content = result if isinstance(result, str) else json.dumps(result) # Select truncation limit and hint based on tool type max_chars, hint = tool_limits.get(func_name, (MAX_VIEW_FILE_CHARS, "")) content = truncate_for_context(content, max_chars=max_chars, tool_hint=hint) messages.append( { "role": "tool", "tool_call_id": tc_id, "content": content, } )

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/possible055/relace-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server