Skip to content

v0.7.0

Latest

Choose a tag to compare

@thetonymaster thetonymaster released this 05 Oct 21:58
· 6 commits to main since this release

Anthropic API coverage release (roadmap specs S10–S15), plus per-client retry/timeout, telemetry spans and Managed Agents (beta). Contains breaking changes: read Upgrading from 0.6 first.

Upgrading from 0.6

Everything here can break a 0.6 caller; the details are in the sections below.

  • Dependency floors: :telemetry ~> 1.3 and req ~> 0.6 and >= 0.6.1. Run mix deps.update telemetry req.
  • :poison is no longer pulled in by Claudio. If your app used it only through Claudio, add it to your own deps.
  • Claudio.Client.new/2 validates its config: a missing/empty :token or an unknown key raises ArgumentError. Fix the config; version: nil now means the default version.
  • Invalid retry / timeout / recv_timeout raise ArgumentError when the client is built (they were ignored or failed on first request). Use true/false/a keyword list for retry, and a non-negative integer (ms) or :infinity for timeouts.
  • Retries now actually happen (408, 429, 5xx, 529, connection errors, every method; streams excepted). If you had retry: true (or a keyword list) configured, it used to have no effect and now retries; set retry: false to keep the old no-retry behaviour. Apps with no retry: keep Req's default (GET/HEAD only). Retry-After is honoured on 429/503/529, also over delay:.
  • [:claudio, :messages, :create, :stop] token keys are absent (not 0) when a 200 carries no usage. Read them with Map.get/3. The error metadata key is deprecated; use error_type.
  • APIError.type is an atom for billing_error, request_too_large and timeout_error (was a string), and a body without error.type is typed from the HTTP status. Match the atoms.
  • Builder functions raise ArgumentError on common mistakes (add_message(:system, ...), string roles, nil content, out-of-range sampling values, string max_tokens/tool_choice, keyword tools/thinking configs); set_max_tokens/2 also rejects non-positive integers. Fix the call as the message says.
  • Option validation: the cache helpers, add_message_with_document/5, search_result_block/4, add_computer_tool/4, Files.upload/3, Batches.list/2, Batches.wait_for_completion/3 and Claudio.Agent.run/4 raise on unknown options (previously ignored; wait_for_completion/3 also on bad :poll_interval / :timeout / :callback values), and cache helpers on a ttl other than "5m"/"1h". Remove the stray option.
  • A streaming Req.Response carries private.claudio. Code matching private: %{} exactly or comparing whole responses must relax the match.
  • Claudio.Agent.run/4 errors are 4-tuples: {:error, reason, last_response_or_nil, messages} (was {:error, reason}). Match the 4-tuple.
  • Batches.get_results/2 returns string-keyed maps (was atom keys). Read "custom_id", not :custom_id.
  • Response shapes changed: stop_reason: :compaction is an atom; server-result blocks and compaction are typed maps (read raw for the old map); usage has new atom keys (nil when unsent); parsed tool_use / server_tool_use blocks gain caller / toolset_name. Update exact-map matches.
  • MCP: ServerConfig loses :tool_configuration (use :default_config / :configs); allow_tools/2 takes exact names (patterns raise); add_mcp_server/2 raises on a duplicate server name.
  • Claudio.Skills sends no beta header and list/2 has no "has_more"; page with next_page, or call Claudio.Client.with_betas(client, ["skills-2025-10-02"]).
  • Stream parse errors carry %Jason.DecodeError{} (was %Poison.ParseError{}), and build_final_message/1 returns an error for a truncated stream.

