Repository navigation
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.3andreq ~> 0.6 and >= 0.6.1. Runmix deps.update telemetry req. :poisonis no longer pulled in by Claudio. If your app used it only through Claudio, add it to your own deps.Claudio.Client.new/2validates its config: a missing/empty:tokenor an unknown key raisesArgumentError. Fix the config;version: nilnow means the default version.- Invalid
retry/timeout/recv_timeoutraiseArgumentErrorwhen the client is built (they were ignored or failed on first request). Usetrue/false/a keyword list forretry, and a non-negative integer (ms) or:infinityfor 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; setretry: falseto keep the old no-retry behaviour. Apps with noretry:keep Req's default (GET/HEAD only).Retry-Afteris honoured on 429/503/529, also overdelay:. [:claudio, :messages, :create, :stop]token keys are absent (not0) when a 200 carries nousage. Read them withMap.get/3. Theerrormetadata key is deprecated; useerror_type.APIError.typeis an atom forbilling_error,request_too_largeandtimeout_error(was a string), and a body withouterror.typeis typed from the HTTP status. Match the atoms.- Builder functions raise
ArgumentErroron common mistakes (add_message(:system, ...), string roles,nilcontent, out-of-range sampling values, stringmax_tokens/tool_choice, keyword tools/thinking configs);set_max_tokens/2also 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/3andClaudio.Agent.run/4raise on unknown options (previously ignored;wait_for_completion/3also on bad:poll_interval/:timeout/:callbackvalues), and cache helpers on attlother than"5m"/"1h". Remove the stray option. - A streaming
Req.Responsecarriesprivate.claudio. Code matchingprivate: %{}exactly or comparing whole responses must relax the match. Claudio.Agent.run/4errors are 4-tuples:{:error, reason, last_response_or_nil, messages}(was{:error, reason}). Match the 4-tuple.Batches.get_results/2returns string-keyed maps (was atom keys). Read"custom_id", not:custom_id.Responseshapes changed:stop_reason: :compactionis an atom; server-result blocks andcompactionare typed maps (readrawfor the old map);usagehas new atom keys (nilwhen unsent); parsedtool_use/server_tool_useblocks gaincaller/toolset_name. Update exact-map matches.- MCP:
ServerConfigloses:tool_configuration(use:default_config/:configs);allow_tools/2takes exact names (patterns raise);add_mcp_server/2raises on a duplicate server name. Claudio.Skillssends no beta header andlist/2has no"has_more"; page withnext_page, or callClaudio.Client.with_betas(client, ["skills-2025-10-02"]).- Stream parse errors carry
%Jason.DecodeError{}(was%Poison.ParseError{}), andbuild_final_message/1returns an error for a truncated stream.
Added
-
ServerConfig.to_toolset/1,set_default_config/2,configure_tool/3,split_raw/1. -
Files.list/2GA pagination options:pageand:ids. -
add_code_execution_tool/2:versionoption. -
Response.stop_details(also accumulated byClaudio.Messages.Stream.build_final_message/1). -
Thinking & effort helpers (
Claudio.Messages.Request), no per-model validation:enable_adaptive_thinking/2(display::summarized/:omitted/:updates;
:updatesdeclaresthinking-display-updates-2026-08-18) anddisable_thinking/1.set_effort/2(:low…:max, GA) andset_task_budget/3(output_config.task_budget,
declarestask-budgets-2026-03-13) — both merge intooutput_config.
-
Response.get_thinking/1,Response.thinking_interrupted?/1,Claudio.Messages.Stream.accumulate_thinking/1. -
Telemetry:
:thinking_tokensin[: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-conversationrole: "system"messages (GA);clear_at:
declaresmid-conversation-system-clear-at-2026-08-21,effort:(per-message effort)
declaresmid-conversation-output-config-2026-07-01.set_speed/2(:fast/:standard, always declaresfast-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(:defaultor up to three models / override
maps; declaresserver-side-fallback-2026-07-01); typed:fallbackcontent blocks (original
kept underraw: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) —
declarescompact-2026-01-12.request_compaction/2— on-demandcompaction: {"type": "summarize"}, declares
compact-2026-09-04;apply_compaction/2continues from a compaction summary (either
kind) by replacing the history with the block onward.add_message/3declares the replay beta for acompactionblock (signed →
compact-2026-09-04, unsigned →compact-2026-01-12).
-
Typed
:compactioncontent blocks (original underraw:, replayed verbatim),
Response.compaction_block/1,Response.context_management(rawapplied_edits; also
read from the streamedmessage_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/3declares it for replayed advisor blocks);
add_computer_toolset/2/add_browser_toolset/2(computer_toolset_20260801/
browser_toolset_20260801, GA);add_computer_tool/4version: :"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/2block_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 streamedmessage_deltacopy after a fallback). -
createand linked stream:startcarryoutput_typeandstop_sequenceswhen set (OTelgen_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 SSEerrorevent becomes anAPIError. -
add_web_search_tool/2/add_web_fetch_tool/2:response_inclusion:(needsversion: :"20260318") andallowed_callers:. -
Request.disable_thinking/2mode: :between_tools— Claude Sonnet 5.5's way to turn off
up-front thinking (thinking: {"type": "between_tools"}).disable_thinking/1still sends
disabled. Theset_tool_choice/2docs now list Sonnet 5.5 among models that 400 on forced tool use. -
Files.upload/3acceptsexpires_in_seconds:(sent as a multipart form field; the API
documents 3600..7776000).upload/3now also rejects unknown options. -
A2A HTTP transport
:headersoption: extra request headers ([{name, value}], e.g. W3C
traceparent/tracestatefor cross-service tracing) are sent on every request. They cannot
replace the transport's owncontent-typeorauthorization(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 boundederror_type(never
nilon an error stop;:unknownwhen unclassified) andstatus_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 thecreatespan whenparse_events/1is given the
whole response; new per-attempt[:claudio, :http, :request, :start | :stop]for every
endpoint (retries visible asattempt).createandcount_tokens:startcarry
server_port; a linked stream:startalso carriesrequest_model, the request params that
were set,server_addressandserver_port.
No event carries headers, bodies, the API key or message content, with two exceptions: the
deprecatederrorkey on a failedcreate :stop(aninspectof the error, which can include
the API's error body or, for a malformed 200, the response body), and the:exceptionevents'
reasonandstacktrace(standard:telemetry.spanbehavior), which can include request or
response data. Exporters should useerror_type.error_typeand 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/2accepts:timeout,:recv_timeoutand:retryper client.
A per-client value wins overconfig :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/2walks cursor-paged lists lazily. Raw-map returns, like
Claudio.Skills. -
Telemetry guide (docs): context propagation into
Tasks and atraceparentrequest
step for trace headers to the API.
Changed
Claudio.MCP.ServerConfig::tool_configurationstruct field removed; tool
selection lives on the toolset (:default_config,:configs).
allow_tools/2now takes exact tool names and raisesArgumentErroron
*/?patterns (the connector matches names literally; a pattern would enable
no tools). Raw maps with legacytool_configurationare translated with a warning.Claudio.Skillsno longer attachesanthropic-beta: skills-2025-10-02(Skills API is GA).
Skills.list/2responses lose"has_more"(verified live;list_versions/3not probed) — page with
next_page→:page, or opt back in with
Claudio.Client.with_betas(client, ["skills-2025-10-02"]).Request.add_mcp_server/2raisesArgumentErrorwhentoolsalready holds an
mcp_toolsetfor that server and the new server carries tool config (it would
otherwise be dropped).ServerConfig.allow_tools/2replaces 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/3store setting keys as strings.Request.add_mcp_server/2raisesArgumentErrorwhen a server with the same
name is already on the request (the connector requires unique server names).Request.add_code_execution_tool/2defaults tocode_execution_20260521
(same runtime as20260120).Response.usagekeeps 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 asnil, so exactusage == %{...}comparisons need the new keys.Claudio.Messages.count_tokens/2strips every field the count endpoint rejects (see Fixed),
for raw maps too.Response.to_assistant_content/1applies the API's continuation rules after a server-side
fallback: before the lastfallbackblock it dropsthinking,redacted_thinking,
connector_textandtool_use, and keepsserver_tool_use/mcp_tool_useonly when their
result is present. Output is unchanged for responses without afallbackblock, or with it
first (the normal non-streaming shape).Response.get_tool_uses/1,Tools.extract_tool_uses/1(sohas_tool_uses?/1) and
MCP.ResultMapper.extract_mcp_calls/1skiptool_useblocks before the lastfallbackblock —
they came from the model that declined.Request.add_message/3declaresserver-side-fallback-2026-07-01when its content holds a
fallbackblock; the API rejects a replayedfallbackblock without it.Response.stop_reasonis:compaction(was the string"compaction").Request.add_message/3sends a typed content block that carriesraw:(e.g. from
Response.compaction_block/1) as its original API map instead of the typed map.- Parsed
tool_useblocks gaincallerandtoolset_name,server_tool_usegainscaller,
web_search_tool_resultgainscallerandraw(nilwhen absent). Code matching the
whole map with==must add them. The same holds for the mapsTools.extract_tool_uses/1
returns (now withtoolset_nameandcaller). Request.add_computer_tool/4raisesArgumentErroron unknown options (they were ignored).Claudio.Agentresumespause_turn(counts toward:max_turns) instead of returning it,
carries the responsecontainerto the next request, and dispatches client-toolset calls
to the handler keyed bytoolset_name. A handler of the wrong arity is now an error result
instead of a crash.Claudio.Agentcontinues afterstop_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
ArgumentErrornaming the handler (was aCaseClauseError).Claudio.Agent.run/4errors 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/2decodes results with string keys (waskeys: :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"}onresponse.contentmust match the
atom type or readraw. - Stream parse errors carry
%Jason.DecodeError{}(was%Poison.ParseError{}). Claudio.MCP.ToolAdapter.to_claudio_tool/2omits 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, ...(wasKeyword.validate!/2's "unknown keys [...]"). A
non-keywordoptsis anArgumentErrornaming the function instead of aFunctionClauseError
fromKeyword. StillArgumentError; code matching the old message text must update. - Stream
:stoperror_typeis an atom for known API error types (as oncreate); unknown identifiers stay strings. Claudio.Messages.Response.get_text/1accepts the legacyClaudio.Messages.create_message/2map.- Builder functions raise
ArgumentErrornaming the fix for common mistakes
(add_message(:system, …), string roles,nilcontent, out-of-range sampling values, string
max_tokens/tool_choice, keyword tools / thinking configs) instead of
FunctionClauseError.set_max_tokens/2now 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 attlother than"5m"/"1h";
add_message_with_document/5andsearch_result_block/4raise on unknown options
(previously ignored). APIError.typeis an atom forbilling_error,request_too_largeandtimeout_error(was a
string); a JSON error body withouterror.typeis typed from the HTTP status, and its
top-levelmessageis used.Client.new/2validates its config: it raisesArgumentErroron a missing/empty:tokenand on unknown config keys, and accepts a keyword list.version: nilnow falls back to the default like the other keys (it used to drop theanthropic-versionheader).- Legacy
Claudio.Messages.create_message/2now emits the[:claudio, :messages, :create]span.
It recognises only the string key"stream" => trueas streaming (as before); an atom
stream: trueis routed, and labelled, as non-streaming. create :stoptoken keys (measurements and metadata) are absent, not0, when a 200
response carries nousage, and a non-integer token value is dropped. Handlers that read
meta.input_tokensunconditionally should useMap.get/3.- A streaming
Req.Responsefromcreate/2/create_message/2now carries
private.claudio(the linkClaudio.Messages.Stream.parse_events/1reads). Code that matchesprivate: %{}
exactly or compares whole responses will see it. Claudio.Client.new/2adds a:claudio_telemetryReq step (request, response and error
steps) that emits the HTTP events. User steps andReq.merge/2keep working; code that
asserts on the step lists will see it.Claudio.Messages.Stream.parse_events/1no longer raises on a malformedmessage_start/
message_delta(a non-mapmessageorusage): it passes the events through.Batches.wait_for_completion/3andBatches.list/2raise on unknown options;
wait_for_completion/3also raises on a non-positive-integer:poll_interval/:timeout
or a:callbackthat is not a 1-arity function.Claudio.Messages.Stream.parse_events/1also accepts the whole%Req.Response{}.- The
:telemetryrequirement is now~> 1.3(was~> 1.0): span stop measurements need
1.3. Applications locked to an older:telemetrywill be asked to update it. - An invalid
retryvalue (anything buttrue,falseor a keyword list) or an unknown
retry key now raisesArgumentError, for per-client and app config alike. Previously
it was silently ignored and Req's default (GET/HEAD-only retries) applied. timeout/recv_timeoutmust be a non-negative integer (ms) or:infinity; anything
else raisesArgumentErrorwhen 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 raisesArgumentErroratClient.new/2(previously a baddelayraisedArithmeticError
from inside the request).Claudio.Messages.Request.add_message_with_image/5also takesmedia_type:as a keyword option;Claudio.Agent.run/4raises 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
errormetadata key on[:claudio, :messages, :create, :stop](aninspectstring that can
contain the API's error response body, or a malformed 200's body). It will be removed in 0.8.0; useerror_type.
Fixed
- MCP connector now emits a valid
mcp-client-2025-11-20request:
Request.add_mcp_server/2adds themcp_toolsetentry totoolsand declares
the beta. Previously the request had no toolset and no beta header and was rejected. Response.usagekeepsoutput_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 whenset_output_config/2was given atom keys — existing keys are stringified first.Claudio.Messages.Stream.build_final_message/1mergesmessage_deltausage overmessage_startusage
instead of replacing it, soinput_tokens(and cache counters) survive when the delta
omits them; previously the parsedResponse.usagecame back as a raw, incomplete map.Claudio.Messages.Stream.build_final_message/1keeps a streamed threshold-compaction summary
(compaction_delta); it was dropped, leaving"content": null.Request.set_context_management/2also declarescompact-2026-01-12when its edits hold a
compact_20260112edit; with onlycontext-management-2025-06-27the API rejects it. Its
doc example ("strategy" => "auto") was not a real API shape and is replaced.Response.to_assistant_content/1re-emitstoolset_nameandcallerontool_use(and
calleronserver_tool_use/web_search_tool_result); replaying a client-toolset call
withouttoolset_namewas rejected.Claudio.Messages.Stream.build_final_message/1decodes streamed tool input (input_json_deltachunks 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 emptyinput. Invalid JSON (e.g. cut off by
max_tokens) returns{:error, {:invalid_tool_input_json, index, partial_json}}.- Streaming:
Claudio.Messages.Stream.parse_events/1no longer tears apart an event split
across network chunks (it emitted anevent:with no data and adata:with no event, which
crashedbuild_final_message/1or 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}whenmessage_stopnever arrived — instead of a message silently missing
content andstop_reason. Streamed usage telemetry mergesmessage_startusage (input
and cache counts were lost). Response.to_assistant_content/1no longer sendsserver_nameon a replayed
mcp_tool_result— the API rejects it, so replaying any MCP-connector turn failed.Request.add_message/3declaresmcp-client-2025-11-20when its content holds an
mcp_tool_useormcp_tool_resultblock; replaying an MCP call on a turn without
add_mcp_server/2was a 400.Request.add_message/3with parsedResponsecontent (e.g.response.content) sends each
block in API shape (keeping acache_controlyou added); it sent"caller": null, which the
API rejects.Claudio.Messages.count_tokens/2also stripstemperature,top_k,top_p,
stop_sequences,metadata,service_tierandcontainer(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/2handles 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 anAPIErrortoo.- 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: falsedisables 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 nestedconfig :claudio, :claudio, ...form was read.Claudio.Batches.create/2dropsstreamfrom%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: truerequests
andmax_turns< 1 raise a clearArgumentError(was aCaseClauseError/ one call); a
failed on-demand compaction no longer re-sends the compaction request every turn.Claudio.MCP.ToolAdapter.to_claudio_tool/2emits tools the API accepts: no"description": null, aninput_schemawith"type", and anArgumentErrorfor names outside
^[a-zA-Z0-9_-]{1,128}$(were 400s).- MCP adapters:
HermesMCPunwraps Hermes response structs (list functions returned
{:ok, []}), passesopts, and documents the client-first module
({Hermes.Client.Base, MyClient}— the old{MyClient, pid}usage crashed);ExMCPrequests
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/4rejects content the API would reject (an empty error result, a
list of non-block values) and raisesArgumentErrorfor unencodable values instead of a
protocol error;Tools.extract_tool_uses/1normalizes a rawtool_usewithoutinput.Requesttool helpers: atom-keyed tool maps no longer get a duplicate JSON key;
add_tool_with_cache/3validates 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/5detects PNG, GIF
and WebP data when no media type is given (it always sentimage/jpeg).- Removed the
:poisondependency 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-Afteron 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_definitionandtool_resulttypespecs described atom-keyed maps;
define_tool/3andcreate_tool_result/4return string-keyed maps. The types now match
(documentation-only; no runtime change).- The legacy streaming
Claudio.Messages.create_message/2now matchescreate/2: it is
never retried (a retriedinto: :selfrequest 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
APIErrorcarries 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. TheAPIErrorkeeps 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
reqrequirement 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/3andSkills.create/2uploads 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.lockdoes not ship with the package: applications using Claudio should run
mix deps.update req mint hpaxthemselves. 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 inmix.exs(hex: [ignore_advisories: ...]).
Internal
- Added Credo (
--strict) and Dialyxir, amix precommitalias, 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 useclaude-haiku-4-5, since Opus 4.7+ and 5.x reject sampling params);enable_thinking/2andset_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