mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex] namespace sleep under clock (#29907)
## Summary - expose the interruptible sleep tool as `clock.sleep` instead of top-level `sleep` - keep `clock.curr_time` and `clock.sleep` in the same model-visible namespace when both features are enabled - update existing core and app-server integration coverage to issue namespaced sleep calls ## Why Sleep is a clock operation. Grouping it with `clock.curr_time` gives the model a more coherent tool surface without changing the sleep feature gate or runtime behavior. ## Validation - `just test -p codex-core sleep_tool_follows_feature_gate` - `just test -p codex-core any_new_input_interrupts_sleep` - `just test -p codex-app-server sleep_emits_started_and_completed_items`
This commit is contained in:
committed by
GitHub
Unverified
parent
134646eff0
commit
800529218a
@@ -32,8 +32,9 @@ async fn sleep_emits_started_and_completed_items() -> Result<()> {
|
||||
vec![
|
||||
responses::sse(vec![
|
||||
responses::ev_response_created("resp-1"),
|
||||
responses::ev_function_call(
|
||||
responses::ev_function_call_with_namespace(
|
||||
CALL_ID,
|
||||
"clock",
|
||||
"sleep",
|
||||
&serde_json::json!({ "duration_ms": DURATION_MS }).to_string(),
|
||||
),
|
||||
|
||||
@@ -55,7 +55,7 @@ impl ToolExecutor<ToolInvocation> for CurrentTimeHandler {
|
||||
fn spec(&self) -> ToolSpec {
|
||||
ToolSpec::Namespace(ResponsesApiNamespace {
|
||||
name: NAMESPACE.to_string(),
|
||||
description: "Tools for reading the current time.".to_string(),
|
||||
description: "Tools for reading and waiting on time.".to_string(),
|
||||
tools: vec![ResponsesApiNamespaceTool::Function(ResponsesApiTool {
|
||||
name: TOOL_NAME.to_string(),
|
||||
description: "Return the current time in UTC.".to_string(),
|
||||
|
||||
@@ -9,6 +9,8 @@ use crate::tools::registry::ToolExecutor;
|
||||
use codex_protocol::items::SleepItem;
|
||||
use codex_protocol::items::TurnItem;
|
||||
use codex_tools::JsonSchema;
|
||||
use codex_tools::ResponsesApiNamespace;
|
||||
use codex_tools::ResponsesApiNamespaceTool;
|
||||
use codex_tools::ResponsesApiTool;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
@@ -17,7 +19,8 @@ use std::collections::BTreeMap;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
const SLEEP_TOOL_NAME: &str = "sleep";
|
||||
const NAMESPACE: &str = "clock";
|
||||
const TOOL_NAME: &str = "sleep";
|
||||
const MAX_SLEEP_DURATION_MS: u64 = 3_600_000;
|
||||
|
||||
pub struct SleepHandler;
|
||||
@@ -36,24 +39,28 @@ fn create_sleep_tool() -> ToolSpec {
|
||||
))),
|
||||
)]);
|
||||
|
||||
ToolSpec::Function(ResponsesApiTool {
|
||||
name: SLEEP_TOOL_NAME.to_string(),
|
||||
description: "Pause execution for a specified duration. The sleep ends early when new input arrives for the active turn. Returns the elapsed wall-clock time."
|
||||
.to_string(),
|
||||
strict: false,
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::object(
|
||||
properties,
|
||||
Some(vec!["duration_ms".to_string()]),
|
||||
/*additional_properties*/ Some(false.into()),
|
||||
),
|
||||
output_schema: None,
|
||||
ToolSpec::Namespace(ResponsesApiNamespace {
|
||||
name: NAMESPACE.to_string(),
|
||||
description: "Tools for reading and waiting on time.".to_string(),
|
||||
tools: vec![ResponsesApiNamespaceTool::Function(ResponsesApiTool {
|
||||
name: TOOL_NAME.to_string(),
|
||||
description: "Pause execution for a specified duration. The sleep ends early when new input arrives for the active turn. Returns the elapsed wall-clock time."
|
||||
.to_string(),
|
||||
strict: false,
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::object(
|
||||
properties,
|
||||
Some(vec!["duration_ms".to_string()]),
|
||||
/*additional_properties*/ Some(false.into()),
|
||||
),
|
||||
output_schema: None,
|
||||
})],
|
||||
})
|
||||
}
|
||||
|
||||
impl ToolExecutor<ToolInvocation> for SleepHandler {
|
||||
fn tool_name(&self) -> ToolName {
|
||||
ToolName::plain(SLEEP_TOOL_NAME)
|
||||
ToolName::namespaced(NAMESPACE, TOOL_NAME)
|
||||
}
|
||||
|
||||
fn spec(&self) -> ToolSpec {
|
||||
@@ -71,7 +78,7 @@ impl ToolExecutor<ToolInvocation> for SleepHandler {
|
||||
} = invocation;
|
||||
let ToolPayload::Function { arguments } = payload else {
|
||||
return Err(FunctionCallError::RespondToModel(format!(
|
||||
"{SLEEP_TOOL_NAME} handler received unsupported payload"
|
||||
"{TOOL_NAME} handler received unsupported payload"
|
||||
)));
|
||||
};
|
||||
let args: SleepArgs = parse_arguments(&arguments)?;
|
||||
|
||||
@@ -726,13 +726,14 @@ async fn sleep_tool_follows_feature_gate() {
|
||||
set_feature(turn, Feature::SleepTool, /*enabled*/ false);
|
||||
})
|
||||
.await;
|
||||
disabled.assert_visible_lacks(&["sleep"]);
|
||||
disabled.assert_visible_lacks(&["clock"]);
|
||||
|
||||
let enabled = probe(|turn| {
|
||||
set_feature(turn, Feature::SleepTool, /*enabled*/ true);
|
||||
})
|
||||
.await;
|
||||
enabled.assert_visible_contains(&["sleep"]);
|
||||
enabled.assert_visible_contains(&["clock"]);
|
||||
assert_eq!(enabled.namespace_function_names("clock"), ["sleep"]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -361,8 +361,9 @@ async fn any_new_input_interrupts_sleep() {
|
||||
|
||||
let first_chunks = vec![
|
||||
chunk(ev_response_created("resp-1")),
|
||||
chunk(ev_function_call(
|
||||
chunk(ev_function_call_with_namespace(
|
||||
FIRST_SLEEP_CALL_ID,
|
||||
"clock",
|
||||
"sleep",
|
||||
&sleep_arguments,
|
||||
)),
|
||||
@@ -370,8 +371,9 @@ async fn any_new_input_interrupts_sleep() {
|
||||
];
|
||||
let second_chunks = vec![
|
||||
chunk(ev_response_created("resp-2")),
|
||||
chunk(ev_function_call(
|
||||
chunk(ev_function_call_with_namespace(
|
||||
SECOND_SLEEP_CALL_ID,
|
||||
"clock",
|
||||
"sleep",
|
||||
&sleep_arguments,
|
||||
)),
|
||||
|
||||
Reference in New Issue
Block a user