mirror of
https://github.com/openai/codex.git
synced 2026-10-24 18:18:41 +00:00
Handle undefined values before JSON serialization in code mode (#43873)
## Why
Passing `undefined` to a tool could fail JSON parsing because V8 stringifies it as the non-JSON text `undefined`.
## What changed
Return no JSON value for JavaScript `undefined`, so explicit `undefined` tool arguments behave like omitted arguments. Attempts to `store` an undefined value report the existing serializability error and preserve the previous stored value.
## Testing
Add regression coverage for storing `undefined` over an existing `null` value, and extend the current-time tool test to cover `{}`, omitted arguments, and explicit `undefined`.
GitOrigin-RevId: 071050ebb2ecc44c8f08453cf8523b4d21728c7e
This commit is contained in:
@@ -297,6 +297,11 @@ pub(super) fn v8_value_to_json(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
value: v8::Local<'_, v8::Value>,
|
||||
) -> Result<Option<JsonValue>, String> {
|
||||
// V8 stringifies undefined as the non-JSON text "undefined".
|
||||
if value.is_undefined() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let tc = std::pin::pin!(v8::TryCatch::new(scope));
|
||||
let mut tc = tc.init();
|
||||
let Some(stringified) = v8::json::stringify(&tc, value) else {
|
||||
|
||||
@@ -474,6 +474,48 @@ async fn stored_values_are_shared_between_cells_but_not_sessions() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn storing_undefined_preserves_the_previous_value() {
|
||||
let service = InProcessCodeModeSession::new();
|
||||
|
||||
let response = execute(
|
||||
&service,
|
||||
ExecuteRequest {
|
||||
source: r#"
|
||||
store("key", null);
|
||||
try {
|
||||
store("key", undefined);
|
||||
} catch (error) {
|
||||
text(String(error));
|
||||
}
|
||||
text(load("key"));
|
||||
"#
|
||||
.to_string(),
|
||||
yield_time_ms: None,
|
||||
..execute_request("")
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
response,
|
||||
RuntimeResponse::Result {
|
||||
code_mode_host_duration: None,
|
||||
cell_id: cell_id("1"),
|
||||
content_items: vec![
|
||||
FunctionCallOutputContentItem::InputText {
|
||||
text: "Unable to store \"key\". Only plain serializable objects can be stored."
|
||||
.to_string(),
|
||||
},
|
||||
FunctionCallOutputContentItem::InputText {
|
||||
text: "null".to_string(),
|
||||
},
|
||||
],
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn shutdown_interrupts_cpu_bound_cells() {
|
||||
let service = InProcessCodeModeSession::new();
|
||||
|
||||
@@ -1695,18 +1695,23 @@ text(JSON.stringify(result));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case("{}"; "object")]
|
||||
#[test_case(""; "omitted")]
|
||||
#[test_case("undefined"; "undefined")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn code_mode_current_time_returns_structured_result() -> Result<()> {
|
||||
async fn code_mode_current_time_returns_structured_result(argument: &str) -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = responses::start_mock_server().await;
|
||||
let (_test, second_mock) = run_code_mode_turn_with_config(
|
||||
&server,
|
||||
"use exec to get the current time",
|
||||
r#"
|
||||
const result = await tools.clock__curr_time({});
|
||||
&format!(
|
||||
r#"
|
||||
const result = await tools.clock__curr_time({argument});
|
||||
text(JSON.stringify(result));
|
||||
"#,
|
||||
),
|
||||
|config| {
|
||||
config
|
||||
.features
|
||||
|
||||
Reference in New Issue
Block a user