Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions src/chat_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,6 @@
resolve_emoji_from_slack,
)
from chat_sdk.errors import ChatError, ChatNotImplementedError, LockError, RateLimitError
from chat_sdk.shared.errors import (
AdapterRateLimitError,
AuthenticationError,
NetworkError,
PermissionError as AdapterPermissionError,
ResourceNotFoundError,
ValidationError,
)
from chat_sdk.from_full_stream import from_full_stream
from chat_sdk.logger import ConsoleLogger, Logger, LogLevel
from chat_sdk.message_history import MessageHistoryCache, MessageHistoryConfig
Expand All @@ -95,6 +87,16 @@
text_input,
)
from chat_sdk.shared.base_format_converter import BaseFormatConverter
from chat_sdk.shared.errors import (
AdapterRateLimitError,
AuthenticationError,
NetworkError,
ResourceNotFoundError,
ValidationError,
)
from chat_sdk.shared.errors import (
PermissionError as AdapterPermissionError,
)
from chat_sdk.shared.streaming_markdown import StreamingMarkdownRenderer
from chat_sdk.state.memory import MemoryStateAdapter
from chat_sdk.thread import ThreadImpl
Expand Down
24 changes: 16 additions & 8 deletions src/chat_sdk/adapters/discord/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import os
import re
from contextvars import ContextVar
from datetime import datetime, timezone
from datetime import UTC, datetime
from typing import Any
from urllib.parse import quote

