feat(slack): expose direct WebClient access via adapter.client#471
Merged
Conversation
Mirror the Linear and GitHub adapter pattern by exposing the underlying @slack/web-api WebClient as `adapter.client` for any Web API call not covered by the SDK's high-level methods. Resolution order: 1. Token from the current request context (multi-workspace webhooks, `withBotToken()`). 2. The default `botToken` when configured as a static string or a synchronous resolver function. Throws AuthenticationError outside of any context in multi-workspace mode, or when `botToken` is configured as an async resolver. For both, bind the token explicitly with `adapter.withBotToken(token, () => ...)`. Internally, the existing private `client` field is renamed to `_client` so the public getter can return per-token cached `WebClient` instances. All internal API calls continue to route through `_client.foo(await this.withToken(...))` unchanged. Also fixes `createSlackAdapter()` silently dropping the `apiUrl` config field, surfaced by the new apiUrl-propagation test.
Add Slack to the "Direct client access" section of the chat-sdk.dev docs (api/chat.mdx, usage.mdx) alongside Linear and GitHub. Update the multi-tenant Callout to spell out both Slack constraints — request context required in multi-workspace mode, and `withBotToken()` required when `botToken` is an async resolver. Add a parallel "Direct WebClient access" section to the Slack adapter README with a usage example, the token resolution order, and the async-resolver workaround.
Demonstrate the new direct WebClient access pattern in the nextjs-chat demo with a "Channel Info (Slack)" button. The handler resolves the Slack adapter from the action event, reaches into `adapter.client.conversations.info` (channels:read scope, already in the example manifest), and renders the result as a Card with channel name, member count, topic, purpose, and the standard flags. Falls back to a friendly message on non-Slack platforms.
Pin the welcome card itself via `adapter.client.pins.add({ channel,
timestamp: event.messageId })` to demonstrate calling a Slack Web API
endpoint not wrapped by the SDK. Adds the required `pins:write` scope
to the example Slack manifest.
Replace the Fields/Section layout in the Channel Info card with a two-column Table for a tidier presentation, and pass `include_num_members: true` so the Members row is actually populated (Slack's `conversations.info` omits it by default).
Adds three tests: - Cache differentiation: distinct tokens produce distinct WebClient instances so per-workspace credentials never bleed across calls. - apiUrl env var resolution: SLACK_API_URL is honored by the WebClient the new getter returns (covers GovSlack-style deployments). - End-to-end multi-workspace token routing: a real block_actions webhook drives `processAction`, and the handler-side `event.adapter.client.token` matches the installation's bot token — proving the request-context-bound client works inside webhook dispatch.
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
visyat
approved these changes
May 9, 2026
visyat
added a commit
that referenced
this pull request
May 9, 2026
…t" (#472) * Revert "feat(slack): expose direct WebClient access via adapter.client (#471)" This reverts commit 8366b8b. * Fix: The `createSlackAdapter()` helper function silently drops the `apiUrl` config field, so custom Slack API URLs (e.g., for GovSlack) are ignored when using the helper. This commit fixes the issue reported at packages/adapter-slack/src/index.ts:5055 **Bug explanation:** The `SlackAdapterConfig` interface defines an `apiUrl` field (line 166) that allows users to override the Slack Web API base URL — useful for GovSlack or self-hosted gateways. The `SlackAdapter` constructor reads this field at line 622: ```typescript const slackApiUrl = config.apiUrl ?? process.env.SLACK_API_URL; ``` However, the `createSlackAdapter()` helper function (around line 5055) constructs a `resolved` config object that includes many fields from the user's config but omits `apiUrl`. This means when a user writes: ```typescript createSlackAdapter({ apiUrl: "https://slack-gov.com/api/" }) ``` The `apiUrl` is silently dropped and the `WebClient` is created without the custom URL. The `SLACK_API_URL` environment variable fallback still works (since it's checked in the constructor), but explicit config via the helper is lost. This is clearly a bug — all other config fields are forwarded through the `resolved` object, and `apiUrl` was simply forgotten. **Fix explanation:** Added `apiUrl: config?.apiUrl,` to the `resolved` config object in `createSlackAdapter()`. This ensures the `apiUrl` value from user config is properly forwarded to the `SlackAdapter` constructor, matching the pattern used for all other optional config fields. Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com> Co-authored-by: visyat <vishal.yathish@gmail.com> --------- Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
21 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Mirrors the Linear and GitHub adapter pattern by exposing the underlying
@slack/web-apiWebClientasadapter.client. Use it for any Slack Web API call not covered by the SDK's high-level methods, e.g.bot.getAdapter("slack").client.pins.add(...).Token resolution order:
adapter.withBotToken(token, fn).botTokenwhen configured as a static string or a synchronous resolver function.Throws
AuthenticationErroroutside any context in multi-workspace mode, or whenbotTokenis configured as an async resolver. In both cases,awaitthe token first and bind it explicitly withadapter.withBotToken(token, () => adapter.client...).Internally the existing private
client: WebClientfield is renamed to_clientso the public getter can return per-token cachedWebClientinstances. All internal API calls continue to route through_client.foo(await this.withToken(...))unchanged.What's changed
Slack adapter (
@chat-adapter/slack)adapter.clientgetter returning a token-boundWebClient.WebClientcache so repeated access reuses instances; distinct workspaces get distinct clients.apiUrlis now stored as a private field and threaded into the boundWebClient, includingSLACK_API_URLenv var resolution.createSlackAdapter()no longer silently drops theapiUrlconfig field — it's now passed through to theSlackAdapterconstructor.Integration tests (
@chat-adapter/integration-tests)injectMockSlackClientupdated to write to the renamed internal_clientfield.Documentation
apps/docs/content/docs/api/chat.mdx— Slack added to "Direct client access" section, code example, type table row, and an updated Callout that spells out both multi-workspace and async-resolver constraints.apps/docs/content/docs/usage.mdx— Slack added to the typed-.clientquick reference.packages/adapter-slack/README.md— new "DirectWebClientaccess" section with usage example, token resolution order, and thewithBotToken()workaround for async resolvers.Example app (
examples/nextjs-chat)slack.client.conversations.info(uses existingchannels:readscope) and renders the result as a two-column<Table>.slack.client.pins.addon the welcome card itself (event.messageId).slack-manifest.ymladdspins:writefor the new Pin button.Tests
packages/adapter-slack/src/index.test.tscovering: static-token binding, sync resolver,withBotTokenoverride of default,apiUrlpropagation through the factory,SLACK_API_URLenv var resolution, distinct-tokens-distinct-clients caching, multi-workspace outside-context throw, async-resolver throw, and end-to-end token routing throughevent.adapter.client.tokeninside a realblock_actionswebhook dispatch.