Changelog
On this page
Version 0.18.0
Every @aparte/* package ships at this version (they are released in lockstep).
Minor Changes
-
8f2d28e: New: run the agent loop on your server.
createAparteAgentHandlerruns it next to your tools and streams it;createAgentStreamRunnerplugsAparteClientinto it — tool rows, Stop, the approval panel and your approval policy work as they do in the browser, and a user can leave in the middle of an approval — another conversation, a closed tab — and answer it when they come back.Until now a server with its own tools could only drive the chat display-only: the message, the segments, closing the turn and four lifecycle events, by hand. The handler pauses the loop on a call that needs a person, or on a tool the browser runs (
ask_user), and ends the stream with a signed continuation ticket; the page asks — the same panel, the same policy — and resumes with the answer, in the same bubble, under one pair of lifecycle events. Nothing is stored on the server: the paused call lives in the conversation you already save (AparteToolCallSegment.deferred) — saved the moment the run pauses, since the reply now dispatchesaparte-message-pausedand the conversation controller persists on it — and a conversation that loads with one resumes on its own — the conversation controller now dispatchesaparte-conversation-loaded, andclient.resumeDeferred()is there for a host that loads conversations another way. A message sent instead of an answer closes the paused call.The server decides which calls need a person; the page’s policy answers when it has an opinion, and otherwise the person is asked. The ticket makes the server run only the calls it emitted, for the reply it paused; it does not make a tool’s input trustworthy, and it is not single-use unless you pass
onTicket. A server tool checks who is calling with the request it gets ascontext.request(AparteToolContext.request, new). AtoolChoicenaming a tool, or a history ending on unanswered calls without a ticket, is refused with a 400, and a server tool’s own error text stays in the server’s log. An approvalpayloadis dropped unless the tool accepts one. Guide: Run the agent loop on your server.@aparte/core -
3776dfc: The model now remembers what its tools returned: each turn’s history carries the tool calls and their results, not only the text. If you serialize the history yourself — a backend of your own that does not go through an aparté provider or
toModelMessages— it now contains assistant messages withtoolCallsandrole: 'tool'messages, the shapes a tool round trip already used inside one turn: map them, or drop both. Through an aparté provider there is nothing to do. Requests grow on tool-heavy conversations;@aparte/plugin-compactionnow counts those calls when it measures a conversation, and summarises long ones as before.Until now
AparteClientsent user and assistant text alone, so aread_fileresult was on screen and gone from the model’s context one turn later — “what did the file say?” had no answer. A reply is now sent in the order it was shown: the text before some calls and those calls as an assistant message, each call’s result as atoolmessage, the text after them as the next assistant message. Every call is answered, because providers reject a call left without one: a refused call sends its reason, a failed one its error line, a stopped one “The tool call was stopped before it ran.” Send, retry and edit build the history through the same function, so they cannot disagree. The compactor’s selector used to count a tool call as free (it has nocontent); it now costs what its arguments and result send the model.@aparte/core,@aparte/plugin-compaction -
65845fe: New:
runStreamAgentcan pause on a tool call it cannot finish and resume it later. Passdeferraland a call that needs a person’s decision, or a tool markedexternal, ends the run onrun-suspendedinstead of waiting; a later run whose history ends on those calls, with their answers indeferral.outcomes, picks up where it stopped. If youswitchexhaustively overStreamRunEvent(an adapter of your own), add a case forrun-suspended; it only occurs when you passdeferral.Nothing changes for a caller that does not pass
deferral: the approval resolver still answers in place. The run that pauses reads the model’s reply to its end, so every call of that reply is pending at once and a client can answer them in one round trip; nothing runs after the pause point, and a call the gate denies is refused, never deferred. A resumed run answers its pending calls through the same code a streamed call takes, and emits notool-startfor them, since their rows exist already.unansweredTail(messages)returns the calls a history leaves unanswered. This is the loop half of running the agent on a server;@aparte/coreadds the server handler and the browser runner.@aparte/engine
Patch Changes
-
81661e9: Stopping a reply no longer prints an uncaught
AbortErrorin the console when the stream had already failed.An aborted
fetcherrors its response body, and the Stop handler then cancelled the reader.reader.cancel()returns a promise that rejects on an errored stream, and it was wrapped in atrythat only catches synchronous errors, so the rejection went unhandled. It is caught now.@aparte/core
Version-only bumps (no changes of their own): @aparte/provider-ai-sdk, @aparte/provider-openai-compat, @aparte/provider-scenario, @aparte/provider-transformers, @aparte/plugin-approval, @aparte/plugin-artifacts, @aparte/plugin-ask-user, @aparte/plugin-marked, @aparte/plugin-model-selector, @aparte/plugin-shiki, @aparte/plugin-streaming-markdown, @aparte/plugin-titler, @aparte/angular, @aparte/react, @aparte/svelte, @aparte/vue, @aparte/locale-fr, @aparte/docs-mcp.