Anthropic and Copilot SSE streams still decode UTF-8 per transport chunk (U+FFFD in CJK output)

Open Beginner friendly
#2,087 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
78/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
rust

Research direction

Read the Anthropic stream loop in crates/jcode-provider-anthropic-runtime/src/lib.rs around line 2349 and the Copilot loop in crates/jcode-provider-copilot-runtime/src/lib.rs around line 730. Compare their handling with the OpenAI/OpenRouter streams and sse_stream_integrity_tests.rs; verify split multibyte CJK characters decode intact in both paths, then run the relevant tests.

Written by the indexing model from the issue text.

Description

Two provider streams still decode each transport chunk independently with String::from_utf8_lossy. When a chunk boundary splits a multi-byte character, both halves turn into U+FFFD. #609/#613 (OpenAI/OpenRouter) and #1944 (OpenCode Go Responses) fixed the same pattern elsewhere, but these two paths were not covered.

At 3c448c32328e39a58e3b7a87915c5479bf46c6ab:

Observed (runtime, not just source inspection)

On v0.99.1 (7206efd0c, Windows, Anthropic provider, claude-opus-5-5), Japanese assistant output intermittently contains ��� in place of exactly one character. The bytes are already persisted in the session journal:

履歴を���認するのだ   (expected: 履歴を確認するのだ)
こう���るのだ        (expected: こうするのだ)

Each occurrence is one 3-byte UTF-8 character replaced by three U+FFFD. That matches a chunk boundary falling inside the character. It happens a few times across long sessions, which fits network-dependent chunking.

(The Copilot path is from source inspection only; I have only observed the Anthropic one.)

Suggested fix

Reuse the existing jcode_core::util::Utf8StreamDecoder in both loops, as the OpenAI/OpenRouter streams already do:

let mut utf8 = jcode_core::util::Utf8StreamDecoder::new();
// ...
buffer.push_str(&utf8.decode(&chunk));

Optionally flush any carried bytes at stream end, and add the same split-at-every-offset CJK test used in sse_stream_integrity_tests.rs.

Dominant language
Rust
Stars
19.9k
Forks
2.3k
Avg merge
2d 9h
Merged PRs (30d)
12

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from 1jehuang/jcode

All issues in 1jehuang/jcode

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.