Skip to content

Python: [Bug]: Streamed code_interpreter code is corrupted when a delta is a prefix of the accumulated code #8903

Closed

Description

Description

_merge_content_item_lists (agent_framework/_types.py) decides whether an incoming code-interpreter input chunk is a delta or a full snapshot by prefix comparison:

if incoming_text.startswith(existing_text): return deepcopy(incoming)   # treated as full value
if existing_text.startswith(incoming_text): return existing             # treated as already present

The OpenAI Responses client emits response.code_interpreter_call_code.delta chunks followed by a .done chunk with the full code, all with the same call_id. Any delta that happens to be a prefix of the accumulated text is silently dropped (very common: a second import line). After that, the final .done snapshot no longer matches either prefix test, so it is concatenated onto the corrupted text.

Expected: the aggregated code_interpreter_tool_call.inputs[0].text equals the code the service executed.
Actual: deltas are dropped, and the full snapshot is appended to the damaged text.

Impact: ChatResponse.from_updates / AgentResponse aggregation, history providers (e.g. Cosmos), telemetry, and replay all see wrong code.

Related: #5793 and PR #5801 (which introduced the coalescing). This looks like an edge case the heuristic in #5801 does not cover.

Suggested fix: don't infer delta vs snapshot. Have the provider mark the .done chunk explicitly (e.g. an additional_properties flag), then replace on that marker and always concatenate otherwise.

Code Sample

from agent_framework import Content, ChatResponse, ChatResponseUpdate

deltas = ["import", " os", "\n", "import", " sys"]
full = "".join(deltas)
mk = lambda t: ChatResponseUpdate(role="assistant", contents=[
    Content.from_code_interpreter_tool_call(call_id="ci_1", inputs=[Content.from_text(t)])])

r = ChatResponse.from_updates([mk(d) for d in deltas])
print(repr(r.messages[0].contents[0].inputs[0].text))   # 'import os\n sys'

r = ChatResponse.from_updates([mk(d) for d in deltas] + [mk(full)])   # + .done snapshot
print(repr(r.messages[0].contents[0].inputs[0].text))   # 'import os\n sysimport os\nimport sys'
# expected: 'import os\nimport sys'

Error Messages / Stack Traces

Package Versions

agent-framework-core 1.19.0 (main @ 853c456)

Python Version

Python 3.11

Additional Context

Found during a code review of main @ 853c456 (2026-09-30); the repro above was run against that commit.

Activity

  1. added
    pythonUsage: [Issues, PRs], Target: Python
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Sep 30, 2026
  2. added
    reproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow
    on Sep 30, 2026
  3. github-actions commented on Sep 30, 2026

    @github-actions
    Contributor

    🤖 Automated triage reproduction notes (agent-authored — trust but verify)

    Agent analysis

    python/packages/core/agent_framework/_types.py::_merge_content_item_lists drops an incoming code-interpreter delta when it prefixes accumulated text, then concatenates the later full snapshot. The minimal repro aggregates same-call updates ["import", " os", "\n", "import", " sys"], optionally followed by their joined value, through ChatResponse.from_updates; test_code_interpreter_stream_preserves_input_code demonstrates both failures.

    • Failing test: python/packages/core/tests/core/test_types.py::test_code_interpreter_stream_preserves_input_code
    • Files examined: python/packages/core/agent_framework/_types.py, python/packages/core/tests/core/test_types.py, python/packages/core/pyproject.toml, python/packages/openai/agent_framework_openai/_chat_client.py, python/packages/openai/tests/openai/test_openai_chat_client.py
    • Tests run: core code-interpreter tests, test_code_interpreter_stream_preserves_input_code, OpenAI code-interpreter delta/done parser tests
    • Reported version: 1.19.0
    • Current version: 1.19.0
  4. added
    agentsUsage: [Issues, PRs], Target: Single agent
    and removed
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Sep 30, 2026
  5. he-yufeng commented on Sep 30, 2026

    @he-yufeng
    Contributor

    Reproduced on current main (7634356) with the exact script from the issue:

    deltas only:      'import os\n sys'
    with .done:       'import os\n sysimport os\nimport sys'
    expected:         'import os\nimport sys'
    

    The second import delta is dropped because "import os\n".startswith("import") satisfies the already-present branch, and once the accumulated text is damaged the final snapshot fails both prefix tests and gets concatenated, exactly as described.

    The _merge_content_item_lists heuristic (_types.py:2333) is doing something undecidable: for a stream that is legally allowed to repeat any prefix (a second import line, a repeated decorator, a re-typed identifier), no string comparison can tell "delta that happens to be a prefix" apart from "snapshot of everything so far". Any pure-string tweak just moves which inputs corrupt.

    So the explicit-marker direction looks like the only clean fix to me: the Responses client knows which chunk is the .done snapshot, and _merge_content_item_lists should replace on that marker and concatenate otherwise. The marker can ride in additional_properties so the aggregation layer stays provider-agnostic, and providers without a done event keep today's heuristic as a fallback.

    Happy to take this one together with the O(n^2) coalescing in #8908, since both live in the same aggregation path and the tests would share fixtures. If you would rather keep them as separate PRs I can split them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

agentsUsage: [Issues, PRs], Target: Single agentpythonUsage: [Issues, PRs], Target: PythonreproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions