feat: add Threads provider with auto token refresh (1.4.0)#5
Merged
rockfordlhotka merged 1 commit intomainfrom May 4, 2026
Merged
feat: add Threads provider with auto token refresh (1.4.0)#5rockfordlhotka merged 1 commit intomainfrom
rockfordlhotka merged 1 commit intomainfrom
Conversation
Adds SocialAgent.Providers.Threads implementing ISocialMediaProvider against Meta's Threads Graph API v1.0. Notifications are synthesized from /me/mentions and /me/replies; per-post likes and follower count are gated behind an IncludePostInsights flag. A new ThreadsTokenRefreshService persists the long-lived OAuth token to a ProviderTokens table and refreshes ahead of expiry, surviving pod restarts. DatabaseMigrationService now adds new tables idempotently on existing live databases. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
The Threads provider has not been tested against the live Threads API. Token acquisition is blocked upstream of any code in this PR: Meta's developer dashboard returns a persistent 404 from the Threads use-case redirect-URI save endpoint (
/apps/{id}/async/threads-login/setting/save/), which prevents registering the OAuth Redirect Callback URL, which prevents completing the OAuth flow, which prevents minting a token. Reproduced with both an existing app and a brand-new app from scratch, in normal and incognito browser sessions, so this is a Meta-side dashboard issue, not an account- or app-state issue. Filed/observable separately; not in scope for this PR.Threads remains disabled by default (
SocialAgent:Providers:Threads:Enabled=falseinappsettings.json), so merging has zero effect on the existing Mastodon and Bluesky behavior. When Meta's dashboard cooperates and a long-lived token can be obtained, the provider activates with config changes only — no code changes required:Live integration tests are gated on
THREADS_ACCESS_TOKENand ready to run once a token exists.Summary
SocialAgent.Providers.ThreadsimplementingISocialMediaProvideragainst Meta's Threads Graph API v1.0. Posts via/me/threads, notifications synthesized from/me/mentions+/me/replies, optional likes/follower count via insights gated behindIncludePostInsights.ThreadsTokenRefreshService(registered only when Threads is enabled) seeds the in-memoryThreadsTokenStorefrom a newProviderTokensdatabase row on startup, callsGET /refresh_access_tokenwhen fewer thanRefreshThresholdDays(default 7) remain, and persists the rotated value — so refreshes survive pod restarts.<Version>+ k8s deployment image tag); the/.well-known/agent-card.jsonnow reports 1.4.0.DatabaseMigrationServicenow performs an idempotentCREATE TABLE IF NOT EXISTS "ProviderTokens"(dialect-aware: PGtext/timestamptz, SQLiteTEXT) afterEnsureCreatedAsync, so existing live PostgreSQL deployments pick up the new table on rollout with zero manual DDL.Notable implementation choices
SocialAgent.Data. Token persistence lives in the host (ThreadsTokenRefreshService); the provider just exposesRefreshTokenAsyncand a singletonThreadsTokenStore.ThreadsTokenStore.Currentfalls back toThreadsOptions.AccessTokenwith an assumed 60-day expiry if the refresh service hasn't seeded yet, so the provider can operate during the brief startup gap before DB seed completes.HttpRequestMessageobjects (rather than mutatingHttpClient.DefaultRequestHeaders) so a token rotation during an in-flight A2A request is safe.SocialNotificationitems haveIsRead = false. Documented as a known limitation; local read-state tracking is a candidate for a follow-up.threads_manage_repliesandthreads_manage_insightsrequire Meta App Review for production tokens. When scopes are missing, affected calls log a warning and return empty rather than failing the poll cycle.Files of note
src/SocialAgent.Providers.Threads/ThreadsProvider.cs— provider impl +RefreshTokenAsyncsrc/SocialAgent.Providers.Threads/ThreadsTokenStore.cs— singleton current-token holdersrc/SocialAgent.Host/Services/ThreadsTokenRefreshService.cs— DB seed + scheduled refreshsrc/SocialAgent.Host/Services/DatabaseMigrationService.cs— adds idempotent table patch pathdocs/providers/threads.md— full provider doc (scopes, refresh, limits, schema)CHANGELOG.md— new file with 1.4.0 entryTest plan
dotnet build SocialAgent.slnx— clean (TreatWarningsAsErrors)dotnet test SocialAgent.slnx --filter "TestCategory!=Integration"— 11/11 pass (4 new inSocialAgent.Providers.Threads.Testscovering options defaults andThreadsTokenStorelazy fallback / set / unconfigured-throws)graph.threads.net— pending Meta dashboard fix per banner above🤖 Generated with Claude Code