mirror of
https://github.com/openai/codex.git
synced 2026-05-20 03:05:02 +00:00
Compare commits
5 Commits
codex-cli-
...
nornagon/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55fe41e243 | ||
|
|
d36118a8fd | ||
|
|
6e946d1707 | ||
|
|
c24b0fcc78 | ||
|
|
2ad649684f |
36
codex-rs/Cargo.lock
generated
36
codex-rs/Cargo.lock
generated
@@ -728,6 +728,7 @@ dependencies = [
|
|||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
"regex-lite",
|
"regex-lite",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
"rust-embed",
|
||||||
"seccompiler",
|
"seccompiler",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_bytes",
|
"serde_bytes",
|
||||||
@@ -4024,6 +4025,41 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-embed"
|
||||||
|
version = "8.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a"
|
||||||
|
dependencies = [
|
||||||
|
"rust-embed-impl",
|
||||||
|
"rust-embed-utils",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-embed-impl"
|
||||||
|
version = "8.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rust-embed-utils",
|
||||||
|
"syn 2.0.104",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-embed-utils"
|
||||||
|
version = "8.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594"
|
||||||
|
dependencies = [
|
||||||
|
"globset",
|
||||||
|
"sha2",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.25"
|
version = "0.1.25"
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ tree-sitter-bash = "0.25.0"
|
|||||||
uuid = { version = "1", features = ["serde", "v4"] }
|
uuid = { version = "1", features = ["serde", "v4"] }
|
||||||
whoami = "1.6.1"
|
whoami = "1.6.1"
|
||||||
wildmatch = "2.4.0"
|
wildmatch = "2.4.0"
|
||||||
|
rust-embed = { version = "8.5.0", features = ["include-exclude"] }
|
||||||
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ use crate::client::ModelClient;
|
|||||||
use crate::client_common::Prompt;
|
use crate::client_common::Prompt;
|
||||||
use crate::client_common::ResponseEvent;
|
use crate::client_common::ResponseEvent;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
|
use crate::config_edit_tool::handle_get_config;
|
||||||
|
use crate::config_edit_tool::handle_set_config;
|
||||||
|
use crate::config_edit_tool::handle_show_codex_docs;
|
||||||
use crate::config_types::ShellEnvironmentPolicy;
|
use crate::config_types::ShellEnvironmentPolicy;
|
||||||
use crate::conversation_history::ConversationHistory;
|
use crate::conversation_history::ConversationHistory;
|
||||||
use crate::conversation_manager::InitialHistory;
|
use crate::conversation_manager::InitialHistory;
|
||||||
@@ -2127,6 +2130,9 @@ async fn handle_function_call(
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
"update_plan" => handle_update_plan(sess, arguments, sub_id, call_id).await,
|
"update_plan" => handle_update_plan(sess, arguments, sub_id, call_id).await,
|
||||||
|
"get_config" => handle_get_config(sess, arguments, sub_id, call_id).await,
|
||||||
|
"set_config" => handle_set_config(sess, arguments, sub_id, call_id).await,
|
||||||
|
"show_codex_docs" => handle_show_codex_docs(sess, arguments, sub_id, call_id).await,
|
||||||
EXEC_COMMAND_TOOL_NAME => {
|
EXEC_COMMAND_TOOL_NAME => {
|
||||||
// TODO(mbolin): Sandbox check.
|
// TODO(mbolin): Sandbox check.
|
||||||
let exec_params = match serde_json::from_str::<ExecCommandParams>(&arguments) {
|
let exec_params = match serde_json::from_str::<ExecCommandParams>(&arguments) {
|
||||||
|
|||||||
347
codex-rs/core/src/config_edit_tool.rs
Normal file
347
codex-rs/core/src/config_edit_tool.rs
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
use crate::codex::Session;
|
||||||
|
use crate::config::find_codex_home;
|
||||||
|
use crate::openai_tools::JsonSchema;
|
||||||
|
use crate::openai_tools::OpenAiTool;
|
||||||
|
use crate::openai_tools::ResponsesApiTool;
|
||||||
|
use crate::protocol::ReviewDecision;
|
||||||
|
use codex_apply_patch::ApplyPatchAction;
|
||||||
|
use codex_apply_patch::MaybeApplyPatchVerified;
|
||||||
|
use codex_apply_patch::maybe_parse_apply_patch_verified;
|
||||||
|
use codex_protocol::models::FunctionCallOutputPayload;
|
||||||
|
use codex_protocol::models::ResponseInputItem;
|
||||||
|
use rust_embed::RustEmbed;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
const CONFIG_TOML_FILE: &str = "config.toml";
|
||||||
|
|
||||||
|
// Embed docs at compile time.
|
||||||
|
// README from the repo root
|
||||||
|
const README_MD: &str = include_str!("../../../README.md");
|
||||||
|
// Entire docs directory from the repo root (only *.md files are embedded)
|
||||||
|
#[derive(RustEmbed)]
|
||||||
|
#[folder = "../../docs"]
|
||||||
|
#[include = "**/*.md"]
|
||||||
|
struct EmbeddedDocs;
|
||||||
|
|
||||||
|
fn push_separator(buf: &mut String) {
|
||||||
|
buf.push_str("\n\n---\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_all_codex_docs() -> String {
|
||||||
|
let mut out = String::new();
|
||||||
|
out.push_str("# Codex Documentation\n\n");
|
||||||
|
out.push_str("<!-- Source: README.md -->\n\n");
|
||||||
|
out.push_str(README_MD);
|
||||||
|
|
||||||
|
// Add markdown files from ../docs recursively (embedded at compile time)
|
||||||
|
let mut paths: Vec<String> = EmbeddedDocs::iter()
|
||||||
|
.map(|p| p.as_ref().to_string())
|
||||||
|
.collect();
|
||||||
|
paths.sort();
|
||||||
|
for path in paths.into_iter() {
|
||||||
|
if let Some(file) = EmbeddedDocs::get(&path) {
|
||||||
|
push_separator(&mut out);
|
||||||
|
out.push_str(&format!("<!-- Source: {path} -->\n\n"));
|
||||||
|
out.push_str(&String::from_utf8_lossy(&file.data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get_config() — fetches the current config.toml.
|
||||||
|
pub(crate) fn create_get_config_tool() -> OpenAiTool {
|
||||||
|
OpenAiTool::Function(ResponsesApiTool {
|
||||||
|
name: "get_config".to_string(),
|
||||||
|
description: "Gets the current ~/.codex/config.toml. If the user asks about their configuration or wants to review it, call this tool and use the result to answer or summarize as needed.".to_string(),
|
||||||
|
strict: false,
|
||||||
|
parameters: JsonSchema::Object {
|
||||||
|
properties: BTreeMap::new(),
|
||||||
|
required: Some(vec![]),
|
||||||
|
additional_properties: Some(false),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set_config(new_config: string) — writes the provided TOML to config.toml.
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct SetConfigArgs {
|
||||||
|
new_config: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn create_set_config_tool() -> OpenAiTool {
|
||||||
|
let mut properties = BTreeMap::new();
|
||||||
|
properties.insert(
|
||||||
|
"new_config".to_string(),
|
||||||
|
JsonSchema::String {
|
||||||
|
description: Some("Full TOML contents to write to ~/.codex/config.toml".to_string()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
OpenAiTool::Function(ResponsesApiTool {
|
||||||
|
name: "set_config".to_string(),
|
||||||
|
description: "Overwrites ~/.codex/config.toml with the provided TOML string. If the user requests configuration changes, construct the full desired TOML and call this tool. The value is validated and a diff will be shown for user approval before writing.".to_string(),
|
||||||
|
strict: false,
|
||||||
|
parameters: JsonSchema::Object {
|
||||||
|
properties,
|
||||||
|
required: Some(vec!["new_config".to_string()]),
|
||||||
|
additional_properties: Some(false),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// show_codex_docs() — returns Codex documentation.
|
||||||
|
pub(crate) fn create_show_codex_docs_tool() -> OpenAiTool {
|
||||||
|
OpenAiTool::Function(ResponsesApiTool {
|
||||||
|
name: "show_codex_docs".to_string(),
|
||||||
|
description: "Returns Codex documentation, including the repo README and all user docs under docs/. Use this when you need information about configuration, setup, features, or usage.".to_string(),
|
||||||
|
strict: false,
|
||||||
|
parameters: JsonSchema::Object {
|
||||||
|
properties: BTreeMap::new(),
|
||||||
|
required: Some(vec![]),
|
||||||
|
additional_properties: Some(false),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_config_path() -> std::io::Result<PathBuf> {
|
||||||
|
let mut p = find_codex_home()?;
|
||||||
|
p.push(CONFIG_TOML_FILE);
|
||||||
|
Ok(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn handle_get_config(
|
||||||
|
_session: &Session,
|
||||||
|
_arguments: String,
|
||||||
|
_sub_id: String,
|
||||||
|
call_id: String,
|
||||||
|
) -> ResponseInputItem {
|
||||||
|
let content = match resolve_config_path().and_then(fs::read_to_string) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => String::new(),
|
||||||
|
Err(e) => {
|
||||||
|
return ResponseInputItem::FunctionCallOutput {
|
||||||
|
call_id,
|
||||||
|
output: FunctionCallOutputPayload {
|
||||||
|
content: format!("failed to read config: {e}"),
|
||||||
|
success: Some(false),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ResponseInputItem::FunctionCallOutput {
|
||||||
|
call_id,
|
||||||
|
output: FunctionCallOutputPayload {
|
||||||
|
content,
|
||||||
|
success: Some(true),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn handle_set_config(
|
||||||
|
session: &Session,
|
||||||
|
arguments: String,
|
||||||
|
sub_id: String,
|
||||||
|
call_id: String,
|
||||||
|
) -> ResponseInputItem {
|
||||||
|
let args: SetConfigArgs = match serde_json::from_str(&arguments) {
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(e) => {
|
||||||
|
return ResponseInputItem::FunctionCallOutput {
|
||||||
|
call_id,
|
||||||
|
output: FunctionCallOutputPayload {
|
||||||
|
content: format!("failed to parse function arguments: {e}"),
|
||||||
|
success: None,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Validate TOML and ensure it can be materialized into a runtime Config.
|
||||||
|
let cfg_toml: crate::config::ConfigToml = match toml::from_str(&args.new_config) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
return ResponseInputItem::FunctionCallOutput {
|
||||||
|
call_id,
|
||||||
|
output: FunctionCallOutputPayload {
|
||||||
|
content: format!("invalid TOML: {e}"),
|
||||||
|
success: Some(false),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let codex_home = match find_codex_home() {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
return ResponseInputItem::FunctionCallOutput {
|
||||||
|
call_id,
|
||||||
|
output: FunctionCallOutputPayload {
|
||||||
|
content: format!("failed to resolve codex_home: {e}"),
|
||||||
|
success: Some(false),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Err(e) = crate::config::Config::load_from_base_config_with_overrides(
|
||||||
|
cfg_toml.clone(),
|
||||||
|
crate::config::ConfigOverrides::default(),
|
||||||
|
codex_home.clone(),
|
||||||
|
) {
|
||||||
|
return ResponseInputItem::FunctionCallOutput {
|
||||||
|
call_id,
|
||||||
|
output: FunctionCallOutputPayload {
|
||||||
|
content: format!("invalid config: {e}"),
|
||||||
|
success: Some(false),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let path = match resolve_config_path() {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
return ResponseInputItem::FunctionCallOutput {
|
||||||
|
call_id,
|
||||||
|
output: FunctionCallOutputPayload {
|
||||||
|
content: format!("failed to resolve config path: {e}"),
|
||||||
|
success: Some(false),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Build a synthetic patch showing the proposed change and ask for patch approval.
|
||||||
|
let current = match std::fs::read_to_string(&path) {
|
||||||
|
Ok(s) => Some(s),
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => None,
|
||||||
|
Err(e) => {
|
||||||
|
return ResponseInputItem::FunctionCallOutput {
|
||||||
|
call_id,
|
||||||
|
output: FunctionCallOutputPayload {
|
||||||
|
content: format!("failed to read existing config: {e}"),
|
||||||
|
success: Some(false),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let make_lines = |s: &str| {
|
||||||
|
let mut v: Vec<&str> = s.split('\n').collect();
|
||||||
|
if v.last().is_some_and(|l| l.is_empty()) {
|
||||||
|
v.pop();
|
||||||
|
}
|
||||||
|
v.into_iter()
|
||||||
|
.map(|l| l.to_string())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
};
|
||||||
|
|
||||||
|
let patch_body = if let Some(curr) = ¤t {
|
||||||
|
let mut body = format!("*** Update File: {}\n@@\n", path.display());
|
||||||
|
for line in make_lines(curr) {
|
||||||
|
body.push_str(&format!("-{line}\n"));
|
||||||
|
}
|
||||||
|
for line in make_lines(&args.new_config) {
|
||||||
|
body.push_str(&format!("+{line}\n"));
|
||||||
|
}
|
||||||
|
body
|
||||||
|
} else {
|
||||||
|
let mut body = format!("*** Add File: {}\n", path.display());
|
||||||
|
for line in make_lines(&args.new_config) {
|
||||||
|
body.push_str(&format!("+{line}\n"));
|
||||||
|
}
|
||||||
|
body
|
||||||
|
};
|
||||||
|
|
||||||
|
let patch_text = format!("*** Begin Patch\n{patch_body}*** End Patch");
|
||||||
|
let argv = vec!["apply_patch".to_string(), patch_text];
|
||||||
|
|
||||||
|
let cwd = path
|
||||||
|
.parent()
|
||||||
|
.map(Path::to_path_buf)
|
||||||
|
.unwrap_or_else(|| PathBuf::from("/"));
|
||||||
|
let action: ApplyPatchAction = match maybe_parse_apply_patch_verified(&argv, &cwd) {
|
||||||
|
MaybeApplyPatchVerified::Body(action) => action,
|
||||||
|
MaybeApplyPatchVerified::CorrectnessError(e) => {
|
||||||
|
return ResponseInputItem::FunctionCallOutput {
|
||||||
|
call_id,
|
||||||
|
output: FunctionCallOutputPayload {
|
||||||
|
content: format!("failed to compute patch diff: {e}"),
|
||||||
|
success: Some(false),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return ResponseInputItem::FunctionCallOutput {
|
||||||
|
call_id,
|
||||||
|
output: FunctionCallOutputPayload {
|
||||||
|
content: "failed to construct patch diff".to_string(),
|
||||||
|
success: Some(false),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let rx = session
|
||||||
|
.request_patch_approval(
|
||||||
|
sub_id.clone(),
|
||||||
|
call_id.clone(),
|
||||||
|
&action,
|
||||||
|
Some("Update Codex configuration file".to_string()),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
match rx.await.unwrap_or_default() {
|
||||||
|
ReviewDecision::Approved | ReviewDecision::ApprovedForSession => { /* proceed */ }
|
||||||
|
ReviewDecision::Denied | ReviewDecision::Abort => {
|
||||||
|
return ResponseInputItem::FunctionCallOutput {
|
||||||
|
call_id,
|
||||||
|
output: FunctionCallOutputPayload {
|
||||||
|
content: "set_config rejected by user".to_string(),
|
||||||
|
success: None,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(parent) = path.parent()
|
||||||
|
&& let Err(e) = fs::create_dir_all(parent)
|
||||||
|
{
|
||||||
|
return ResponseInputItem::FunctionCallOutput {
|
||||||
|
call_id,
|
||||||
|
output: FunctionCallOutputPayload {
|
||||||
|
content: format!("failed to create config directory: {e}"),
|
||||||
|
success: Some(false),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
match fs::write(&path, args.new_config.as_bytes()) {
|
||||||
|
Ok(_) => ResponseInputItem::FunctionCallOutput {
|
||||||
|
call_id,
|
||||||
|
output: FunctionCallOutputPayload {
|
||||||
|
content: format!("wrote {}", path.display()),
|
||||||
|
success: Some(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Err(e) => ResponseInputItem::FunctionCallOutput {
|
||||||
|
call_id,
|
||||||
|
output: FunctionCallOutputPayload {
|
||||||
|
content: format!("failed to write config: {e}"),
|
||||||
|
success: Some(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn handle_show_codex_docs(
|
||||||
|
_session: &Session,
|
||||||
|
_arguments: String,
|
||||||
|
_sub_id: String,
|
||||||
|
call_id: String,
|
||||||
|
) -> ResponseInputItem {
|
||||||
|
let content = build_all_codex_docs();
|
||||||
|
ResponseInputItem::FunctionCallOutput {
|
||||||
|
call_id,
|
||||||
|
output: FunctionCallOutputPayload {
|
||||||
|
content,
|
||||||
|
success: Some(true),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,6 +44,7 @@ mod conversation_manager;
|
|||||||
mod event_mapping;
|
mod event_mapping;
|
||||||
pub use conversation_manager::ConversationManager;
|
pub use conversation_manager::ConversationManager;
|
||||||
pub use conversation_manager::NewConversation;
|
pub use conversation_manager::NewConversation;
|
||||||
|
mod config_edit_tool;
|
||||||
// Re-export common auth types for workspace consumers
|
// Re-export common auth types for workspace consumers
|
||||||
pub use auth::AuthManager;
|
pub use auth::AuthManager;
|
||||||
pub use auth::CodexAuth;
|
pub use auth::CodexAuth;
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ use serde_json::json;
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::config_edit_tool::create_get_config_tool;
|
||||||
|
use crate::config_edit_tool::create_set_config_tool;
|
||||||
|
use crate::config_edit_tool::create_show_codex_docs_tool;
|
||||||
use crate::model_family::ModelFamily;
|
use crate::model_family::ModelFamily;
|
||||||
use crate::plan_tool::PLAN_TOOL;
|
use crate::plan_tool::PLAN_TOOL;
|
||||||
use crate::protocol::AskForApproval;
|
use crate::protocol::AskForApproval;
|
||||||
@@ -572,6 +575,10 @@ pub(crate) fn get_openai_tools(
|
|||||||
if config.web_search_request {
|
if config.web_search_request {
|
||||||
tools.push(OpenAiTool::WebSearch {});
|
tools.push(OpenAiTool::WebSearch {});
|
||||||
}
|
}
|
||||||
|
// Always include internal config tools.
|
||||||
|
tools.push(create_get_config_tool());
|
||||||
|
tools.push(create_set_config_tool());
|
||||||
|
tools.push(create_show_codex_docs_tool());
|
||||||
|
|
||||||
// Include the view_image tool so the agent can attach images to context.
|
// Include the view_image tool so the agent can attach images to context.
|
||||||
if config.include_view_image_tool {
|
if config.include_view_image_tool {
|
||||||
@@ -647,7 +654,15 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq_tool_names(
|
assert_eq_tool_names(
|
||||||
&tools,
|
&tools,
|
||||||
&["local_shell", "update_plan", "web_search", "view_image"],
|
&[
|
||||||
|
"local_shell",
|
||||||
|
"update_plan",
|
||||||
|
"web_search",
|
||||||
|
"get_config",
|
||||||
|
"set_config",
|
||||||
|
"show_codex_docs",
|
||||||
|
"view_image",
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -668,7 +683,15 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq_tool_names(
|
assert_eq_tool_names(
|
||||||
&tools,
|
&tools,
|
||||||
&["shell", "update_plan", "web_search", "view_image"],
|
&[
|
||||||
|
"shell",
|
||||||
|
"update_plan",
|
||||||
|
"web_search",
|
||||||
|
"get_config",
|
||||||
|
"set_config",
|
||||||
|
"show_codex_docs",
|
||||||
|
"view_image",
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -728,13 +751,16 @@ mod tests {
|
|||||||
&[
|
&[
|
||||||
"shell",
|
"shell",
|
||||||
"web_search",
|
"web_search",
|
||||||
|
"get_config",
|
||||||
|
"set_config",
|
||||||
|
"show_codex_docs",
|
||||||
"view_image",
|
"view_image",
|
||||||
"test_server/do_something_cool",
|
"test_server/do_something_cool",
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tools[3],
|
tools[6],
|
||||||
OpenAiTool::Function(ResponsesApiTool {
|
OpenAiTool::Function(ResponsesApiTool {
|
||||||
name: "test_server/do_something_cool".to_string(),
|
name: "test_server/do_something_cool".to_string(),
|
||||||
parameters: JsonSchema::Object {
|
parameters: JsonSchema::Object {
|
||||||
@@ -841,11 +867,14 @@ mod tests {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
let tools = get_openai_tools(&config, Some(tools_map));
|
let tools = get_openai_tools(&config, Some(tools_map));
|
||||||
// Expect shell first, followed by MCP tools sorted by fully-qualified name.
|
// Expect shell first, followed by built-in config tools, then MCP tools sorted by fully-qualified name.
|
||||||
assert_eq_tool_names(
|
assert_eq_tool_names(
|
||||||
&tools,
|
&tools,
|
||||||
&[
|
&[
|
||||||
"shell",
|
"shell",
|
||||||
|
"get_config",
|
||||||
|
"set_config",
|
||||||
|
"show_codex_docs",
|
||||||
"view_image",
|
"view_image",
|
||||||
"test_server/cool",
|
"test_server/cool",
|
||||||
"test_server/do",
|
"test_server/do",
|
||||||
@@ -893,11 +922,19 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq_tool_names(
|
assert_eq_tool_names(
|
||||||
&tools,
|
&tools,
|
||||||
&["shell", "web_search", "view_image", "dash/search"],
|
&[
|
||||||
|
"shell",
|
||||||
|
"web_search",
|
||||||
|
"get_config",
|
||||||
|
"set_config",
|
||||||
|
"show_codex_docs",
|
||||||
|
"view_image",
|
||||||
|
"dash/search",
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tools[3],
|
tools[6],
|
||||||
OpenAiTool::Function(ResponsesApiTool {
|
OpenAiTool::Function(ResponsesApiTool {
|
||||||
name: "dash/search".to_string(),
|
name: "dash/search".to_string(),
|
||||||
parameters: JsonSchema::Object {
|
parameters: JsonSchema::Object {
|
||||||
@@ -953,10 +990,18 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq_tool_names(
|
assert_eq_tool_names(
|
||||||
&tools,
|
&tools,
|
||||||
&["shell", "web_search", "view_image", "dash/paginate"],
|
&[
|
||||||
|
"shell",
|
||||||
|
"web_search",
|
||||||
|
"get_config",
|
||||||
|
"set_config",
|
||||||
|
"show_codex_docs",
|
||||||
|
"view_image",
|
||||||
|
"dash/paginate",
|
||||||
|
],
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tools[3],
|
tools[6],
|
||||||
OpenAiTool::Function(ResponsesApiTool {
|
OpenAiTool::Function(ResponsesApiTool {
|
||||||
name: "dash/paginate".to_string(),
|
name: "dash/paginate".to_string(),
|
||||||
parameters: JsonSchema::Object {
|
parameters: JsonSchema::Object {
|
||||||
@@ -1008,9 +1053,21 @@ mod tests {
|
|||||||
)])),
|
)])),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq_tool_names(&tools, &["shell", "web_search", "view_image", "dash/tags"]);
|
assert_eq_tool_names(
|
||||||
|
&tools,
|
||||||
|
&[
|
||||||
|
"shell",
|
||||||
|
"web_search",
|
||||||
|
"get_config",
|
||||||
|
"set_config",
|
||||||
|
"show_codex_docs",
|
||||||
|
"view_image",
|
||||||
|
"dash/tags",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tools[3],
|
tools[6],
|
||||||
OpenAiTool::Function(ResponsesApiTool {
|
OpenAiTool::Function(ResponsesApiTool {
|
||||||
name: "dash/tags".to_string(),
|
name: "dash/tags".to_string(),
|
||||||
parameters: JsonSchema::Object {
|
parameters: JsonSchema::Object {
|
||||||
@@ -1065,9 +1122,20 @@ mod tests {
|
|||||||
)])),
|
)])),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq_tool_names(&tools, &["shell", "web_search", "view_image", "dash/value"]);
|
assert_eq_tool_names(
|
||||||
|
&tools,
|
||||||
|
&[
|
||||||
|
"shell",
|
||||||
|
"web_search",
|
||||||
|
"get_config",
|
||||||
|
"set_config",
|
||||||
|
"show_codex_docs",
|
||||||
|
"view_image",
|
||||||
|
"dash/value",
|
||||||
|
],
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tools[3],
|
tools[6],
|
||||||
OpenAiTool::Function(ResponsesApiTool {
|
OpenAiTool::Function(ResponsesApiTool {
|
||||||
name: "dash/value".to_string(),
|
name: "dash/value".to_string(),
|
||||||
parameters: JsonSchema::Object {
|
parameters: JsonSchema::Object {
|
||||||
|
|||||||
@@ -191,7 +191,15 @@ async fn prompt_tools_are_consistent_across_requests() {
|
|||||||
let expected_instructions: &str = include_str!("../../prompt.md");
|
let expected_instructions: &str = include_str!("../../prompt.md");
|
||||||
// our internal implementation is responsible for keeping tools in sync
|
// our internal implementation is responsible for keeping tools in sync
|
||||||
// with the OpenAI schema, so we just verify the tool presence here
|
// with the OpenAI schema, so we just verify the tool presence here
|
||||||
let expected_tools_names: &[&str] = &["shell", "update_plan", "apply_patch", "view_image"];
|
let expected_tools_names: &[&str] = &[
|
||||||
|
"shell",
|
||||||
|
"update_plan",
|
||||||
|
"apply_patch",
|
||||||
|
"get_config",
|
||||||
|
"set_config",
|
||||||
|
"show_codex_docs",
|
||||||
|
"view_image",
|
||||||
|
];
|
||||||
let body0 = requests[0].body_json::<serde_json::Value>().unwrap();
|
let body0 = requests[0].body_json::<serde_json::Value>().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
body0["instructions"],
|
body0["instructions"],
|
||||||
|
|||||||
Reference in New Issue
Block a user