Dual-host SDK routing for /send-mail#59
Conversation
Greptile SummaryThis PR introduces dual-host routing across all three SDKs (Node, Python, Go) so
Confidence Score: 5/5Safe to merge — the routing change is well-isolated behind wrapper methods, the credential migration is graceful, and existing test suites cover the key paths. The dual-host split is contained to constructor internals and single send-path call sites; no operation-visible behavior changes for callers who use the documented surface. Pre-dual-host credential migration auto-cleans with a clear user notice rather than hard-failing. Back-compat aliases are in place in Go and Python. The only open item is two exported helpers in api-command.ts that are never called, which does not affect runtime behavior. sdk-node/src/oclif/api-command.ts — the exported resolveCliAuthFromFlags and baseUrlOverriddenFromFlags helpers are currently dead code; worth clarifying if they are intended for future wiring or can be removed. Important Files Changed
Reviews (4): Last reviewed commit: "Reject legacy base_url kwarg on Primitiv..." | Re-trigger Greptile |
The /send-mail endpoint is served by an attachments-supporting host (api.primitive.dev/v1, Cloudflare Worker, ~30 MiB raw body cap), distinct from the primary API host that everything else uses (primitive.dev/api/v1, Vercel, 4.5 MB body cap). This PR makes all three SDKs route /send-mail to the attachments host automatically so callers can send messages with attachments without having to think about the host split. OpenAPI spec: - Adds the attachments-supporting host as a second top-level servers entry. - Marks /send-mail with a per-operation servers list that names the attachments host first. The two server entries are documentation; the routing is enforced by the hand-written SDK wrappers. sdk-node: - PrimitiveApiClient now holds two underlying generated clients. The existing .client property targets host 1 unchanged; a new ._sendClient property targets host 2. The PrimitiveClient.send wrapper passes ._sendClient to the generated sendEmail under the hood. - Constructor opts apiBaseUrl1 and apiBaseUrl2 (defaults to the production hosts). Override exists for staging/local testing and is intentionally not documented to customers. - CLI commands: --base-url flag and PRIMITIVE_API_URL env removed. Replaced by hidden --api-base-url-1 / --api-base-url-2 and PRIMITIVE_API_BASE_URL_1 / _2 envs. Fish completion no longer advertises the override flags. - HOST_2_OPERATIONS set in api-command.ts dispatches the auto-generated sending:send-email command through the host-2 client. As more endpoints migrate to host 2 over time, add their sdkName to the set. sdk-python: - PrimitiveClient now constructs api_client and api_send_client (both AuthenticatedClient). The send / asend / forward / aforward wrappers route through api_send_client; reply stays on api_client. Constructor takes api_base_url_1 / api_base_url_2. - Test helpers install MockTransport on both clients so the existing send-routing tests still capture requests. sdk-go: - Top-level Client now holds two underlying ogen clients. NewClient builds both with the same auth and production defaults. NewClientWithOptions accepts a ClientOptions struct with APIBaseURL1 / APIBaseURL2 overrides. NewClientFromAPI keeps a single-client shape for tests. - The Send method routes to the host-2 client; Reply stays on host 1. Migration story: when we move another endpoint to host 2, add the sdkName to HOST_2_OPERATIONS in sdk-node, switch the relevant wrapper in sdk-python and sdk-go to api_send_client, and add the per-op servers override to the OpenAPI spec. The Vercel host can either keep serving the moved endpoint for back-compat or return a 410 with a migration hint, decided per endpoint. Override notes: PRIMITIVE_API_BASE_URL_1 and PRIMITIVE_API_BASE_URL_2 (env), apiBaseUrl1 / apiBaseUrl2 (TS), api_base_url_1 / api_base_url_2 (Python), and ClientOptions.APIBaseURL1 / APIBaseURL2 (Go) are deliberately undocumented in customer-facing material. They exist for internal staging/local testing only; production defaults are correct and should not be overridden by customers.
Post-dual-host the shallow copy shares both underlying clients, not just api_client; the docstring only mentioned the latter and could mislead readers wondering whether the send-host client is cloned. P2 from Greptile on PR #59.
PR #59 renames the saved credential field from base_url to api_base_url_1. Before this change, every CLI command for an already-logged-in user would hard-fail on upgrade with a generic 'credentials malformed' error. Detect the old shape specifically in parseCredentials (presence of base_url with no api_base_url_1), throw a tagged sentinel error, and have loadCliCredentials catch it: delete the stale credentials file and emit a single-line stderr notice 'You've been logged out: your saved Primitive CLI credentials were created by an older CLI version and are no longer compatible. Run primitive login to re-authenticate.' Subsequent commands then behave as 'not logged in' and either use the env API key or prompt for login. The detect-and-clear path is idempotent: once the file is gone the branch never fires again. Genuine malformed credentials (truly broken JSON, missing required field, etc.) still throw the original generic error so they don't get swallowed. Test added for the migration path.
Without this guard, a Python caller still passing base_url= to PrimitiveClient.__init__ would have the value swallowed by **client_kwargs and forwarded into AuthenticatedClient(base_url=api_base_url_1, **client_kwargs), producing 'got multiple values for keyword argument base_url' pointing at internal SDK code rather than the call site. Catch the legacy kwarg up front and raise a TypeError that names the rename explicitly so the fix is obvious. create_client and client module-level factories inherit the guard via delegation. Two tests added. Greptile P1 on PR #59.
Release that ships dual-host routing for /send-mail (PR #59): the Node, Python, and Go SDKs now route attachment-supporting sends to api.primitive.dev/v1 automatically while every other operation continues to hit primitive.dev/api/v1. Customers don't see the split; the routing is internal.
Summary
/send-mail lives on a different host than the rest of the API (Cloudflare Worker at api.primitive.dev/v1, larger body cap for attachments). This PR makes all three SDKs route /send-mail there automatically so customers don't have to know about the split.
Customer-visible shape
The same /send-mail invocation auto-routes to the attachments-supporting host without the caller passing anything host-related. Every other operation continues to hit the primary host.
Override knobs
For internal staging/local testing only. Not documented in any customer-facing material.
Migration story
When we move another endpoint to host 2:
What this does NOT do (deliberately deferred)
Test plan
Open questions for review: