Skip to content

Add Nightmarket tools — API marketplace for AI agents#1111

Open
streacy wants to merge 2 commits intoMervinPraison:mainfrom
streacy:add-nightmarket-skill
Open

Add Nightmarket tools — API marketplace for AI agents#1111
streacy wants to merge 2 commits intoMervinPraison:mainfrom
streacy:add-nightmarket-skill

Conversation

@streacy
Copy link

@streacy streacy commented Mar 5, 2026

What this adds

A new nightmarket_tools.py module in src/praisonai-agents/praisonaiagents/tools/ that gives PraisonAI agents the ability to discover and call paid third-party API services through the Nightmarket marketplace.

Functions

  • nightmarket_search(query, sort) — Search the marketplace for available API services
  • nightmarket_service_details(endpoint_id) — Get full details including request/response examples
  • nightmarket_call(endpoint_id, method, body, headers, payment_signature) — Call a service with optional x402 payment proof

How it works

Nightmarket is an API marketplace where AI agents find and pay for third-party services (data enrichment, analytics, AI models, content generation, etc.). Every call settles on-chain in USDC on Base using the x402 payment protocol — no API keys or subscriptions needed.

The tool uses only stdlib (urllib, json) with no additional dependencies.

Usage

from praisonaiagents.tools.nightmarket_tools import nightmarket_search, nightmarket_call

# Find weather APIs
services = nightmarket_search("weather")

# Call one
response = nightmarket_call("abc123", method="GET")
# Returns 402 payment details on first call — pay via CrowPay, then retry with payment_signature

Why this is useful for PraisonAI

PraisonAI agents can use these tools to autonomously access external APIs without configuring API keys for each service. The agent searches the marketplace, pays per-call in USDC, and gets the response. Pairs with CrowPay for automatic payment handling.

Summary by CodeRabbit

  • New Features
    • Added Nightmarket marketplace integration: search services with sorting, view full service details, and invoke marketplace services (supports GET/POST/PUT/PATCH/DELETE).
    • Built-in handling for paid offerings: prompts and structured payment guidance when payment is required, plus robust error reporting and safe header handling.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant enhancement to PraisonAI agents by integrating the Nightmarket API marketplace. This integration provides agents with the ability to programmatically discover and utilize a wide array of third-party API services. By leveraging the x402 payment protocol, agents can autonomously handle per-call payments, eliminating the need for manual API key management and fostering a more dynamic and self-sufficient operational model for accessing external functionalities.

Highlights

  • New Module for Nightmarket Tools: A new nightmarket_tools.py module has been added to src/praisonai-agents/praisonaiagents/tools/, providing PraisonAI agents with capabilities to interact with the Nightmarket API marketplace.
  • API Discovery Functionality: The nightmarket_search(query, sort) function allows agents to search the marketplace for available API services based on a query and sorting preference.
  • Service Detail Retrieval: The nightmarket_service_details(endpoint_id) function enables agents to retrieve comprehensive details, including request and response examples, for a specific Nightmarket service.
  • API Calling with x402 Payment Protocol: The nightmarket_call(endpoint_id, method, body, headers, payment_signature) function facilitates calling Nightmarket API services, incorporating the x402 payment protocol for on-chain, per-call payments without requiring traditional API keys.
  • Enhanced Agent Autonomy: These tools empower PraisonAI agents to autonomously discover, access, and pay for external third-party APIs, streamlining integration and expanding their capabilities.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py
    • Added nightmarket_search function to query the Nightmarket API marketplace.
    • Added nightmarket_service_details function to retrieve detailed information about specific services.
    • Added nightmarket_call function to execute API calls, supporting x402 payment handling.
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@qodo-code-review
Copy link

Review Summary by Qodo

Add Nightmarket API marketplace tools for AI agents

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Adds Nightmarket API marketplace integration for AI agents
• Enables agents to discover and call paid third-party services
• Implements x402 payment protocol for on-chain USDC settlements
• Uses only stdlib dependencies (urllib, json)
Diagram
flowchart LR
  Agent["AI Agent"] -->|search| Search["nightmarket_search()"]
  Search -->|query marketplace| NM["Nightmarket API"]
  NM -->|returns services| Agent
  Agent -->|get details| Details["nightmarket_service_details()"]
  Details -->|fetch endpoint| NM
  NM -->|service info| Agent
  Agent -->|call service| Call["nightmarket_call()"]
  Call -->|x402 request| NM
  NM -->|402 payment required| CrowPay["CrowPay"]
  CrowPay -->|payment signature| Call
  Call -->|retry with signature| NM
  NM -->|API response| Agent
