Skip to content

feat(nodes): add tool_insforge — InsForge backend agent-tool node - #1714

Open
chinesepowered wants to merge 2 commits into
rocketride-org:developfrom
chinesepowered:feat/RR-1711-tool-insforge
Open

feat(nodes): add tool_insforge — InsForge backend agent-tool node#1714
chinesepowered wants to merge 2 commits into
rocketride-org:developfrom
chinesepowered:feat/RR-1711-tool-insforge

Conversation

@chinesepowered

@chinesepowered chinesepowered commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Closes #1711

What

Adds tool_insforge, an agent-tool node for InsForge — an open-source backend platform (Postgres + auth + storage + edge functions + realtime). Binds to an agent's tool channel like the other tool_* nodes; no data lanes.

Why this isn't a services.insforge.json preset

The obvious question first, since this repo already set the precedent. db_postgres handles Supabase as a branded preset:

// nodes/src/nodes/db_postgres/services.supabase.json
// Reuses the PostgreSQL implementation, Supabase is managed Postgres,
// so this is a branded preset, not a separate node (no duplicated code).

That works because Supabase hands out a real Postgres connection string. A managed InsForge project documents no direct connection — only a PostgREST-style REST API (GET/POST/PATCH/DELETE /api/database/records/{table}, POST /api/database/rpc/{fn}, Bearer auth). There is nothing for psycopg to dial, so the preset route isn't available.

Self-hosted deployments that do expose their own Postgres are already served by db_postgres / store_postgres; the README says so, and points there for bulk pipeline work.

Tools

Read (always available): records_select, storage_list_buckets, storage_list_objects, storage_get_download_url

Write (require allow_writes): records_insert, records_upsert, records_update, records_delete, rpc_call, storage_delete_object

Database tools return a uniform {count, rows, query} envelope so an agent sees one contract regardless of which it called.

Safety

  • Read-only by default. allow_writes ships off, so binding this node to an agent cannot change the backend until an operator opts in. rpc_call is gated with the writes because nothing in a function's name reveals whether it mutates.
  • No unfiltered writes. records_update / records_delete refuse to run without at least one filter — PostgREST applies an empty filter set to every row, so an omitted filter would rewrite or empty the whole table.
  • Path-safe identifiers. Table / bucket / function names must be bare identifiers; object keys may be path-like but reject .. segments and are escaped per segment. No agent-supplied value can redirect a request off the configured host.
  • Filters validated against the documented PostgREST operator set rather than passed through as arbitrary query parameters.
  • Inserts are not retried — a retried insert would silently duplicate rows.
  • Storage downloads return a URL, not file bytes.

Shared helper change

Adds request_with_retry to ai.common.utils.http_retry for PATCH / DELETE / PUT, which this REST surface needs and which post_with_retry / get_with_retry didn't cover. The module already factored out _retrying / _is_retryable for exactly this kind of reuse.

Purely additive — the existing helpers are untouched. Their 11 tests still pass, and 7 new ones cover the addition.

Testing

  • pytest nodes/test/test_tool_insforge.py74 passed (services.json schema, base-URL normalisation, filter grammar, path-traversal guards, write gating, unfiltered-write refusal, config/env resolution)
  • pytest packages/ai/tests/ai/common/utils/test_http_retry.py18 passed (11 pre-existing + 7 new)
  • ruff check / ruff format --check — clean across all touched files

Tests follow the test_tool_n8n.py convention: inject import stubs only for modules not already present, then remove them so nothing leaks into the shared session.

Deliberately out of scope

  • File upload. The current API negotiates uploads through a multi-step upload-strategy handshake whose response shape I could not verify against a live project, and the single-call PUT alternative is deprecated upstream. Left out rather than guessed at — happy to add it if someone with a project can confirm the contract.
  • Vector / pgvector search. InsForge runs pgvector in-database but exposes no vector or embedding REST endpoints; a store_insforge would have to go through the admin raw-SQL endpoint, which isn't appropriate for a pipeline node.
  • The AI / model gateway. Deprecated upstream in favour of calling OpenRouter directly, and llm_openai_api already presets arbitrary OpenAI-compatible endpoints.
  • Auth, payments, messaging, analytics. No clear agent or pipeline use case yet.

