-
Notifications
You must be signed in to change notification settings - Fork 46k
feat(backend): platform server linking API for multi-platform CoPilot #12615
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
Merged
Merged
Changes from 40 commits
Commits
Show all changes
44 commits
Select commit
Hold shift + click to select a range
ae6ee04
feat: Platform bot linking API for multi-platform CoPilot
Bentlybro cfdef71
style: lint + format platform linking routes
Bentlybro 88cfe03
fix: use Model.prisma() pattern for pyright compatibility
Bentlybro b228165
chore: update openapi.json with platform-linking endpoints
Bentlybro 7993ae7
fix: import ordering (isort)
Bentlybro 5b0bc35
fix: Address review feedback on platform linking PR
Bentlybro f46355e
fix: suppress pyright error on intentionally invalid test input
Bentlybro b636ab6
fix: use model_validate for invalid platform test (no type ignore)
Bentlybro 3952519
feat: Bot chat proxy — CoPilot streaming via bot API key
Bentlybro af14907
fix: correct unsubscribe_from_session parameter (subscriber_queue not…
Bentlybro 95f436d
fix: Address review round 2 — all 4 blockers
Bentlybro 9685838
refactor(backend): split platform-linking routes into models, auth, r…
Bentlybro be82e38
ci: regenerate openapi.json after platform-linking schema changes
Bentlybro 9c7dcbc
feat(backend): server-level platform linking with impersonation fix
Bentlybro 017134b
ci: regenerate openapi.json after server-level platform linking changes
Bentlybro c42398f
fix(backend): warn loudly when bot API key bypass is active + documen…
Bentlybro ce8af2a
chore: ignore copilot-bot/node_modules and .claude/ in root gitignore
Bentlybro 2431318
fix(backend): DM fallback in resolve — users already linked via a ser…
Bentlybro c743882
fix(backend): append ?platform= to link URL so frontend knows the sou…
Bentlybro 51c6401
feat(backend): add GET /tokens/{token}/info endpoint — returns platfo…
Bentlybro 7f10dfa
ci: regenerate openapi.json — add LinkTokenInfoResponse and /tokens/{…
Bentlybro aadb7cc
Update openapi.json
Bentlybro 8a1a87b
Minor formatting cleanup in platform_linking
Bentlybro ff0c576
Update platform_cost_test.py
Bentlybro 3fadfc8
Ensure create_chat_session persists sessions
Bentlybro be8cc33
fix(backend): fix test assertion for link_url with platform query param
Bentlybro 4a2de44
refactor(backend): extract find_server_link helper, cache auth settings
Bentlybro 9bd8806
test(backend): update mocks for find_server_link helper + clear auth …
Bentlybro b7080d2
feat(backend): separate server vs user (DM) platform linking
Bentlybro 84953f7
fix(backend): address PR review findings on platform linking
Bentlybro 6cec213
fix(backend): stop logging platform user IDs, add APIKeyHeader scheme
Bentlybro d60fc4e
fix: apply CodeRabbit auto-fixes
coderabbitai[bot] ac38853
fix(backend): split USER-link schema migration + lint trailing newlines
Bentlybro f72095c
fix(backend): update create_link_token tests for find_server_link ref…
Bentlybro 0407f85
Merge remote-tracking branch 'origin/dev' into feat/platform-bot-linking
Bentlybro b34abb2
test(backend): mock transaction() in platform-linking route test
Bentlybro f754b63
style(backend): black format routes_test.py
Bentlybro 724c90c
test(backend): expand platform_linking route coverage
Bentlybro d048eb3
fix(backend/platform-linking): address PR #12615 review
Bentlybro 77c7b05
refactor(backend/platform-linking): move bot-facing routes to AppServ…
Bentlybro 18fd4da
fix(backend/platform-linking): raise on duplicate chat message; sync …
Bentlybro 228ab06
refactor(backend/platform-linking): route DB calls through DatabaseMa…
Bentlybro cf02901
style(backend/platform-linking): isort db_manager.py import
Bentlybro a03bd53
fix(backend/platform-linking): catch UniqueViolationError directly; l…
Bentlybro File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,6 @@ | ||
| *.ignore.* | ||
| *.ign.* | ||
| .application.logs | ||
|
|
||
| # Claude Code local settings only — the rest of .claude/ is shared (skills etc.) | ||
| .claude/settings.local.json |
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
1 change: 1 addition & 0 deletions
1
autogpt_platform/backend/backend/api/features/platform_linking/__init__.py
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """Platform bot linking — user-facing REST routes.""" |
166 changes: 166 additions & 0 deletions
166
autogpt_platform/backend/backend/api/features/platform_linking/routes.py
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,166 @@ | ||
| """User-facing platform_linking REST routes (JWT auth).""" | ||
|
|
||
| import logging | ||
| from typing import Annotated | ||
|
|
||
| from autogpt_libs import auth | ||
| from fastapi import APIRouter, HTTPException, Path, Security | ||
|
|
||
| from backend.platform_linking.links import ( | ||
| confirm_server_link, | ||
| confirm_user_link, | ||
| delete_server_link, | ||
| delete_user_link, | ||
| get_link_token_info, | ||
| list_server_links, | ||
| list_user_links, | ||
| ) | ||
| from backend.platform_linking.models import ( | ||
| ConfirmLinkResponse, | ||
| ConfirmUserLinkResponse, | ||
| DeleteLinkResponse, | ||
| LinkTokenInfoResponse, | ||
| PlatformLinkInfo, | ||
| PlatformUserLinkInfo, | ||
| ) | ||
| from backend.util.exceptions import ( | ||
| LinkAlreadyExistsError, | ||
| LinkFlowMismatchError, | ||
| LinkTokenExpiredError, | ||
| NotAuthorizedError, | ||
| NotFoundError, | ||
| ) | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| router = APIRouter() | ||
|
|
||
| TokenPath = Annotated[ | ||
| str, | ||
| Path(max_length=64, pattern=r"^[A-Za-z0-9_-]+$"), | ||
| ] | ||
|
|
||
|
|
||
| def _translate(exc: Exception) -> HTTPException: | ||
| if isinstance(exc, NotFoundError): | ||
| return HTTPException(status_code=404, detail=str(exc)) | ||
| if isinstance(exc, NotAuthorizedError): | ||
| return HTTPException(status_code=403, detail=str(exc)) | ||
| if isinstance(exc, LinkAlreadyExistsError): | ||
| return HTTPException(status_code=409, detail=str(exc)) | ||
| if isinstance(exc, LinkTokenExpiredError): | ||
| return HTTPException(status_code=410, detail=str(exc)) | ||
| if isinstance(exc, LinkFlowMismatchError): | ||
| return HTTPException(status_code=400, detail=str(exc)) | ||
| return HTTPException(status_code=500, detail="Internal error.") | ||
|
|
||
|
|
||
| @router.get( | ||
| "/tokens/{token}/info", | ||
| response_model=LinkTokenInfoResponse, | ||
| dependencies=[Security(auth.requires_user)], | ||
|
ntindle marked this conversation as resolved.
|
||
| summary="Get display info for a link token", | ||
| ) | ||
| async def get_link_token_info_route(token: TokenPath) -> LinkTokenInfoResponse: | ||
| try: | ||
| return await get_link_token_info(token) | ||
| except (NotFoundError, LinkTokenExpiredError) as exc: | ||
| raise _translate(exc) from exc | ||
|
|
||
|
|
||
| @router.post( | ||
| "/tokens/{token}/confirm", | ||
| response_model=ConfirmLinkResponse, | ||
| dependencies=[Security(auth.requires_user)], | ||
| summary="Confirm a SERVER link token (user must be authenticated)", | ||
| ) | ||
| async def confirm_link_token( | ||
| token: TokenPath, | ||
| user_id: Annotated[str, Security(auth.get_user_id)], | ||
| ) -> ConfirmLinkResponse: | ||
| try: | ||
| return await confirm_server_link(token, user_id) | ||
|
ntindle marked this conversation as resolved.
Outdated
|
||
| except ( | ||
| NotFoundError, | ||
| LinkFlowMismatchError, | ||
| LinkTokenExpiredError, | ||
| LinkAlreadyExistsError, | ||
| ) as exc: | ||
| raise _translate(exc) from exc | ||
|
|
||
|
|
||
| @router.post( | ||
| "/user-tokens/{token}/confirm", | ||
| response_model=ConfirmUserLinkResponse, | ||
| dependencies=[Security(auth.requires_user)], | ||
| summary="Confirm a USER link token (user must be authenticated)", | ||
| ) | ||
| async def confirm_user_link_token( | ||
| token: TokenPath, | ||
| user_id: Annotated[str, Security(auth.get_user_id)], | ||
| ) -> ConfirmUserLinkResponse: | ||
| try: | ||
| return await confirm_user_link(token, user_id) | ||
| except ( | ||
| NotFoundError, | ||
| LinkFlowMismatchError, | ||
| LinkTokenExpiredError, | ||
| LinkAlreadyExistsError, | ||
| ) as exc: | ||
| raise _translate(exc) from exc | ||
|
|
||
|
|
||
| @router.get( | ||
| "/links", | ||
| response_model=list[PlatformLinkInfo], | ||
| dependencies=[Security(auth.requires_user)], | ||
| summary="List all platform servers linked to the authenticated user", | ||
| ) | ||
| async def list_my_links( | ||
| user_id: Annotated[str, Security(auth.get_user_id)], | ||
| ) -> list[PlatformLinkInfo]: | ||
| return await list_server_links(user_id) | ||
|
|
||
|
|
||
| @router.get( | ||
| "/user-links", | ||
| response_model=list[PlatformUserLinkInfo], | ||
| dependencies=[Security(auth.requires_user)], | ||
| summary="List all DM links for the authenticated user", | ||
| ) | ||
| async def list_my_user_links( | ||
| user_id: Annotated[str, Security(auth.get_user_id)], | ||
| ) -> list[PlatformUserLinkInfo]: | ||
| return await list_user_links(user_id) | ||
|
|
||
|
|
||
| @router.delete( | ||
| "/links/{link_id}", | ||
| response_model=DeleteLinkResponse, | ||
| dependencies=[Security(auth.requires_user)], | ||
| summary="Unlink a platform server", | ||
| ) | ||
| async def delete_link( | ||
| link_id: str, | ||
| user_id: Annotated[str, Security(auth.get_user_id)], | ||
| ) -> DeleteLinkResponse: | ||
| try: | ||
| return await delete_server_link(link_id, user_id) | ||
| except (NotFoundError, NotAuthorizedError) as exc: | ||
| raise _translate(exc) from exc | ||
|
|
||
|
|
||
| @router.delete( | ||
| "/user-links/{link_id}", | ||
| response_model=DeleteLinkResponse, | ||
| dependencies=[Security(auth.requires_user)], | ||
| summary="Unlink a DM / user link", | ||
| ) | ||
| async def delete_user_link_route( | ||
| link_id: str, | ||
| user_id: Annotated[str, Security(auth.get_user_id)], | ||
| ) -> DeleteLinkResponse: | ||
| try: | ||
| return await delete_user_link(link_id, user_id) | ||
| except (NotFoundError, NotAuthorizedError) as exc: | ||
| raise _translate(exc) from exc | ||
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.