├── .gitignore ├── .prettierrc ├── CHANGELOG.md ├── README.md ├── index.html ├── lib ├── agent-context.tsx ├── get-path-prefix.ts ├── index.ts ├── use-agent-context.ts ├── use-agent-debug.ts ├── use-agent-memo.ts ├── use-agent-state.ts ├── use-agent-tool.ts ├── use-agent.ts └── zod-parse-json.ts ├── package-lock.json ├── package.json ├── src ├── demos │ ├── agentic-counter │ │ ├── index.css │ │ ├── index.html │ │ └── main.jsx │ ├── hierarchy │ │ ├── index.css │ │ ├── index.html │ │ └── main.jsx │ └── john-age-changer │ │ ├── index.css │ │ ├── index.html │ │ └── main.jsx ├── index.css ├── main.jsx └── vite-env.d.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.lib.json ├── tsconfig.node.json └── vite.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120 3 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v1.0.0-beta.27 2 | 3 | - Breaking change: `useAgent` now returns a simplified `run` method with agent lifecycle hooks as parameters 4 | - Added: `status` property in the agent returned by `useAgent` 5 | 6 | # v1.0.0-beta.25 7 | 8 | - Changed: parallel tool-use is disabled by default 9 | 10 | # v1.0.0-beta.24 11 | 12 | - Changed: improve tool rendering to agent 13 | 14 | # v1.0.0-beta.23 15 | 16 | - Added: `useAgentContext` for building custom agents 17 | 18 | # v1.0.0-beta.22 19 | 20 | - Added: tool name auto-aliasing so you can use the same tool name with different implementations 21 | 22 | # v1.0.0-beta.20 23 | 24 | - Added: partial support `AgentContext` to allow hierarchical organization of agents 25 | - Changed: improved state and tool description for agent 26 | - Fixed: some characters in tool name will cause error 27 | 28 | # v1.0.0-beta.19 29 | 30 | - Added: `description` field for state hook 31 | - Added: model override 32 | 33 | # v1.0.0-beta.18 34 | 35 | - Added: require `dependencies` for memo to prevent infinite loops 36 | 37 | # v1.0.0-beta.17 38 | 39 | - Added: abortable agent runs 40 | 41 | # v1.0.0-beta.16 42 | 43 | - Added: `dependencies` field in memo and tool hooks options 44 | - Added: `description` field in tool hook option 45 | 46 | # v1.0.0-beta.14 47 | 48 | - Added: `enabled` option in all the hooks to dynamically show/hide state/tool 49 | - Added: `useAgentContext` hook to expose tools and states for building a custom agent 50 | 51 | # v1.0.0-beta.9 52 | 53 | - Initial beta release 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Agent Hooks 2 | 3 | | Agentic Counter Demo | Agentic Todo Demo | 4 | | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 5 | | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/edit/react-agentic-counter?file=src%2Fmain.jsx) | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/chuanqisun/react-agent-hooks?file=src%2Fmain.jsx) | 6 | 7 | Turn React Hooks into LLM Tools 8 | 9 | - 🪝 Familiar: same semantics as React hooks 10 | - 🤝 Symbiotic: human interface and Agent interface derived from the same state. 11 | - 🛡️ Safe: developer controls the schema for Agentic state change. 12 | - ➕ Incremental adoption: use as much or as little as you want. 13 | - 📦 Composable: fully interoperable with classic React hooks. 14 | - 🔮 Future-ready: forward-compatible with MCP and llms.txt. 15 | 16 | **Before** 17 | 18 | ```jsx 19 | import { useCallback, useState } from "react"; 20 | 21 | function MyComponent() { 22 | const [name, setName] = useState("John Doe"); 23 | const [age, setAge] = useState(30); 24 | const adjust = useCallback((delta) => setAge((prev) => prev + delta), []); 25 | 26 | return ( 27 |
28 |

{name}

29 |

{age}