Notes for review

  • I have not run this against a live InsForge project. Endpoint paths, headers and the Prefer semantics come from the published REST docs; the tests cover the node's own logic, not the wire contract. Worth a sanity check from anyone with an account.
  • The icon is an original geometric mark, not InsForge's brand logo — it's monochrome, so the build auto-tints it via currentColor. Swap it for the official asset if the project would rather ship that.
  • docs/README-nodes.md is updated by hand per its own note. The header's service count was already stale before this change, so I left it alone rather than half-correct it.

Summary by CodeRabbit

  • New Features

    • Added an InsForge agent tool for querying database records, invoking RPC, and accessing storage (including download URLs).
    • Introduced safe, configurable write access—mutations are disabled by default and protected by filter requirements.
    • Added a generalized HTTP helper with consistent retry/backoff behavior for additional HTTP methods.
  • Documentation

    • Added complete documentation for the InsForge tool, including configuration, supported operations, and safety constraints.
  • Tests

    • Added coverage for URL normalization, filter/path validation, write gating, and the new retry helper behavior.

Exposes an InsForge project's REST API to agents: PostgREST-style record
select / insert / upsert / update / delete, database function (RPC)
calls, and storage bucket listing, download URLs and object deletes.

InsForge cannot be handled the way db_postgres handles Supabase. That
preset works because Supabase hands out a real Postgres connection
string; a managed InsForge project documents no direct connection, only
a PostgREST-style REST API, so there is nothing for psycopg to dial.
Self-hosted deployments that do expose their own Postgres are already
served by db_postgres and store_postgres.

Safety properties:

  * Read-only by default. Every mutating tool is gated behind an
    allow_writes switch that ships off, so binding this node to an agent
    cannot change the backend until an operator opts in. rpc_call is
    gated with them since nothing in a function's name reveals whether
    it mutates.
  * records_update and records_delete refuse to run without a filter.
    PostgREST applies an empty filter set to every row, so an omitted
    filter would rewrite or empty the whole table.
  * Table, bucket and function names must be bare identifiers; object
    keys may be path-like but reject '..' segments and are escaped per
    segment, so no agent-supplied value can redirect a request off the
    configured host.
  * Filters are validated against the documented PostgREST operator set
    rather than passed through as arbitrary query parameters.
  * Inserts bypass the retry helper, since a retried insert would
    silently duplicate rows.
  * Storage downloads return a URL rather than file bytes.

Adds request_with_retry to the shared http_retry helper for PATCH /
DELETE / PUT, which the REST surface needs and which post_with_retry and
get_with_retry did not cover. Purely additive: the existing helpers are
untouched and their tests still pass.

File upload is deliberately not implemented — the current API negotiates
uploads through a multi-step upload-strategy handshake whose response
shape could not be verified against a live project, and the single-call
alternative is deprecated upstream.
@github-actions github-actions Bot added docs Documentation module:nodes Python pipeline nodes module:ai AI/ML modules labels Jul 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor
🤖 Internal: Discord sync marker

Auto-managed by the Discord notification workflow. Stores the linked Discord message ID and forum thread ID. Do not edit or delete.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: adc85bdd-e3b1-4584-ad36-6c526352e974

📥 Commits

Reviewing files that changed from the base of the PR and between 061846a and 8cf5ee3.

📒 Files selected for processing (5)
  • nodes/src/nodes/tool_insforge/IInstance.py
  • nodes/src/nodes/tool_insforge/README.md
  • nodes/src/nodes/tool_insforge/insforge_client.py
  • nodes/src/nodes/tool_insforge/requirements.txt
  • nodes/test/test_tool_insforge.py

📝 Walkthrough

Walkthrough

Adds the tool_insforge agent-tool node, exposing InsForge database and storage REST operations with configuration resolution, read-only write gating, input/path validation, structured outputs, documentation, service registration, and tests. It also exports a generalized HTTP retry helper for idempotent methods.

Changes

InsForge agent tool