Loading

Grey Divider

File Changes

1. src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py ✨ Enhancement +131/-0

Nightmarket API marketplace integration module

• Introduces three new functions for Nightmarket API marketplace integration
• nightmarket_search() queries marketplace with optional filtering and sorting
• nightmarket_service_details() retrieves full service specifications and examples
• nightmarket_call() executes API calls with x402 payment protocol support
• Handles 402 payment responses and integrates with CrowPay for payment authorization
• Uses only stdlib imports (urllib, json) with no external dependencies

src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py


Grey Divider

Qodo Logo

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 5, 2026

📝 Walkthrough

Walkthrough

Adds a new Nightmarket API client module with search, service-details, and service-invocation functions; and registers those functions in the tools mapping.

Changes

Cohort / File(s) Summary
Nightmarket API Client
src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py
New module implementing nightmarket_search(), nightmarket_service_details(), and nightmarket_call() with request building, safe header filtering, endpoint ID validation, JSON parsing, and HTTP/402 payment handling.
Tool mappings update
src/praisonai-agents/praisonaiagents/tools/__init__.py
Added mappings for nightmarket_search, nightmarket_service_details, and nightmarket_call in TOOL_MAPPINGS to expose the new module's functions.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Agent as nightmarket_call
    participant API as Nightmarket API
    participant Payment as Payment System

    Client->>Agent: nightmarket_call(endpoint_id, method, params, body, headers)
    Agent->>API: HTTP request (with safe headers, optional body/params)
    API-->>Agent: 402 Payment Required (payment details)
    Agent-->>Client: Return payment-needed structure (endpoint, amount, currency, instructions)

    Client->>Payment: Process payment (external)
    Payment-->>Client: payment_signature

    Client->>Agent: nightmarket_call(..., payment_signature)
    Agent->>API: HTTP request with payment signature header
    API-->>Agent: 200 OK (service response)
    Agent-->>Client: Decoded JSON response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 I hopped to the market beneath moonlight's glow,
Found endpoints and services in tidy row,
I nibble a query, I tumble a call,
Pay with a signature — then answers fall. ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding Nightmarket tools for API marketplace functionality to enable AI agents to discover and call paid third-party services.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review
Copy link

qodo-code-review bot commented Mar 5, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Not exported via tools🐞 Bug ✓ Correctness
Description
The module documents from praisonaiagents.tools import nightmarket_*, but the tools package only
exposes names present in TOOL_MAPPINGS; since nightmarket_* are not mapped, that import will
raise AttributeError and tool profiles can’t resolve these tools.
Code

src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py[4]

+from praisonaiagents.tools import nightmarket_search, nightmarket_service_details, nightmarket_call
Evidence
nightmarket_tools.py instructs importing via praisonaiagents.tools, but
praisonaiagents.tools.__getattr__ only resolves attributes listed in TOOL_MAPPINGS and otherwise
raises AttributeError. The TOOL_MAPPINGS dict currently ends without any nightmarket entries, so
the documented import path cannot work.

src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py[3-5]
src/praisonai-agents/praisonaiagents/tools/init.py[195-200]
src/praisonai-agents/praisonaiagents/tools/init.py[140-166]
src/praisonai-agents/praisonaiagents/tools/init.py[241-253]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`nightmarket_tools.py` documents importing `nightmarket_search`, `nightmarket_service_details`, and `nightmarket_call` from `praisonaiagents.tools`, but the tools package lazily exposes only names listed in `TOOL_MAPPINGS`. Because the Nightmarket functions are not added to `TOOL_MAPPINGS`, the documented import path fails with `AttributeError`.
## Issue Context
`praisonaiagents.tools.__getattr__` checks `TOOL_MAPPINGS` and raises if the requested name is absent.
## Fix Focus Areas
- src/praisonai-agents/praisonaiagents/tools/__init__.py[26-166]
- src/praisonai-agents/praisonaiagents/tools/__init__.py[195-253]
- src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py[1-18]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Invalid params example🐞 Bug ✓ Correctness
Description
The docstring example calls nightmarket_call(..., params=...) but nightmarket_call does not
accept a params argument, so the documented usage will raise TypeError and there’s no documented
way to pass query params for GET calls.
Code

