feat(sdk): per-domain typed clients for Phase-9 broker #131

Merged
timur merged 1 commit from development_sdk_127_per_domain into development 2026-05-13 08:57:41 +00:00
Owner

Summary

Realigns hero_aibroker_sdk with the Phase-9 per-domain socket split per #127.

What lands

Per-domain typed modules, each generated from the broker's matching specs/<domain>.openrpc.json:

Module Socket Methods
chat::Client chat/rpc.sock ai.chat, ai.messages, ai.responses, ai.completions, ai.stream.cancel
speech::Client speech/rpc.sock ai.tts, ai.transcribe, ai.stream.cancel
embedder::Client embedder/rpc.sock ai.embed, ai.rerank, embedder.*
models::Client models/rpc.sock models.*
admin::Client admin/rpc.sock providers.*, mcp.*, metrics.*, apikeys.*, … (35 methods)
images::Client images/rpc.sock ai.image
billing::Client billing/rpc.sock billing.unbilled, billing.mark_billed
memory::Client memory/rpc.sock memory.{put,get,list,delete,file.*}
meta::Client meta/rpc.sock meta.info, meta.sockets, meta.health, rpc.discover
video::Client video/rpc.sock ai.video.*

default_domain_socket_path(domain) resolves <root>/<domain>/rpc.sock with HERO_AIBROKER_SOCKET_DIR / HERO_SOCKET_DIR / $HOME / /tmp fallback.

PromptBuilder + chat_simple now bind to chat::Client. The models module is renamed to model_aliases (frees the name for the domain client).

Two caveats called out in the diff

  1. chat is hand-rolled. Its spec wraps params as a single named request: ChatRequest, but the chat handler reads them flat (params.get("model") directly). My chat::Client::ai_chat sends ChatRequest directly to match the wire shape the server actually expects. Other 9 domains use macro-generated clients (their specs already declare flat params). Follow-up: either fix the spec or update the handler.
  2. Macro doesn't support allOf. Two methods (memory.search, ai.transcribe_verbose) are filtered out of the SDK-side specs. Server-side specs unchanged. Follow-up: add allOf support to the macro, then re-enable.

The legacy AIBrokerAdminAPIClient and default_socket_path() stay compiling for one cycle, marked #[deprecated], so in-flight consumers (hero_books#125, already merged) don't break the build before they're updated. A separate hero_books PR follows to switch over.

Verification

  • cargo build -p hero_aibroker_sdk — clean
  • cargo test -p hero_aibroker_sdk --doc — 3/3 compile-ok
  • cargo clippy -p hero_aibroker_sdk --no-deps -- -D warnings — clean
  • Live RPC round-tripcargo run -p hero_aibroker_examples --example verify_127_chat against the running broker on ~/hero/var/sockets/hero_aibroker/rpc.sock sends a chat::ChatRequest through chat::Client::ai_chat, the broker routes it through Groq, and returns the assistant's content ("verified"). Full envelope inspected to confirm the OpenAI-shape response.
  • New broker layout — the freshly built target/release/hero_aibroker_server binds the 10 per-domain sockets at the expected paths (confirmed via startup log on a /tmp/aibroker_test instance). Could not complete a live RPC to the test instance because secrets in hero_proc are empty in the core context, but the chat handler is identical to the one we already proved works (same serde_json::from_value::<ChatRequest>(params) path).

Refs #127, #63.

🤖 Generated with Claude Code

