# `Jido.Messaging.SessionKey`
[🔗](https://github.com/agentjido/jido_messaging/blob/v1.0.0/lib/jido_messaging/session_key.ex#L1)

Session key derivation for conversation scoping.

Provides a canonical way to derive a unique session identifier from a message context.
This enables applications to scope conversations by channel, bridge, room, and optionally thread.

The session key is a tuple that can be used as a process registry key, ETS key,
or any other lookup mechanism that requires a unique conversation identifier.

## Usage

    # Derive session key from message context
    key = SessionKey.from_context(msg_context)
    #=> {:telegram, "bot_123", "chat_456", nil}

    # With thread scoping
    key = SessionKey.from_context(threaded_msg_context)
    #=> {:telegram, "bot_123", "chat_456", "thread_789"}

    # Convert to string for string-keyed stores
    SessionKey.to_string(key)
    #=> "telegram:bot_123:chat_456"

# `t`

```elixir
@type t() ::
  {channel_type :: atom(), bridge_id :: String.t(), room_id :: String.t(),
   thread_id :: String.t() | nil}
```

# `from_context`

```elixir
@spec from_context(Jido.Messaging.MsgContext.t()) :: t()
```

Derives a session key from a MsgContext.

Uses `room_id` if resolved (internal UUID), otherwise falls back to `external_room_id`.
Thread scoping uses resolved `thread_id` when present and otherwise falls back to
`external_thread_id`.

## Examples

    iex> ctx = %MsgContext{channel_type: :telegram, bridge_id: "bot_1", external_room_id: "123", room_id: nil, thread_id: nil}
    iex> SessionKey.from_context(ctx)
    {:telegram, "bot_1", "123", nil}

    iex> ctx = %MsgContext{channel_type: :discord, bridge_id: "guild_1", external_room_id: "ch_1", room_id: "uuid-123", thread_id: "thread_456"}
    iex> SessionKey.from_context(ctx)
    {:discord, "guild_1", "uuid-123", "thread_456"}

# `parse`

```elixir
@spec parse(String.t()) :: {:ok, t()} | {:error, :invalid_format}
```

Parses a string representation back into a session key tuple.

Returns `{:ok, key}` on success, `{:error, :invalid_format}` on failure.

## Examples

    iex> SessionKey.parse("telegram:bot_1:chat_123")
    {:ok, {:telegram, "bot_1", "chat_123", nil}}

    iex> SessionKey.parse("discord:guild_1:ch_1:thread_456")
    {:ok, {:discord, "guild_1", "ch_1", "thread_456"}}

    iex> SessionKey.parse("invalid")
    {:error, :invalid_format}

# `same_room?`

```elixir
@spec same_room?(t(), t()) :: boolean()
```

Checks if two session keys belong to the same conversation scope.

Two keys match if channel_type, bridge_id, and room_id are equal.
Thread IDs are not compared (both threaded and non-threaded messages in the same room match).

## Examples

    iex> key1 = {:telegram, "bot_1", "chat_123", nil}
    iex> key2 = {:telegram, "bot_1", "chat_123", "thread_456"}
    iex> SessionKey.same_room?(key1, key2)
    true

    iex> key1 = {:telegram, "bot_1", "chat_123", nil}
    iex> key2 = {:telegram, "bot_1", "chat_456", nil}
    iex> SessionKey.same_room?(key1, key2)
    false

# `to_string`

```elixir
@spec to_string(t()) :: String.t()
```

Converts a session key to a string representation.

Useful for string-keyed stores like Redis or as a log identifier.
Thread ID is omitted if nil.

## Examples

    iex> SessionKey.to_string({:telegram, "bot_1", "chat_123", nil})
    "telegram:bot_1:chat_123"

    iex> SessionKey.to_string({:discord, "guild_1", "ch_1", "thread_456"})
    "discord:guild_1:ch_1:thread_456"

---

*Consult [api-reference.md](api-reference.md) for complete listing*