src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py[R12-14]

+# Call a service (returns 402 info for payment)
+response = nightmarket_call("abc123", method="GET", params={"city": "NYC"})
+
Evidence
The usage example passes a params keyword, but the function signature only accepts endpoint_id,
method, body, headers, and payment_signature. Python will raise `TypeError:
nightmarket_call() got an unexpected keyword argument 'params'`.

src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py[12-14]
src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py[77-83]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The module-level usage example calls `nightmarket_call(..., params={...})`, but `nightmarket_call` has no `params` parameter, so the example fails at runtime.
## Issue Context
If Nightmarket endpoints need query parameters for GET requests, the function should support encoding them into the request URL.
## Fix Focus Areas
- src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py[1-18]
- src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py[77-117]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. 402 JSON parse brittle🐞 Bug ⛯ Reliability
Description
The 402 handler always attempts json.loads(e.read().decode()); if the 402 response body is empty
or not valid JSON, it will throw and the outer handler will return a generic {error: ...} instead
of structured payment-required details.
Code

src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py[R118-127]

+        except HTTPError as e:
+            if e.code == 402:
+                payment_info = json.loads(e.read().decode()) if e.read else {}
+                payment_header = e.headers.get("PAYMENT-REQUIRED", "")
+                return {
+                    "status": 402,
+                    "message": "Payment required. Use CrowPay to authorize payment, then retry with payment_signature.",
+                    "payment_required": payment_info,
+                    "payment_header": payment_header,
+                }
Evidence
The conditional if e.read is effectively always true (it’s a method), so the code always tries to
parse the error body as JSON. Any JSONDecodeError will be caught by the outer except Exception
and converted into {"error": ...}, preventing callers from receiving the intended 402 response
payload with payment instructions.

src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py[118-131]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The 402 payment-required handler assumes the error body is JSON and unconditionally runs `json.loads(...)`. If the body is empty or non-JSON, parsing fails and the outer exception handler returns a generic error dict instead of the intended 402 payment payload.
## Issue Context
Payment flows are typically latency/edge-case heavy; this should be defensive so callers reliably get 402 metadata.
## Fix Focus Areas
- src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py[118-131]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Copy link
Contributor

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

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 introduces useful tools for interacting with the Nightmarket API marketplace, with a clean implementation using standard libraries. However, significant security concerns exist regarding user-supplied input, which could be influenced by prompt injection. Specifically, the endpoint_id parameter is susceptible to path traversal on the remote API, and the headers parameter in nightmarket_call allows for arbitrary header injection, potentially leading to SSRF. Sanitizing these inputs is crucial for improving security. Beyond security, several code-related issues need addressing: urllib imports should be at the module level, there's a potential bug in error handling for 402 responses that could lead to a JSONDecodeError, and the nightmarket_call function doesn't handle GET parameters as implied by its docstring. A critical omission is that the new tools are not registered in src/praisonai-agents/praisonaiagents/tools/__init__.py within the TOOL_MAPPINGS, preventing their discovery by the agent framework.

Comment on lines +77 to +83
def nightmarket_call(
endpoint_id: str,
method: str = "GET",
body: Optional[Dict] = None,
headers: Optional[Dict] = None,
payment_signature: Optional[str] = None,
) -> Dict:
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The module's docstring provides an example of calling this function with a params argument for GET request parameters: nightmarket_call("abc123", method="GET", params={"city": "NYC"}). However, the function signature does not include a params argument, and there's no logic to handle it. This is a significant omission that limits the tool's functionality.

To fix this, you should:

  1. Add params: Optional[Dict] = None to the function signature.
  2. Update the function's docstring to describe the params argument.
  3. Add logic within the function to urlencode the params and append them to the URL for GET requests, similar to how it's done in nightmarket_search.

return result
except HTTPError as e:
if e.code == 402:
payment_info = json.loads(e.read().decode()) if e.read else {}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

This line has a potential bug. The condition if e.read will always be true because e.read is a method object. If the response body from e.read() is empty, json.loads('') will raise a json.decoder.JSONDecodeError. The code should first read the response body into a variable and then attempt to parse it only if it's not empty.

