diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 1b77f50..6538ca9 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.7.0" + ".": "0.8.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 02e38ed..28d4e49 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 34 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/linq%2Flinq-api-v3-37a94929911f4b5944524a51b722bb0eea0b0b167fd79814181f58ed877aa341.yml -openapi_spec_hash: a3d001964480c894a5b6cb9ffa4fa47b +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/linq/linq-api-v3-68d71a6222b9b76fc00fbf74cb8ac86e5e5a6a65a472969269c912235e3c4cf7.yml +openapi_spec_hash: 8fdc1be6f5741bc4d478af7eddfff24c config_hash: 3ab31decde29ec61e02db7dcb5313f82 diff --git a/CHANGELOG.md b/CHANGELOG.md index c1b99d8..00dd039 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## 0.8.0 (2026-05-07) + +Full Changelog: [v0.7.0...v0.8.0](https://github.com/linq-team/linq-python/compare/v0.7.0...v0.8.0) + +### Features + +* **chat-service:** expose health_score.updated_at on chat reads + webhooks ([ae5f146](https://github.com/linq-team/linq-python/commit/ae5f1461f6468c48ec88d999d59293e2f364ddd6)) +* remove health_score from synapse — health_status is the contract ([5ec27d1](https://github.com/linq-team/linq-python/commit/5ec27d1544c85532b8d0344b3108a88cf474db90)) + + +### Bug Fixes + +* **chat-service:** stamp health_status from risk-service sync reply ([ff22296](https://github.com/linq-team/linq-python/commit/ff222969e755bd20e352de665b5e7fe47ea99705)) + + +### Chores + +* **internal:** reformat pyproject.toml ([3ab97d9](https://github.com/linq-team/linq-python/commit/3ab97d9f5daea08054fba6086ca2c1849289d8ff)) + + +### Documentation + +* **openapi:** add HealthStatus [BETA] + deprecate HealthScore ([d4e3711](https://github.com/linq-team/linq-python/commit/d4e37118b71348796cb029bed6f3b831c3996941)) +* **openapi:** add named examples to unblock docs OpExample ([6a37d71](https://github.com/linq-team/linq-python/commit/6a37d71c0aa57feefc85810a5012da74153b835f)) + ## 0.7.0 (2026-04-30) Full Changelog: [v0.6.0...v0.7.0](https://github.com/linq-team/linq-python/compare/v0.6.0...v0.7.0) diff --git a/pyproject.toml b/pyproject.toml index 4893044..5ac8aba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "linq-python" -version = "0.7.0" +version = "0.8.0" description = "The official Python library for the linq-api-v3 API" dynamic = ["readme"] license = "Apache-2.0" @@ -154,7 +154,7 @@ show_error_codes = true # # We also exclude our `tests` as mypy doesn't always infer # types correctly and Pyright will still catch any type errors. -exclude = ['src/linq/_files.py', '_dev/.*.py', 'tests/.*'] +exclude = ["src/linq/_files.py", "_dev/.*.py", "tests/.*"] strict_equality = true implicit_reexport = true diff --git a/src/linq/_version.py b/src/linq/_version.py index 38bd5ac..57ea29d 100644 --- a/src/linq/_version.py +++ b/src/linq/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "linq" -__version__ = "0.7.0" # x-release-please-version +__version__ = "0.8.0" # x-release-please-version diff --git a/src/linq/types/chat.py b/src/linq/types/chat.py index 43aaeba..1d0138d 100644 --- a/src/linq/types/chat.py +++ b/src/linq/types/chat.py @@ -2,26 +2,37 @@ from typing import List, Optional from datetime import datetime +from typing_extensions import Literal from .._models import BaseModel from .shared.chat_handle import ChatHandle from .shared.service_type import ServiceType -__all__ = ["Chat", "HealthScore"] +__all__ = ["Chat", "HealthStatus"] -class HealthScore(BaseModel): - """**[BETA]** Health assessment for a chat. +class HealthStatus(BaseModel): + """**[BETA]** Current health for a chat. - Higher `score` is healthier. - `null` when a score isn't available yet. Scoring may change during beta. + Always present — chats start at `healthy` and may shift based on engagement and delivery signals on the conversation. Many `at_risk` or `critical` chats on a single line increase the risk of line flagging. + + Switch on `status` to gate sends or surface line health in your UI — the enum is the long-term contract. Each status carries a `doc_url` that deep-links to the relevant section of the Chat Health guide. + + See the [Chat Health guide](/guides/chats/chat-health) for what each status means and how to react. """ - reason: str - """Short summary of what's affecting the score. Empty when the score is 100.""" + doc_url: str + """Deep-link to the relevant section of the Chat Health guide for this status.""" - score: int - """Health score from 0 to 100. Higher is healthier.""" + status: Literal["healthy", "at_risk", "critical", "opted_out"] + """Current health bucket for the chat. + + See the [Chat Health guide](/guides/chats/chat-health) for what each value means + and how to react. `doc_url` deep-links to the relevant section. + """ + + updated_at: datetime + """When this status last changed.""" class Chat(BaseModel): @@ -45,6 +56,21 @@ class Chat(BaseModel): participant). """ + health_status: HealthStatus + """**[BETA]** Current health for a chat. + + Always present — chats start at `healthy` and may shift based on engagement and + delivery signals on the conversation. Many `at_risk` or `critical` chats on a + single line increase the risk of line flagging. + + Switch on `status` to gate sends or surface line health in your UI — the enum is + the long-term contract. Each status carries a `doc_url` that deep-links to the + relevant section of the Chat Health guide. + + See the [Chat Health guide](/guides/chats/chat-health) for what each status + means and how to react. + """ + is_archived: bool """Whether the chat is archived""" @@ -54,12 +80,5 @@ class Chat(BaseModel): updated_at: datetime """When the chat was last updated""" - health_score: Optional[HealthScore] = None - """**[BETA]** Health assessment for a chat. - - Higher `score` is healthier. `null` when a score isn't available yet. Scoring - may change during beta. - """ - service: Optional[ServiceType] = None """Messaging service type""" diff --git a/src/linq/types/chat_create_response.py b/src/linq/types/chat_create_response.py index 50c26d0..19f3a10 100644 --- a/src/linq/types/chat_create_response.py +++ b/src/linq/types/chat_create_response.py @@ -1,27 +1,39 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal from .._models import BaseModel from .chats.sent_message import SentMessage from .shared.chat_handle import ChatHandle from .shared.service_type import ServiceType -__all__ = ["ChatCreateResponse", "Chat", "ChatHealthScore"] +__all__ = ["ChatCreateResponse", "Chat", "ChatHealthStatus"] -class ChatHealthScore(BaseModel): - """**[BETA]** Health assessment for a chat. +class ChatHealthStatus(BaseModel): + """**[BETA]** Current health for a chat. - Higher `score` is healthier. - `null` when a score isn't available yet. Scoring may change during beta. + Always present — chats start at `healthy` and may shift based on engagement and delivery signals on the conversation. Many `at_risk` or `critical` chats on a single line increase the risk of line flagging. + + Switch on `status` to gate sends or surface line health in your UI — the enum is the long-term contract. Each status carries a `doc_url` that deep-links to the relevant section of the Chat Health guide. + + See the [Chat Health guide](/guides/chats/chat-health) for what each status means and how to react. """ - reason: str - """Short summary of what's affecting the score. Empty when the score is 100.""" + doc_url: str + """Deep-link to the relevant section of the Chat Health guide for this status.""" - score: int - """Health score from 0 to 100. Higher is healthier.""" + status: Literal["healthy", "at_risk", "critical", "opted_out"] + """Current health bucket for the chat. + + See the [Chat Health guide](/guides/chats/chat-health) for what each value means + and how to react. `doc_url` deep-links to the relevant section. + """ + + updated_at: datetime + """When this status last changed.""" class Chat(BaseModel): @@ -42,6 +54,21 @@ class Chat(BaseModel): participant). """ + health_status: ChatHealthStatus + """**[BETA]** Current health for a chat. + + Always present — chats start at `healthy` and may shift based on engagement and + delivery signals on the conversation. Many `at_risk` or `critical` chats on a + single line increase the risk of line flagging. + + Switch on `status` to gate sends or surface line health in your UI — the enum is + the long-term contract. Each status carries a `doc_url` that deep-links to the + relevant section of the Chat Health guide. + + See the [Chat Health guide](/guides/chats/chat-health) for what each status + means and how to react. + """ + is_group: bool """Whether this is a group chat""" @@ -51,13 +78,6 @@ class Chat(BaseModel): service: ServiceType """Messaging service type""" - health_score: Optional[ChatHealthScore] = None - """**[BETA]** Health assessment for a chat. - - Higher `score` is healthier. `null` when a score isn't available yet. Scoring - may change during beta. - """ - class ChatCreateResponse(BaseModel): """Response for creating a new chat with an initial message""" diff --git a/src/linq/types/chat_created_webhook_event.py b/src/linq/types/chat_created_webhook_event.py index 88678bc..0ac3883 100644 --- a/src/linq/types/chat_created_webhook_event.py +++ b/src/linq/types/chat_created_webhook_event.py @@ -2,13 +2,38 @@ from typing import List, Optional from datetime import datetime +from typing_extensions import Literal from .._models import BaseModel from .shared.chat_handle import ChatHandle from .webhook_event_type import WebhookEventType from .shared.service_type import ServiceType -__all__ = ["ChatCreatedWebhookEvent", "Data"] +__all__ = ["ChatCreatedWebhookEvent", "Data", "DataHealthStatus"] + + +class DataHealthStatus(BaseModel): + """**[BETA]** Current health for a chat. + + Always present — chats start at `healthy` and may shift based on engagement and delivery signals on the conversation. Many `at_risk` or `critical` chats on a single line increase the risk of line flagging. + + Switch on `status` to gate sends or surface line health in your UI — the enum is the long-term contract. Each status carries a `doc_url` that deep-links to the relevant section of the Chat Health guide. + + See the [Chat Health guide](/guides/chats/chat-health) for what each status means and how to react. + """ + + doc_url: str + """Deep-link to the relevant section of the Chat Health guide for this status.""" + + status: Literal["healthy", "at_risk", "critical", "opted_out"] + """Current health bucket for the chat. + + See the [Chat Health guide](/guides/chats/chat-health) for what each value means + and how to react. `doc_url` deep-links to the relevant section. + """ + + updated_at: datetime + """When this status last changed.""" class Data(BaseModel): @@ -37,6 +62,21 @@ class Data(BaseModel): participant). """ + health_status: DataHealthStatus + """**[BETA]** Current health for a chat. + + Always present — chats start at `healthy` and may shift based on engagement and + delivery signals on the conversation. Many `at_risk` or `critical` chats on a + single line increase the risk of line flagging. + + Switch on `status` to gate sends or surface line health in your UI — the enum is + the long-term contract. Each status carries a `doc_url` that deep-links to the + relevant section of the Chat Health guide. + + See the [Chat Health guide](/guides/chats/chat-health) for what each status + means and how to react. + """ + is_group: bool """Whether this is a group chat""" diff --git a/src/linq/types/message_edited_webhook_event.py b/src/linq/types/message_edited_webhook_event.py index 4833b6e..1202081 100644 --- a/src/linq/types/message_edited_webhook_event.py +++ b/src/linq/types/message_edited_webhook_event.py @@ -7,7 +7,31 @@ from .shared.chat_handle import ChatHandle from .webhook_event_type import WebhookEventType -__all__ = ["MessageEditedWebhookEvent", "Data", "DataChat", "DataPart"] +__all__ = ["MessageEditedWebhookEvent", "Data", "DataChat", "DataChatHealthStatus", "DataPart"] + + +class DataChatHealthStatus(BaseModel): + """**[BETA]** Current health for a chat. + + Always present — chats start at `healthy` and may shift based on engagement and delivery signals on the conversation. Many `at_risk` or `critical` chats on a single line increase the risk of line flagging. + + Switch on `status` to gate sends or surface line health in your UI — the enum is the long-term contract. Each status carries a `doc_url` that deep-links to the relevant section of the Chat Health guide. + + See the [Chat Health guide](/guides/chats/chat-health) for what each status means and how to react. + """ + + doc_url: str + """Deep-link to the relevant section of the Chat Health guide for this status.""" + + status: Literal["healthy", "at_risk", "critical", "opted_out"] + """Current health bucket for the chat. + + See the [Chat Health guide](/guides/chats/chat-health) for what each value means + and how to react. `doc_url` deep-links to the relevant section. + """ + + updated_at: datetime + """When this status last changed.""" class DataChat(BaseModel): @@ -16,6 +40,21 @@ class DataChat(BaseModel): id: str """Chat identifier""" + health_status: DataChatHealthStatus + """**[BETA]** Current health for a chat. + + Always present — chats start at `healthy` and may shift based on engagement and + delivery signals on the conversation. Many `at_risk` or `critical` chats on a + single line increase the risk of line flagging. + + Switch on `status` to gate sends or surface line health in your UI — the enum is + the long-term contract. Each status carries a `doc_url` that deep-links to the + relevant section of the Chat Health guide. + + See the [Chat Health guide](/guides/chats/chat-health) for what each status + means and how to react. + """ + is_group: bool """Whether this is a group chat""" diff --git a/src/linq/types/message_event_v2.py b/src/linq/types/message_event_v2.py index 06360ab..28ba778 100644 --- a/src/linq/types/message_event_v2.py +++ b/src/linq/types/message_event_v2.py @@ -12,21 +12,31 @@ from .schemas_text_part_response import SchemasTextPartResponse from .schemas_media_part_response import SchemasMediaPartResponse -__all__ = ["MessageEventV2", "Chat", "ChatHealthScore", "Part", "PartSchemasLinkPartResponse", "ReplyTo"] +__all__ = ["MessageEventV2", "Chat", "ChatHealthStatus", "Part", "PartSchemasLinkPartResponse", "ReplyTo"] -class ChatHealthScore(BaseModel): - """**[BETA]** Health assessment for a chat. +class ChatHealthStatus(BaseModel): + """**[BETA]** Current health for a chat. - Higher `score` is healthier. - `null` when a score isn't available yet. Scoring may change during beta. + Always present — chats start at `healthy` and may shift based on engagement and delivery signals on the conversation. Many `at_risk` or `critical` chats on a single line increase the risk of line flagging. + + Switch on `status` to gate sends or surface line health in your UI — the enum is the long-term contract. Each status carries a `doc_url` that deep-links to the relevant section of the Chat Health guide. + + See the [Chat Health guide](/guides/chats/chat-health) for what each status means and how to react. """ - reason: str - """Short summary of what's affecting the score. Empty when the score is 100.""" + doc_url: str + """Deep-link to the relevant section of the Chat Health guide for this status.""" - score: int - """Health score from 0 to 100. Higher is healthier.""" + status: Literal["healthy", "at_risk", "critical", "opted_out"] + """Current health bucket for the chat. + + See the [Chat Health guide](/guides/chats/chat-health) for what each value means + and how to react. `doc_url` deep-links to the relevant section. + """ + + updated_at: datetime + """When this status last changed.""" class Chat(BaseModel): @@ -35,11 +45,19 @@ class Chat(BaseModel): id: str """Chat identifier""" - health_score: Optional[ChatHealthScore] = None - """**[BETA]** Health assessment for a chat. + health_status: ChatHealthStatus + """**[BETA]** Current health for a chat. + + Always present — chats start at `healthy` and may shift based on engagement and + delivery signals on the conversation. Many `at_risk` or `critical` chats on a + single line increase the risk of line flagging. + + Switch on `status` to gate sends or surface line health in your UI — the enum is + the long-term contract. Each status carries a `doc_url` that deep-links to the + relevant section of the Chat Health guide. - Higher `score` is healthier. `null` when a score isn't available yet. Scoring - may change during beta. + See the [Chat Health guide](/guides/chats/chat-health) for what each status + means and how to react. """ is_group: Optional[bool] = None diff --git a/tests/api_resources/test_contact_card.py b/tests/api_resources/test_contact_card.py index 11bd6a6..ae0bc6c 100644 --- a/tests/api_resources/test_contact_card.py +++ b/tests/api_resources/test_contact_card.py @@ -24,7 +24,7 @@ class TestContactCard: @parametrize def test_method_create(self, client: LinqAPIV3) -> None: contact_card = client.contact_card.create( - first_name="John", + first_name="Acme", phone_number="+15551234567", ) assert_matches_type(SetContactCard, contact_card, path=["response"]) @@ -33,10 +33,10 @@ def test_method_create(self, client: LinqAPIV3) -> None: @parametrize def test_method_create_with_all_params(self, client: LinqAPIV3) -> None: contact_card = client.contact_card.create( - first_name="John", + first_name="Acme", phone_number="+15551234567", image_url="https://cdn.linqapp.com/contact-card/example.jpg", - last_name="Doe", + last_name="Support", ) assert_matches_type(SetContactCard, contact_card, path=["response"]) @@ -44,7 +44,7 @@ def test_method_create_with_all_params(self, client: LinqAPIV3) -> None: @parametrize def test_raw_response_create(self, client: LinqAPIV3) -> None: response = client.contact_card.with_raw_response.create( - first_name="John", + first_name="Acme", phone_number="+15551234567", ) @@ -57,7 +57,7 @@ def test_raw_response_create(self, client: LinqAPIV3) -> None: @parametrize def test_streaming_response_create(self, client: LinqAPIV3) -> None: with client.contact_card.with_streaming_response.create( - first_name="John", + first_name="Acme", phone_number="+15551234567", ) as response: assert not response.is_closed @@ -159,7 +159,7 @@ class TestAsyncContactCard: @parametrize async def test_method_create(self, async_client: AsyncLinqAPIV3) -> None: contact_card = await async_client.contact_card.create( - first_name="John", + first_name="Acme", phone_number="+15551234567", ) assert_matches_type(SetContactCard, contact_card, path=["response"]) @@ -168,10 +168,10 @@ async def test_method_create(self, async_client: AsyncLinqAPIV3) -> None: @parametrize async def test_method_create_with_all_params(self, async_client: AsyncLinqAPIV3) -> None: contact_card = await async_client.contact_card.create( - first_name="John", + first_name="Acme", phone_number="+15551234567", image_url="https://cdn.linqapp.com/contact-card/example.jpg", - last_name="Doe", + last_name="Support", ) assert_matches_type(SetContactCard, contact_card, path=["response"]) @@ -179,7 +179,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncLinqAPIV3) @parametrize async def test_raw_response_create(self, async_client: AsyncLinqAPIV3) -> None: response = await async_client.contact_card.with_raw_response.create( - first_name="John", + first_name="Acme", phone_number="+15551234567", ) @@ -192,7 +192,7 @@ async def test_raw_response_create(self, async_client: AsyncLinqAPIV3) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncLinqAPIV3) -> None: async with async_client.contact_card.with_streaming_response.create( - first_name="John", + first_name="Acme", phone_number="+15551234567", ) as response: assert not response.is_closed