What happened?
TexeraAgent.sendMessage is written to never reject: every failure is turned into an error step and returned as { error } on the resolved AgentMessageResult. One path breaks that contract.
The catch block reads .name off the caught value without guarding it:
// texera-agent.ts:659-660
} catch (error: any) {
const isAborted = error.name === "AbortError" || this.abortController?.signal.aborted;
If the value thrown is null or undefined, that line throws a TypeError inside the catch, so it propagates out and sendMessage rejects instead of resolving. Callers that only handle the documented resolved shape get an unhandled rejection, and the turn leaves no error step behind — the failure is invisible in the ReAct history.
Confirmed against main:
TypeError: null is not an object (evaluating 'error.name')
with the returned value undefined (the promise rejected rather than resolving).
The same line is the only place in the method that dereferences the caught value before it has been normalized; every other use goes through error instanceof Error ? error.message : String(error), which already handles this correctly.
How to reproduce?
Drive the agent with a model that throws a falsy value:
const model = new MockLanguageModelV4({
doGenerate: async () => {
throw null;
},
});
const agent = new TexeraAgent({ model, modelType: "m", agentId: "a", systemPrompt: "s" });
await agent.sendMessage("hi");
// rejects with TypeError: null is not an object (evaluating 'error.name')
// expected: resolves with { error: "null", stopped: false }
MockLanguageModelV4 comes from the ai/test export, so this needs no network.
Version/Branch
1.3.0-incubating-SNAPSHOT (main)
Expected behavior
The catch should normalize before inspecting, e.g.
const isAborted = (error as any)?.name === "AbortError" || this.abortController?.signal.aborted;
so that a falsy throw takes the ordinary error-step path and sendMessage keeps its promise never to reject.
Additional context
Found while raising texera-agent.ts coverage from 51% to 99.8%. A provider throwing null is uncommon, so the practical impact is low — but the cost of the fix is one optional-chain, and the current behaviour is the single exception to an otherwise total contract.
What happened?
TexeraAgent.sendMessageis written to never reject: every failure is turned into an error step and returned as{ error }on the resolvedAgentMessageResult. One path breaks that contract.The catch block reads
.nameoff the caught value without guarding it:If the value thrown is
nullorundefined, that line throws aTypeErrorinside the catch, so it propagates out andsendMessagerejects instead of resolving. Callers that only handle the documented resolved shape get an unhandled rejection, and the turn leaves no error step behind — the failure is invisible in the ReAct history.Confirmed against
main:with the returned value
undefined(the promise rejected rather than resolving).The same line is the only place in the method that dereferences the caught value before it has been normalized; every other use goes through
error instanceof Error ? error.message : String(error), which already handles this correctly.How to reproduce?
Drive the agent with a model that throws a falsy value:
MockLanguageModelV4comes from theai/testexport, so this needs no network.Version/Branch
1.3.0-incubating-SNAPSHOT (main)
Expected behavior
The catch should normalize before inspecting, e.g.
so that a falsy throw takes the ordinary error-step path and
sendMessagekeeps its promise never to reject.Additional context
Found while raising
texera-agent.tscoverage from 51% to 99.8%. A provider throwingnullis uncommon, so the practical impact is low — but the cost of the fix is one optional-chain, and the current behaviour is the single exception to an otherwise total contract.