Skip to content

feat(slack): add webhook primitives subpath#538

Merged
dancer merged 8 commits into
mainfrom
josh/slack-webhook-primitives
May 22, 2026
Merged

feat(slack): add webhook primitives subpath#538
dancer merged 8 commits into
mainfrom
josh/slack-webhook-primitives

Conversation

@dancer

@dancer dancer commented May 19, 2026

Copy link
Copy Markdown
Collaborator

summary

adds a lightweight @chat-adapter/slack/webhook subpath for lower-level Slack webhook handling without pulling in the full Slack adapter runtime

this exposes primitives for verifying Slack requests, reading signed webhook bodies, parsing Events API callbacks, slash commands, and interactive payloads, and returning provider-native continuation data for message-like payloads

this gives framework and infrastructure integrations a small Slack-owned surface they can use directly while keeping chat, Slack SDK clients, adapter state, dedupe, locks, and subscriptions out of the primitive path

@vercel

vercel Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chat Ready Ready Preview, Comment, Open in v0 May 22, 2026 12:00am
chat-sdk-nextjs-chat Ready Ready Preview, Comment, Open in v0 May 22, 2026 12:00am

Comment thread packages/adapter-slack/src/webhook-utils.ts Outdated

@visyat visyat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor NIT: Can we move webhook-* into src/webhook/* instead?

@dancer dancer merged commit b332a03 into main May 22, 2026
17 checks passed
@dancer dancer deleted the josh/slack-webhook-primitives branch May 22, 2026 00:04
patrick-chinchill added a commit to Chinchill-AI/chat-sdk-python that referenced this pull request Jun 18, 2026
…559) (#139)

* feat(slack): webhook primitives subpath (vercel/chat#538)

Port of upstream b332a03 — adds chat_sdk.adapters.slack.webhook, a
lightweight runtime-free subpath for lower-level Slack webhook handling:
verifying Slack requests (HMAC v0 + custom verifiers), reading signed
webhook bodies, parsing Events API callbacks, slash commands, and
interactive payloads into typed dataclasses with provider-native
continuation data.

The adapter now verifies through the shared verify_slack_request /
verify_slack_signature primitives (single implementation — the inline
_verify_signature method is removed, matching upstream), reads bodies via
the shared read_slack_request_body helper, and sources
SlackWebhookVerifier from webhook/types.py. The slack package __init__ is
now lazy (PEP 562) so importing the webhook subpath does not pull in the
full adapter runtime — the Python analog of upstream's package subpath
export boundary.

Python-specific notes: verify_slack_signature is sync (no WebCrypto);
verify helpers accept a pre-read body= for non-re-readable framework
requests; now() returns epoch seconds.

https://claude.ai/code/session_013zwTcMek5rNqBTQvs2oF64

* feat(slack): format primitives subpath (vercel/chat#547)

Port of upstream 4c46c26 — adds chat_sdk.adapters.slack.format, a
runtime-free subpath for Slack formatting helpers: plain_text/mrkdwn text
objects, mrkdwn escaping/unescaping, user/channel/user-group/special
mentions, links, localized date tokens, mrkdwn-to-Markdown normalization,
Markdown-bold conversion, and ID-based bare-mention linking — without the
full Slack adapter, slack_sdk, or the chat runtime.

Python-specific notes: option objects (SlackTextOptions, SlackDateOptions)
become keyword-only arguments; format_slack_date accepts datetime or an
integer unix timestamp (TS Date | number) and the TypeError message says
'datetime' instead of 'Date'.

https://claude.ai/code/session_013zwTcMek5rNqBTQvs2oF64

* feat(slack): api primitives subpath (vercel/chat#548, #559)

Port of upstream aba6aa9 (api/client.ts) and 6ed4a43 (api/extra.ts) —
adds chat_sdk.adapters.slack.api, a runtime-free subpath exposed upstream
as @chat-adapter/slack/api. Provides fetch-based primitives for calling
Slack Web API methods (call_slack_api), posting/updating/deleting messages
(post_slack_message, post_slack_ephemeral, update_slack_message,
delete_slack_message), sending interaction response_url payloads
(send_slack_response_url), uploading files through Slack's external upload
flow (upload_slack_files), fetching private Slack file URLs with bearer
auth (fetch_slack_file), fetching thread replies with cursor pagination
(fetch_slack_thread_replies), and opening modal views (open_slack_view) —
without the full Slack adapter, slack_sdk, Socket Mode, or the chat
runtime.

Importing this subpath never imports an HTTP client: the default fetch
lazily imports httpx only when a request is actually made (matching the
high-level adapter's optional-httpx pattern), and any async HTTP stack can
be injected via the fetch= parameter. SlackBotToken is declared locally
rather than imported from the adapter's types module, so the subpath stays
self-contained and runtime-free — mirroring upstream's independent
declaration in api/client.ts.

Python-specific notes: option objects become keyword-only arguments;
camelCase request fields are emitted at the Slack serialization boundary
(markdown_text, reply_broadcast, thread_ts, ...) while the API is
snake_case. Python-specific hardening (divergences, see
docs/UPSTREAM_SYNC.md Known Non-Parity): send_slack_response_url requires
an https://*.slack.com URL and fetch_slack_file requires a Slack-owned
file host before forwarding the bearer token (SSRF / token-leak guards
mirroring the high-level adapter); upstream validates neither.

Tests port api/index.test.ts and api/boundary.test.ts with injected
AsyncMock fetches (no network), plus the two divergence guards.

https://claude.ai/code/session_013zwTcMek5rNqBTQvs2oF64

* feat(slack): block kit primitives subpath (vercel/chat#555, #559)

Port of upstream dbd8dc5 (blocks/index.ts, types.ts, limits.ts,
errors.ts) and 6ed4a43 (blocks/input.ts) — adds
chat_sdk.adapters.slack.blocks, a runtime-free subpath exposed upstream as
@chat-adapter/slack/blocks. Converts Chat SDK-style card objects into
Slack Block Kit blocks (card_to_slack_blocks / card_to_block_kit), a
Markdown fallback (card_to_slack_fallback_text / card_to_fallback_text),
and emoji-placeholder codes (convert_slack_emoji_placeholders), enforcing
docs-backed Slack size limits (LIMITS) for headers, images, actions,
select options, fields, and tables. Also ports the generic input-request
helpers: input_request_to_slack_blocks (buttons / select / radio /
freeform), parse_slack_input_response, build_slack_freeform_view,
parse_slack_freeform_value, and answered_slack_input_blocks — without the
full Slack adapter, slack_sdk, or the chat runtime. The only cross-module
dependency is the sibling format subpath (markdown_bold_to_slack_mrkdwn),
which is itself runtime-free.

Python-specific notes: the card-input shapes (SlackCardElement and
children) are self-contained TypedDicts declared here rather than imported
from chat_sdk.cards, keeping the subpath runtime-free. Input field names
are snake_case (image_url, initial_option, request_id, allow_freeform,
selected_option_value, action_id, block_id), matching chat_sdk.cards and
the Python port convention — upstream uses camelCase. Emitted Block Kit
dicts keep Slack's API field names verbatim (alt_text, action_id,
block_id, static_select, column_settings, raw_text, private_metadata),
which is the Slack serialization boundary. Discriminated-union dispatch on
the literal type key uses per-branch casts / arg-type ignores, mirroring
the high-level adapter's cards.py.

Tests port blocks/index.test.ts and blocks/boundary.test.ts with full
output equality, plus extra coverage of the parse-None and
string-metadata paths.

https://claude.ai/code/session_013zwTcMek5rNqBTQvs2oF64

* fix(slack): port dropped parse fields + fix select-action label (4.30 fidelity)

Port the SlackFile/SlackUser/SlackViewStateValue primitives and the
helpers (parseFiles/inferFileType/parseUser/findPromptBlock/
readPromptText/parseViewValues) that the webhook parse.py port dropped vs
upstream chat@4.30.0 packages/adapter-slack/src/webhook/parse.ts:

- files[]: populated on every message-like event via _parse_files
  (mimetype-inferred type, raw retained).
- SlackAction.label: now prefers the selected option's text and falls
  back to the element text (parse.ts:276); surface selected_option_label.
- SlackUser: attached as the user object on block_actions / view_submission
  / view_closed payloads and on each parsed action.
- block_actions: restore message_blocks / message_prompt_block /
  message_prompt_text; view_submission: restore callback_id /
  private_metadata / values (parseViewValues).

Extend webhook primitive tests to lock in every newly-ported field; add
Known-Non-Parity rows for the slack/api SSRF + token-leak guards.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants