Skip to content

chore: prep 0.0.1a4 release#10

Merged
patrick-chinchill merged 1 commit into
mainfrom
ship
Apr 7, 2026
Merged

chore: prep 0.0.1a4 release#10
patrick-chinchill merged 1 commit into
mainfrom
ship

Conversation

@patrick-chinchill

Copy link
Copy Markdown
Collaborator

Final cosmetic fixes before going public.

@patrick-chinchill patrick-chinchill merged commit 8fb7547 into main Apr 7, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request increments the project version to 0.0.1a4 across the CHANGELOG, README, and pyproject.toml files. It also introduces GitHub configuration files, including a CODEOWNERS file and issue templates for bug reports and feature requests. One review comment suggests updating the version example in the bug report template to align with the new release version.

---

**Adapter**: (e.g., Slack, Discord, Teams)
**Version**: (e.g., 0.0.1a3)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The version example in the bug report template should be updated to 0.0.1a4 to match the current release version, ensuring that users are encouraged to provide the most relevant version information.

Suggested change
**Version**: (e.g., 0.0.1a3)
**Version**: (e.g., 0.0.1a4)

patrick-chinchill added a commit that referenced this pull request May 28, 2026
Ports vercel/chat#391 — adds chat.get_user(adapter, user_id) and per-adapter get_user implementations across all 8 platforms.

- Chat.get_user(adapter: str | Adapter, user_id: str) -> User | None resolves string adapter names through registered adapters
- Adapter Protocol gains async def get_user(user_id: str) -> User | None
- User extended with optional email, display_name, avatar_url
- Per-adapter implementations: Slack users.info, Discord GET /users/{id}, Google Chat users.get, GitHub GET /users/{login}, Linear GraphQL user(id:), Teams Graph /users/{aadObjectId} (uses #85's AAD cache), Telegram getChat (best-effort), WhatsApp (Cloud API has no separate lookup)
- Slack get_user awaits _resolve_token_async() so it works under callable bot_token resolvers (#87) when called from background contexts (cron, etc.)
- Lazy imports of platform SDKs inside each method (hazard #10)
- Resolved merge conflicts with #85/#86/#87/#88/#89 — all surface areas coexist

26 new tests in test_get_user_adapters.py + 3 in test_chat_faithful.py.
patrick-chinchill added a commit that referenced this pull request Jun 18, 2026
* feat(slack): expose web_client property on SlackAdapter (#98)

Port the Slack adapter's direct WebClient access from upstream
vercel/chat (commits 8366b8b / fdebde7 / 2f108bd, PRs #471/#476/#478).

- Add ``SlackAdapter.web_client``: a synchronous ``slack_sdk.WebClient``
  bound to the current request-context token (multi-workspace) or the
  configured default token (single-workspace). Token resolution uses the
  existing 3-level resolver via ``_get_token()``: ContextVar token >
  static ``bot_token`` config > ``AuthenticationError`` (no ``or``
  fallbacks).
- Add ``_get_web_client_for_token`` mirroring upstream's
  ``getClientForToken`` — one cached ``WebClient`` per distinct token.
  Kept separate from the async ``_client_cache`` (``AsyncWebClient``).
  ``slack_sdk`` import stays deferred (optional dependency, hazard #10).
- Add deprecated ``client`` property alias delegating to ``web_client``
  (one-release deprecation; emits ``DeprecationWarning``).

Tests (tests/test_slack_web_client.py) mirror upstream's "webClient
getter" block: single-tenant binding + per-token caching identity,
multi-tenant ContextVar resolution under ``with_bot_token``, no-context
and unresolved-async-resolver -> ``AuthenticationError``, and the
deprecated alias returning the same object plus emitting the warning.

https://claude.ai/code/session_01FyMxQn2BEAzmwKS1GZczKj

* fix(slack): evict web_client cache on invalidation + clarify caching test (review)

Two review follow-ups on the web_client port:

- Gemini: `_invalidate_client` cleared only the async `_client_cache`, leaving
  stale/revoked synchronous `WebClient` instances in `_web_client_cache` on
  token revocation / auth-error eviction. Pop the token from both caches.
  New `test_invalidate_client_clears_web_client_cache` is load-bearing.

- github-code-quality: `assert adapter.web_client is adapter.web_client`
  tripped "comparison of identical values". The test is a genuine caching
  check (the property is invoked twice), but binding each access to a name
  makes that intent explicit and silences the false-positive.

https://claude.ai/code/session_01FyMxQn2BEAzmwKS1GZczKj

* fix(slack): invoke sync token resolvers in web_client / _get_token (codex review)

The sync ``_get_token`` path only handled the static-string and primed
cache cases — a sync ``bot_token`` callable (used e.g. for secret
rotation or lazy load from a sync source) raised
``AuthenticationError`` from ``web_client`` outside any webhook /
ContextVar scope until an async path had primed the cache. Proactive
sends from single-workspace apps using a sync resolver therefore
failed.

Detect the sync-resolver case via ``inspect.iscoroutinefunction``,
invoke the callable, validate the result, and prime
``_default_bot_token_cache`` with the same semantics the async
``_resolve_default_token`` path uses. Async resolvers still raise from
the sync property (cannot be awaited). Defensive check for sync
callables that *return* a coroutine (rare but real:
``lambda: some_async_fn()``) — refuse to cache the coroutine.

The two existing tests that asserted the previous deficient behavior
(sync resolver raising before resolution) are updated to assert the
new correct behavior; the cache-refresh regression test switches to an
async resolver so its sanity precondition still holds.

* fix(slack): close orphan coroutine before raising awaitable-resolver error (audit)

The defensive `inspect.isawaitable(resolved)` branch in `_get_token`'s
sync-callable handler raised AuthenticationError but never closed the
coroutine the resolver returned. Callers saw a noisy
`RuntimeWarning: coroutine was never awaited` on every triggering call.

Close the awaitable via its `close()` method (Coroutine protocol) before
raising. The existing regression test
`test_sync_callable_returning_coroutine_raises` is strengthened to capture
warnings and assert none of "never awaited" kind leaked — confirmed
load-bearing under `pytest -W error::RuntimeWarning`.

https://claude.ai/code/session_01FyMxQn2BEAzmwKS1GZczKj

* fix(slack): honor bot_token rotation contract in sync _get_token + invalidation

Addresses two P2 review findings on PR #127:

**Codex P2 — sync resolver rotation broken**
The previous sync-callable branch in ``_get_token`` cached the first
resolved value in ``_default_bot_token_cache`` and the cache-first
early-return prevented re-invocation, freezing rotating resolvers
(e.g., secret-manager-backed). The contract on
``SlackAdapterConfig.bot_token`` says callable resolvers are "called
on each use to support rotation."

Track ``_is_dynamic_bot_token`` at construction time. In ``_get_token``,
sync dynamic resolvers now invoke fresh on every call and never write
the process-wide cache. Static-string configs keep their cache fast
path (nothing to rotate). Async resolvers still require a webhook /
``current_token_async`` entry to be awaited.

The previously-added test
``test_sync_current_token_with_sync_resolver_invokes_resolver``
asserted the cache was primed — flipped to assert the inverse, with a
cross-reference to the dedicated rotation pin in
``test_sync_callable_invoked_fresh_each_access``.

**CodeRabbit P2 — _invalidate_client retained revoked tokens**
``_invalidate_client(token)`` evicted the WebClient and AsyncWebClient
caches but left ``_default_bot_token_cache`` / ``_resolved_default_token``
holding the revoked value, so the next ``_get_token`` returned the
same token and the adapter just rebuilt clients around it.

Now clears the resolved-token caches for dynamic-resolver configs so
the next access re-invokes the resolver. Guarded on
``_is_dynamic_bot_token`` so static-string configs retain their cache
(no refresh path — clearing would only make subsequent sync access
raise with no way to recover).

Tests: rewrote the caching test to assert rotation (resolver invoked
fresh on every access, cache stays None); added invalidation tests
covering dynamic-resolver clearing, ContextVar clearing, static-string
no-op, and token-mismatch no-op. Full suite green (4067 passed) under
``-W error::RuntimeWarning``.

https://claude.ai/code/session_01FyMxQn2BEAzmwKS1GZczKj

* chore(tests): address CodeRabbit duplicate-test + iter-StopIteration findings

CodeRabbit review on the latest #127 HEAD surfaced two test-quality
issues:

1. ``TestWebClientAsyncResolver.test_unresolved_async_resolver_raises``
   duplicated ``TestWebClientSyncResolver.test_async_callable_in_sync_context_raises``
   (same async-resolver-in-sync-context path, but the latter is stronger
   — it also validates the error message wording). Removed the redundant
   wrapper class to honor this repo's "no duplicate tests" CLAUDE.md
   rule.

2. Rotation pin used ``tokens = iter(["xoxb-sync-1", ...])`` which would
   ``StopIteration`` if the test grew to a 4th access. Switched to
   ``f"xoxb-sync-{calls['n']}"`` so the resolver scales with call count;
   existing assertions on the literal values still hold.

68 tests still pass under ``pytest -W error::RuntimeWarning``.

https://claude.ai/code/session_01FyMxQn2BEAzmwKS1GZczKj

---------

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.

1 participant