anomalyco / anomalyco/opencode
Object.prototype stream key breaks the tool-call stream with an untyped defect
@jlongster is already working on this.
Since Aug 24, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
The tool-call accumulator in packages/llm/src/protocols/utils/tool-stream.ts stores pending calls in a plain object keyed by the provider's stream id:
type State<K extends StreamKey> = Partial<Record<K, PendingTool>>
const empty = () => ({})
On the OpenAI Responses protocol that key is event.item_id, a free-form string from the server (Schema.optional(Schema.String)). If it is an Object.prototype member name — toString, constructor, valueOf, hasOwnProperty — then tools[key] returns the inherited function instead of undefined, which is truthy. appendExisting, finish and finishWithInput therefore skip their if (!tool) return ... guards and build an event with id/name undefined.
The schema constructor then throws Expected string, got undefined synchronously, outside the Effect error channel, so the stream dies with a defect instead of yielding a typed provider-output error.
Reproduced against dev (3f2e0e8):
appendExisting("toString") -> throw: Expected string, got undefined (tool-stream.ts:89)
finish("constructor") -> throw: Expected string, got undefined (tool-stream.ts:171)
appendOrStart("toString") -> OK (its id/name guard catches it)
The value comes from the remote provider response (any OpenAI-compatible server, proxy or gateway), not from local input.
Happy to send a PR: reading pending calls through an own-property check (Object.hasOwn) fixes it without changing the state shape.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.