feat: expose readable state property on useAgent and AgentClient#1152
Merged
Conversation
Add a readable `state` property to AgentClient and the object returned by `useAgent`, making client state observable and reactive in React. Implementation: AgentClient now stores `state` (updated on CF_AGENT_STATE broadcasts and on client setState). The React hook tracks state in local React state (`agent.state`) and updates it on server broadcasts and client setState (causing re-renders). `setState` now updates the local property optimistically. Other changes: updated docs and many example/demo components to read from `agent.state` (with safe optional chaining and sensible defaults), added unit/integration tests and TypeScript tests to validate behavior and types, and created a changeset describing the minor feature. Backwards compatible: existing `onStateUpdate` callbacks continue to work.
🦋 Changeset detectedLatest commit: dc45862 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
agents
@cloudflare/ai-chat
@cloudflare/codemode
hono-agents
@cloudflare/shell
@cloudflare/think
@cloudflare/voice
@cloudflare/worker-bundler
commit: |
Update client SDK docs to clarify the ordering and timing of setState: the state is sent to the agent, onStateChanged runs, the agent broadcasts the new state, and agent.state updates on the next render for React (or immediately for AgentClient). Add a unit test (useAgent.test.tsx) that verifies agent.state updates on the next React render after setState, ensuring the hook follows the documented semantics.
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.
Summary
Closes #1004
Both
useAgent(React) andAgentClient(vanilla JS) now expose astateproperty that tracks the current agent state. Previously, state was write-only viasetState()— reading state required manually tracking it through theonStateUpdatecallback with a separateuseState. This made{ ...agent.state, field: newValue }silently spreadundefined, producing partial state that replaced the full server state.useAgent:agent.stateis reactive — backed by ReactuseState, the component re-renders when state changes from either the server or client-sidesetState()AgentClient:client.stateupdates synchronously onsetState()and when server broadcasts arriveState | undefined— starts undefined, populated when the server sends state on connect (frominitialState) or whensetState()is calledonStateUpdatecallback continues to work exactly as before — the new property is additiveBefore (workaround from the issue)
After
Changes
Core (
packages/agents/)src/react.tsxuseStatefor state tracking inuseAgent. Updated all 3 overload signatures to include `state: Statesrc/client.tsTests
src/react-tests/useAgent.test.tsxsrc/react-tests/client.test.tssrc/tests-d/typed-use-agent.test-d.tsstate satisfies {} | undefinedsrc/tests-d/untyped-use-agent.test-d.tssrc/tests-d/typed-agent-client.test-d.tsDocs (5 files)
docs/client-sdk.mdagent.state. State sync section restructured: "Reading State" → "Pushing State Updates" → "Listening for State Changes". Return value and AgentClient methods sections liststate.docs/state.mdagent.statepattern. Fixed import paths (agents/reactnot@cloudflare/agents/react).docs/getting-started.mduseState/onStateUpdateworkaround.docs/adding-to-existing-project.mddocs/webhooks.mdExamples (11 files)
All examples migrated from
useState+onStateUpdateworkaround to readingagent.statedirectly:examples/tictactoeconst state = agent.state ?? defaultStateexamples/github-webhookrepoStaterefs withagent.stateexamples/assistantconst agents = agent.state?.agents ?? []playground/StateDemoplayground/ReadonlyDemoconst state = agent.state ?? initialStateplayground/SecureDemoplayground/ReceiveDemoplayground/RoutingDemoconst connectionCount = agent.state?.counter ?? 0playground/PipelineDemoconst lastRun = agent.state?.lastRun ?? nullplayground/WorkersDemoplayground/McpClientDemoconst isConnected = !!agent.state?.connectedServerDesign decisions
State not reset on disconnect —
agent.stateretains the last known value during reconnection. The server re-sends authoritative state on reconnect viaCF_AGENT_STATE, which overwrites any stale value. This prevents UI flashing. Users can checkagent.identifiedto know if state might be stale.Optimistic client updates — When
setState()is called,agent.stateupdates immediately before the message is sent to the server. If the server rejects it (readonly connection),onStateUpdateErrorfires but state is already set. The server will re-send authoritative state, correcting the optimistic value.State | undefinedtype — State starts asundefinedand is populated when the server sends state on connect. This matches the existingonStateUpdatesemantics and is safe with optional chaining (agent.state?.field).Test plan
npm run check— all 65 projects typechecknpm run test— all tests passnpm run test:react— 76 integration tests pass (15 new)npm run test:workers— 747 tests passstatetype on typed/untypeduseAgentandAgentClient.d.tsfiles includestate: State | undefinedin public APIMade with Cursor