Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions packages/ai_assistant/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,20 @@ async def tool_permission_handler(
**3. Hero & Meta**
* **Stats**: Weapon, Vitality, Spirit. **Flex Slots**: Unlocked by destroying objectives.
* **Beta Status**: Game is in closed-beta. Patches are frequent; prioritize tool data over memory.

**4. Deadlock API**
The Deadlock API is a community-driven, open-source project providing comprehensive game data access.
* **Main Website**: https://deadlock-api.com/
* **Game Data API**: https://api.deadlock-api.com/ — matches, players, leaderboards, hero/item stats, MMR.
* Swagger Docs: https://api.deadlock-api.com/docs
* **Assets API**: https://assets.deadlock-api.com/ — hero portraits, item icons, ability details, game metadata.
* Swagger Docs: https://assets.deadlock-api.com/docs
* **GitHub**: https://github.com/deadlock-api/
* **Discord**: https://discord.gg/XMF9Xrgfqu
* **Patreon**: https://www.patreon.com/user?u=68961896
* When users ask about available API endpoints, how to use the API, or what data the API provides,
use the `deadlock_api_schema` tool to fetch the full OpenAPI spec and provide accurate answers.
* Use `deadlock_api_info` for quick resource links and general API information.
</knowledge_base>

<response_formatting>
Expand Down
4 changes: 1 addition & 3 deletions packages/api/patreon_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,9 +538,7 @@ async def test_fetch_user_identity_success_with_active_patron(self) -> None:
},
"relationships": {
"campaign": {"data": {"id": "test-campaign-id"}},
"currently_entitled_tiers": {
"data": [{"type": "tier", "id": tier.tier_id}]
},
"currently_entitled_tiers": {"data": [{"type": "tier", "id": tier.tier_id}]},
},
}
],
Expand Down
147 changes: 137 additions & 10 deletions packages/tools/openapi/deadlock_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,34 +73,64 @@ async def create_deadlock_api_tools(

# Deadlock API resource URLs
DEADLOCK_API_INFO = {
"overview": {
"description": (
"The Deadlock API is a community-driven, open-source project providing comprehensive data access "
"for Valve's game Deadlock. It offers two main APIs: a Game Data API for match data, player stats, "
"leaderboards, and analytics; and an Assets API for hero portraits, item icons, ability images, "
"and other game media. Both APIs are free to use, well-documented via OpenAPI/Swagger specs, and "
"do not require authentication for most endpoints."
),
},
"main_website": {
"url": "https://deadlock-api.com/",
"description": "Main website for the Deadlock API project",
},
"assets_api": {
"url": "https://assets.deadlock-api.com/",
"description": "API for game assets including images, sounds, hero portraits, item icons, and media files",
"openapi_spec": "https://assets.deadlock-api.com/openapi.json",
"description": (
"Main website for the Deadlock API project with overview, documentation links, and getting started guides"
),
},
"data_api": {
"url": "https://api.deadlock-api.com/",
"description": "API for match data, player statistics, analytics, match histories, leaderboards, and more",
"description": (
"Game Data API — provides match data, player statistics, hero win rates, item pick rates, "
"leaderboards, match histories, MMR tracking, and more. Endpoints include match details, "
"player profiles, hero/item stats, and ranked leaderboards."
),
"openapi_spec": "https://api.deadlock-api.com/openapi.json",
"docs": "https://api.deadlock-api.com/docs",
},
"assets_api": {
"url": "https://assets.deadlock-api.com/",
"description": (
"Assets API — provides game assets including hero portraits, ability icons, item icons, "
"hero stats, item stats, ability details, and raw game data. Use this to get hero/item metadata, "
"images, and detailed game balance information."
),
"openapi_spec": "https://assets.deadlock-api.com/openapi.json",
"docs": "https://assets.deadlock-api.com/docs",
},
"github": {
"url": "https://github.com/deadlock-api/",
"description": "GitHub organization with source code, documentation, and community contributions",
"description": (
"GitHub organization with source code, documentation, and community contributions "
"for all Deadlock API projects"
),
},
"discord": {
"url": "https://discord.gg/XMF9Xrgfqu",
"description": "Discord community server for support, discussions, and announcements",
"description": "Discord community server for API support, bug reports, feature requests, and announcements",
},
"patreon": {
"url": "https://www.patreon.com/user?u=68961896",
"description": "Patreon page to support the Deadlock API project development",
"description": "Patreon page to support ongoing Deadlock API development and server costs",
},
}

# OpenAPI spec URLs for schema fetching
OPENAPI_SPEC_URLS: dict[str, str] = {
"data": "https://api.deadlock-api.com/openapi.json",
"assets": "https://assets.deadlock-api.com/openapi.json",
}
Comment on lines +128 to +132
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

DEADLOCK_API_INFO embeds the OpenAPI spec URLs as string literals while the same URLs are also defined in OPENAPI_SPEC_URLS. This duplication risks the two getting out of sync over time. Consider referencing OPENAPI_SPEC_URLS['data'] / OPENAPI_SPEC_URLS['assets'] when populating the openapi_spec fields (or building DEADLOCK_API_INFO from OPENAPI_SPEC_URLS) so there’s a single source of truth.

Copilot uses AI. Check for mistakes.


class DeadlockAPIInfoTool(BaseTool):
"""Tool that returns information about the Deadlock API resources.
Expand Down Expand Up @@ -151,6 +181,101 @@ def _create_result_summary(self, result: dict[str, Any]) -> str:
return f"Returned info for {len(result)} Deadlock API resources"


class DeadlockAPISchemaFetchTool(BaseTool):
"""Tool that fetches OpenAPI schemas for the Deadlock APIs.

Allows the agent to retrieve the full OpenAPI specification for either
the Game Data API or the Assets API, enabling it to answer detailed
questions about available endpoints, parameters, and response formats.
"""

def __init__(
self,
sse_callback: SSECallback,
timeout: float = 60.0,
) -> None:
super().__init__(sse_callback, timeout)
self._http_client: httpx.AsyncClient | None = None

@property
def name(self) -> str:
return "deadlock_api_schema"

async def _get_client(self) -> httpx.AsyncClient:
"""Get or create the HTTP client."""
if self._http_client is None:
self._http_client = httpx.AsyncClient(timeout=self._timeout)
return self._http_client

def get_definition(self) -> dict[str, Any]:
"""Get tool definition for agent configuration."""
return {
"name": self.name,
"description": (
"Fetch the OpenAPI schema for a Deadlock API. Use this to answer questions about "
"available API endpoints, request parameters, response formats, and how to use the API. "
"Pass api='data' for the Game Data API (matches, players, leaderboards) or "
"api='assets' for the Assets API (heroes, items, images, game data)."
),
"parameters": {
"type": "object",
"properties": {
"api": {
"type": "string",
"enum": ["data", "assets"],
"description": (
"Which API schema to fetch: "
"'data' for the Game Data API (api.deadlock-api.com) or "
"'assets' for the Assets API (assets.deadlock-api.com)"
),
},
},
"required": ["api"],
},
}

@retry(max_attempts=3, base_delay=1.0)
async def _run(self, api: str) -> dict[str, Any]:
"""Fetch the OpenAPI schema for the specified API.

Args:
api: Which API to fetch the schema for ('data' or 'assets')

Returns:
The OpenAPI specification as a dictionary

Raises:
ValueError: If api is not 'data' or 'assets'
OpenAPIConnectionError: If the schema cannot be fetched
"""
Comment on lines +238 to +250
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

This method requires 2 positional arguments, whereas overridden BaseTool._run requires 1.

Suggested change
async def _run(self, api: str) -> dict[str, Any]:
"""Fetch the OpenAPI schema for the specified API.
Args:
api: Which API to fetch the schema for ('data' or 'assets')
Returns:
The OpenAPI specification as a dictionary
Raises:
ValueError: If api is not 'data' or 'assets'
OpenAPIConnectionError: If the schema cannot be fetched
"""
async def _run(self, *args: Any, **kwargs: Any) -> dict[str, Any]:
"""Fetch the OpenAPI schema for the specified API.
Args:
api: Which API to fetch the schema for ('data' or 'assets').
May be provided as the first positional argument or as a keyword
argument named 'api'.
Returns:
The OpenAPI specification as a dictionary
Raises:
ValueError: If api is not provided or is not 'data' or 'assets'
OpenAPIConnectionError: If the schema cannot be fetched
"""
api = args[0] if args else kwargs.get("api")
if api is None:
raise ValueError("Missing required 'api' argument. Must be one of: "
+ ", ".join(sorted(OPENAPI_SPEC_URLS)))

Copilot uses AI. Check for mistakes.
if api not in OPENAPI_SPEC_URLS:
raise ValueError(f"Invalid API name: {api}. Must be one of: {', '.join(sorted(OPENAPI_SPEC_URLS))}")

spec_url = OPENAPI_SPEC_URLS[api]

try:
client = await self._get_client()
response = await client.get(spec_url)
response.raise_for_status()
return response.json()
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

DeadlockAPISchemaFetchTool._run doesn’t handle JSON decoding/parsing failures from response.json(). If the endpoint returns non-JSON (e.g., HTML error page with 200), this will raise a ValueError that bypasses the intended OpenAPIConnectionError path and results in inconsistent error reporting/retries. Consider catching JSON parse errors (or a broad Exception) around response.json() and re-raising as OpenAPIConnectionError with a helpful message (similar to OpenAPIToolGenerator.fetch_spec).

Suggested change
return response.json()
try:
return response.json()
except ValueError as e:
# Handle cases where the endpoint returns non-JSON (e.g., HTML error page with 2xx status)
raise OpenAPIConnectionError(
f"Failed to parse {api} API schema as JSON from {spec_url}"
) from e

Copilot uses AI. Check for mistakes.
except httpx.HTTPStatusError as e:
raise OpenAPIConnectionError(f"Failed to fetch {api} API schema: HTTP {e.response.status_code}") from e
except httpx.RequestError as e:
raise OpenAPIConnectionError(f"Network error fetching {api} API schema: {e}") from e

def _create_result_summary(self, result: dict[str, Any]) -> str:
info = result.get("info", {})
title = info.get("title", "Unknown API")
paths = result.get("paths", {})
return f"Schema for {title}: {len(paths)} endpoints"

async def close(self) -> None:
"""Close the HTTP client."""
if self._http_client:
await self._http_client.aclose()
self._http_client = None


class DeadlockAPICallTool(BaseTool):
"""Generic tool for calling any Deadlock API endpoint.

Expand Down Expand Up @@ -283,11 +408,13 @@ async def close(self) -> None:
"DeadlockAPIToolGenerator",
"DeadlockAPICallTool",
"DeadlockAPIInfoTool",
"DeadlockAPISchemaFetchTool",
"create_deadlock_api_tools",
"DEADLOCK_API_SPEC_URL",
"DEADLOCK_API_TOOL_PREFIX",
"DEADLOCK_API_BASE_URL",
"DEADLOCK_API_EXCLUDED_OPERATIONS",
"DEADLOCK_API_INFO",
"OPENAPI_SPEC_URLS",
"VALID_HTTP_METHODS",
]
Loading
Loading