-
Notifications
You must be signed in to change notification settings - Fork 1
feat(linear): agent-session types + kind discriminator (chat@4.31/#151 — L1/5) #168
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,26 @@ | ||
| """Linear adapter for chat-sdk.""" | ||
|
|
||
| from chat_sdk.adapters.linear.adapter import LinearAdapter, create_linear_adapter | ||
| from chat_sdk.adapters.linear.types import LinearInstallation | ||
| from chat_sdk.adapters.linear.types import ( | ||
| AgentSessionEventWebhookPayload, | ||
| LinearAdapterMode, | ||
| LinearAgentSessionCommentRawMessage, | ||
| LinearAgentSessionThreadId, | ||
| LinearCommentRawMessage, | ||
| LinearInstallation, | ||
| LinearRawMessage, | ||
| LinearThreadId, | ||
| ) | ||
|
|
||
| __all__ = ["LinearAdapter", "LinearInstallation", "create_linear_adapter"] | ||
| __all__ = [ | ||
| "AgentSessionEventWebhookPayload", | ||
| "LinearAdapter", | ||
| "LinearAdapterMode", | ||
| "LinearAgentSessionCommentRawMessage", | ||
| "LinearAgentSessionThreadId", | ||
| "LinearCommentRawMessage", | ||
| "LinearInstallation", | ||
| "LinearRawMessage", | ||
| "LinearThreadId", | ||
| "create_linear_adapter", | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,19 +2,33 @@ | |
|
|
||
| Based on the Linear API and webhook format. | ||
| See: https://linear.app/developers | ||
|
|
||
| Agent-session types (``mode``, the ``kind`` discriminator, the agent-session | ||
| raw-message variant, and ``AgentSessionEventWebhookPayload``) are a faithful | ||
| port of upstream ``packages/adapter-linear/src/types.ts`` (vercel/chat, | ||
| adapter-linear 4.27.0 / chat@4.31.0). Upstream re-uses ``@linear/sdk`` and | ||
| ``@linear/sdk/webhooks`` types; our adapter is raw GraphQL, so the | ||
| agent-session webhook payload is hand-authored to mirror the SDK shape that | ||
| upstream ``index.ts`` consumes. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from dataclasses import dataclass | ||
| from typing import Any, TypedDict | ||
| from typing import Any, Literal, TypedDict | ||
|
|
||
| from chat_sdk.logger import Logger | ||
|
|
||
| # ============================================================================= | ||
| # Configuration | ||
| # ============================================================================= | ||
|
|
||
| # Incoming webhook handling mode for the Linear adapter. Mirrors upstream | ||
| # ``LinearAdapterMode`` (types.ts:44). ``"comments"`` is the data-change | ||
| # webhook model (the existing behavior); ``"agent-sessions"`` is the app-actor | ||
| # model used for ``AgentSessionEvent`` webhooks. | ||
| LinearAdapterMode = Literal["agent-sessions", "comments"] | ||
|
|
||
|
|
||
| @dataclass | ||
| class LinearAdapterBaseConfig: | ||
|
|
@@ -28,6 +42,11 @@ class LinearAdapterBaseConfig: | |
| api_url: str | None = None | ||
| # Logger instance for error reporting. Defaults to ConsoleLogger. | ||
| logger: Logger | None = None | ||
| # Controls which inbound Linear webhook model should trigger message | ||
| # handling. Defaults to "comments". Use "agent-sessions" for app-actor | ||
| # installs. Faithful port of upstream ``config.mode ?? "comments"`` | ||
| # (types.ts:67, index.ts:236). | ||
| mode: LinearAdapterMode | None = None | ||
| # Bot display name for @-mention detection. | ||
| # Defaults to LINEAR_BOT_USERNAME env var or "linear-bot". | ||
| user_name: str | None = None | ||
|
|
@@ -130,6 +149,23 @@ class LinearThreadId: | |
| issue_id: str | ||
| # Root comment ID for comment-level threads (optional) | ||
| comment_id: str | None = None | ||
| # Agent session UUID for app-actor interactions (optional). Faithful port of | ||
| # upstream ``LinearThreadId.agentSessionId`` (types.ts:189). | ||
| agent_session_id: str | None = None | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class LinearAgentSessionThreadId(LinearThreadId): | ||
| """Decoded thread ID for Linear threads associated with agent sessions. | ||
|
|
||
| Faithful port of upstream ``LinearAgentSessionThreadId`` | ||
| (``LinearThreadId & { agentSessionId: string }``, types.ts:203). Narrows | ||
| ``agent_session_id`` to a required, non-optional ``str`` so that downstream | ||
| agent-session code (L4) can rely on the field being present. | ||
| """ | ||
|
|
||
| # Required for agent-session threads (narrows the optional base field). | ||
| agent_session_id: str = "" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This subclass is documented and exported as the required-session form, but the default allows Useful? React with 👍 / 👎. |
||
|
|
||
|
|
||
| # ============================================================================= | ||
|
|
@@ -222,19 +258,189 @@ class ReactionWebhookPayload(TypedDict, total=False): | |
| webhookTimestamp: int | ||
|
|
||
|
|
||
| # ----------------------------------------------------------------------------- | ||
| # Agent-session webhook payload (mode="agent-sessions") | ||
| # ----------------------------------------------------------------------------- | ||
| # | ||
| # Hand-authored to mirror upstream ``@linear/sdk/webhooks`` | ||
| # ``AgentSessionEventWebhookPayload`` as consumed by upstream | ||
| # ``adapter-linear/src/index.ts``. Webhook payloads are external JSON, so the | ||
| # RAW Linear wire keys (camelCase) are kept verbatim — no snake_case aliasing. | ||
| # These are NetworkError-prone external inputs; ``total=False`` mirrors the | ||
| # optional-heavy SDK shape and lets the parse logic (L3) read fields defensively. | ||
|
|
||
|
|
||
| class AgentSessionCommentChild(TypedDict, total=False): | ||
| """Root comment of the thread an agent session is attached to. | ||
|
|
||
| Mirrors ``CommentChildWebhookPayload`` as consumed for | ||
| ``agentSession.comment`` (index.ts:1026-1035). | ||
| """ | ||
|
|
||
| id: str | ||
| body: str | ||
| userId: str | ||
|
|
||
|
|
||
| class AgentSessionIssueChild(TypedDict, total=False): | ||
| """Issue an agent session is associated with. | ||
|
|
||
| Mirrors ``IssueWithDescriptionChildWebhookPayload`` as consumed for | ||
| ``agentSession.issue`` (index.ts:959). | ||
| """ | ||
|
|
||
| id: str | ||
|
|
||
|
|
||
| class AgentSessionUserChild(TypedDict, total=False): | ||
| """Human user responsible for the agent session. | ||
|
|
||
| Mirrors ``UserChildWebhookPayload`` as consumed for | ||
| ``agentSession.creator`` (index.ts:1037-1044). | ||
| """ | ||
|
|
||
| id: str | ||
| name: str | ||
| email: str | ||
| avatarUrl: str | ||
| url: str | ||
|
|
||
|
|
||
| class AgentSessionWebhookPayload(TypedDict, total=False): | ||
| """The agent session an ``AgentSessionEvent`` belongs to. | ||
|
|
||
| Mirrors ``AgentSessionWebhookPayload`` (@linear/sdk/webhooks) as consumed by | ||
| upstream ``index.ts``. | ||
| """ | ||
|
|
||
| id: str | ||
| appUserId: str | ||
| issueId: str | ||
| issue: AgentSessionIssueChild | ||
| commentId: str | ||
| sourceCommentId: str | ||
| comment: AgentSessionCommentChild | ||
| creator: AgentSessionUserChild | ||
| url: str | ||
| status: str | ||
| summary: str | ||
| sourceMetadata: dict[str, Any] | ||
|
|
||
|
|
||
| class AgentActivityWebhookContent(TypedDict, total=False): | ||
| """Content of an agent activity (e.g. the prompt body). | ||
|
|
||
| Mirrors the ``content`` discriminated object consumed at index.ts:984. | ||
| """ | ||
|
|
||
| type: str # e.g. "prompt" | ||
| body: str | ||
|
|
||
|
|
||
| class AgentActivityWebhookPayload(TypedDict, total=False): | ||
| """The agent activity that triggered a ``prompted`` event. | ||
|
|
||
| Mirrors ``AgentActivityWebhookPayload`` (@linear/sdk/webhooks) as consumed | ||
| by upstream ``index.ts`` (e.g. index.ts:968-998). | ||
| """ | ||
|
|
||
| id: str | ||
| sourceCommentId: str | ||
| content: AgentActivityWebhookContent | ||
| user: AgentSessionUserChild | ||
| createdAt: str | ||
|
|
||
|
|
||
| class GuidanceRuleWebhookPayload(TypedDict, total=False): | ||
| """A single guidance rule for the agent's behavior.""" | ||
|
|
||
| body: str | ||
|
|
||
|
|
||
| class CommentChildWebhookPayload(TypedDict, total=False): | ||
| """A comment in the thread before the agent session was initiated.""" | ||
|
|
||
| id: str | ||
| body: str | ||
|
|
||
|
|
||
| class AgentSessionEventWebhookPayload(TypedDict, total=False): | ||
| """Webhook payload for ``AgentSessionEvent`` events (mode="agent-sessions"). | ||
|
|
||
| Faithful port of upstream ``AgentSessionEventWebhookPayload`` | ||
| (@linear/sdk/webhooks), hand-authored to match the shape consumed by | ||
| upstream ``adapter-linear/src/index.ts``. Raw Linear wire keys (camelCase). | ||
| """ | ||
|
|
||
| type: str # "AgentSessionEvent" | ||
| action: str # "created" | "prompted" | ||
| createdAt: str | ||
| appUserId: str | ||
| oauthClientId: str | ||
| organizationId: str | ||
| webhookId: str | ||
| webhookTimestamp: int | ||
| # Formatted prompt with relevant context; present only for "created" events. | ||
| promptContext: str | ||
| agentSession: AgentSessionWebhookPayload | ||
| agentActivity: AgentActivityWebhookPayload | ||
| guidance: list[GuidanceRuleWebhookPayload] | ||
| previousComments: list[CommentChildWebhookPayload] | ||
|
|
||
|
|
||
| # Union type for all webhook payloads | ||
| LinearWebhookPayload = CommentWebhookPayload | ReactionWebhookPayload | ||
| LinearWebhookPayload = CommentWebhookPayload | ReactionWebhookPayload | AgentSessionEventWebhookPayload | ||
|
|
||
|
|
||
| # ============================================================================= | ||
| # Raw Message Type | ||
| # ============================================================================= | ||
| # | ||
| # Discriminated union on the ``kind`` field. Faithful port of upstream | ||
| # ``LinearRawMessage`` (types.ts:279). Every variant MUST carry ``kind`` so the | ||
| # union discriminates cleanly; constructing a raw message without ``kind`` | ||
| # breaks the union (emit/parse symmetry). The existing (comment-based) | ||
| # producers in ``adapter.py`` all set ``kind="comment"``. | ||
|
|
||
|
|
||
| class LinearCommentRawMessage(TypedDict, total=False): | ||
| """Platform-specific raw message for a standard Linear comment. | ||
|
|
||
| Faithful port of upstream ``LinearCommentRawMessage`` (types.ts:258). | ||
| ``kind`` and ``comment`` are required; ``organizationId`` is part of the | ||
| upstream base but our raw-GraphQL producers do not always have it on hand, | ||
| so it stays optional (``total=False``) for back-compat with the existing | ||
| comment path. | ||
| """ | ||
|
|
||
| # Raw message kind discriminator. Always "comment" for this variant. | ||
| kind: Literal["comment"] | ||
| # The raw comment data from webhook or API. | ||
| comment: LinearCommentData | ||
| # Organization ID from the webhook or request context. | ||
| organizationId: str | ||
|
|
||
|
|
||
| class LinearAgentSessionCommentRawMessage(TypedDict, total=False): | ||
| """Platform-specific raw message for a comment backed by an agent session. | ||
|
|
||
| class LinearRawMessage(TypedDict, total=False): | ||
| """Platform-specific raw message type for Linear.""" | ||
| Faithful port of upstream ``LinearAgentSessionCommentRawMessage`` | ||
| (types.ts:265). Carries the agent session the comment belongs to plus an | ||
| optional prompt context. | ||
| """ | ||
|
|
||
| # The raw comment data from webhook or API | ||
| # Raw message kind discriminator. Always "agent_session_comment". | ||
| kind: Literal["agent_session_comment"] | ||
| # The visible Linear comment backing this message. | ||
| comment: LinearCommentData | ||
| # Organization ID from the webhook | ||
| # The agent session the comment belongs to. | ||
| agentSessionId: str | ||
| # The prompt context associated with this agent session comment (optional). | ||
| agentSessionPromptContext: str | ||
| # Organization ID from the webhook or request context. | ||
| organizationId: str | ||
|
|
||
|
|
||
| # Platform-specific raw message type for Linear (discriminated union on `kind`). | ||
| # Faithful port of upstream ``LinearRawMessage`` (types.ts:279). | ||
| LinearRawMessage = LinearCommentRawMessage | LinearAgentSessionCommentRawMessage | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding
modehere changes the generated positional__init__for allLinearAdapter*Configdataclasses. Existing callers that pass positional args afterloggernow have theiruser_namebound tomode, theirwebhook_secretbound touser_name, and so on, which can make LinearAdapter fail authentication/webhook-secret setup or silently use the wrong bot name. Make this new option keyword-only (or otherwise avoid shifting the public positional signature).Useful? React with 👍 / 👎.