Layer / File(s) Summary
Generalized HTTP retry support
packages/ai/src/ai/common/utils/http_retry.py, packages/ai/src/ai/common/utils/__init__.py, packages/ai/tests/ai/common/utils/test_http_retry.py
Adds and exports request_with_retry for arbitrary HTTP methods, with retry tests for PATCH, DELETE, and PUT.
InsForge client and request contracts
nodes/src/nodes/tool_insforge/insforge_client.py, nodes/src/nodes/tool_insforge/requirements.txt
Adds URL, identifier, object-key, filter, limit, authentication, retry, error-mapping, parsing, and row-envelope helpers for InsForge REST calls.
Tool configuration and operations
nodes/src/nodes/tool_insforge/IGlobal.py, nodes/src/nodes/tool_insforge/IInstance.py
Adds credential resolution and lifecycle state, database CRUD/RPC tools, storage tools, write gating, and validation for unsafe or incomplete requests.
Registration, documentation, and validation
nodes/src/nodes/tool_insforge/services.json, nodes/src/nodes/tool_insforge/__init__.py, nodes/src/nodes/tool_insforge/README.md, nodes/test/test_tool_insforge.py, docs/README-nodes.md
Registers and documents the tool, exports its interfaces, adds the Agent Tools catalog entry, and tests configuration, client helpers, validation, write gating, and initialization.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Agent
  participant IInstance
  participant insforge_client
  participant InsForgeREST
  Agent->>IInstance: Invoke an InsForge tool
  IInstance->>insforge_client: Validate inputs and build request
  insforge_client->>InsForgeREST: Send authenticated REST request
  InsForgeREST-->>insforge_client: Return response
  insforge_client-->>IInstance: Parse result or error
  IInstance-->>Agent: Return structured output
Loading

Possibly related PRs

Suggested reviewers: jmaionchi, rod-christensen, stepmikhaylov

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Core node, safety, and retry requirements are met, but the linked scope includes storage upload support and this PR does not add it. Add the storage upload tool, or document and reconcile its exclusion with the linked issue before merging.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise, specific, and matches the main change: introducing the tool_insforge agent-tool node.
Out of Scope Changes check ✅ Passed The extra docs, tests, and shared retry helper all support the InsForge node and match the stated objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@nodes/src/nodes/tool_insforge/IInstance.py`:
- Around line 371-374: Update the storage_list_objects limit schema description
to interpolate DEFAULT_LIMIT and MAX_LIMIT rather than hardcoding 100 and 1000.
Import DEFAULT_LIMIT alongside MAX_LIMIT from .insforge_client, matching the
existing records_select schema pattern.

In `@nodes/src/nodes/tool_insforge/insforge_client.py`:
- Around line 280-303: Broaden the exception handling in the request flow around
get_with_retry, requests.post, and request_with_retry by adding a fallback for
requests.exceptions.RequestException after the specific HTTPError, Timeout, and
ConnectionError handlers. Translate other request-related failures into the
documented actionable ValueError instead of allowing raw transport exceptions to
escape.

In `@nodes/src/nodes/tool_insforge/README.md`:
- Around line 90-96: Update the HTTP failure mapping section to qualify the
429/5xx retry behavior by naming the retryable methods, consistent with the
exception documented near line 83. Explicitly state that POST failures are
surfaced without retry, while preserving the existing exponential-backoff
behavior for supported non-POST methods.
- Around line 67-73: Align the README filter grammar with the validation
performed by encode_filters: either add operator-specific validation so in
requires parenthesized set values and other operators reject unsupported
comma-separated forms, or revise the documentation to describe the broader
grammar currently accepted. Ensure the documented rejection behavior and
examples accurately match the implemented validation.

In `@nodes/src/nodes/tool_insforge/requirements.txt`:
- Line 1: Update the dependency declaration in requirements.txt from unpinned
requests to the minimum safe constraint requests>=2.34.2.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b97be456-60b7-400a-a753-7da2ac16230b

📥 Commits

Reviewing files that changed from the base of the PR and between 1483794 and 061846a.

⛔ Files ignored due to path filters (1)
  • nodes/src/nodes/tool_insforge/insforge.svg is excluded by !**/*.svg
📒 Files selected for processing (12)
  • docs/README-nodes.md
  • nodes/src/nodes/tool_insforge/IGlobal.py
  • nodes/src/nodes/tool_insforge/IInstance.py
  • nodes/src/nodes/tool_insforge/README.md
  • nodes/src/nodes/tool_insforge/__init__.py
  • nodes/src/nodes/tool_insforge/insforge_client.py
  • nodes/src/nodes/tool_insforge/requirements.txt
  • nodes/src/nodes/tool_insforge/services.json
  • nodes/test/test_tool_insforge.py
  • packages/ai/src/ai/common/utils/__init__.py
  • packages/ai/src/ai/common/utils/http_retry.py
  • packages/ai/tests/ai/common/utils/test_http_retry.py

Comment thread nodes/src/nodes/tool_insforge/IInstance.py
Comment thread nodes/src/nodes/tool_insforge/insforge_client.py
Comment thread nodes/src/nodes/tool_insforge/README.md Outdated
Comment thread nodes/src/nodes/tool_insforge/README.md Outdated
Comment thread nodes/src/nodes/tool_insforge/requirements.txt Outdated
- encode_filters now validates the two operator-specific shapes it documents:
  'in' requires a parenthesised set and 'is' one of null/true/false/unknown.
  "in.1,2,3" is the mistake a model actually makes, and PostgREST answers it
  with a 400 that names no column.
- call() catches requests.exceptions.RequestException as a fallback, so the
  rarer transport failures reach the agent as a reportable message instead of
  a raw traceback.
- README: qualify the 429/5xx retry claim, which omitted that POST is exempt,
  and describe what the filter validation actually checks. Same correction in
  the call() docstring.
- storage_list_objects interpolates DEFAULT_LIMIT/MAX_LIMIT rather than
  restating 100/1000.
- Pin requests>=2.34.2, matching tool_github, tool_n8n and tool_cognee.
- Test stubs model requests' exception hierarchy instead of aliasing the
  classes to a flat Exception, which let a fallback handler swallow cases the
  specific handlers own.
@chinesepowered

Copy link
Copy Markdown
Contributor Author

Agreed with all five. Fixed in 8cf5ee3.

Filter grammar vs. validation (README) — right, and the gap was worth closing in code rather than in prose. encode_filters checked the column, the operator, and that a value was present, but not the two operator-specific shapes the README advertises. It now rejects in without a parenthesised set and is with anything other than null/true/false/unknown. in.1,2,3 is the form a model actually produces, and PostgREST answers it with a 400 that names no column, so the local error is the more useful one.

I left eq.1,2,3 alone — a comma is a legal value for eq, so rejecting it would break valid filters. The README now says which parts are checked locally and that other values are Postgres' to interpret, so the claim matches the code either way.

POST retry exemption (README) — correct, the bullet was unqualified. It now names the retried methods and states the POST exemption, cross-referencing the Safety section. The same unqualified claim was in the call() docstring; fixed there too.

RequestException fallback — taken. ChunkedEncodingError and InvalidURL were reaching the agent as raw tracebacks. The catch is last, so the specific handlers keep their cases.

Writing the test for it surfaced something in the test file: the import stubs aliased HTTPError, Timeout and RequestException all to bare Exception, so under a bare interpreter the first except matched everything and except-ordering wasn't modelled at all. The stubs now mirror requests' real hierarchy.

DEFAULT_LIMIT/MAX_LIMIT interpolation and requests>=2.34.2 — both taken as suggested. The pin matches tool_github, tool_n8n and tool_cognee.

83 tests pass (was 74), ruff clean.

@chinesepowered

Copy link
Copy Markdown
Contributor Author

On the inconclusive Linked Issues check: the icon is present and committed — nodes/src/nodes/tool_insforge/insforge.svg, tracked since the first commit and referenced from services.json. CodeRabbit path-filters SVGs out of review, so it could not see the file rather than finding it missing. git ls-files nodes/src/nodes/tool_insforge/ lists all eight files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation module:ai AI/ML modules module:nodes Python pipeline nodes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(nodes): add tool_insforge — InsForge backend agent-tool node

1 participant