30 | 31 | 32 |
33 | ); 34 | } 35 | ``` 36 | 37 | **After** 38 | 39 | ```jsx 40 | import { useAgent, useAgentState, useAgentTool } from "react-agent-hooks"; 41 | 42 | export function MyComponent() { 43 | const agent = useAgent({ apiKey: "******" }); 44 | const [name, setName] = useAgentState("Name", "John Doe"); 45 | const [age, setAge] = useAgentState("Age", 30); 46 | const adjust = useCallback((delta) => setAge((prev) => prev + delta), []); 47 | useAgentTool("Change age", z.number().describe("the delta of age change"), adjust); 48 | 49 | return ( 50 |
51 |

{name}

52 |

{age}

53 | 54 | 55 |
56 | ); 57 | } 58 | ``` 59 | 60 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz_small.svg)](https://stackblitz.com/edit/react-agentic-counter?file=src%2Fmain.jsx) 61 | 62 | ## Get Started 63 | 64 | ```sh 65 | npm install react-agent-hooks 66 | ``` 67 | 68 | ## Usage 69 | 70 | ### Give Agent "Eyes" 👀 71 | 72 | ```tsx 73 | import { useAgentMemo } from "react-agent-hooks"; 74 | 75 | function MyComponent() { 76 | const [name, setName] = useState("John Doe"); 77 | const [age, setAge] = useState(30); 78 | 79 | // Describe a readable state to the Agent 80 | useAgentMemo("User's profile", () => ({ name, age }), [name, age]); 81 | 82 | return ( 83 |
84 |

{name}

85 |

{age}

86 |
87 | ); 88 | } 89 | ``` 90 | 91 | ### Give Agent "Hands" 👏 92 | 93 | ```tsx 94 | import {z} from "zod"; 95 | import { useAgentState, useAgentTool } from "react-agent-hooks"; 96 | 97 | function MyComponent() { 98 | 99 | // Describe a readable state to the Agent while exposing a setter function to developer 100 | const [foodPreferences, setFoodPreferences] = useAgentState("food preference", ["Pizza", "Sushi"]); 101 | 102 | // Wrap the setter as a tool and describe it to the Agent 103 | const addFoodPreference = useAgentTool("add-food-preference", z.object(foodItems: z.array(z.string())), (foodItems) => { 104 | setFoodPreferences((prev) => [...prev, ...foodItems]); 105 | }); 106 | const removeFoodPreference = useAgentTool("remove-food-preference", z.object(foodItems: z.array(z.string())), (foodItems) => { 107 | setFoodPreferences((prev) => prev.filter((item) => !foodItems.includes(item))); 108 | }); 109 | 110 | return 113 | } 114 | ``` 115 | 116 | ### Run the Agent 117 | 118 | ```tsx 119 | import { useAgent } from "react-agent-hooks"; 120 | 121 | function MyApp() { 122 | // Run the Agent with a prompt 123 | // Agent always sees the latest states from `useAgentState`, `useAgentMemo`, and can uses the latest tools from `useAgentTool` 124 | const agent = useAgent({ apiKey: "******" }); 125 | 126 | // Call the Agent 127 | const handleFormSubmit = (e) => { 128 | e.preventDefault(); 129 | const input = e.target.elements[0].value; 130 | agent.run(input); 131 | }; 132 | 133 | return ( 134 |
135 | 136 | 137 |
138 | ); 139 | } 140 | ``` 141 | 142 | ### Compose Agentic Application 143 | 144 | Inside a component, use the `enabled` option to dynamically show/hide states and tools to the Agent. 145 | 146 | ```tsx 147 | const shouldShowFeature = true; // You can dynamically decide this value 148 | useAgentMemo("User's profile", () => ({ name, age }), [name, age], { enabled: shouldShowFeature }); 149 | useAgentState("some state", { name: "Some state" }, { enabled: shouldShowFeature }); 150 | useAgentTool( 151 | "update state", 152 | z.object({ name: z.string() }), 153 | (newState) => { 154 | setSomeState(newState); 155 | }, 156 | { enabled: shouldShowFeature }, 157 | ); 158 | ``` 159 | 160 | In a component tree, use JSX to dynamically show/hide states and tools to the Agent. 161 | 162 | ```tsx 163 | function ParentComponent() { 164 | // A higher level component can dynamically decide what lower level states/tools are available 165 | const = [shouldShowFeature, setShouldShowFeature] = useAgentState("toggle feature", z.boolean(), true); 166 | 167 | useAgentTool("toggle feature", z.object({}), () => setShouldShowFeature(prev) => !prev); 168 | 169 | return {shouldShowFeatureB ? : null}; 170 | } 171 | 172 | function ChildComponent() { 173 | // The state and tool will be available to the Agent only if the child component is rendered 174 | useAgentState("some state", { name: "Some state" }); 175 | useAgentTool("update state", z.object({ name: z.string() }), (newState) => { 176 | setSomeState(newState); 177 | }); 178 | 179 | return
...
; 180 | } 181 | ``` 182 | 183 | ### Build a custom Agent 184 | 185 | Access currently active states and tools with `useAgentContext` hook. Here is an example of building your own agent 186 | 187 | ```tsx 188 | export function useMyAgent() { 189 | const openai = new OpenAI({ dangerouslyAllowBrowser: true, apiKey: "******" }); 190 | const agentContext = useAgentContext(); 191 | 192 | const run = async (prompt: string) => { 193 | const task = openai.beta.chat.completions.runTools({ 194 | stream: true, 195 | model: "gpt-4.1", 196 | messages: [ 197 | { 198 | role: "system", 199 | content: ` 200 | User is interacting with a web app in the following state: 201 | \`\`\`yaml 202 | ${agentContext.stringifyStates()} 203 | \`\`\` 204 | 205 | Based on user's instruction or goals, either answer user's question based on app state, or use one of the provided tools to update the state. 206 | Short verbal answer/confirmation in the end. 207 | `.trim(), 208 | }, 209 | { 210 | role: "user", 211 | content: prompt, 212 | }, 213 | ], 214 | tools: agentContext.getTools(), 215 | }); 216 | 217 | return task; 218 | }; 219 | 220 | return { 221 | run, 222 | }; 223 | } 224 | ``` 225 | 226 | ### Scale-up with Context 227 | 228 | The `AgentContext` is an optional React Context to help you hierarchically organizing states and tools. 229 | This prevents naming collisions and reduces agent confusion from too many similar states and tools. 230 | 231 | ```tsx 232 | import { AgentContext } from "react-agent-hooks"; 233 | 234 | function MyApp() { 235 | return ( 236 | 237 | 238 |