Sync SDK parity: add Swift A2a aliases and Python parity endpoints#190
Conversation
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughThis PR expands the Python, Rust, and Swift SDKs with a renamed ChangesDeliveryStatus Vocabulary Rename (Cross-SDK)
New API Surface: Delivery, A2A, Directory, Routing, Nodes, Triggers, Cert, Console
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d9b4b33e5e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| self._client = client | ||
|
|
||
| def invoke(self, name: str, input: dict[str, Any] | None = None) -> Any: | ||
| return self._client.post(f"/v1/actions/{_enc(name)}/invoke", {"input": input}) |
There was a problem hiding this comment.
Omit input when invoking actions without payload
When invoke() is called without an input, this sends {"input": null}. The action route validates the body with invokeActionSchema in packages/engine/src/routes/action.ts, where input is an optional record but not nullable, so the common no-argument invocation path returns 400 invalid invocation body instead of invoking the action. Build the body without the input key when it is None; the async twin has the same issue.
Useful? React with 👍 / 👎.
| return A2aAgentCard.model_validate(result) | ||
|
|
||
| def route(self, skill: str, message: str | None = None) -> RouteResult: | ||
| result = self._client.post("/v1/route", {"skill": skill, "message": message}) |
There was a problem hiding this comment.
Omit message when routing without a message
When callers use route(skill) without the optional message, this posts message: null. The /route handler validates message as an optional string with a default in packages/engine/src/routes/routing.ts, so omission is accepted but null is rejected and route('skill') returns 400 skill is required. Only include message when it is not None; the async variant has the same bug.
Useful? React with 👍 / 👎.
| return DirectoryAgent.model_validate(result) | ||
|
|
||
| def delete_directory_agent(self, slug: str) -> None: | ||
| self._client.delete(f"/v1/directory/agents/{_enc(slug)}") |
There was a problem hiding this comment.
Handle 204 responses for directory deletion
The directory delete endpoint succeeds with c.body(null, 204) in packages/engine/src/routes/directory.ts, but this new wrapper calls HttpClient.delete(), whose response handling always attempts to parse JSON. As a result, a successful delete_directory_agent() raises a JSON parse error instead of returning None; the async wrapper has the same behavior.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/sdk-python/src/relay_sdk/agent.py (1)
162-179: 💤 Low valueParameter
inputshadows Python builtin.The parameter name
inputshadows the built-ininput()function. While unlikely to cause issues in this context, renaming it improves clarity.♻️ Suggested rename
- def invoke(self, name: str, input: dict[str, Any] | None = None) -> Any: - return self._client.post(f"/v1/actions/{_enc(name)}/invoke", {"input": input}) + def invoke(self, name: str, input_data: dict[str, Any] | None = None) -> Any: + return self._client.post(f"/v1/actions/{_enc(name)}/invoke", {"input": input_data})Apply the same change to
_AsyncActionsNamespace.invoke()at line 561.Also applies to: 555-572
🤖 Prompt for 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. In `@packages/sdk-python/src/relay_sdk/agent.py` around lines 162 - 179, Rename the parameter named input in _ActionsNamespace.invoke to avoid shadowing the builtin (e.g., use input_data or payload), update its type hint from input: dict[str, Any] | None to input_data: dict[str, Any] | None, and replace all internal uses such as {"input": input} with {"input": input_data} (and likewise pass input_data to any downstream calls). Make the same change in _AsyncActionsNamespace.invoke (rename parameter and update its type hint and usage) so both synchronous and async invoke methods remain consistent.Source: Linters/SAST tools
🤖 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.
Nitpick comments:
In `@packages/sdk-python/src/relay_sdk/agent.py`:
- Around line 162-179: Rename the parameter named input in
_ActionsNamespace.invoke to avoid shadowing the builtin (e.g., use input_data or
payload), update its type hint from input: dict[str, Any] | None to input_data:
dict[str, Any] | None, and replace all internal uses such as {"input": input}
with {"input": input_data} (and likewise pass input_data to any downstream
calls). Make the same change in _AsyncActionsNamespace.invoke (rename parameter
and update its type hint and usage) so both synchronous and async invoke methods
remain consistent.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: af879e0a-6be4-40c1-bc87-e8d357092649
📒 Files selected for processing (5)
memory/workspace/.relay/state.jsonpackages/sdk-python/src/relay_sdk/agent.pypackages/sdk-python/src/relay_sdk/client.pypackages/sdk-python/src/relay_sdk/models.pypackages/sdk-python/src/relay_sdk/relay.py
Rebased #190 onto main (resolving the #188 origin-contract changes: origin_surface is gone; only origin_actor + origin_client/origin_version remain — confirmed no origin_surface references survive). Correctness fixes layered on top of #190's parity additions: - DeliveryStatus: updated the stale Literal["accepted","delivered", "deferred","failed"] to the canonical #193 enum Literal["queued","delivered","acked","failed","dead_lettered"] (packages/types/src/delivery.ts). "delivered" now means in-flight awaiting ack; "acked" is terminal success; accepted/deferred removed. - Delivery model: aligned with the canonical DeliverySchema by adding the missing fields seq, location_type, location_node_id, expires_at, delivered_at, acked_at, dead_lettered_at to match the TS SDK surface. - channels.set_topic: corrected the route from PATCH /v1/channels/{name} to PATCH /v1/channels/{name}/topic to match the TS setTopic() and the dedicated openapi endpoint (it was colliding with channels.update). - channels.invite: corrected the request body field from {"agent": ...} to {"agent_name": ...} to match InviteRequestSchema / the TS SDK wire shape (Python sends keys verbatim with no camel->snake conversion). - Updated test_channels_set_topic to assert the corrected /topic route. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
22f0e93 to
faccfb4
Compare
Add the relay-level surfaces that were missing from the Swift SDK:
- nodes namespace: list (GET /v1/nodes, capability/name filters), get
(GET /v1/nodes/{name}) with NodeRosterEntry + NodeCapability models
- triggers namespace: create/list/get/update/delete full lifecycle
(POST/GET/PATCH/DELETE /v1/triggers[/{id}]) with Trigger,
CreateTriggerRequest, UpdateTriggerRequest models
- activity feed: activity(limit) -> GET /v1/activity
- workspace-level DM queries: allDMConversations (GET
/v1/dm/conversations/all) and dmMessages (GET
/v1/dm/conversations/{id}/messages)
Fix the stale DeliveryStatus enum to the current statuses
(queued|delivered|acked|failed|dead_lettered), replacing the old
accepted/deferred values.
All routes verified present in openapi.yaml. Adds tests for nodes,
triggers, workspace DM queries, activity, and the delivery-status enum.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring the Rust SDK to 100% feature parity with the TypeScript reference
SDK. Every new route is documented in openapi.yaml.
New RelayCast surfaces:
- Workspace bootstrap: lookup_workspace (GET /v1/workspaces/by-name/{name})
- A2A: register_a2a, list_a2a_agents, remove_a2a_agent, get_a2a_agent_card
- Routing: route, route_feedback, get_routing_config, update_routing_config
- Directory: search_directory, publish_to_directory, list_directory,
get_directory_agent, update_directory_agent, delete_directory_agent,
list_directory_ratings, rate_directory_agent
- Skills: import_skills, search_skills
- Fleet nodes: list_nodes, get_node
- Triggers: create_trigger, list_triggers, get_trigger, update_trigger,
delete_trigger
- Certification: certify, get_certification, certification_badge_url,
monitor_certification
- Console: console_messages, console_stats (ConsoleOverview), console_agents,
console_costs
New AgentClient surfaces:
- channels mute_channel / unmute_channel
- invite_to_channel fixed to send documented `agent_name` body
Models: added serde structs for A2A cards/records, directory agents/skills/
ratings, routing config/weights, skill search results, node roster with
capability objects, triggers, certification runs, and console stats —
all snake_case to match the wire contract.
DeliveryStatus enum updated to the canonical lifecycle
(queued|delivered|acked|failed|dead_lettered); tests updated to match.
Adds parity tests for every new surface.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…view-sdks-for-functionality-parity
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
packages/sdk-python/src/relay_sdk/agent.py (1)
168-169: 💤 Low valueConsider renaming
inputparameter to avoid shadowing Python builtin.The parameter name
inputshadows the Python builtin function. While the scope is limited and the name is semantically appropriate for action invocation, renaming toinput_dataoraction_inputwould satisfy linters.🤖 Prompt for 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. In `@packages/sdk-python/src/relay_sdk/agent.py` around lines 168 - 169, The parameter name `input` in the invoke method shadows Python's builtin function. Rename the parameter `input` to `input_data` (or a similar alternative like `action_input`) in the method signature, and update the corresponding reference to this parameter in the dictionary being passed to `self._client.post()` to use the new parameter name.Source: Linters/SAST tools
packages/sdk-python/src/relay_sdk/relay.py (1)
200-202: 💤 Low valueInconsistent DELETE request pattern.
remove_a2a_agentusesself._client.request("DELETE", ...)while other delete methods likedelete_directory_agent(line 266) useself._client.delete(...). Consider using thedelete()helper for consistency.♻️ Suggested change for consistency
def remove_a2a_agent(self, name: str) -> RemoveA2aAgentResponse: - result = self._client.request("DELETE", f"/v1/a2a/agents/{_enc(name)}") + result = self._client.delete(f"/v1/a2a/agents/{_enc(name)}") return RemoveA2aAgentResponse.model_validate(result)Note: This assumes
delete()returns the response body. Ifdelete()returnsNoneand the endpoint returns a response body, the currentrequest()approach is correct.🤖 Prompt for 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. In `@packages/sdk-python/src/relay_sdk/relay.py` around lines 200 - 202, The remove_a2a_agent method uses self._client.request("DELETE", ...) while other delete methods like delete_directory_agent use self._client.delete(...). Replace the self._client.request("DELETE", ...) call with self._client.delete(...) to match the pattern used by other delete methods, but first verify that the delete() method returns the response body rather than None, since the code needs to pass the result to RemoveA2aAgentResponse.model_validate().packages/sdk-rust/src/relay.rs (1)
721-759: ⚡ Quick winConsolidate workspace bootstrap request handling to avoid drift.
Line 732 introduces a second standalone bootstrap request path with duplicated header/error mapping. Please extract/reuse a shared helper with
create_workspaceso future contract/header changes stay consistent in both flows.🤖 Prompt for 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. In `@packages/sdk-rust/src/relay.rs` around lines 721 - 759, The lookup_workspace function contains duplicated request handling logic (headers setup and error mapping) that already exists in create_workspace, creating a maintenance risk. Extract a shared helper function that encapsulates the common pattern of setting the standard headers (Content-Type, X-SDK-Version, X-Relaycast-Origin-Client, X-Relaycast-Origin-Version) and handling the ApiResponse error mapping logic (checking json.ok, handling 404 status, creating default ApiErrorInfo). Refactor both lookup_workspace and create_workspace to use this shared helper so that future changes to request headers or error handling remain consistent across both functions.
🤖 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 `@packages/sdk-python/src/relay_sdk/agent.py`:
- Around line 412-419: The `DeliveryStatus` enum is being assigned directly to
the query dictionary which expects string values, causing incorrect
serialization. In packages/sdk-python/src/relay_sdk/agent.py at lines 412-419 in
the sync `deliveries()` method, change `query["status"] = status` to
`query["status"] = status.value` to extract the string value from the enum.
Apply the identical fix in packages/sdk-python/src/relay_sdk/agent.py at lines
802-809 in the async `deliveries()` method by changing `query["status"] =
status` to `query["status"] = status.value`.
---
Nitpick comments:
In `@packages/sdk-python/src/relay_sdk/agent.py`:
- Around line 168-169: The parameter name `input` in the invoke method shadows
Python's builtin function. Rename the parameter `input` to `input_data` (or a
similar alternative like `action_input`) in the method signature, and update the
corresponding reference to this parameter in the dictionary being passed to
`self._client.post()` to use the new parameter name.
In `@packages/sdk-python/src/relay_sdk/relay.py`:
- Around line 200-202: The remove_a2a_agent method uses
self._client.request("DELETE", ...) while other delete methods like
delete_directory_agent use self._client.delete(...). Replace the
self._client.request("DELETE", ...) call with self._client.delete(...) to match
the pattern used by other delete methods, but first verify that the delete()
method returns the response body rather than None, since the code needs to pass
the result to RemoveA2aAgentResponse.model_validate().
In `@packages/sdk-rust/src/relay.rs`:
- Around line 721-759: The lookup_workspace function contains duplicated request
handling logic (headers setup and error mapping) that already exists in
create_workspace, creating a maintenance risk. Extract a shared helper function
that encapsulates the common pattern of setting the standard headers
(Content-Type, X-SDK-Version, X-Relaycast-Origin-Client,
X-Relaycast-Origin-Version) and handling the ApiResponse error mapping logic
(checking json.ok, handling 404 status, creating default ApiErrorInfo). Refactor
both lookup_workspace and create_workspace to use this shared helper so that
future changes to request headers or error handling remain consistent across
both functions.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: db9d69d1-90f8-4413-a875-c6012214b7b3
📒 Files selected for processing (13)
packages/sdk-python/src/relay_sdk/agent.pypackages/sdk-python/src/relay_sdk/client.pypackages/sdk-python/src/relay_sdk/models.pypackages/sdk-python/src/relay_sdk/relay.pypackages/sdk-python/tests/test_agent.pypackages/sdk-rust/src/agent.rspackages/sdk-rust/src/lib.rspackages/sdk-rust/src/relay.rspackages/sdk-rust/src/types.rspackages/sdk-rust/tests/parity.rspackages/sdk-swift/Sources/Relaycast/Models.swiftpackages/sdk-swift/Sources/Relaycast/RelayCast.swiftpackages/sdk-swift/Tests/RelaycastTests/RelaycastTests.swift
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/sdk-python/src/relay_sdk/client.py
- packages/sdk-python/src/relay_sdk/models.py
| def deliveries(self, *, status: DeliveryStatus | None = None, limit: int | None = None) -> list[DeliveryItem]: | ||
| query: dict[str, str] = {} | ||
| if status: | ||
| query["status"] = status | ||
| if limit is not None: | ||
| query["limit"] = str(limit) | ||
| result = self.client.get("/v1/deliveries", query or None) | ||
| return [DeliveryItem.model_validate(d) for d in result] |
There was a problem hiding this comment.
Type mismatch: DeliveryStatus enum assigned where str is expected. Both sync and async deliveries() methods assign the DeliveryStatus enum directly to the query dictionary, which expects string values. This will serialize incorrectly (e.g., "DeliveryStatus.queued" instead of "queued").
packages/sdk-python/src/relay_sdk/agent.py#L412-L419: Changequery["status"] = statustoquery["status"] = status.valuein syncdeliveries().packages/sdk-python/src/relay_sdk/agent.py#L802-L809: Apply the same fix in asyncdeliveries().
📍 Affects 1 file
packages/sdk-python/src/relay_sdk/agent.py#L412-L419(this comment)packages/sdk-python/src/relay_sdk/agent.py#L802-L809
🤖 Prompt for 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.
In `@packages/sdk-python/src/relay_sdk/agent.py` around lines 412 - 419, The
`DeliveryStatus` enum is being assigned directly to the query dictionary which
expects string values, causing incorrect serialization. In
packages/sdk-python/src/relay_sdk/agent.py at lines 412-419 in the sync
`deliveries()` method, change `query["status"] = status` to `query["status"] =
status.value` to extract the string value from the enum. Apply the identical fix
in packages/sdk-python/src/relay_sdk/agent.py at lines 802-809 in the async
`deliveries()` method by changing `query["status"] = status` to `query["status"]
= status.value`.
Motivation
Description
registerA2a,listA2aAgents,removeA2aAgent,getA2aAgentCard) inRelayCast.swiftso Swift exposes TypeScript-styleA2amethod names while preserving existingA2Amethods.putsupport to HTTP clients inclient.pyto match TypeScriptPUTusage.register_a2a,list_a2a_agents,remove_a2a_agent,get_a2a_agent_card), routing, directory, skills, ratings, and routing config update endpoints inrelay.py.deliveries,ack_delivery,fail_delivery,defer_delivery), an actions namespace for invocation (actions.invoke,actions.complete_invocation,actions.get_invocation), channel update/mute/unmute helpers, andpostconvenience method (with async counterparts) inagent.py.models.py.sdk-swift-parity-a2a-aliases(Swift) andsdk-python-ts-parity(Python) and PR records were created.Testing
swift testinpackages/sdk-swiftand all Swift unit tests passed.python -m compileall srcand an import/parity smoke check succeeded for the modified Python surfaces.python -m pytest/uv run pytestfor Python tests but automated test runs were blocked by missing dev dependencies in the execution environment and a PyPI download failure forpackaging==26.0, so full Python test suite could not be executed.Codex Task