Turns omitted MaxTokens on the provider request, so Bedrock applied its
conservative 4096 default and silently truncated long writes/edits with
stopReason=length. In the TUI this read like the interaction timed out.
Thread the resolved model's catalog MaxOutput through to the request:
catalog Model.MaxOutput -> Resolved.MaxOutput -> Agent.MaxTokens
-> provider.Request.MaxTokens
Zero still falls back to each provider's own default, so models without a
catalog MaxOutput are unaffected. The SDK path inherits this via NewAgent.
Also surface StopLength explicitly in the TUI ('response hit the output
limit -- ask it to continue') instead of ending silently.
Tests: TestAgentPropagatesMaxTokens (Agent.MaxTokens reaches the wire) and
TestBedrockBuildRequestMaxTokens (non-zero flows through; zero -> 4096).
A turn aborted mid-flight (cancel, connection drop, dev-server
ECONNREFUSED) can leave an assistant tool_use block with no matching
tool_result in the live transcript. repairToolUseResultPairs already
fixes this, but only ran in OpenSession (load time), so an in-process
abort left the transcript broken until restart. The next request was
then rejected by Anthropic/OpenAI with 'tool_use ids were found without
tool_result blocks'.
Run the same repair on the outbound messages in oneTurn. It is pure and
a no-op on valid transcripts, so there is no hot-path cost beyond a
single linear scan and no behavior change for healthy sessions.
On the openai-codex (Responses API) route a tool result serialized to a
string-only function_call_output, dropping ImageBlock content, and the
agent loop's tool-image mirror only fired for provider "openai". So
images returned by read reached the TUI but never the model, which then
correctly reported it received no image content.
Extend the mirror to fire for "openai-codex" too (its client already
serializes user-message images as input_image, so the bytes arrive),
and have the codex tool-result serializer emit a short placeholder for
an image-only result instead of an empty output the API may reject.
Adds a test covering both behaviors.
Single Go module, four top-level packages under packages/. Import
paths become github.com/patriceckhart/zot/packages/<name>; downstream
consumers can depend on individual packages without pulling the rest.
Layout:
packages/provider/ LLM clients + catalog
packages/provider/auth/ credential store + OAuth + login server
packages/core/ agent loop, sessions, cost
packages/tui/ terminal toolkit + chat view
packages/agent/ CLI wiring, system prompt
extensions/ extproto/ modes/ tools/ skills/ swarm/
sdk/ (was pkg/zotcore, package renamed zotcore -> sdk)
ext/ (was pkg/zotext, package renamed zotext -> ext)
internal/ and pkg/ removed. The internal/assets logo moved into
packages/provider/auth/assets.
Public Go SDK identifiers renamed:
pkg/zotcore (package zotcore) -> packages/agent/sdk (package sdk)
pkg/zotext (package zotext) -> packages/agent/ext (package ext)
This breaks Go-based extensions and embedders; the JSON wire protocol
for extensions and RPC is unchanged, so non-Go extensions, already-
built extension binaries, and zot rpc consumers are unaffected.
Docs, examples, and the built-in write-zot-extension skill updated
for the new paths and identifiers. Shadow-bug fixes in code samples
(ext := ext.New -> e := ext.New).