ag-ui-protocol / ag-ui-protocol/ag-ui
[Bug]: AbstractAgent's public methods are unbound, so `const { addMessage } = agent` throws
- Langage dominant
- Python
- Étoiles
- 15.9k
- Forks
- 1.4k
- Merge moyen
- 1 j 17 h
- PR mergées (30 j)
- 163
Description
## Describe the Bug
`AbstractAgent`'s public mutator methods are unbound prototype methods, so they cannot be pulled off the instance. Destructuring one, or passing it as a callback, drops `this` and throws.
```ts
const { agent } = useAgent({ agentId: "my_agent" });
const { addMessage } = agent;
addMessage({ id: crypto.randomUUID(), role: "user", content: "hi" });
// TypeError: Cannot read properties of undefined (reading 'messages')
```
Destructuring a hook's return value is the normal React idiom, so downstream users reach for it and get an error that points at `messages` rather than at the real cause. Two users hit this independently in CopilotKit/CopilotKit#2872, one on `addMessage` and one on `setState`.
Affected methods, all in `sdks/typescript/packages/client/src/agent/agent.ts`:
| Method | Line (`main` @ `54c155826`) | Failure |
|---|---|---|
| `addMessage` | 599 | `Cannot read properties of undefined (reading 'messages')` |
| `addMessages` | 640 | same |
| `setMessages` | 684 | same |
| `setState` | 701 | `Cannot set properties of undefined (setting 'state')` |
`subscribe` (139) and `use` (156) have the same shape and are equally exposed.
## Steps to Reproduce
```bash
mkdir agui-bind && cd agui-bind
npm init -y && npm pkg set type=module
npm i @ag-ui/client@0.0.59 rxjs
```
```js
// repro.mjs
import { AbstractAgent } from "@ag-ui/client";
class MyAgent extends AbstractAgent {
run() { throw new Error("not used"); }
}
const agent = new MyAgent({ agentId: "my_agent" });
agent.addMessage({ id: "1", role: "user", content: "hi" });
console.log("bound call :", agent.messages.length, "message(s)");
const { addMessage, setState } = agent;
for (const [name, fn] of [["addMessage", addMessage], ["setState", setState]]) {
try {
fn(name === "addMessage" ? { id: "2", role: "user", content: "hi" } : { a: 1 });
console.log(`${name.padEnd(12)}: no error`);
} catch (e) {
console.log(`${name.padEnd(12)}: ${e.constructor.name}: ${e.message}`);
}
}
const cloned = agent.clone();
console.log("clone owns addMessage?", Object.prototype.hasOwnProperty.call(cloned, "addMessage"));
```
```
$ node repro.mjs
bound call : 1 message(s)
addMessage : TypeError: Cannot read properties of undefined (reading 'messages')
setState : TypeError: Cannot set properties of undefined (setting 'state')
clone owns addMessage? false
```
## Expected Behavior
A public method taken off the instance keeps working:
```ts
const { addMessage } = agent;
addMessage(message); // appends to agent.messages
```
## Actual Behavior
`TypeError: Cannot read properties of undefined (reading 'messages')`, thrown from `this.messages.push(message)` with `this === undefined`.
## Suggested Fix
Bind the public mutator methods in the `AbstractAgent` constructor.
One objection was raised downstream: these need to stay regular methods so that subclasses can override them. Binding in the constructor does not conflict with that. The bind reads through the prototype chain at construction time, so it captures the subclass override, and `super.addMessage(...)` still resolves normally:
```js
class Base {
constructor() { this.log = []; this.addMessage = this.addMessage.bind(this); }
addMessage(m) { this.log.push(`base:${m}`); }
}
class Sub extends Base {
addMessage(m) { this.log.push(`sub:${m}`); super.addMessage(m); }
}
const s = new Sub();
const { addMessage } = s;
addMessage("x");
// s.log === ["sub:x", "base:x"]
```
**One wrinkle worth catching in the same change:** `clone()` (line 581) builds the copy with `Object.create(Object.getPrototypeOf(this))` and assigns fields by hand, so it never runs the constructor. Constructor-bound own methods therefore do not exist on a clone, and the clone falls back to the unbound prototype methods — the last line of the repro above shows this. `clone()` needs to rebind as well, otherwise the fix silently does not apply to cloned agents (and `HttpAgent.clone()` inherits the same gap).
There is precedent for this class of fix in the same file: #1937 bound the default `fetch` in `HttpAgent` for the same reason, with coverage added in #1964.
## Environment
- `@ag-ui/client` 0.0.59 (latest on npm), also confirmed on `main` at `54c155826`
- Node 22, macOS
## Downstream Report
CopilotKit/CopilotKit#2872
Guide de contribution
Ouvrir le guide de contribution
Évaluation
Cette issue n'a pas encore été évaluée.