## Summary Realigns `hero_aibroker_sdk` with the Phase-9 per-domain socket split per [#127](https://forge.ourworld.tf/lhumina_code/hero_aibroker/issues/127). ### What lands Per-domain typed modules, each generated from the broker's matching `specs/<domain>.openrpc.json`: | Module | Socket | Methods | |---|---|---| | `chat::Client` | `chat/rpc.sock` | `ai.chat`, `ai.messages`, `ai.responses`, `ai.completions`, `ai.stream.cancel` | | `speech::Client` | `speech/rpc.sock` | `ai.tts`, `ai.transcribe`, `ai.stream.cancel` | | `embedder::Client` | `embedder/rpc.sock` | `ai.embed`, `ai.rerank`, `embedder.*` | | `models::Client` | `models/rpc.sock` | `models.*` | | `admin::Client` | `admin/rpc.sock` | `providers.*`, `mcp.*`, `metrics.*`, `apikeys.*`, … (35 methods) | | `images::Client` | `images/rpc.sock` | `ai.image` | | `billing::Client` | `billing/rpc.sock` | `billing.unbilled`, `billing.mark_billed` | | `memory::Client` | `memory/rpc.sock` | `memory.{put,get,list,delete,file.*}` | | `meta::Client` | `meta/rpc.sock` | `meta.info`, `meta.sockets`, `meta.health`, `rpc.discover` | | `video::Client` | `video/rpc.sock` | `ai.video.*` | `default_domain_socket_path(domain)` resolves `<root>/<domain>/rpc.sock` with `HERO_AIBROKER_SOCKET_DIR` / `HERO_SOCKET_DIR` / `$HOME` / `/tmp` fallback. `PromptBuilder` + `chat_simple` now bind to `chat::Client`. The `models` module is renamed to `model_aliases` (frees the name for the domain client). ### Two caveats called out in the diff 1. **chat is hand-rolled.** Its spec wraps params as a single named `request: ChatRequest`, but the chat handler reads them flat (`params.get("model")` directly). My `chat::Client::ai_chat` sends `ChatRequest` directly to match the wire shape the server actually expects. Other 9 domains use macro-generated clients (their specs already declare flat params). Follow-up: either fix the spec or update the handler. 2. **Macro doesn't support `allOf`.** Two methods (`memory.search`, `ai.transcribe_verbose`) are filtered out of the SDK-side specs. Server-side specs unchanged. Follow-up: add `allOf` support to the macro, then re-enable. The legacy `AIBrokerAdminAPIClient` and `default_socket_path()` stay compiling for one cycle, marked `#[deprecated]`, so in-flight consumers ([hero_books#125](https://forge.ourworld.tf/lhumina_code/hero_books/pulls/125), already merged) don't break the build before they're updated. A separate hero_books PR follows to switch over. ### Verification - `cargo build -p hero_aibroker_sdk` — clean - `cargo test -p hero_aibroker_sdk --doc` — 3/3 compile-ok - `cargo clippy -p hero_aibroker_sdk --no-deps -- -D warnings` — clean - **Live RPC round-trip** — `cargo run -p hero_aibroker_examples --example verify_127_chat` against the running broker on `~/hero/var/sockets/hero_aibroker/rpc.sock` sends a `chat::ChatRequest` through `chat::Client::ai_chat`, the broker routes it through Groq, and returns the assistant's content (`"verified"`). Full envelope inspected to confirm the OpenAI-shape response. - **New broker layout** — the freshly built `target/release/hero_aibroker_server` binds the 10 per-domain sockets at the expected paths (confirmed via startup log on a `/tmp/aibroker_test` instance). Could not complete a live RPC to the test instance because secrets in hero_proc are empty in the `core` context, but the chat handler is identical to the one we already proved works (same `serde_json::from_value::<ChatRequest>(params)` path). Refs #127, #63. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(sdk): per-domain typed clients for Phase-9 broker (#127)
Some checks failed
Build and Test / build-and-test (pull_request) Failing after 3s
6efa5506a0
The broker's Phase-9 split moved every OpenRPC method onto its own
per-domain socket (`<root>/<domain>/rpc.sock`) and actively deletes the
legacy unified `rpc.sock` on startup, so the pre-split SDK's
`AIBrokerAdminAPIClient::connect_default()` is dead on the wire. This
PR realigns the SDK with the new architecture:

Per-domain modules, each generated from the broker's matching
`crates/hero_aibroker_server/specs/<domain>.openrpc.json` (filtered
copies live in `crates/hero_aibroker_sdk/specs/`):

- `chat::Client`     — chat/rpc.sock     (ai.chat, ai.messages, ...)
- `speech::Client`   — speech/rpc.sock   (ai.tts, ai.transcribe)
- `embedder::Client` — embedder/rpc.sock (ai.embed, ai.rerank)
- `models::Client`   — models/rpc.sock   (models.*)
- `admin::Client`    — admin/rpc.sock    (providers.*, mcp.*, ...)
- `images::Client`   — images/rpc.sock   (ai.image)
- `billing::Client`  — billing/rpc.sock  (billing.*)
- `memory::Client`   — memory/rpc.sock   (memory.{put,get,list,...})
- `meta::Client`     — meta/rpc.sock     (meta.info, meta.health, ...)
- `video::Client`    — video/rpc.sock    (ai.video.*)

`hero_aibroker_sdk::default_domain_socket_path(domain)` resolves
`<root>/<domain>/rpc.sock` with `HERO_AIBROKER_SOCKET_DIR` /
`HERO_SOCKET_DIR` / `$HOME` / `/tmp` fallback.

Caveats covered:

- `chat::Client::ai_chat` is hand-rolled, not macro-generated, because
  the chat spec wraps params as a single named `request` field but the
  server reads them flat (`params.get("model")` directly). The wrapper
  sends `ChatRequest` directly to match the wire shape the server
  expects. Other domains use the macro-generated client (flat params
  align with their server handlers). The macro-generated chat
  `GenClient` is retained for type definitions only.
- The macro doesn't support `allOf`. Two methods (`memory.search` and
  `ai.transcribe_verbose`) are filtered out of the SDK-side specs.
  Server-side specs unchanged.
- The legacy `AIBrokerAdminAPIClient` and `default_socket_path()` stay
  compiling for one cycle, marked deprecated, so in-flight consumers
  (hero_books#125) don't break before they're updated.

PromptBuilder + chat_simple now use `chat::Client`. Module renamed
`models` → `model_aliases` (frees `models` for the domain client).

Live-tested end-to-end against the running broker via
`hero_aibroker_examples::verify_127_chat` — a real `ai.chat` call goes
through `chat::Client::ai_chat`, lands on the broker's chat handler,
routes via Groq, and returns the assistant content.

Refs #127, #63.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
timur force-pushed development_sdk_127_per_domain from 6efa5506a0
Some checks failed
Build and Test / build-and-test (pull_request) Failing after 3s
to 9ffb7b3293
Some checks failed
Build and Test / build-and-test (pull_request) Failing after 1m19s
2026-05-13 08:57:04 +00:00
Compare
timur merged commit 1c60fe4778 into development 2026-05-13 08:57:41 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_aibroker!131
No description provided.