v0.2.1
A fix release. Three of the fixes are about a token count that read 0, or a request that read 400, against endpoints that speak a dialect almost right. They were found the same way: one prompt sent through every provider in a real config, and the bytes on the wire read back.
Added
/modeloffers the agent's candidate set. The picker used to ask the one provider the run had resolved to and fork on the answer — a list when the listing worked, a bare name field when it did not. It now lists whatagents.<name>.modelsdeclares: entries andprovider:ids as written, and everyprovider:*wildcard fetched in one concurrent round (the wait is the slowest endpoint, not the sum; Esc drops them all). A wildcard whose listing fails is one row saying so, not a surface that gives up. The list is a combo box: the query field is always open, filters as you type, and its text commits as typed through ause "…" as typedrow after the last real one.NO_COLORis honoured — and so areTERM=dumband a piped stdout. None of the three did anything before: every style was hard-wired on, so a terminal that asked for no color still got bold, faint and underline, and a bare reset where each color had been. Now the decision is made once at startup and the chat — replies, tool output, diffs, links — is plain text with no escape sequence at all, while the frame around it (composer, status row, panels) keeps its bold, faint and reverse video and drops every color, so it stays readable. Images still render in color; their pixels are the picture.IOTA_LOG=<path>writes iota's internal diagnostics to a file — its own events at debug level, its libraries' at info, one timestamped line each. It is a developer's tap: nothing you need to see depends on it.
Changed
-M provider:idbrings themodels:entry serving that pair, or nothing — never the first candidate's knobs.-Mused to replace the model id and leave every other field of the entry the run had resolved to first in place, soiota run chat -M claude:claude-haiku-4-5-20251001on an agent whose first candidate declarestemperatureandtop_psent both to Claude, which refuses the pair: a 400 for a flag that named a different model. The flag now resolves to the entry that serves exactly that provider and id when there is one — its own knobs travel with it — and to the bare pair otherwise, every other field at its default; the agent's overrides apply as before. A bare-M idis<current provider>:idand follows the same rule.-M "",provider:*and entry names are unchanged.effort: xhighandeffort: maxreach Gemini asthinkingLevel: HIGH. Gemini 3 knows LOW, MEDIUM and HIGH; the value used to go up uppercased and verbatim, so an agent witheffort: maxgot a 400 from Vertex on every call. OpenAI and Anthropic still receive the value as written.- rustls is 0.23.45 (RUSTSEC-2026-0285), and
cargo denynow gates the dependency graph in CI: licenses, advisories, duplicate versions, sources.
Fixed
-
Input tokens no longer read 0 on Anthropic-compatible endpoints that report them late. The Anthropic stream carries the input count in
message_startand the output count inmessage_delta; GLM's endpoint (open.bigmodel.cn) sendsmessage_startwith zeros and the whole figure inmessage_delta.message_delta's usage is now laid overmessage_start's, so whichever event carries a number wins. The official API reports the same numbers as before. -
A usage object carrying both OpenAI namings decodes. zenmux reports
input_tokensbesideprompt_tokens,output_tokensbesidecompletion_tokensand both*_tokens_details— every figure under both names. The decoder treated the pair as a duplicate field and dropped the event it rode in: in a stream that is the final event, so the reply arrived and the usage read 0 with no error; on a-mrun the whole call failed withchat error: duplicate field 'input_tokens'. The object is now read name by name, the Responses name first. -
An MCP tool that declares no input goes out without an empty schema on the Responses wire.
"parameters":{}— a schema with notype— was still sent on/responseswhere chat-completions had already dropped it. -
The MCP handshake reports iota's real version.
clientInfo.versionhad been the literal1.0.0since the port began. -
An MCP server that lists the same tool twice now says so. The duplicate was skipped silently: the warning went to a log channel nothing listened to, and the release build had compiled it out entirely. It is now one
Warning: mcp server <name>: duplicate wire tool name <wire>, skippingline on stderr for a-mrun, and a⚠ MCP <name>: …notice in the chat. -
edit_fileno longer corrupts files that are not UTF-8. It read the whole file through a lossy decode and wrote the decoded result back, so one edit to a source file in Latin-1, Shift-JIS or GBK replaced every byte in it that is not valid UTF-8 withU+FFFD— across the whole file, not only the span being edited — and the diff you were shown came from the same decoded buffer, so it looked like nothing had happened. The search, the uniqueness check and the replacement now run on the file's bytes and the original bytes are written back; only the diff and the numbered snippet are still text, because a terminal renders text. This bug is in 0.1.0 and 0.2.0: a file one of those versions edited may have lost its non-ASCII content, and the loss is not recoverable from the diff — check it against version control.