Repository: UKGovernmentBEIS/inspect_ai
Issue: [Bug]: AttributeError in ChatMessageTool validator when handling string-based tool errors
Root cause: Validation Gap

## Environment Information

<details>
<summary>Environment Details</summary>

**OS:** macOS 15.5 (arm64)  
**CPU:** Apple M2 Pro  
**Python:** 3.12.9  
**Clang:** 17.0.0 (clang-1700.0.13.5)  
**CMake:** 3.31.5  

**Key Dependencies:**
- `inspect-ai==0.3.114`
- `multiagent-inspect==0.0.7`
- `openai==1.97.1`
- `tiktoken==0.9.0`

</details>

## Problem Description

The `convert_tool_error_to_error` validator in the `ChatMessageTool` class fails when processing tool call errors that are represented as raw strings. The validator incorrectly assumes its `values` parameter is always a dictionary and calls `.get()` on it, causing an `AttributeError` when a string is passed instead.

**Root Cause:** The validator lacks proper type checking and assumes a dictionary-like interface without validation.

## Steps to Reproduce

### Method 1: Direct instantiation
```python
from inspect_ai.model import ChatMessageTool

try:
    # This simulates how unhandled tool errors might be processed
    ChatMessageTool(
        tool_call_id="123",
        content="An error occurred"  # Raw string instead of wrapped error
    )
except AttributeError as e:
    print(f"Bug reproduced: {e}")
```

### Method 2: Model validation with raw string
```python
from inspect_ai.model import ChatMessageTool

try:
    ChatMessageTool.model_validate("A raw error string")
except AttributeError as e:
    print(f"Bug reproduced: {e}")
```

### Method 3: Realistic scenario simulation
```python
import asyncio
import types
from inspect_ai.model import get_model

class MockModel:
    async def generate_loop(self, *args, **kwargs):
        # Simulate malformed tool message from provider
        bad_message = types.SimpleNamespace(
            role="tool",
            content="irrelevant",
            tool_error="boom"  # String instead of dict-like object
        )
        return [], types.SimpleNamespace(message=bad_message)

async def reproduce_bug():
    # Monkey-patch for testing
    import inspect_ai.model as model_module
    model_module.get_model = lambda: MockModel()
    await get_model().generate_loop()

# Run: asyncio.run(reproduce_bug())
```

## Actual Behavior

The application crashes with an unhandled `AttributeError`:

```
Traceback (most recent call last):
  File "inspect_ai/model/_chat_message.py", line 192, in convert_tool_error_to_error
    tool_error = values.get("tool_error", None)
                 ^^^^^^^^^^
AttributeError: 'str' object has no attribute 'get'
```

## Expected Behavior

The validator should handle non-dictionary inputs gracefully by either:

1. **Silently ignoring** non-mapping payloads and continuing processing
2. **Raising a clear ValidationError** that explains the contract violation

The validator should **never** crash with an unhandled `AttributeError`.

## Suggested Fix

Add type checking to the validator:

```python
@field_validator("tool_error", mode="before")
@classmethod
def convert_tool_error_to_error(cls, values):
    # Add type checking
    if not isinstance(values, dict):
        # Option 1: Return original value
        return values
        # Option 2: Raise clear validation error
        # raise ValueError("tool_error must be a dictionary-like object")
    
    tool_error = values.get("tool_error", None)
    # ... rest of existing logic
```

If "values" is a string rather than a dict then the entire pydantic object is invalid (it won't have a "role" or "content", etc.) and other hard errors will occur soon after. This is basically totally invalid data and should fail with a hard error.