mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-10-27 04:30:59 +00:00
fix: tolerate missing text in streamed thinking chunks (#852)
## Summary This makes streamed text/thinking chunk handling defensive when gateways emit empty or malformed delta payloads. - Guard `text_delta.text` and `thinking_delta.thinking` before reading `.length` - Skip missing/non-string text or thinking content the same way empty chunks are already skipped - Add regression coverage for a missing `thinking` delta before a real text delta - Add direct `toAcpNotifications` coverage for missing/non-string text and thinking chunks ## Why The adapter already skips empty thinking chunks, but some Anthropic-compatible gateways can emit signature-only or omitted thinking deltas where `thinking` is absent rather than an empty string. In that case the adapter currently throws: ```text Cannot read properties of undefined (reading 'length') ``` That aborts the ACP session even though the following text chunks are valid. This change keeps the existing behavior for valid chunks, while treating missing/non-string chunk text as empty content. ## Test Plan - `npm run check` - `npm test -- --run` - `npm run build` Also verified manually through a VibeAround Web Chat flow using Claude Code ACP with an Anthropic-compatible custom endpoint; the session completed and returned a normal assistant response. --------- Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
This commit is contained in:
@@ -2029,7 +2029,7 @@ export class ClaudeAcpAgent {
|
||||
// create a block the consolidated handler's `text.length > 0`
|
||||
// guard can never consume, stalling the diff cursor and
|
||||
// re-emitting the next block as a duplicate.
|
||||
if (chunk && chunk.text.length > 0) {
|
||||
if (chunk?.text) {
|
||||
const index = message.event.index;
|
||||
const last = streamedBlocks[streamedBlocks.length - 1];
|
||||
if (last && last.index === index && last.type === chunk.type) {
|
||||
@@ -5091,8 +5091,8 @@ export function toAcpNotifications(
|
||||
let update: SessionNotification["update"] | null = null;
|
||||
switch (chunk.type) {
|
||||
case "text":
|
||||
case "text_delta":
|
||||
if (chunk.text.length > 0) {
|
||||
case "text_delta": {
|
||||
if (chunk.text) {
|
||||
update = {
|
||||
sessionUpdate: role === "assistant" ? "agent_message_chunk" : "user_message_chunk",
|
||||
content: {
|
||||
@@ -5102,6 +5102,7 @@ export function toAcpNotifications(
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "image":
|
||||
update = {
|
||||
sessionUpdate: role === "assistant" ? "agent_message_chunk" : "user_message_chunk",
|
||||
@@ -5114,10 +5115,10 @@ export function toAcpNotifications(
|
||||
};
|
||||
break;
|
||||
case "thinking":
|
||||
case "thinking_delta":
|
||||
case "thinking_delta": {
|
||||
// Recent models default `thinking.display` to "omitted", which streams
|
||||
// signature-only thinking blocks whose text is empty.
|
||||
if (chunk.thinking.length > 0) {
|
||||
if (chunk.thinking) {
|
||||
update = {
|
||||
sessionUpdate: "agent_thought_chunk",
|
||||
content: {
|
||||
@@ -5127,6 +5128,7 @@ export function toAcpNotifications(
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "tool_use":
|
||||
case "server_tool_use":
|
||||
case "mcp_tool_use": {
|
||||
|
||||
Reference in New Issue
Block a user