Added

  • ServerConfig.to_toolset/1, set_default_config/2, configure_tool/3, split_raw/1.

  • Files.list/2 GA pagination options :page and :ids.

  • add_code_execution_tool/2 :version option.

  • Response.stop_details (also accumulated by Claudio.Messages.Stream.build_final_message/1).

  • Thinking & effort helpers (Claudio.Messages.Request), no per-model validation:

    • enable_adaptive_thinking/2 (display: :summarized / :omitted / :updates;
      :updates declares thinking-display-updates-2026-08-18) and disable_thinking/1.
    • set_effort/2 (:low … :max, GA) and set_task_budget/3 (output_config.task_budget,
      declares task-budgets-2026-03-13) — both merge into output_config.
  • Response.get_thinking/1, Response.thinking_interrupted?/1, Claudio.Messages.Stream.accumulate_thinking/1.

  • Telemetry: :thinking_tokens in [:claudio, :messages, :create, :stop] and
    [:claudio, :messages, :stream, :usage] metadata, when the API reports it.

  • 5.x request surface (Claudio.Messages.Request), no per-model or placement validation:

    • add_system_message/3 — mid-conversation role: "system" messages (GA); clear_at:
      declares mid-conversation-system-clear-at-2026-08-21, effort: (per-message effort)
      declares mid-conversation-output-config-2026-07-01.
    • set_speed/2 (:fast / :standard, always declares fast-mode-2026-02-01),
      set_inference_geo/2 (:global / :us, GA), enable_cache_diagnostics/2 (GA).
  • Response.diagnostics (raw cache-diagnostics map).

  • Refusal fallbacks: Request.set_fallbacks/2 (:default or up to three models / override
    maps; declares server-side-fallback-2026-07-01); typed :fallback content blocks (original
    kept under raw: and replayed verbatim); Response.fallbacks/1, Response.served_by/1;
    usage.iterations.

  • Context management (Claudio.Messages.Request), no local limits (the API's 400 is
    authoritative):

    • add_clear_tool_uses/2, add_clear_thinking/2 (always placed first) — declare
      context-management-2025-06-27; add_compaction/2 (threshold, compact_20260112) —
      declares compact-2026-01-12.
    • request_compaction/2 — on-demand compaction: {"type": "summarize"}, declares
      compact-2026-09-04; apply_compaction/2 continues from a compaction summary (either
      kind) by replacing the history with the block onward.
    • add_message/3 declares the replay beta for a compaction block (signed →
      compact-2026-09-04, unsigned → compact-2026-01-12).
  • Typed :compaction content blocks (original under raw:, replayed verbatim),
    Response.compaction_block/1, Response.context_management (raw applied_edits; also
    read from the streamed message_delta).

  • Tool extensions (Claudio.Messages.Request): add_tool/3 (defer_loading:,
    allowed_callers: — :direct / :code_execution → "code_execution_20260120");
    add_tool_search_tool/2 (:regex / :bm25, GA); add_advisor_tool/3 (declares
    advisor-tool-2026-03-01; add_message/3 declares it for replayed advisor blocks);
    add_computer_toolset/2 / add_browser_toolset/2 (computer_toolset_20260801 /
    browser_toolset_20260801, GA); add_computer_tool/4 version: :"20251124".

  • Shallowly typed server-result blocks (web_fetch_tool_result, code_execution_tool_result,
    bash_code_execution_tool_result, text_editor_code_execution_tool_result,
    tool_search_tool_result, advisor_tool_result, container_upload; raw: replayed
    verbatim), Response.get_server_tool_results/1,2, Response.container (also from the
    stream).

  • Tools.create_tool_result/4 (toolset_name:); Tools.halt_result/1, Tools.halt_text/1.

  • Thinking block binding: enable_adaptive_thinking/2 block_binding: and
    Request.set_thinking_block_binding/2 (:error / :drop_block; declare
    thinking-binding-controls-2026-08-01); Response.input_transformations (raw list, nil
    without the beta; replaced by a streamed message_delta copy after a fallback).

  • create and linked stream :start carry output_type and stop_sequences when set (OTel gen_ai.output.type, gen_ai.request.stop_sequences).

  • Claudio.Messages.Stream.to_response/2 — consume a stream in one pass (on_text: / on_event: callbacks) and get a %Response{}; an SSE error event becomes an APIError.

  • add_web_search_tool/2 / add_web_fetch_tool/2: response_inclusion: (needs version: :"20260318") and allowed_callers:.

  • Request.disable_thinking/2 mode: :between_tools — Claude Sonnet 5.5's way to turn off
    up-front thinking (thinking: {"type": "between_tools"}). disable_thinking/1 still sends
    disabled. The set_tool_choice/2 docs now list Sonnet 5.5 among models that 400 on forced tool use.

  • Files.upload/3 accepts expires_in_seconds: (sent as a multipart form field; the API
    documents 3600..7776000). upload/3 now also rejects unknown options.

  • A2A HTTP transport :headers option: extra request headers ([{name, value}], e.g. W3C
    traceparent/tracestate for cross-service tracing) are sent on every request. They cannot
    replace the transport's own content-type or authorization (same name in any case is
    dropped).

  • Telemetry for OpenTelemetry/GenAI dashboards (see guides/telemetry.md):
    [:claudio, :messages, :create] gains request params (max_tokens, temperature, top_p,
    top_k, effort), server_address, response fields (response_id, response_model — the
    fallback model when one served — stop_reason, request_id), a bounded error_type (never
    nil on an error stop; :unknown when unclassified) and status_code, and token counts as
    measurements (still also metadata).
    New [:claudio, :messages, :count_tokens] span; new [:claudio, :messages, :stream, :start | :stop]
    around each stream consumption (duration measured from the start of consumption to its end,
    tokens, exactly one :stop), linked to the create span when parse_events/1 is given the
    whole response; new per-attempt [:claudio, :http, :request, :start | :stop] for every
    endpoint (retries visible as attempt). create and count_tokens :start carry
    server_port; a linked stream :start also carries request_model, the request params that
    were set, server_address and server_port.
    No event carries headers, bodies, the API key or message content, with two exceptions: the
    deprecated error key on a failed create :stop (an inspect of the error, which can include
    the API's error body or, for a malformed 200, the response body), and the :exception events'
    reason and stacktrace (standard :telemetry.span behavior), which can include request or
    response data. Exporters should use error_type. error_type and token values are bounded:
    a server-supplied type string passes only if it is an identifier ([a-z][a-z0-9_]{0,63}),
    else :unknown, and a token key is kept only when its value is a non-negative integer.

  • Claudio.Client.new/2 accepts :timeout, :recv_timeout and :retry per client.
    A per-client value wins over config :claudio, Claudio.Client, which remains the
    fallback, so one application can run clients with different retry behaviour.

  • Managed Agents foundation (beta managed-agents-2026-04-01, attached per request):
    Claudio.ManagedAgents.Agents (create / get incl. version: / update / list / archive /
    list_versions), Claudio.ManagedAgents.Environments (create / get / update / list / archive /
    delete), Claudio.ManagedAgents.Sessions (create / get / update / list / archive / delete,
    send_events/3, list_events/3, session resources). List options encode the API's bracketed
    filters (statuses: [...] → statuses[]=…, created_at: [gte: dt] → created_at[gte]=…).
    Claudio.ManagedAgents.stream/2 walks cursor-paged lists lazily. Raw-map returns, like
    Claudio.Skills.

  • Telemetry guide (docs): context propagation into Tasks and a traceparent request
    step for trace headers to the API.

Changed

  • Claudio.MCP.ServerConfig: :tool_configuration struct field removed; tool
    selection lives on the toolset (:default_config, :configs).
    allow_tools/2 now takes exact tool names and raises ArgumentError on
    */? patterns (the connector matches names literally; a pattern would enable
    no tools). Raw maps with legacy tool_configuration are translated with a warning.
  • Claudio.Skills no longer attaches anthropic-beta: skills-2025-10-02 (Skills API is GA).
    Skills.list/2 responses lose "has_more" (verified live; list_versions/3 not probed) — page with
    next_page → :page, or opt back in with
    Claudio.Client.with_betas(client, ["skills-2025-10-02"]).
  • Request.add_mcp_server/2 raises ArgumentError when tools already holds an
    mcp_toolset for that server and the new server carries tool config (it would
    otherwise be dropped).
  • ServerConfig.allow_tools/2 replaces any previous allowlist: tools enabled only by
    an earlier call are no longer enabled (other per-tool settings are kept).
    set_default_config/2 / configure_tool/3 store setting keys as strings.
  • Request.add_mcp_server/2 raises ArgumentError when a server with the same
    name is already on the request (the connector requires unique server names).
  • Request.add_code_execution_tool/2 defaults to code_execution_20260521
    (same runtime as 20260120).
  • Response.usage keeps every field the API returns: documented fields are atom keys
    (new: cache_creation, service_tier, inference_geo, speed, iterations); any other field is kept
    under the key it arrived with instead of being dropped. Documented fields the API did not
    send now appear as nil, so exact usage == %{...} comparisons need the new keys.
  • Claudio.Messages.count_tokens/2 strips every field the count endpoint rejects (see Fixed),
    for raw maps too.
  • Response.to_assistant_content/1 applies the API's continuation rules after a server-side
    fallback: before the last fallback block it drops thinking, redacted_thinking,
    connector_text and tool_use, and keeps server_tool_use / mcp_tool_use only when their
    result is present. Output is unchanged for responses without a fallback block, or with it
    first (the normal non-streaming shape).
  • Response.get_tool_uses/1, Tools.extract_tool_uses/1 (so has_tool_uses?/1) and
    MCP.ResultMapper.extract_mcp_calls/1 skip tool_use blocks before the last fallback block —
    they came from the model that declined.
  • Request.add_message/3 declares server-side-fallback-2026-07-01 when its content holds a
    fallback block; the API rejects a replayed fallback block without it.
  • Response.stop_reason is :compaction (was the string "compaction").
  • Request.add_message/3 sends a typed content block that carries raw: (e.g. from
    Response.compaction_block/1) as its original API map instead of the typed map.
  • Parsed tool_use blocks gain caller and toolset_name, server_tool_use gains caller,
    web_search_tool_result gains caller and raw (nil when absent). Code matching the
    whole map with == must add them. The same holds for the maps Tools.extract_tool_uses/1
    returns (now with toolset_name and caller).
  • Request.add_computer_tool/4 raises ArgumentError on unknown options (they were ignored).
  • Claudio.Agent resumes pause_turn (counts toward :max_turns) instead of returning it,
    carries the response container to the next request, and dispatches client-toolset calls
    to the handler keyed by toolset_name. A handler of the wrong arity is now an error result
    instead of a crash.
  • Claudio.Agent continues after stop_reason: :compaction (Request.apply_compaction/2, then
    another call with no user turn; counts toward :max_turns) instead of returning the summary
    as the final reply. A handler returning anything other than {:ok, _} / {:error, _} raises
    ArgumentError naming the handler (was a CaseClauseError).
  • Claudio.Agent.run/4 errors keep the history: an API error mid-loop returns
    {:error, reason, last_response_or_nil, messages} (was {:error, reason}, which discarded
    executed turns). Match the 4-tuple, like :max_turns_exceeded.
  • Claudio.Batches.get_results/2 decodes results with string keys (was keys: :atoms),
    like every other response, and returns {:error, {:invalid_result_line, n, line}} for a
    malformed line instead of silently dropping it.
  • Server-result blocks that were raw string-keyed maps in 0.6 (web_fetch_tool_result,
    code_execution_tool_result, bash_code_execution_tool_result,
    text_editor_code_execution_tool_result, tool_search_tool_result, advisor_tool_result,
    container_upload, compaction) now parse as typed maps (%{type: :atom, ..., raw: map});
    code matching %{"type" => "web_fetch_tool_result"} on response.content must match the
    atom type or read raw.
  • Stream parse errors carry %Jason.DecodeError{} (was %Poison.ParseError{}).
  • Claudio.MCP.ToolAdapter.to_claudio_tool/2 omits a nil "description" and raises on invalid
    names (see Fixed).
  • The Messages streaming path requires a complete stream: a block that never closes is an error
    (see Fixed).
  • Unknown-option errors name the function: Request.add_web_search_tool/2: unknown option :max_use; allowed: :version, ... (was Keyword.validate!/2's "unknown keys [...]"). A
    non-keyword opts is an ArgumentError naming the function instead of a FunctionClauseError
    from Keyword. Still ArgumentError; code matching the old message text must update.
  • Stream :stop error_type is an atom for known API error types (as on create); unknown identifiers stay strings.
  • Claudio.Messages.Response.get_text/1 accepts the legacy Claudio.Messages.create_message/2 map.
  • Builder functions raise ArgumentError naming the fix for common mistakes
    (add_message(:system, …), string roles, nil content, out-of-range sampling values, string
    max_tokens / tool_choice, keyword tools / thinking configs) instead of
    FunctionClauseError. set_max_tokens/2 now also rejects non-positive integers.
  • Cache helpers (set_system_with_cache/3, add_message_with_cache/4, add_tool_with_cache/3,
    set_cache_control/2) raise on unknown options and on a ttl other than "5m"/"1h";
    add_message_with_document/5 and search_result_block/4 raise on unknown options
    (previously ignored).
  • APIError.type is an atom for billing_error, request_too_large and timeout_error (was a
    string); a JSON error body without error.type is typed from the HTTP status, and its
    top-level message is used.
  • Client.new/2 validates its config: it raises ArgumentError on a missing/empty :token and on unknown config keys, and accepts a keyword list. version: nil now falls back to the default like the other keys (it used to drop the anthropic-version header).
  • Legacy Claudio.Messages.create_message/2 now emits the [:claudio, :messages, :create] span.
    It recognises only the string key "stream" => true as streaming (as before); an atom
    stream: true is routed, and labelled, as non-streaming.
  • create :stop token keys (measurements and metadata) are absent, not 0, when a 200
    response carries no usage, and a non-integer token value is dropped. Handlers that read
    meta.input_tokens unconditionally should use Map.get/3.
  • A streaming Req.Response from create/2 / create_message/2 now carries
    private.claudio (the link Claudio.Messages.Stream.parse_events/1 reads). Code that matches private: %{}
    exactly or compares whole responses will see it.
  • Claudio.Client.new/2 adds a :claudio_telemetry Req step (request, response and error
    steps) that emits the HTTP events. User steps and Req.merge/2 keep working; code that
    asserts on the step lists will see it.
  • Claudio.Messages.Stream.parse_events/1 no longer raises on a malformed message_start /
    message_delta (a non-map message or usage): it passes the events through.
  • Batches.wait_for_completion/3 and Batches.list/2 raise on unknown options;
    wait_for_completion/3 also raises on a non-positive-integer :poll_interval / :timeout
    or a :callback that is not a 1-arity function.
  • Claudio.Messages.Stream.parse_events/1 also accepts the whole %Req.Response{}.
  • The :telemetry requirement is now ~> 1.3 (was ~> 1.0): span stop measurements need
    1.3. Applications locked to an older :telemetry will be asked to update it.
  • An invalid retry value (anything but true, false or a keyword list) or an unknown
    retry key now raises ArgumentError, for per-client and app config alike. Previously
    it was silently ignored and Req's default (GET/HEAD-only retries) applied.
  • timeout / recv_timeout must be a non-negative integer (ms) or :infinity; anything
    else raises ArgumentError when the client is built (per-client and app config alike).
    Previously a bad value failed on the first request with an error that did not name it.
  • retry: values (delay, max_delay, max_retries) must be non-negative integers; anything
    else raises ArgumentError at Client.new/2 (previously a bad delay raised ArithmeticError
    from inside the request).
  • Claudio.Messages.Request.add_message_with_image/5 also takes media_type: as a keyword option; Claudio.Agent.run/4 raises on unknown options.

Deprecated

  • Claudio.Tools.create_tool_result_message/1 (identity function): pass the result list straight to
    Claudio.Messages.Request.add_message/3.
  • The error metadata key on [:claudio, :messages, :create, :stop] (an inspect string that can
    contain the API's error response body, or a malformed 200's body). It will be removed in 0.8.0; use error_type.

Fixed

  • MCP connector now emits a valid mcp-client-2025-11-20 request:
    Request.add_mcp_server/2 adds the mcp_toolset entry to tools and declares
    the beta. Previously the request had no toolset and no beta header and was rejected.
  • Response.usage keeps output_tokens_details (raw map, e.g. thinking_tokens);
    it was dropped by the usage parser.
  • set_output_format/2 (and the new output-config helpers) no longer emit a duplicate
    key when set_output_config/2 was given atom keys — existing keys are stringified first.
  • Claudio.Messages.Stream.build_final_message/1 merges message_delta usage over message_start usage
    instead of replacing it, so input_tokens (and cache counters) survive when the delta
    omits them; previously the parsed Response.usage came back as a raw, incomplete map.
  • Claudio.Messages.Stream.build_final_message/1 keeps a streamed threshold-compaction summary
    (compaction_delta); it was dropped, leaving "content": null.
  • Request.set_context_management/2 also declares compact-2026-01-12 when its edits hold a
    compact_20260112 edit; with only context-management-2025-06-27 the API rejects it. Its
    doc example ("strategy" => "auto") was not a real API shape and is replaced.
  • Response.to_assistant_content/1 re-emits toolset_name and caller on tool_use (and
    caller on server_tool_use / web_search_tool_result); replaying a client-toolset call
    without toolset_name was rejected.
  • Claudio.Messages.Stream.build_final_message/1 decodes streamed tool input (input_json_delta chunks on
    tool_use / server_tool_use / mcp_tool_use) into the block's "input"; it was left as an
    undecoded "partial_json" string with an empty input. Invalid JSON (e.g. cut off by
    max_tokens) returns {:error, {:invalid_tool_input_json, index, partial_json}}.
  • Streaming: Claudio.Messages.Stream.parse_events/1 no longer tears apart an event split
    across network chunks (it emitted an event: with no data and a data: with no event, which
    crashed build_final_message/1 or silently lost the event). CRLF line endings, multi-line
    data: and a final event without a trailing blank line are handled. build_final_message/1
    keeps interleaved blocks (keyed by index) and returns an error for a truncated stream —
    {:incomplete_stream, indexes} for a block that never closed, {:incomplete_stream, :no_message_stop} when message_stop never arrived — instead of a message silently missing
    content and stop_reason. Streamed usage telemetry merges message_start usage (input
    and cache counts were lost).
  • Response.to_assistant_content/1 no longer sends server_name on a replayed
    mcp_tool_result — the API rejects it, so replaying any MCP-connector turn failed.
  • Request.add_message/3 declares mcp-client-2025-11-20 when its content holds an
    mcp_tool_use or mcp_tool_result block; replaying an MCP call on a turn without
    add_mcp_server/2 was a 400.
  • Request.add_message/3 with parsed Response content (e.g. response.content) sends each
    block in API shape (keeping a cache_control you added); it sent "caller": null, which the
    API rejects.
  • Claudio.Messages.count_tokens/2 also strips temperature, top_k, top_p,
    stop_sequences, metadata, service_tier and container (Request and raw-map forms) — each
    made the count endpoint return 400, so a request using them couldn't be counted.
  • Claudio.APIError.from_response/2 handles non-JSON bodies (an empty 5xx, a proxy's HTML
    page — streaming or not) and odd JSON error shapes instead of raising; the type comes from the
    HTTP status (429 → :rate_limit_error, 529 → :overloaded_error, …). A 200 whose body isn't
    a JSON object is an APIError too.
  • Retries actually happen: the documented config :claudio, Claudio.Client, retry: ...
    was a no-op placeholder (and Req's default never retries POST). It now retries 408, 429,
    5xx, 529 and connection errors on every method; retry: false disables retries (Req's
    GET/HEAD default too). Streaming requests are not retried.
  • config :claudio, default_api_version: ..., default_beta_features: [...] (the documented
    form) is honoured; only the nested config :claudio, :claudio, ... form was read.
  • Claudio.Batches.create/2 drops stream from %Request{} items (batch items are never
    streamed).
  • Claudio.Agent: a handler returning {:error, %SomeException{}} / {:error, {:tuple, ...}}
    no longer crashes with a protocol error (the reason becomes text); stream: true requests
    and max_turns < 1 raise a clear ArgumentError (was a CaseClauseError / one call); a
    failed on-demand compaction no longer re-sends the compaction request every turn.
  • Claudio.MCP.ToolAdapter.to_claudio_tool/2 emits tools the API accepts: no "description": null, an input_schema with "type", and an ArgumentError for names outside
    ^[a-zA-Z0-9_-]{1,128}$ (were 400s).
  • MCP adapters: HermesMCP unwraps Hermes response structs (list functions returned
    {:ok, []}), passes opts, and documents the client-first module
    ({Hermes.Client.Base, MyClient} — the old {MyClient, pid} usage crashed); ExMCP requests
    format: :map (list functions returned {:ok, []} under ex_mcp 1.5's default); no adapter
    reports a bug inside the library as "not available". Version constraints corrected
    (ex_mcp ~> 1.5, mcp_ex ~> 0.1).
  • Tools.create_tool_result/4 rejects content the API would reject (an empty error result, a
    list of non-block values) and raises ArgumentError for unencodable values instead of a
    protocol error; Tools.extract_tool_uses/1 normalizes a raw tool_use without input.
  • Request tool helpers: atom-keyed tool maps no longer get a duplicate JSON key;
    add_tool_with_cache/3 validates its options (:ttl, :allowed_callers) instead of silently
    dropping others; add_web_search_tool/2, add_web_fetch_tool/2, add_text_editor_tool/2
    validate options and accept dated version atoms; add_message_with_image/5 detects PNG, GIF
    and WebP data when no media type is given (it always sent image/jpeg).
  • Removed the :poison dependency and a bug where every GET/DELETE request (Models, Files,
    Batches, Admin, Skills) sent the literal body "Elixir.Poison" (#17). If your app used Poison
    only through Claudio, add it yourself.
  • Retries honour Retry-After on 529 (overloaded) as well as 429/503, and also when
    retry: [delay: …] is set (previously a configured delay overrode the server's Retry-After).
  • Claudio.Tools.tool_definition and tool_result typespecs described atom-keyed maps;
    define_tool/3 and create_tool_result/4 return string-keyed maps. The types now match
    (documentation-only; no runtime change).
  • The legacy streaming Claudio.Messages.create_message/2 now matches create/2: it is
    never retried (a retried into: :self request left the failed attempt's body messages in
    the caller's mailbox), and a non-200 error body is drained off the mailbox, so the
    APIError carries the API's message instead of a generic "Streaming request failed".
  • Draining a streaming error body (both streaming paths) now cancels the response when its 2s
    deadline expires, so chunks still in flight no longer land in the caller's mailbox after
    the call returns, and a transport error mid-body ends the drain at once instead of
    waiting out the deadline. The APIError keeps the HTTP status either way.
  • Draining a streaming error body now receives only the messages of its own response (a selective
    receive on the body's ref). Unrelated messages in the caller's mailbox are no longer taken off
    and re-sent to the end, so their order is untouched.
  • Telemetry guide: the OpenTelemetry handler records Erlang errors (:function_clause, …) as
    exceptions; it is now checked in CI (scripts/check_otel_guide.exs).

Security

  • The req requirement is now ~> 0.6 and >= 0.6.1 (was ~> 0.5). Every req
    release before 0.6.1 carries EEF-CVE-2026-49755 (decompression-bomb DoS) and
    everything before 0.6.0 carries EEF-CVE-2026-49756 (multipart header injection, which
    Files.upload/3 and Skills.create/2 uploads would reach). Applications locked to an
    older req will be asked to update it. Verified: the suite passes on req 0.6.1
    (with finch 0.22.0, mint 1.11.0) as well as the locked 0.7.4.
  • Locked dependencies updated past Hex security advisories (req 0.5.15 → 0.7.4,
    mint 1.6.2 → 1.11.0, hpax 1.0.0 → 1.1.0, and the test-only plug/cowboy stack).
    mix.lock does not ship with the package: applications using Claudio should run
    mix deps.update req mint hpax themselves. Test-only cowboy/cowlib are pinned to
    2.18.0/2.19.0 (cowlib 2.20.0 does not compile on OTP 26); three cowlib advisories in
    that test-only server are acknowledged in mix.exs (hex: [ignore_advisories: ...]).

Internal

  • Added Credo (--strict) and Dialyxir, a mix precommit alias, and CI checks for
    Credo, Dialyzer, docs --warnings-as-errors, hex.audit, and unlocked dependencies.

Docs

  • Examples use claude-opus-5-5 (sampling-setter examples use claude-haiku-4-5, since Opus 4.7+ and 5.x reject sampling params); enable_thinking/2 and set_tool_choice/2
    document the 400s on current models; Files documented as GA.
  • README and the getting-started guide: install snippet (~> 0.7), working streaming, Batches
    and telemetry examples (the old ones called functions or events that don't exist); CLAUDE.md
    arities and key-style notes corrected; the CHANGELOG is published on hexdocs; CI covers
    Elixir 1.15 / OTP 25, 1.18 / OTP 27 and 1.19 / OTP 28.

Full changelog: CHANGELOG.md · Diff: v0.6.0...v0.7.0