Skip to content

fix(code-interpreter): collect non-standard MIME types into Result.extra - #1985

Open
lemonzoey wants to merge 1 commit into
e2b-dev:mainfrom
lemonzoey:zhouyi/fix-python-result-extra-mime
Open

lemonzoey wants to merge 1 commit into
e2b-dev:mainfrom
lemonzoey:zhouyi/fix-python-result-extra-mime

Conversation

@lemonzoey

@lemonzoey lemonzoey commented Oct 15, 2026 •

Copy link
Copy Markdown

Summary

The Python SDK's Result.__init__ declared an extra field for data "not part of the standard types", but unknown keys from the kernel message were dropped via **kwargs instead of collected into extra. Non-standard MIME types the IPython kernel sends — e.g. application/vnd.plotly.v1+json, bokeh, vega-lite — were silently lost, while the JS SDK's Result constructor already sweeps them into extra. Fixes #1984.

The change

Result.__init__ now collects **kwargs into extra (merged with an explicitly passed extra), so non-standard MIME types are retained. Because formats() already iterates self.extra, the custom MIME types are now listed by formats(). serialize_results reads extra keys from Result.extra rather than treating them as object attributes (which raised AttributeError once extra keys entered formats()).

-        self.extra = extra
+        if kwargs:
+            self.extra = {**(extra or {}), **kwargs}
+        else:
+            self.extra = extra
             if key == "chart":
                 ...
+            elif result.extra is not None and key in result.extra:
+                serialized_dict[key] = result.extra[key]
             else:
                 serialized_dict[key] = result[key]

Usage example

from e2b_code_interpreter import Sandbox

sandbox = await Sandbox.create()
execution = await sandbox.run_code("""
import plotly.graph_objects as go
go.Figure(data=[go.Scatter(x=[1, 2], y=[3, 4])]).show()
""")

result = execution.results[0]

# Before: None — the plotly payload was dropped at construction
# After:
result.extra  # {'application/vnd.plotly.v1+json': {...}}
'application/vnd.plotly.v1+json' in result.formats()  # True

# And it round-trips through to_json:
import json
json.loads(execution.to_json())["results"][0]  # includes the plotly payload

This matches the JS SDK, whose Result constructor already sweeps unknown keys into extra:

const result = execution.results[0]
result.extra // { 'application/vnd.plotly.v1+json': {...} }

Testing

Adds packages/code-interpreter-python/tests/test_result_extra.py (no sandbox needed): collects a non-standard MIME type into extra, lists it in formats(), preserves it through to_json(), leaves extra empty for a bare result, and merges an explicit extra with unknown kwargs. All 18 no-credential tests pass (5 new + 13 existing test_deserialize.py). ruff format / ruff check clean.

Scope

Python-only — the JS SDK already does the right thing. This is the Python side of the Result serialization surface and does not overlap the open cluster: #1939/#1971 (data in toJSON), #1948 (chart in toJSON), #1826/#1893 (chart deserialization). None touch Python Result.__init__'s extra collection.

🤖 Generated with kscc


Devin Review

The Python SDK's `Result.__init__` declared an `extra` field for data
"not part of the standard types", but unknown keys from the kernel
message were dropped via `**kwargs` instead of collected into `extra`.
Non-standard representations the IPython kernel sends — e.g.
`application/vnd.plotly.v1+json`, bokeh, vega-lite — were silently lost,
while the JS SDK's `Result` constructor already sweeps them into
`extra`.

Collect `**kwargs` into `extra` (merged with an explicitly passed
`extra`), so non-standard MIME types are retained, listed by
`formats()`, and preserved by `Execution.to_json()`. `serialize_results`
reads extra keys from `Result.extra` rather than treating them as
object attributes.

Co-Authored-By: Kscc <noreply@owtffssent.com>
@changeset-bot

changeset-bot Bot commented Oct 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ace7cd7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@e2b/code-interpreter-python Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-15T03:20:10.748207Z ace7cd7 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TASTE.md compliance review (SDK design principles, e2b-dev/sdk-harness).

Checked: T-1 (cross-SDK parity), T-16 (dataclass results / dict options), T-18 (boundary mapping), T-62/T-63 (errors), T-69/T-71 (docstrings).

Violations: 1 (2 inline comments, same root cause)

  • T-1: The constructor now sweeps unknown keys into extra, which matches JS. to_json(), however, flattens those keys into the top level of each result, while JS Result.toJSON() nests them under extra. The new test asserts the flattened shape, so it needs updating too.

No other violations in the changed lines. The Result.extra default of None (JS uses {}) was already there before this PR and isn't counted.

Written by Devin

Comment on lines +347 to +348
elif result.extra is not None and key in result.extra:
serialized_dict[key] = result.extra[key]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

T-1 — JS, sync Python, and async Python must mirror each other 1:1 in semantics, not just names.

This flattens each non-standard MIME type into the top level of the serialized result ({"text": ..., "application/vnd.plotly.v1+json": ...}). JS Result.toJSON() (packages/code-interpreter-js/src/messaging.ts) nests them instead: ...(Object.keys(this.extra).length > 0 ? { extra: this.extra } : {}). So the same execution would serialize to two different shapes depending on the SDK, which goes against the PR's own goal of "matching the JS SDK". It also lets an arbitrary kernel MIME key share a namespace with the standard keys (text, html, …).

Use JS's nested shape:

Suggested change
elif result.extra is not None and key in result.extra:
serialized_dict[key] = result.extra[key]
elif result.extra is not None and key in result.extra:
serialized_dict.setdefault("extra", {})[key] = result.extra[key]

assert json.loads(execution.to_json())["results"] == [
{
"text": "hello",
"application/vnd.plotly.v1+json": {"data": [{"x": [1, 2]}]},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

T-1 (follow-up to the serialize_results comment) — this assertion locks in the flattened shape, which differs from JS Result.toJSON(). That method emits non-standard MIME types under an extra key. To match JS:

Suggested change
"application/vnd.plotly.v1+json": {"data": [{"x": [1, 2]}]},
"extra": {"application/vnd.plotly.v1+json": {"data": [{"x": [1, 2]}]}},

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment on lines +347 to +348
elif result.extra is not None and key in result.extra:
serialized_dict[key] = result.extra[key]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Extra HTML replaces the actual result

When extra contains html, serialize_results writes that value instead of result.html. The serialized execution contains different HTML from the result users received.

Learn more

Result.formats() lists standard fields and then keys in extra, including duplicate names. Previously, both passes through serialize_results read the standard attribute, so an explicit extra key could not replace the result's HTML. The new branch selects extra for both passes and overwrites the standard value. The same collision affects other standard fields besides text, which is reset later.

Example: Execution(results=[Result(html='<b>new</b>', extra={'html': '<i>old</i>'})]).to_json() now contains "html": "<i>old</i>", while result.html remains <b>new</b>.

Recommended fix: Keep standard fields authoritative in serialize_results, and only read result.extra[key] for keys not represented by standard fields; account for duplicate keys from formats().

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +174 to +175
if kwargs:
self.extra = {**(extra or {}), **kwargs}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Plain metadata appears as a MIME format

When a result message includes plain, Result.extra retains it as a custom format. JS Result excludes plain, so Python now advertises a non-MIME key in formats().

Learn more

_parse_output passes every result message field into Result. The JS result constructor treats plain as a known field and skips it when collecting extra representations, but the Python sweep accepts it. formats() then reports plain and Execution.to_json() includes it among the result representations.

Example: For a result message {'type': 'result', 'text': 'hello', 'plain': 'hello'}, Python reports ['text', 'plain']; the JS result omits plain from extra and formats.

Recommended fix: Exclude protocol-only keys such as plain from kwargs before merging MIME representations into extra, matching the JS constructor's known-key list.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

This branch has not been deployed

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Python Code Interpreter Result drops non-standard MIME types instead of collecting them into extra

1 participant