llm 0.33 takes the pin out and moves to httpx2

Version 0.32.1 held fresh installs together by pinning the OpenAI Python package below 3.0.0. That was a holding action, and 0.33 does the actual repair: it upgrades to the OpenAI library 3.x and switches its HTTP client from httpx to httpx2. The release also carries changes you will notice in daily use, including a --key option on the embedding commands and server-side tool results that finally show up in llm logs.

Version 0.32.1 shipped on August 21 with a single line of content: a version pin that stopped fresh installs from breaking. The repair it was standing in for arrived a day later. llm 0.33, released on August 22, upgrades to the OpenAI Python library 3.x and moves the HTTP client dependency from httpx to httpx2. The pin comes out The break that 0.32.1 patched came from upstream: the OpenAI package stopped depending on httpx, and llm imported httpx without declaring it. Pinning the package below 3.0.0 kept the old dependency arriving, which restored installs without a rewrite. It also froze llm on an old major version of a library it does not control, which is a debt with interest. 0.33 pays it. The dependency is now declared and the client is httpx2, so a fresh install no longer depends on what somebody else happens to pull in. What else changed Three of the additions matter if you use llm for more than one-off prompts. llm embed and llm embed-multi accept --key, and the Python methods accept key=. The resolved key is passed per call, so a plugin no longer has to mutate shared model state to use a different one. Plugins that read self.key keep working through a compatibility fallback. llm logs now shows the output of server-side tool calls in a Tool results section, and marks them with a server_executed key in --json and --short output. Until now that output was simply absent from the log. llm prompt -t can be repeated, so the model configuration from one template combines with the prompt from another. Errors that used to be silent schema_dsl() now raises a descriptive ValueError for an unknown field type or a duplicate field name. Before this it treated an unknown type as a string and let a repeated name overwrite the earlier field, which is the kind of default that turns a typo into a wrong result rather than a stack trace. Reasoning stream events that carry provider metadata but no text are kept as ReasoningPart objects, so opaque state such as Anthropic signatures and redacted thinking survives a round trip instead of being dropped.