Skip to content

Add the domain tools to the Python toolkit - #76

Open
josephfeleke wants to merge 2 commits into
mainfrom
joseph/domain-tools-python
Open

josephfeleke wants to merge 2 commits into
mainfrom
joseph/domain-tools-python

Conversation

@josephfeleke

@josephfeleke josephfeleke commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Why

The Python toolkit should offer the same domain tools as the Node toolkit, so an agent built on the OpenAI Agents SDK, LangChain or LiveKit can set up a custom domain over the API too.

What

  • list_domains, get_domain, create_domain, get_domain_setup_link and verify_domain, placed after delete_inbox as in Node.
  • The descriptions match the Node toolkit, with the Python field names (conflicting_provider, provider_name).
  • create_domain does not offer allow_conflicting_provider, for the reason given in Add create_domain #74. It parses its arguments, so the OpenAI and LiveKit adapters, which pass raw arguments, cannot send it either.
  • As with the other Python tools, results are the SDK models, and verify_domain returns None, which the adapters turn into "OK".
  • The OpenAI adapter's strict schema makes the model send every key, with null for the flags it leaves unset. The SDK sends those as null, and the API reads a null in a create body as unset (asInputSchema), so the API defaults still apply.

Test

uv run pytest (68 passed). tests/test_domains.py runs the real Python SDK over httpx.MockTransport, so a wrong method or keyword name fails the way it would against the API:

  • the catalog position, each tool's permission wording, and snake_case field names in every description
  • params: required fields, and each flag's stated default
  • the method, path, query and body each tool sends, including false flags; allow_conflicting_provider is never sent; a create without a domain makes no request
  • the OpenAI adapter: strict schema, a create with every key present as null (no flag gets a value the model did not choose), the setup link URL, "OK" for verify, and the 422 conflict raised with the provider named
  • LangChain and LiveKit: only the flags the model set carry a value, an override the model was not offered is dropped, a refused verify is an error message, a 404 is a ToolError
  • create, link, verify, then get_domain until VERIFIED

Each rule was broken on purpose and confirmed to fail at least one test: raw arguments passed to the SDK, false flags dropped, the stop condition removed, verify and the setup link calling get, the page token dropped, a camelCase field in a description, the wrong permission named, and a flag made required.

list_domains, get_domain, create_domain, get_domain_setup_link and
verify_domain, with the same descriptions as the Node toolkit, using
the Python field names.
return next(t for t in tools if t.name == name)


def test_domain_tools_sit_after_delete_inbox_and_before_the_thread_tools():

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Brittle position test (nit). This pins the exact span between delete_inbox and list_threads, so any tool inserted there later fails the test without a defect. Suggest asserting the five names are present and contiguous.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Keeping the exact span. The order is what the model sees, and this test is what keeps the Python order the same as Node, where tests/domains.test.ts pins the same span. A tool added there later should update both on purpose.

assert "HTTP 422" in str(raised.value)


def test_langchain_create_domain_passes_the_flags_through():

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

LangChain nulls not pinned (nit). LangChain fills omitted optional fields with None, so the flags the model left out reach the API as null. That is safe only because the API reads a create-body null as unset. The OpenAI test pins this, the LangChain one does not. Suggest asserting the non-null part of the body is exactly what the model set.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in bf41c5d: the test now asserts that the non-null part of the body is exactly {domain, subdomains_enabled}, as the OpenAI test does.

ThreadIdField = Annotated[str, Field(description="ID of thread")]
MessageIdField = Annotated[str, Field(description="ID of message")]
AttachmentIdField = Annotated[str, Field(description="ID of attachment")]
DomainIdField = Annotated[str, Field(description="ID of domain (from list_domains or create_domain)")]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Field description (nit). get_domain also returns the ID, not only list_domains and create_domain.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Leaving as is. The description names where an agent gets an ID it does not have yet. Calling get_domain already needs the ID, so it is not a source.

func=delete_inbox,
),
Tool(
name="list_domains",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Observation, not a defect. list_domains uses ListItemsParams, so the SDK's ascending is not exposed. That matches list_inboxes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed, a deliberate match with list_inboxes. No change.

… setup and verify guidance

create_domain no longer offers allow_conflicting_provider, and parses its
arguments so the OpenAI and LiveKit adapters, which pass raw arguments,
cannot send it either. The setup link and verify_domain descriptions
match the Node wording. The LangChain create test now checks that only
the flags the model set carry a value.
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