Version Packages#1373
Merged
Merged
Conversation
54f8dac to
b067526
Compare
b067526 to
e04d0ef
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
agents@0.11.6
Patch Changes
#1393
5aaf7c4Thanks @threepointone! - Migrate facet (sub-agent) bootstrap to the documented Cloudflare facet API: passid: parentNs.idFromName(name)toctx.facets.get()so the facet has its ownctx.id.name. Drops the__ps_namestorage write andsetName()bootstrap from_cf_initAsFacet.Why this matters. Facets spawned without an explicit
idinherit the parent DO'sctx.id, so on a facetctx.id.namewas the parent's name andthis.namesilently misreported as the parent's name. Anything that readthis.namefrom inside a sub-agent (includingselfPath,parentPath, and any user code) was getting the wrong value. With the explicitidpassed at facet creation time, the runtime gives the facet a realctx.id.name === nameand PartyServer's existing 0.5.xnamegetter resolvesthis.namecorrectly without any override mechanism, storage write, or cold-wake hydrate cost. Cold-wake recovery happens for free becauseidFromNameis deterministic and the factory re-runs on resume.This requires
partyserver≥ 0.5.3 (bumped in this release); 0.5.3 is byte-identical to 0.5.2 at runtime, only adds documentation and test coverage of the explicit-idfacet pattern.Other changes:
subAgent()is called from a parent class that isn't bound as a Durable Object namespace, the framework now throws a descriptive error pointing atwrangler.jsonc. Ifthis.constructor.namelooks minified (e.g._a), the message includes a bundler-config hint about preserving class names._cf_initAsFacetnow assertsthis.name === nameso any future bug in the parent's id construction surfaces immediately instead of silently mis-identifying the facet.alarm()docstring clarified to reflect the new resolution path (this.namefromctx.id.name, not from a storage hydrate).setName("default")+ explicitonStart()call pairs inoauth2-mcp-client,wait-connections-e2e, andcreate-oauth-providertest files have been removed; they were originally needed for partyserver 0.4.x bootstrap but became actualctx.id.namemismatches under partyserver 0.5.x.Backward-compatible for all public APIs:
subAgent(),parentAgent(),hasSubAgent(),listSubAgents(),deleteSubAgent(), andabortSubAgent()keep their signatures and semantics. The change is purely in the facet bootstrap internals; the user-facing effect is thatthis.nameinside a sub-agent now correctly reports the sub-agent's own name (was previously the parent's name when run against partyserver 0.5.x).See Document facet bootstrap pattern, no runtime change partykit#386 for the partyserver-side documentation companion.
#1395
63cfae6Thanks @threepointone! - Share submit concurrency bookkeeping throughagents/chatand use it from both chat agents.This extracts the
latest/merge/drop/debounceadmission state machine into aSubmitConcurrencyControllerexported fromagents/chat.AIChatAgentsemantics (including merge persistence) are preserved.Thinknow picks up the same pending-enqueue protection, so an overlapping submit is still detected while an accepted request is between admission and turn queue registration.Additional fixes:
Thinknow captures the turn generation immediately after admission and threads it into_turnQueue.enqueue, so a clear that lands between admission and queue registration cannot run a stale turn.#1396
fdf5a8aThanks @threepointone! - Fix Think persisting a duplicate orphan assistant row when a user submits during a streaming tool turn (#1381).When
useAgentChatposts an in-flight assistant snapshot it minted optimistically (client-generated ID,state: "input-available"), Session's INSERT-OR-IGNORE-by-ID would store it as a separate row alongside the eventual server-owned assistant for the sametoolCallId. The next turn'sconvertToModelMessagesthen produced a malformed Anthropic prompt and the provider rejected it.reconcileMessagesandresolveToolMergeIdnow live inagents/chatand Think runs them in_handleChatRequestbefore persistence. Staleinput-availablesnapshots pick up the server's tool output viamergeServerToolOutputs, and any incoming assistant whosetoolCallIdalready exists on a server row adopts the server's ID so persistence updates the existing row instead of inserting an orphan.@cloudflare/ai-chatkeeps its existing reconciler behavior; the only change is that it now importsreconcileMessages/resolveToolMergeIdfromagents/chatinstead of a local file.@cloudflare/ai-chat@0.5.2
Patch Changes
#1374
a6e22c3Thanks @threepointone! - FixuseAgentChatrecreating the AI SDK Chat instance — and orphaning any in-flightresumeStream— wheneveragent.nametransitions in place.The
useAgent({ basePath })+static options = { sendIdentityOnConnect: true }pattern lets the server own the Durable Object instance name. The browser starts with a placeholder ("default"), thenuseAgentmutates the agent object's.nameto the server-assigned value when the identity frame arrives.useAgentChatpreviously includedagent.namein the stable chat id it passed touseChat({ id }), so the transition changed the id and the AI SDK recreated the underlying Chat instance. The useEffect that fireschatRef.current.resumeStream()is keyed on the ref object, not the Chat instance, so it does not re-fire on recreation — the resumed stream kept feeding chunks into the orphaned Chat's state while React subscribed to the new Chat's state, so the user saw an empty assistant reply after a mid-stream refresh until the server's finalCF_AGENT_CHAT_MESSAGESbroadcast landed.useAgentChatnow distinguishes an in-placeagent.namemutation from a genuine "consumer switched chats" event by checking the agent object's reference identity:agentreference,namemutation → not a chat switch; keep the Chat instance stable.agentreference → chat switch; recompute the stable chat id so the AI SDK recreates the Chat against the new conversation.The stable id is also still upgraded once from the identity-only fallback to the URL-resolved key when the WebSocket handshake completes.
Consumers who want to switch chats without remounting should pass a different
agentobject (e.g. a newuseAgent({...})call with a differentname). To get a completely fresh Chat (e.g. when mounting a different chat tab), the conventional React pattern —key={chatId}on the parent or swapping the subtree — continues to work.#1395
63cfae6Thanks @threepointone! - Share submit concurrency bookkeeping throughagents/chatand use it from both chat agents.This extracts the
latest/merge/drop/debounceadmission state machine into aSubmitConcurrencyControllerexported fromagents/chat.AIChatAgentsemantics (including merge persistence) are preserved.Thinknow picks up the same pending-enqueue protection, so an overlapping submit is still detected while an accepted request is between admission and turn queue registration.Additional fixes:
Thinknow captures the turn generation immediately after admission and threads it into_turnQueue.enqueue, so a clear that lands between admission and queue registration cannot run a stale turn.#1396
fdf5a8aThanks @threepointone! - Fix Think persisting a duplicate orphan assistant row when a user submits during a streaming tool turn (#1381).When
useAgentChatposts an in-flight assistant snapshot it minted optimistically (client-generated ID,state: "input-available"), Session's INSERT-OR-IGNORE-by-ID would store it as a separate row alongside the eventual server-owned assistant for the sametoolCallId. The next turn'sconvertToModelMessagesthen produced a malformed Anthropic prompt and the provider rejected it.reconcileMessagesandresolveToolMergeIdnow live inagents/chatand Think runs them in_handleChatRequestbefore persistence. Staleinput-availablesnapshots pick up the server's tool output viamergeServerToolOutputs, and any incoming assistant whosetoolCallIdalready exists on a server row adopts the server's ID so persistence updates the existing row instead of inserting an orphan.@cloudflare/ai-chatkeeps its existing reconciler behavior; the only change is that it now importsreconcileMessages/resolveToolMergeIdfromagents/chatinstead of a local file.@cloudflare/shell@0.3.4
Patch Changes
#1384
a7059d4Thanks @threepointone! - IntroduceWorkspaceFsLike— the minimumWorkspacesurface required byWorkspaceFileSystemandcreateWorkspaceStateBackend.WorkspaceFileSystem's constructor andcreateWorkspaceStateBackend's parameter both now accept anyWorkspaceFsLike(aPick<Workspace, …>of the 16 filesystem methods the adapter reaches for) rather than a concreteWorkspace. Non-breaking —Workspacestill satisfiesWorkspaceFsLikeso every existing call site keeps working without changes.This unlocks wrapping a real
Workspacebehind your own layer — most commonly a cross-DO proxy that forwards each call to a parent agent's workspace over RPC — and still using it as the storage for codemode'sstate.*sandbox API viacreateWorkspaceStateBackend. Seeexamples/assistantfor the end-to-end pattern withSharedWorkspace.@cloudflare/think@0.4.1
Patch Changes
#1395
63cfae6Thanks @threepointone! - Share submit concurrency bookkeeping throughagents/chatand use it from both chat agents.This extracts the
latest/merge/drop/debounceadmission state machine into aSubmitConcurrencyControllerexported fromagents/chat.AIChatAgentsemantics (including merge persistence) are preserved.Thinknow picks up the same pending-enqueue protection, so an overlapping submit is still detected while an accepted request is between admission and turn queue registration.Additional fixes:
Thinknow captures the turn generation immediately after admission and threads it into_turnQueue.enqueue, so a clear that lands between admission and queue registration cannot run a stale turn.#1394
a0a0d17Thanks @threepointone! - think: addbeforeSteplifecycle hook andoutputpassthrough onTurnConfig.beforeStep(ctx)— new lifecycle hook called before each AI SDK step in the agentic loop, wired tostreamText({ prepareStep }). Receives aPrepareStepContext(the AI SDK'sPrepareStepFunctionparameter —steps,stepNumber,model,messages,experimental_context) and may return aStepConfig(PrepareStepResult) to overridemodel,toolChoice,activeTools,system,messages,experimental_context, orproviderOptionsfor the current step. UsebeforeTurnfor turn-wide assembly andbeforeStepwhen the decision depends on the step number or previous step results. Resolves #1363.TurnConfig.output— new optional field onTurnConfigforwarded tostreamText. Accepts the AI SDK's structured-output spec (e.g.Output.object({ schema }),Output.text()) so a single agent can keep tools enabled on intermediate turns and return schema-validated structured output on a designated turn — without losing tools at model construction. Combine withactiveTools: []for providers that strip tools whenresponseFormat: "json"is active (e.g.workers-ai-provider). Resolves #1383.@cloudflare/think:PrepareStepFunction,PrepareStepResult,PrepareStepContext,StepConfig.beforeStepis available to subclasses; it is not dispatched to extensions (the AI SDKprepareStepboundary surfaces non-serializable inputs likeLanguageModelinstances). The AI SDK does not exposeoutputormaxStepsper step — set those at the turn level viaTurnConfig. All other extension hook subscriptions are unchanged.#1372
040da0fThanks @threepointone! - Remove Think's unused internalsession_idconfig scaffolding and move Think's private config into a dedicatedthink_configtable.Older builds wrote Think-owned config into Session's shared
assistant_config(session_id, key, value)table even though Think never actually had top-level multi-session support and_sessionId()always returned the empty string. Think now stores its private config rows inthink_config(key, value), which better matches the shipped model of one Think Durable Object per conversation and avoids overloading Session's shared metadata table.Existing Durable Objects are migrated automatically on startup: legacy Think-owned keys stored in
assistant_configwithsession_id = ''are copied intothink_configbefore config reads and writes continue.#1396
fdf5a8aThanks @threepointone! - Fix Think persisting a duplicate orphan assistant row when a user submits during a streaming tool turn (#1381).When
useAgentChatposts an in-flight assistant snapshot it minted optimistically (client-generated ID,state: "input-available"), Session's INSERT-OR-IGNORE-by-ID would store it as a separate row alongside the eventual server-owned assistant for the sametoolCallId. The next turn'sconvertToModelMessagesthen produced a malformed Anthropic prompt and the provider rejected it.reconcileMessagesandresolveToolMergeIdnow live inagents/chatand Think runs them in_handleChatRequestbefore persistence. Staleinput-availablesnapshots pick up the server's tool output viamergeServerToolOutputs, and any incoming assistant whosetoolCallIdalready exists on a server row adopts the server's ID so persistence updates the existing row instead of inserting an orphan.@cloudflare/ai-chatkeeps its existing reconciler behavior; the only change is that it now importsreconcileMessages/resolveToolMergeIdfromagents/chatinstead of a local file.#1374
a6e22c3Thanks @threepointone! - Fix stream resumption on page refresh: do not broadcastcf_agent_chat_messagesfrom Think'sonConnectwhile a resumable stream is in flight.Previously, Think unconditionally sent a
cf_agent_chat_messagesframe on every new WebSocket connection. When a client refreshed during an active chat turn, that broadcast arrived in the same connect sequence ascf_agent_stream_resumingand overwrote the in-progress assistant message the client was about to rebuild from the resumed stream. The assistant reply would stay hidden until the server finished the turn and re-broadcast the persisted history.Now Think only broadcasts
cf_agent_chat_messageson connect when there is no active resumable stream. During an active stream the resume flow is the authoritative source of state:STREAM_RESUMINGtriggers replay of buffered chunks, and the final state broadcast happens when the turn completes. This matches the behavior thatAIChatAgentalready had.Marked the internal
_resumableStreamfield asprotected(previouslyprivate) so framework subclasses and focused tests can coordinate around the resume lifecycle.#1384
a7059d4Thanks @threepointone! - IntroduceWorkspaceLike— type thethis.workspacefield as the minimum surface Think actually uses instead of the concreteWorkspaceclass.Think'sworkspaceis now typed asWorkspaceLike(Pick<Workspace, "readFile" | "writeFile" | "readDir" | "rm" | "glob" | "mkdir" | "stat">) rather thanWorkspace.createWorkspaceTools()likewise accepts anyWorkspaceLike. The default runtime value is unchanged — a fullWorkspacebacked by the DO's SQLite — so the vast majority of consumers need no changes.This unlocks patterns like a shared workspace across multiple agents: a child agent can override
workspacewith a proxy that forwards each call to a parent DO via RPC, and the rest of Think's workspace-aware code (the builtin tools, lifecycle hooks) keeps working without cast gymnastics. Seeexamples/assistantfor the cross-chat shared workspace built on this.Consumers who use
createWorkspaceStateBackend(workspace)from@cloudflare/shell(codemode'sstate.*API) still need a concreteWorkspace— that helper reaches for more of the filesystem surface thanWorkspaceLikecovers.