Expand Down Expand Up @@ -275,7 +275,9 @@ async def _verify_signature(
verify_key.verify(message, bytes.fromhex(signature))
return True
except ImportError:
self._logger.error("PyNaCl is required for Discord signature verification. Install with: pip install PyNaCl")
self._logger.error(
"PyNaCl is required for Discord signature verification. Install with: pip install PyNaCl"
)
return False
except Exception as exc:
self._logger.warn("Discord signature verification failed", {"error": str(exc)})
Expand Down Expand Up @@ -320,7 +322,9 @@ def _handle_component_interaction(
channel_type = channel.get("type", 0)
is_thread = channel_type in (CHANNEL_TYPE_PUBLIC_THREAD, CHANNEL_TYPE_PRIVATE_THREAD)
parent_channel_id = (
channel.get("parent_id", interaction_channel_id) if is_thread and channel.get("parent_id") else interaction_channel_id
channel.get("parent_id", interaction_channel_id)
if is_thread and channel.get("parent_id")
else interaction_channel_id
)

thread_id = self.encode_thread_id(
Expand Down Expand Up @@ -391,7 +395,9 @@ def _handle_application_command_interaction(
channel_type = channel.get("type", 0)
is_thread = channel_type in (CHANNEL_TYPE_PUBLIC_THREAD, CHANNEL_TYPE_PRIVATE_THREAD)
parent_channel_id = (
channel.get("parent_id", interaction_channel_id) if is_thread and channel.get("parent_id") else interaction_channel_id
channel.get("parent_id", interaction_channel_id)
if is_thread and channel.get("parent_id")
else interaction_channel_id
)

channel_id = self.encode_thread_id(
Expand Down Expand Up @@ -534,7 +540,9 @@ async def _handle_forwarded_message(
mentions = data.get("mentions", [])
is_user_mentioned = data.get("is_mention", False) or any(m.get("id") == self._application_id for m in mentions)
mention_roles = data.get("mention_roles", [])
is_role_mentioned = bool(self._mention_role_ids) and any(role_id in self._mention_role_ids for role_id in mention_roles)
is_role_mentioned = bool(self._mention_role_ids) and any(
role_id in self._mention_role_ids for role_id in mention_roles
)
is_mentioned = is_user_mentioned or is_role_mentioned

# If mentioned and not in a thread, create one
Expand Down Expand Up @@ -578,7 +586,7 @@ async def _handle_forwarded_message(
metadata=MessageMetadata(
date_sent=datetime.fromisoformat(data.get("timestamp", ""))
if data.get("timestamp")
else datetime.now(timezone.utc),
else datetime.now(UTC),
edited=False,
),
attachments=[
Expand Down Expand Up @@ -1158,7 +1166,7 @@ def _parse_discord_message(self, raw: dict[str, Any], thread_id: str) -> Message
is_me=is_me,
),
metadata=MessageMetadata(
date_sent=datetime.fromisoformat(msg["timestamp"]) if msg.get("timestamp") else datetime.now(timezone.utc),
date_sent=datetime.fromisoformat(msg["timestamp"]) if msg.get("timestamp") else datetime.now(UTC),
edited=msg.get("edited_timestamp") is not None,
edited_at=datetime.fromisoformat(msg["edited_timestamp"]) if msg.get("edited_timestamp") else None,
),
Expand Down Expand Up @@ -1203,7 +1211,7 @@ async def _create_discord_thread(
message_id: str,
) -> dict[str, str]:
"""Create a Discord thread from a message."""
thread_name = f"Thread {datetime.now(timezone.utc).isoformat()}"
thread_name = f"Thread {datetime.now(UTC).isoformat()}"

self._logger.debug(
"Discord API: POST thread",
Expand Down
16 changes: 10 additions & 6 deletions src/chat_sdk/adapters/github/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import re
import time
from collections.abc import AsyncIterable
from datetime import datetime, timezone
from datetime import UTC, datetime
from typing import Any

from chat_sdk.adapters.github.cards import card_to_github_markdown
Expand Down Expand Up @@ -148,7 +148,8 @@ def __init__(self, config: GitHubAdapterConfig | None = None) -> None:
else:
self._app_credentials = {"app_id": app_id, "private_key": private_key}
self._logger.info(
"GitHub adapter initialized in multi-tenant mode (installation ID will be extracted from webhooks)"
"GitHub adapter initialized in multi-tenant mode "
"(installation ID will be extracted from webhooks)"
)
else:
raise ValidationError(
Expand Down Expand Up @@ -366,7 +367,7 @@ def _parse_issue_comment(
metadata=MessageMetadata(
date_sent=datetime.fromisoformat(created_at.replace("Z", "+00:00"))
if created_at
else datetime.now(tz=timezone.utc),
else datetime.now(tz=UTC),
edited=edited,
edited_at=datetime.fromisoformat(updated_at.replace("Z", "+00:00")) if edited and updated_at else None,
),
Expand Down Expand Up @@ -408,7 +409,7 @@ def _parse_review_comment(
metadata=MessageMetadata(
date_sent=datetime.fromisoformat(created_at.replace("Z", "+00:00"))
if created_at
else datetime.now(tz=timezone.utc),
else datetime.now(tz=UTC),
edited=edited,
edited_at=datetime.fromisoformat(updated_at.replace("Z", "+00:00")) if edited and updated_at else None,
),
Expand Down Expand Up @@ -682,7 +683,10 @@ async def fetch_thread(self, thread_id: str) -> ThreadInfo:
def encode_thread_id(self, platform_data: GitHubThreadId) -> str:
"""Encode platform data into a thread ID string."""
if platform_data.review_comment_id:
return f"github:{platform_data.owner}/{platform_data.repo}:{platform_data.pr_number}:rc:{platform_data.review_comment_id}"
return (
f"github:{platform_data.owner}/{platform_data.repo}"
f":{platform_data.pr_number}:rc:{platform_data.review_comment_id}"
)
return f"github:{platform_data.owner}/{platform_data.repo}:{platform_data.pr_number}"

def decode_thread_id(self, thread_id: str) -> GitHubThreadId:
Expand Down Expand Up @@ -762,7 +766,7 @@ async def list_threads(
metadata=MessageMetadata(
date_sent=datetime.fromisoformat(pr.get("created_at", "").replace("Z", "+00:00"))
if pr.get("created_at")
else datetime.now(tz=timezone.utc),
else datetime.now(tz=UTC),
edited=pr.get("created_at") != pr.get("updated_at"),
),
)
Expand Down
3 changes: 2 additions & 1 deletion src/chat_sdk/adapters/github/cards.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ def _render_text(text: dict[str, Any]) -> list[str]:
def _render_fields(fields: dict[str, Any]) -> list[str]:
"""Render fields as key-value pairs."""
return [
f"**{_escape_markdown(f.get('label', ''))}:** {_escape_markdown(f.get('value', ''))}" for f in fields.get("children", [])
f"**{_escape_markdown(f.get('label', ''))}:** {_escape_markdown(f.get('value', ''))}"
for f in fields.get("children", [])
]


Expand Down
13 changes: 5 additions & 8 deletions src/chat_sdk/adapters/github/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import annotations

from dataclasses import dataclass
from typing import Literal, TypedDict, Union
from typing import Literal, TypedDict

from chat_sdk.logger import Logger

Expand Down Expand Up @@ -81,12 +81,9 @@ class GitHubAdapterAutoConfig(GitHubAdapterBaseConfig, total=False):


# Union of all configuration types
GitHubAdapterConfig = Union[
GitHubAdapterPATConfig,
GitHubAdapterAppConfig,
GitHubAdapterMultiTenantAppConfig,
GitHubAdapterAutoConfig,
]
GitHubAdapterConfig = (
GitHubAdapterPATConfig | GitHubAdapterAppConfig | GitHubAdapterMultiTenantAppConfig | GitHubAdapterAutoConfig
)

# =============================================================================
# Thread ID
Expand Down Expand Up @@ -290,7 +287,7 @@ class GitHubRawReviewComment(TypedDict):
pr_number: int


GitHubRawMessage = Union[GitHubRawIssueComment, GitHubRawReviewComment]
GitHubRawMessage = GitHubRawIssueComment | GitHubRawReviewComment

# =============================================================================
# GitHub API Response Types
Expand Down
20 changes: 13 additions & 7 deletions src/chat_sdk/adapters/google_chat/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import re
import time
from collections.abc import AsyncIterable, Awaitable, Callable
from datetime import datetime, timezone
from datetime import UTC, datetime
from typing import Any

from chat_sdk.adapters.google_chat.cards import card_to_google_card
Expand Down Expand Up @@ -135,7 +135,9 @@ def __init__(self, config: GoogleChatAdapterConfig | None = None) -> None:
self._pubsub_topic = config.pubsub_topic or os.environ.get("GOOGLE_CHAT_PUBSUB_TOPIC")
self._impersonate_user = config.impersonate_user or os.environ.get("GOOGLE_CHAT_IMPERSONATE_USER")
self._endpoint_url = config.endpoint_url
self._google_chat_project_number = config.google_chat_project_number or os.environ.get("GOOGLE_CHAT_PROJECT_NUMBER")
self._google_chat_project_number = config.google_chat_project_number or os.environ.get(
"GOOGLE_CHAT_PROJECT_NUMBER"
)
self._pubsub_audience = config.pubsub_audience or os.environ.get("GOOGLE_CHAT_PUBSUB_AUDIENCE")

# In-progress subscription creations to prevent duplicate requests
Expand Down Expand Up @@ -433,7 +435,9 @@ async def on_thread_subscribe(self, thread_id: str) -> None:
)

if not self._pubsub_topic:
self._logger.warn("No pubsubTopic configured, skipping space subscription. Set GOOGLE_CHAT_PUBSUB_TOPIC env var.")
self._logger.warn(
"No pubsubTopic configured, skipping space subscription. Set GOOGLE_CHAT_PUBSUB_TOPIC env var."
)
return

decoded = self.decode_thread_id(thread_id)
Expand Down Expand Up @@ -470,7 +474,9 @@ async def _ensure_space_subscription(self, space_name: str) -> None:
# Check if we already have a valid subscription
cached = await self._state.get(cache_key)
if cached:
expire_time = cached.get("expire_time", 0) if isinstance(cached, dict) else getattr(cached, "expire_time", 0)
expire_time = (
cached.get("expire_time", 0) if isinstance(cached, dict) else getattr(cached, "expire_time", 0)
)
time_until_expiry = expire_time - int(time.time() * 1000)
if time_until_expiry > SUBSCRIPTION_REFRESH_BUFFER_MS:
self._logger.debug(
Expand Down Expand Up @@ -2270,7 +2276,7 @@ async def list_threads(
last_reply_at=last_reply_at,
)
)
count += 1
count += 1 # noqa: SIM113

self._logger.debug(
"GChat API: listThreads result",
Expand Down Expand Up @@ -2677,9 +2683,9 @@ def _parse_message_metadata(message: dict[str, Any]) -> Any:
try:
date_sent = datetime.fromisoformat(create_time.replace("Z", "+00:00"))
except (ValueError, AttributeError):
date_sent = datetime.now(tz=timezone.utc)
date_sent = datetime.now(tz=UTC)
else:
date_sent = datetime.now(tz=timezone.utc)
date_sent = datetime.now(tz=UTC)

return MessageMetadata(
date_sent=date_sent,
Expand Down
3 changes: 2 additions & 1 deletion src/chat_sdk/adapters/google_chat/cards.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
card_child_to_fallback_text,
table_element_to_ascii,
)
from chat_sdk.shared import card_to_fallback_text as shared_card_to_fallback_text, create_emoji_converter
from chat_sdk.shared import card_to_fallback_text as shared_card_to_fallback_text
from chat_sdk.shared import create_emoji_converter

# Convert emoji placeholders in text to GChat format (Unicode).
convert_emoji = create_emoji_converter("gchat")
Expand Down
18 changes: 6 additions & 12 deletions src/chat_sdk/adapters/linear/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import os
import re
import time
from datetime import datetime, timezone
from datetime import UTC, datetime
from typing import Any

from chat_sdk.adapters.linear.cards import card_to_linear_markdown
Expand Down Expand Up @@ -414,7 +414,7 @@ def _build_message(
raw=LinearRawMessage(comment=comment),
author=author,
metadata=MessageMetadata(
date_sent=datetime.fromisoformat(created_at) if created_at else datetime.now(timezone.utc),
date_sent=datetime.fromisoformat(created_at) if created_at else datetime.now(UTC),
edited=created_at != updated_at,
edited_at=datetime.fromisoformat(updated_at) if (created_at != updated_at and updated_at) else None,
),
Expand All @@ -432,10 +432,7 @@ async def post_message(

# Render message to markdown
card = extract_card(message)
if card:
body = card_to_linear_markdown(card)
else:
body = self._format_converter.render_postable(message)
body = card_to_linear_markdown(card) if card else self._format_converter.render_postable(message)

# Convert emoji placeholders to unicode
body = convert_emoji_placeholders(body, "linear")
Expand Down Expand Up @@ -496,10 +493,7 @@ async def edit_message(
decoded = self.decode_thread_id(thread_id)

card = extract_card(message)
if card:
body = card_to_linear_markdown(card)
else:
body = self._format_converter.render_postable(message)
body = card_to_linear_markdown(card) if card else self._format_converter.render_postable(message)

body = convert_emoji_placeholders(body, "linear")

Expand Down Expand Up @@ -754,7 +748,7 @@ def _comment_node_to_message(
is_me=user_id == self._bot_user_id,
),
metadata=MessageMetadata(
date_sent=datetime.fromisoformat(node["createdAt"]) if node.get("createdAt") else datetime.now(timezone.utc),
date_sent=datetime.fromisoformat(node["createdAt"]) if node.get("createdAt") else datetime.now(UTC),
edited=node.get("createdAt") != node.get("updatedAt"),
edited_at=(
datetime.fromisoformat(node["updatedAt"])
Expand Down Expand Up @@ -854,7 +848,7 @@ def parse_message(self, raw: LinearRawMessage) -> Message:
),
metadata=MessageMetadata(
date_sent=(
datetime.fromisoformat(comment["created_at"]) if comment.get("created_at") else datetime.now(timezone.utc)
datetime.fromisoformat(comment["created_at"]) if comment.get("created_at") else datetime.now(UTC)
),
edited=comment.get("created_at") != comment.get("updated_at"),
edited_at=(
Expand Down
3 changes: 2 additions & 1 deletion src/chat_sdk/adapters/linear/cards.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ def _render_text(text: TextElement) -> list[str]:
def _render_fields(fields: FieldsElement) -> list[str]:
"""Render fields as key-value pairs."""
return [
f"**{_escape_markdown(f.get('label', ''))}:** {_escape_markdown(f.get('value', ''))}" for f in fields.get("children", [])
f"**{_escape_markdown(f.get('label', ''))}:** {_escape_markdown(f.get('value', ''))}"
for f in fields.get("children", [])
]


Expand Down
4 changes: 3 additions & 1 deletion src/chat_sdk/adapters/linear/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ class LinearAdapterAppConfig(LinearAdapterBaseConfig):


# Union type for all config options
LinearAdapterConfig = LinearAdapterBaseConfig | LinearAdapterAPIKeyConfig | LinearAdapterOAuthConfig | LinearAdapterAppConfig
LinearAdapterConfig = (
LinearAdapterBaseConfig | LinearAdapterAPIKeyConfig | LinearAdapterOAuthConfig | LinearAdapterAppConfig
)


# =============================================================================
Expand Down
Loading
Loading