All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[0.2.0] - 2026-07-14
Changed
:gunis now an optional dependency;:mintis a new optional alternative. Previouslyxaihard-requiredgun(and its transitivecowlibdependency) for every consumer, even ones that only useXai.Realtime(WebSocket, viawebsockex— no gun/cowlib involved at all).Xai.Client.new/1now accepts an:adapteroption threaded through toGRPC.Stub.connect/2(mirrorsgrpc's ownGRPC.Client.Adapters.Gun/.Mintsplit); Gun remains the default when unspecified for backward compatibility.- Breaking for
Xai.Chat/Xai.Videousers: add{:gun, "~> 2.0"}to your ownmix.exsto keep the previous default behavior (it's no longer pulled in transitively). Or add{:mint, "~> 1.9"}and passadapter: GRPC.Client.Adapters.Mint— Mint has no:cowlibin its dependency tree at all. - No change needed for
Xai.Realtime-only users (e.g. streaming TTS) — that path never depended on gun/cowlib and still doesn't.
- Breaking for
[0.1.1] - 2026-07-14
Fixed
Xai.Realtime.close/1no longer crashes the connection process: nohandle_cast/2clause was defined for the:closecast it sends, so it fell through touse WebSockex's default (raising) implementation. Added the missing clause.Xai.Realtime.handle_disconnect/2no longer unconditionally reconnects. It now terminates cleanly on a locally-initiated close (close/1, or the internal "error" event handler) and applies capped exponential backoff (500ms up to 30s, giving up after 5 attempts) on genuine remote/error disconnects, instead of hot-looping forever on e.g. a persistent auth failure.Xai.Video.generate/2no longer silently drops:aspect_ratioand:resolutionoptions — they're now passed through toGenerateVideoRequest.Xai.Video.extend/2now passes:timeoutto the gRPC call and to polling (fromoptsor the client's configured default), matchinggenerate/2instead of always falling back to the gRPC library's own default.Xai.Clientno longer infers TLS fromString.ends_with?(endpoint, ":443")(silently dropping to plaintext — and sending the Bearer API key unencrypted — for any custom endpoint on a non-:443port). TLS is now controlled by an explicit:ssloption, defaulting totrue.
Added
- Test coverage for
Xai.Realtime.handle_cast/2(:close) andhandle_disconnect/2(local-close termination, reconnect, and attempt-cap termination), previously untested. Xai.Video.build_generate_request/1andbuild_extend_request/1extracted as testable helpers, with tests covering the:aspect_ratio/:resolutionfix above (test/xai/video_test.exswas previously a placeholder with no real assertions).
[0.1.0] - 2026-07-14
First published release — nothing shipped to Hex before this version, so everything below (including the initial implementation) is folded into it.
Added
- Initial implementation of the xAI Elixir SDK:
Xai.Client– core client for gRPC connections.Xai.Chat– chat completions with support for messages, streaming, and reasoning.Xai.Video– image and video generation with automatic polling for async operations.- WebSocket support for realtime features via
websockex(streaming TTS, voice agent API).
- Real generated protobuf bindings (via
mix proto.generate, see below) for chat, video, image, sample, usage, deferred, and documents — replacing the hand-written placeholder structs the implementation started from. LICENSE(Apache-2.0, matching themix.exsdeclaration).mix quality: a single gate that chainsformat --check-formatted,compile --warnings-as-errors,deps.unlock --check-unused,credo --strict, unit tests, anddialyzer. Run it before committing/pushing.mix quality.audit:mix hex.auditas a separate, non-gating alias — it can fail for reasons outside this repo's control (an unpatched upstream CVE), so it isn't part ofmix quality..formatter.exs,.credo.exs(both exclude the generated, gitignoredlib/xai/proto/), and.dialyzer_ignore.exs(documents each currently-accepted dialyzer finding and why).- Test suite: unit tests (no network required), integration tests (gated behind
XAI_API_KEY), Mox-based mocking. AGENTS.mdfor AI coding agents.
Fixed
Xai.Chat.stream/2no longer crashes: it was callingGRPC.Stub.recv/2in a polling loop against a value that gRPC's server-streaming API already resolves to{:ok, Enumerable.t()}before it's returned, sorecv/2had no matching clause and would raise.Xai.Video's privatepoll_for_video/4was checkingif attempts <= 0, do: {:error, :timeout}without anelse; the result was discarded and the retry budget was never actually enforced. Rewritten as pattern-matched function heads.Xai.Client.new/1silently swallowed gRPC connection failures viaelem(connect(...), 1); a failed connect now raises with the real error instead of storing it as a bogus channel.Xai.Chat.extract_delta/1matched on%{message: ...}, but streamed chunks carry adeltakey; it always returned"".Xai.Clientconnected withssl: [verify: :verify_none]for the default TLS endpoint, disabling certificate verification. Now verifies peer certs against the system CA store.Xai.Realtime.connect_tts/1registered under a hardcoded{:via, Registry, {Xai.RealtimeRegistry, :default}}name, but that registry is never started — the first call would crash, and only one TTS connection could ever be alive at a time. Removed the vestigial naming.mix proto.generatenever actually worked:.protobuf.exswas never read by anything (the installedprotobuf_generateversion takes CLI flags, not a config file). Replaced with a working alias inmix.exs(@proto_files+ explicit flags) and regenerated real bindings from thepriv/xai_protosubmodule.- README's Quick Start examples referenced
response.contentand an undefinedextract_text/1that don't match the real generated response shape; fixed to useresponse.outputs |> hd() |> ...andXai.Chat.extract_delta/1. mix hex.build/hex.publishwould have failed outright (Can't build package with overridden dependency gun); removed the unnecessaryoverride: trueon thegundep — it wasn't resolving a real conflict (~> 2.0already satisfiesgrpc's own~> 2.4.0).package[:files]wasn't set, so Hex's default (lib,priv, ...) would have swept the ~160KB vendoredpriv/xai_protogit submodule (raw.protosources, buf tooling, CI configs) into the published package. Now an explicit allowlist.
Changed
- Dropped the unused
grpc_serverdependency (and its transitiveflow/gen_stage/cowboytree), added for a server-side feature that was never implemented. - Added
:inetsand:ssltoextra_applications(needed by the integration test's:httpccall and by TLS gRPC connections). - Bumped minimum Elixir requirement from
~> 1.16to~> 1.18. - Replaced direct
Jasonusage with the built-inJSONmodule (Elixir 1.18+) inXai.Realtimeand the integration test; dropped the directjasondependency.jasonstill appears inmix.lockas a transitive dependency (grpc_core,protobuf, andcredoeach require it directly), but our own code no longer calls it. - Aliased the generated gRPC stub modules locally in
Xai.Chat/Xai.Videoinstead of reaching through the nestedProto.Chat.Stub/Proto.Video.Stubpath, clearing Credo's strict-mode design suggestions.
Security
- API keys are never committed. Integration tests require the
XAI_API_KEYenvironment variable.