Suggested change
payment_info = json.loads(e.read().decode()) if e.read else {}
payment_info = json.loads(body.decode()) if (body := e.read()) else {}

try:
from urllib.request import urlopen, Request

url = f"{NIGHTMARKET_BASE_URL}/marketplace/{endpoint_id}"
Copy link
Contributor

Choose a reason for hiding this comment

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

security-medium medium

The endpoint_id parameter is directly concatenated into the URL without validation or sanitization. This allows an attacker (e.g., via prompt injection) to manipulate the URL path and access unintended endpoints on the nightmarket.ai domain. For example, providing ../../other-endpoint would result in a request to https://nightmarket.ai/api/other-endpoint instead of the intended marketplace endpoint.

from urllib.request import urlopen, Request
from urllib.error import HTTPError

url = f"{NIGHTMARKET_BASE_URL}/x402/{endpoint_id}"
Copy link
Contributor

Choose a reason for hiding this comment

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

security-medium medium

The endpoint_id parameter is directly concatenated into the URL without validation or sanitization. This allows an attacker (e.g., via prompt injection) to manipulate the URL path and access unintended endpoints on the nightmarket.ai domain. For example, providing ../../other-endpoint would result in a request to https://nightmarket.ai/api/other-endpoint instead of the intended x402 endpoint.

url = f"{NIGHTMARKET_BASE_URL}/x402/{endpoint_id}"
req_headers = {"Accept": "application/json"}
if headers:
req_headers.update(headers)
Copy link
Contributor

Choose a reason for hiding this comment

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

security-medium medium

The nightmarket_call function accepts an arbitrary dictionary of headers and applies them to the outgoing request. This allows an attacker to inject sensitive headers, such as Host, which can be used to perform SSRF attacks if the request passes through a proxy, or to bypass security controls on the target server. It is recommended to restrict the allowed headers to a safe subset or block sensitive headers like Host, Content-Length, and Connection.

Comment on lines +40 to +41
from urllib.request import urlopen, Request
from urllib.parse import urlencode
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

For better code organization and to adhere to PEP 8, imports should be placed at the top of the file. The urllib imports are repeated in nightmarket_search, nightmarket_service_details, and nightmarket_call. Please move all urllib imports to the top of the module, with the other imports.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py (1)

49-49: Consider centralizing timeout configuration for consistency.

Timeouts are hardcoded to two values (15s/30s). Exposing a module-level timeout constant (or function arg) makes behavior tunable and consistent with other configurable HTTP paths.

Also applies to: 70-70, 115-115

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py` at line 49,
Replace the hardcoded timeouts used in urlopen(...) calls with a single
module-level constant (e.g. DEFAULT_HTTP_TIMEOUT) and optionally allow an
override via a function argument; locate the urlopen(req, timeout=15)
occurrences in nightmarket_tools.py (the calls at the lines shown plus the ones
at 70 and 115) and change them to use the constant (or the passed-in timeout
parameter) so all HTTP timeouts are configurable and consistent across the
module.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py`:
- Around line 77-113: The nightmarket_call function signature and implementation
lack support for a params argument even though examples/docstring reference it;
add an optional params: Dict = None parameter to nightmarket_call and, inside
the function (before creating Request), if params is provided build a query
string using urllib.parse.urlencode (handle list values appropriately or use
doseq=True) and append it to the base URL (only for methods where a query string
is valid, e.g., GET/DELETE), ensuring body is not used for GET requests; also
update req creation to use the new URL and ensure the docstring mentions params
in the args list.
- Around line 29-83: Add the Nightmarket tool functions to the TOOL_MAPPINGS
registry: import nightmarket_search, nightmarket_service_details, and
nightmarket_call and add entries mapping stable keys (e.g. "nightmarket_search",
"nightmarket_service_details", "nightmarket_call") to the corresponding
callables in the TOOL_MAPPINGS dict so the framework can discover them; ensure
the import uses the module where those functions are defined and follow the same
entry structure as existing tools (name -> function reference and any metadata
if required).
- Around line 118-121: In the except HTTPError as e block, avoid calling
e.read() twice and protect json.loads from invalid or empty bodies: read the
response once into a variable (e.g., body = e.read()), check if body is
non-empty, then try to json.loads(body.decode()) inside a try/except (falling
back to {} on decode errors or empty body); keep retrieving the PAYMENT-REQUIRED
header via e.headers.get("PAYMENT-REQUIRED", "") and assign results to
payment_info and payment_header accordingly.

---

Nitpick comments:
In `@src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py`:
- Line 49: Replace the hardcoded timeouts used in urlopen(...) calls with a
single module-level constant (e.g. DEFAULT_HTTP_TIMEOUT) and optionally allow an
override via a function argument; locate the urlopen(req, timeout=15)
occurrences in nightmarket_tools.py (the calls at the lines shown plus the ones
at 70 and 115) and change them to use the constant (or the passed-in timeout
parameter) so all HTTP timeouts are configurable and consistent across the
module.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 68c8e898-773f-4c6c-ba71-ac7e8cc6b175

📥 Commits

Reviewing files that changed from the base of the PR and between e854a50 and 2331c7e.

📒 Files selected for processing (1)
  • src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py

…ster tools

- Add params parameter to nightmarket_call for query string support
- Fix e.read bug (was checking method object, now reads body once)
- Add endpoint_id validation to prevent path traversal
- Filter headers to safe subset to prevent header injection
- Move imports to module level per PEP 8
- Register nightmarket tools in TOOL_MAPPINGS for framework discovery
- Use logger.exception for proper traceback logging
@streacy
Copy link
Author

streacy commented Mar 5, 2026

Addressed all review feedback:

  • params support added: nightmarket_call now accepts params: Optional[Dict] for query string parameters, matching the documented example
  • e.read bug fixed: Was checking method object (always truthy) — now reads body once into variable with proper JSON decode error handling
  • Path traversal prevention: Added _validate_endpoint_id() to reject ../ and / in endpoint IDs
  • Header injection mitigation: Added _SAFE_HEADERS allowlist to filter user-provided headers
  • Imports moved to top: All urllib imports at module level per PEP 8
  • TOOL_MAPPINGS registered: Added all three functions to __init__.py so framework discovery works
  • Logging improved: Using logger.exception() for proper traceback visibility

Thanks for the thorough reviews!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py (1)

90-132: ⚠️ Potential issue | 🟠 Major

Enforce allowed HTTP methods and reject incompatible body usage.

Line 132 currently accepts any method string, and a provided body is silently ignored unless the method is POST/PUT/PATCH. This can cause surprising behavior for paid calls.

Proposed fix
 def nightmarket_call(
     endpoint_id: str,
     method: str = "GET",
     params: Optional[Dict] = None,
@@
 ) -> Dict:
@@
     try:
+        allowed_methods = {"GET", "POST", "PUT", "PATCH", "DELETE"}
+        method_u = method.upper()
+        if method_u not in allowed_methods:
+            raise ValueError(f"Unsupported method: {method_u}")
+        if body is not None and method_u not in {"POST", "PUT", "PATCH"}:
+            raise ValueError(f"Body is not allowed for method: {method_u}")
+
         safe_id = _validate_endpoint_id(endpoint_id)
         url = f"{NIGHTMARKET_BASE_URL}/x402/{safe_id}"
@@
-        if body and method.upper() in ("POST", "PUT", "PATCH"):
+        if body and method_u in ("POST", "PUT", "PATCH"):
             data = json.dumps(body).encode()
             req_headers["Content-Type"] = "application/json"
 
-        req = Request(url, data=data, headers=req_headers, method=method.upper())
+        req = Request(url, data=data, headers=req_headers, method=method_u)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py` around lines
90 - 132, The nightmarket_call function currently accepts arbitrary HTTP methods
and silently ignores a provided body unless method is POST/PUT/PATCH; update
nightmarket_call to (1) normalize method to upper() and validate it against an
allowlist (e.g., {"GET","POST","PUT","PATCH","DELETE"}) and raise a ValueError
for disallowed methods, and (2) if a body is supplied but the normalized method
is not one that supports a body (POST/PUT/PATCH), raise a ValueError indicating
incompatible body usage; keep the rest of the request construction (req_headers,
Content-Type, Request(...)) the same so the check occurs before Request
creation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py`:
- Around line 137-153: The outer exception handler is collapsing non-402
HTTPError cases into a generic exception; after the HTTPError except that
handles 402, change the branch that currently does `raise` to instead build and
return a structured response for other HTTP errors: read e.read() into raw_body
(safely decode/JSON-decode like the 402 branch), capture relevant headers
(e.headers) and status (e.code), and return a dict such as {"status": e.code,
"message": f"HTTP error {e.code}", "body": parsed_body, "headers":
dict(e.headers)} so callers get status/body/headers instead of a generic error;
update the HTTPError handler where variables `e`, `raw_body`, `payment_info`,
and `payment_header` are used to implement this.

