mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-09 07:08:09 -05:00
We want to provide Single Sign-On for multiple AutoGPT apps that use the Platform as their backend. ### Changes 🏗️ Backend: - DB + logic + API for OAuth flow (w/ tests) - DB schema additions for OAuth apps, codes, and tokens - Token creation/validation/management logic - OAuth flow endpoints (app info, authorize, token exchange, introspect, revoke) - E2E OAuth API integration tests - Other OAuth-related endpoints (upload app logo, list owned apps, external `/me` endpoint) - App logo asset management - Adjust external API middleware to support auth with access token - Expired token clean-up job - Add `OAUTH_TOKEN_CLEANUP_INTERVAL_HOURS` setting (optional) - `poetry run oauth-tool`: dev tool to test the OAuth flows and register new OAuth apps - `poetry run export-api-schema`: dev tool to quickly export the OpenAPI schema (much quicker than spinning up the backend) Frontend: - Frontend UI for app authorization (`/auth/authorize`) - Re-redirect after login/signup - Frontend flow to batch-auth integrations on request of the client app (`/auth/integrations/setup-wizard`) - Debug `CredentialInputs` component - Add `/profile/oauth-apps` management page - Add `isOurProblem` flag to `ErrorCard` to hide action buttons when the error isn't our fault - Add `showTitle` flag to `CredentialsInput` to hide built-in title for layout reasons DX: - Add [API guide](https://github.com/Significant-Gravitas/AutoGPT/blob/pwuts/sso/docs/content/platform/integrating/api-guide.md) and [OAuth guide](https://github.com/Significant-Gravitas/AutoGPT/blob/pwuts/sso/docs/content/platform/integrating/oauth-guide.md) ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Manually verify test coverage of OAuth API tests - Test `/auth/authorize` using `poetry run oauth-tool test-server` - [x] Works - [x] Looks okay - Test `/auth/integrations/setup-wizard` using `poetry run oauth-tool test-server` - [x] Works - [x] Looks okay - Test `/profile/oauth-apps` page - [x] All owned OAuth apps show up - [x] Enabling/disabling apps works - [ ] ~~Uploading logos works~~ can only test this once deployed to dev #### For configuration changes: - [x] `.env.default` is updated or already compatible with my changes - [x] `docker-compose.yml` is updated or already compatible with my changes - [x] I have included a list of my configuration changes in the PR description (under **Changes**)
153 lines
5.1 KiB
Python
153 lines
5.1 KiB
Python
"""External API routes for chat tools - stateless HTTP endpoints.
|
|
|
|
Note: These endpoints use ephemeral sessions that are not persisted to Redis.
|
|
As a result, session-based rate limiting (max_agent_runs, max_agent_schedules)
|
|
is not enforced for external API calls. Each request creates a fresh session
|
|
with zeroed counters. Rate limiting for external API consumers should be
|
|
handled separately (e.g., via API key quotas).
|
|
"""
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from fastapi import APIRouter, Security
|
|
from prisma.enums import APIKeyPermission
|
|
from pydantic import BaseModel, Field
|
|
|
|
from backend.data.auth.base import APIAuthorizationInfo
|
|
from backend.server.external.middleware import require_permission
|
|
from backend.server.v2.chat.model import ChatSession
|
|
from backend.server.v2.chat.tools import find_agent_tool, run_agent_tool
|
|
from backend.server.v2.chat.tools.models import ToolResponseBase
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
tools_router = APIRouter(prefix="/tools", tags=["tools"])
|
|
|
|
# Note: We use Security() as a function parameter dependency (auth: APIAuthorizationInfo = Security(...))
|
|
# rather than in the decorator's dependencies= list. This avoids duplicate permission checks
|
|
# while still enforcing auth AND giving us access to auth for extracting user_id.
|
|
|
|
|
|
# Request models
|
|
class FindAgentRequest(BaseModel):
|
|
query: str = Field(..., description="Search query for finding agents")
|
|
|
|
|
|
class RunAgentRequest(BaseModel):
|
|
"""Request to run or schedule an agent.
|
|
|
|
The tool automatically handles the setup flow:
|
|
- First call returns available inputs so user can decide what values to use
|
|
- Returns missing credentials if user needs to configure them
|
|
- Executes when inputs are provided OR use_defaults=true
|
|
- Schedules execution if schedule_name and cron are provided
|
|
"""
|
|
|
|
username_agent_slug: str = Field(
|
|
...,
|
|
description="The marketplace agent slug (e.g., 'username/agent-name')",
|
|
)
|
|
inputs: dict[str, Any] = Field(
|
|
default_factory=dict,
|
|
description="Dictionary of input values for the agent",
|
|
)
|
|
use_defaults: bool = Field(
|
|
default=False,
|
|
description="Set to true to run with default values (user must confirm)",
|
|
)
|
|
schedule_name: str | None = Field(
|
|
None,
|
|
description="Name for scheduled execution (triggers scheduling mode)",
|
|
)
|
|
cron: str | None = Field(
|
|
None,
|
|
description="Cron expression (5 fields: minute hour day month weekday)",
|
|
)
|
|
timezone: str = Field(
|
|
default="UTC",
|
|
description="IANA timezone (e.g., 'America/New_York', 'UTC')",
|
|
)
|
|
|
|
|
|
def _create_ephemeral_session(user_id: str | None) -> ChatSession:
|
|
"""Create an ephemeral session for stateless API requests."""
|
|
return ChatSession.new(user_id)
|
|
|
|
|
|
@tools_router.post(
|
|
path="/find-agent",
|
|
)
|
|
async def find_agent(
|
|
request: FindAgentRequest,
|
|
auth: APIAuthorizationInfo = Security(
|
|
require_permission(APIKeyPermission.USE_TOOLS)
|
|
),
|
|
) -> dict[str, Any]:
|
|
"""
|
|
Search for agents in the marketplace based on capabilities and user needs.
|
|
|
|
Args:
|
|
request: Search query for finding agents
|
|
|
|
Returns:
|
|
List of matching agents or no results response
|
|
"""
|
|
session = _create_ephemeral_session(auth.user_id)
|
|
result = await find_agent_tool._execute(
|
|
user_id=auth.user_id,
|
|
session=session,
|
|
query=request.query,
|
|
)
|
|
return _response_to_dict(result)
|
|
|
|
|
|
@tools_router.post(
|
|
path="/run-agent",
|
|
)
|
|
async def run_agent(
|
|
request: RunAgentRequest,
|
|
auth: APIAuthorizationInfo = Security(
|
|
require_permission(APIKeyPermission.USE_TOOLS)
|
|
),
|
|
) -> dict[str, Any]:
|
|
"""
|
|
Run or schedule an agent from the marketplace.
|
|
|
|
The endpoint automatically handles the setup flow:
|
|
- Returns missing inputs if required fields are not provided
|
|
- Returns missing credentials if user needs to configure them
|
|
- Executes immediately if all requirements are met
|
|
- Schedules execution if schedule_name and cron are provided
|
|
|
|
For scheduled execution:
|
|
- Cron format: "minute hour day month weekday"
|
|
- Examples: "0 9 * * 1-5" (9am weekdays), "0 0 * * *" (daily at midnight)
|
|
- Timezone: Use IANA timezone names like "America/New_York"
|
|
|
|
Args:
|
|
request: Agent slug, inputs, and optional schedule config
|
|
|
|
Returns:
|
|
- setup_requirements: If inputs or credentials are missing
|
|
- execution_started: If agent was run or scheduled successfully
|
|
- error: If something went wrong
|
|
"""
|
|
session = _create_ephemeral_session(auth.user_id)
|
|
result = await run_agent_tool._execute(
|
|
user_id=auth.user_id,
|
|
session=session,
|
|
username_agent_slug=request.username_agent_slug,
|
|
inputs=request.inputs,
|
|
use_defaults=request.use_defaults,
|
|
schedule_name=request.schedule_name or "",
|
|
cron=request.cron or "",
|
|
timezone=request.timezone,
|
|
)
|
|
return _response_to_dict(result)
|
|
|
|
|
|
def _response_to_dict(result: ToolResponseBase) -> dict[str, Any]:
|
|
"""Convert a tool response to a dictionary for JSON serialization."""
|
|
return result.model_dump()
|