# Strands Adapter (`frisk_sdk.adapters.strands`)

This adapter integrates Frisk with the [Strands Agent SDK](https://github.com/strands-agents/sdk-python).

## Install

The Frisk SDK package includes the adapter code. You also need Strands in your app environment:

```bash
uv add strands-agents
```

## Imports

```python
from frisk_sdk.adapters.strands import Frisk, StrandsFrameworkAdapter
from frisk_sdk import Frisk as BaseFrisk
```

`frisk_sdk.adapters.strands.Frisk` is an alias for `FriskStrands`.

## Quick Start (recommended)

Use the preconfigured Strands client:

```python
from strands import Agent
from frisk_sdk.adapters.strands import Frisk

frisk = Frisk(api_key="YOUR_FRISK_API_KEY")
session_id = frisk.session()


def lookup_weather(city: str) -> str:
    return f"Sunny in {city}"


wrapped_tools = frisk.wrap_tools([lookup_weather])

agent = Agent(tools=wrapped_tools)
result = agent(
    "What is the weather in San Francisco?",
    invocation_state={"frisk_session_id": str(session_id)},
)
print(result)
```

## Enforcing policies in Strands with hooks

Use `tool_hook()` and pass it to the agent `hooks` list. The hook reads
`frisk_session_id` from `invocation_state` for each invocation.

```python
from strands import Agent
from frisk_sdk.adapters.strands import Frisk

frisk = Frisk(api_key="YOUR_FRISK_API_KEY")
session_id = frisk.session()

agent = Agent(
    tools=frisk.wrap_tools([lookup_weather]),
    hooks=[frisk.tool_hook()],
)

agent(
    "weather in sf",
    invocation_state={"frisk_session_id": str(session_id)},
)
```

## Alternative: explicit adapter wiring

```python
from strands import Agent
from frisk_sdk import Frisk
from frisk_sdk.adapters.strands import StrandsFrameworkAdapter

frisk = Frisk(
    api_key="YOUR_FRISK_API_KEY",
    adapter=StrandsFrameworkAdapter(),
)

wrapped_tools = frisk.wrap_tools([my_tool])
agent = Agent(tools=wrapped_tools)
```

If you use base `Frisk`, you can instantiate the hook directly:

```python
from frisk_sdk.adapters.strands import FriskStrandsToolHook

hook = FriskStrandsToolHook(frisk)
agent = Agent(tools=wrapped_tools, hooks=[hook])
```

## What the adapter normalizes

Tool call shape expected by `normalize_tool_call`:

```python
{
  "toolUseId": "tooluse_123",   # optional, generated when missing
  "name": "tool_name",
  "input": {"arg": "value"}     # dict
}
```

The adapter also accepts:

- Object-style tool calls with attributes (`toolUseId`, `name`, `input`).
- `args` as a fallback when `input` is not present.
- `id` as a fallback when `toolUseId` is not present.

## Prompt extraction

`extract_prompt(agent_state)` checks these in order:

1. `agent_state["messages"]` for the latest user message with text blocks.
2. `agent_state["prompt"]` as a fallback.

For Strands-style messages, user text is extracted from:

```python
{"role": "user", "content": [{"text": "..."}, ...]}
```

## Redaction behavior

The adapter uses Frisk redaction helpers for:

- `serialize_tool_args(...)`
- `serialize_agent_state(...)`

Redaction options passed by Frisk (or direct adapter calls) are applied before serialization.

## Errors to expect

- Missing tool name: raises `ValueError`.
- Non-dict tool input: raises `ValueError`.
- Wrapping plain callables without `strands-agents` installed: raises `ImportError`.