---

Duplicate comments:
In `@src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py`:
- Around line 90-132: The nightmarket_call function currently accepts arbitrary
HTTP methods and silently ignores a provided body unless method is
POST/PUT/PATCH; update nightmarket_call to (1) normalize method to upper() and
validate it against an allowlist (e.g., {"GET","POST","PUT","PATCH","DELETE"})
and raise a ValueError for disallowed methods, and (2) if a body is supplied but
the normalized method is not one that supports a body (POST/PUT/PATCH), raise a
ValueError indicating incompatible body usage; keep the rest of the request
construction (req_headers, Content-Type, Request(...)) the same so the check
occurs before Request creation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cb24ce47-a44d-42de-aac1-50ed68cb12a2

📥 Commits

Reviewing files that changed from the base of the PR and between 2331c7e and 48c065e.

📒 Files selected for processing (2)
  • src/praisonai-agents/praisonaiagents/tools/__init__.py
  • src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py

Comment on lines +137 to +153
except HTTPError as e:
if e.code == 402:
raw_body = e.read()
try:
payment_info = json.loads(raw_body.decode()) if raw_body else {}
except (json.JSONDecodeError, UnicodeDecodeError):
payment_info = {"raw_body": raw_body.decode(errors="replace")} if raw_body else {}
payment_header = e.headers.get("PAYMENT-REQUIRED", "")
return {
"status": 402,
"message": "Payment required. Use CrowPay to authorize payment, then retry with payment_signature.",
"payment_required": payment_info,
"payment_header": payment_header,
}
raise
except Exception as e:
logger.exception(f"Nightmarket call failed: {e}")
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Return structured non-402 HTTP errors instead of collapsing to generic error.

After Line 151 re-raises, the outer handler at Line 152 strips status/body context into {"error": ...}. That makes 404/429/500 handling harder for callers.

Proposed fix
         except HTTPError as e:
             if e.code == 402:
                 raw_body = e.read()
                 try:
                     payment_info = json.loads(raw_body.decode()) if raw_body else {}
                 except (json.JSONDecodeError, UnicodeDecodeError):
                     payment_info = {"raw_body": raw_body.decode(errors="replace")} if raw_body else {}
                 payment_header = e.headers.get("PAYMENT-REQUIRED", "")
                 return {
                     "status": 402,
                     "message": "Payment required. Use CrowPay to authorize payment, then retry with payment_signature.",
                     "payment_required": payment_info,
                     "payment_header": payment_header,
                 }
-            raise
+            raw_body = e.read()
+            try:
+                error_body = json.loads(raw_body.decode()) if raw_body else {}
+            except (json.JSONDecodeError, UnicodeDecodeError):
+                error_body = {"raw_body": raw_body.decode(errors="replace")} if raw_body else {}
+            return {
+                "status": e.code,
+                "error": str(e),
+                "response": error_body,
+            }
🧰 Tools
🪛 Ruff (0.15.2)

[warning] 153-153: Redundant exception object included in logging.exception call

(TRY401)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai-agents/praisonaiagents/tools/nightmarket_tools.py` around lines
137 - 153, The outer exception handler is collapsing non-402 HTTPError cases
into a generic exception; after the HTTPError except that handles 402, change
the branch that currently does `raise` to instead build and return a structured
response for other HTTP errors: read e.read() into raw_body (safely
decode/JSON-decode like the 402 branch), capture relevant headers (e.headers)
and status (e.code), and return a dict such as {"status": e.code, "message":
f"HTTP error {e.code}", "body": parsed_body, "headers": dict(e.headers)} so
callers get status/body/headers instead of a generic error; update the HTTPError
handler where variables `e`, `raw_body`, `payment_info`, and `payment_header`
are used to implement this.

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