2025-10-30 10:28:32 +00:00
|
|
|
use crate::config::types::ReasoningSummaryFormat;
|
chore: refactor tool handling (#4510)
# Tool System Refactor
- Centralizes tool definitions and execution in `core/src/tools/*`:
specs (`spec.rs`), handlers (`handlers/*`), router (`router.rs`),
registry/dispatch (`registry.rs`), and shared context (`context.rs`).
One registry now builds the model-visible tool list and binds handlers.
- Router converts model responses to tool calls; Registry dispatches
with consistent telemetry via `codex-rs/otel` and unified error
handling. Function, Local Shell, MCP, and experimental `unified_exec`
all flow through this path; legacy shell aliases still work.
- Rationale: reduce per‑tool boilerplate, keep spec/handler in sync, and
make adding tools predictable and testable.
Example: `read_file`
- Spec: `core/src/tools/spec.rs` (see `create_read_file_tool`,
registered by `build_specs`).
- Handler: `core/src/tools/handlers/read_file.rs` (absolute `file_path`,
1‑indexed `offset`, `limit`, `L#: ` prefixes, safe truncation).
- E2E test: `core/tests/suite/read_file.rs` validates the tool returns
the requested lines.
## Next steps:
- Decompose `handle_container_exec_with_params`
- Add parallel tool calls
2025-10-03 13:21:06 +01:00
|
|
|
use crate::tools::handlers::apply_patch::ApplyPatchToolType;
|
2025-08-22 13:42:34 -07:00
|
|
|
|
2025-09-14 15:45:15 -07:00
|
|
|
/// The `instructions` field in the payload sent to a model should always start
|
|
|
|
|
/// with this content.
|
|
|
|
|
const BASE_INSTRUCTIONS: &str = include_str!("../prompt.md");
|
2025-09-15 08:17:13 -07:00
|
|
|
const GPT_5_CODEX_INSTRUCTIONS: &str = include_str!("../gpt_5_codex_prompt.md");
|
2025-09-14 15:45:15 -07:00
|
|
|
|
2025-08-04 23:50:03 -07:00
|
|
|
/// A model family is a group of models that share certain characteristics.
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
|
|
|
pub struct ModelFamily {
|
|
|
|
|
/// The full model slug used to derive this model family, e.g.
|
|
|
|
|
/// "gpt-4.1-2025-04-14".
|
|
|
|
|
pub slug: String,
|
|
|
|
|
|
|
|
|
|
/// The model family name, e.g. "gpt-4.1". Note this should able to be used
|
|
|
|
|
/// with [`crate::openai_model_info::get_model_info`].
|
|
|
|
|
pub family: String,
|
|
|
|
|
|
|
|
|
|
/// True if the model needs additional instructions on how to use the
|
|
|
|
|
/// "virtual" `apply_patch` CLI.
|
|
|
|
|
pub needs_special_apply_patch_instructions: bool,
|
|
|
|
|
|
|
|
|
|
// Whether the `reasoning` field can be set when making a request to this
|
|
|
|
|
// model family. Note it has `effort` and `summary` subfields (though
|
|
|
|
|
// `summary` is optional).
|
|
|
|
|
pub supports_reasoning_summaries: bool,
|
|
|
|
|
|
2025-09-04 11:00:01 -07:00
|
|
|
// Define if we need a special handling of reasoning summary
|
|
|
|
|
pub reasoning_summary_format: ReasoningSummaryFormat,
|
|
|
|
|
|
2025-08-04 23:50:03 -07:00
|
|
|
// This should be set to true when the model expects a tool named
|
|
|
|
|
// "local_shell" to be provided. Its contract must be understood natively by
|
|
|
|
|
// the model such that its description can be omitted.
|
|
|
|
|
// See https://platform.openai.com/docs/guides/tools-local-shell
|
|
|
|
|
pub uses_local_shell_tool: bool,
|
2025-08-15 11:55:53 -04:00
|
|
|
|
2025-10-05 17:10:49 +01:00
|
|
|
/// Whether this model supports parallel tool calls when using the
|
|
|
|
|
/// Responses API.
|
|
|
|
|
pub supports_parallel_tool_calls: bool,
|
|
|
|
|
|
2025-08-22 13:42:34 -07:00
|
|
|
/// Present if the model performs better when `apply_patch` is provided as
|
|
|
|
|
/// a tool call instead of just a bash command
|
|
|
|
|
pub apply_patch_tool_type: Option<ApplyPatchToolType>,
|
2025-09-14 15:45:15 -07:00
|
|
|
|
|
|
|
|
// Instructions to use for querying the model
|
|
|
|
|
pub base_instructions: String,
|
2025-10-03 17:58:03 +01:00
|
|
|
|
|
|
|
|
/// Names of beta tools that should be exposed to this model family.
|
|
|
|
|
pub experimental_supported_tools: Vec<String>,
|
2025-10-20 11:29:49 -07:00
|
|
|
|
|
|
|
|
/// Percentage of the context window considered usable for inputs, after
|
|
|
|
|
/// reserving headroom for system prompts, tool overhead, and model output.
|
|
|
|
|
/// This is applied when computing the effective context window seen by
|
|
|
|
|
/// consumers.
|
|
|
|
|
pub effective_context_window_percent: i64,
|
2025-10-27 18:46:30 +00:00
|
|
|
|
|
|
|
|
/// If the model family supports setting the verbosity level when using Responses API.
|
|
|
|
|
pub support_verbosity: bool,
|
2025-08-04 23:50:03 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
macro_rules! model_family {
|
|
|
|
|
(
|
|
|
|
|
$slug:expr, $family:expr $(, $key:ident : $value:expr )* $(,)?
|
|
|
|
|
) => {{
|
|
|
|
|
// defaults
|
|
|
|
|
let mut mf = ModelFamily {
|
|
|
|
|
slug: $slug.to_string(),
|
|
|
|
|
family: $family.to_string(),
|
|
|
|
|
needs_special_apply_patch_instructions: false,
|
|
|
|
|
supports_reasoning_summaries: false,
|
2025-09-04 11:00:01 -07:00
|
|
|
reasoning_summary_format: ReasoningSummaryFormat::None,
|
2025-08-04 23:50:03 -07:00
|
|
|
uses_local_shell_tool: false,
|
2025-10-05 17:10:49 +01:00
|
|
|
supports_parallel_tool_calls: false,
|
2025-08-22 13:42:34 -07:00
|
|
|
apply_patch_tool_type: None,
|
2025-09-14 15:45:15 -07:00
|
|
|
base_instructions: BASE_INSTRUCTIONS.to_string(),
|
2025-10-03 17:58:03 +01:00
|
|
|
experimental_supported_tools: Vec::new(),
|
2025-10-20 11:29:49 -07:00
|
|
|
effective_context_window_percent: 95,
|
2025-10-27 18:46:30 +00:00
|
|
|
support_verbosity: false,
|
2025-08-04 23:50:03 -07:00
|
|
|
};
|
|
|
|
|
// apply overrides
|
|
|
|
|
$(
|
|
|
|
|
mf.$key = $value;
|
|
|
|
|
)*
|
|
|
|
|
Some(mf)
|
|
|
|
|
}};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns a `ModelFamily` for the given model slug, or `None` if the slug
|
|
|
|
|
/// does not match any known model family.
|
2025-10-21 17:11:12 +01:00
|
|
|
pub fn find_family_for_model(slug: &str) -> Option<ModelFamily> {
|
2025-08-04 23:50:03 -07:00
|
|
|
if slug.starts_with("o3") {
|
|
|
|
|
model_family!(
|
|
|
|
|
slug, "o3",
|
|
|
|
|
supports_reasoning_summaries: true,
|
2025-09-14 20:20:37 -05:00
|
|
|
needs_special_apply_patch_instructions: true,
|
2025-08-04 23:50:03 -07:00
|
|
|
)
|
|
|
|
|
} else if slug.starts_with("o4-mini") {
|
|
|
|
|
model_family!(
|
|
|
|
|
slug, "o4-mini",
|
|
|
|
|
supports_reasoning_summaries: true,
|
2025-09-14 20:20:37 -05:00
|
|
|
needs_special_apply_patch_instructions: true,
|
2025-08-04 23:50:03 -07:00
|
|
|
)
|
|
|
|
|
} else if slug.starts_with("codex-mini-latest") {
|
|
|
|
|
model_family!(
|
|
|
|
|
slug, "codex-mini-latest",
|
|
|
|
|
supports_reasoning_summaries: true,
|
|
|
|
|
uses_local_shell_tool: true,
|
2025-09-14 20:20:37 -05:00
|
|
|
needs_special_apply_patch_instructions: true,
|
2025-08-04 23:50:03 -07:00
|
|
|
)
|
|
|
|
|
} else if slug.starts_with("gpt-4.1") {
|
|
|
|
|
model_family!(
|
|
|
|
|
slug, "gpt-4.1",
|
|
|
|
|
needs_special_apply_patch_instructions: true,
|
|
|
|
|
)
|
2025-09-10 06:02:02 +08:00
|
|
|
} else if slug.starts_with("gpt-oss") || slug.starts_with("openai/gpt-oss") {
|
2025-08-22 13:42:34 -07:00
|
|
|
model_family!(slug, "gpt-oss", apply_patch_tool_type: Some(ApplyPatchToolType::Function))
|
2025-08-04 23:50:03 -07:00
|
|
|
} else if slug.starts_with("gpt-4o") {
|
2025-09-14 20:20:37 -05:00
|
|
|
model_family!(slug, "gpt-4o", needs_special_apply_patch_instructions: true)
|
2025-08-04 23:50:03 -07:00
|
|
|
} else if slug.starts_with("gpt-3.5") {
|
2025-09-14 20:20:37 -05:00
|
|
|
model_family!(slug, "gpt-3.5", needs_special_apply_patch_instructions: true)
|
2025-10-05 17:10:49 +01:00
|
|
|
} else if slug.starts_with("test-gpt-5-codex") {
|
|
|
|
|
model_family!(
|
|
|
|
|
slug, slug,
|
|
|
|
|
supports_reasoning_summaries: true,
|
|
|
|
|
reasoning_summary_format: ReasoningSummaryFormat::Experimental,
|
|
|
|
|
base_instructions: GPT_5_CODEX_INSTRUCTIONS.to_string(),
|
|
|
|
|
experimental_supported_tools: vec![
|
2025-10-08 11:02:50 +01:00
|
|
|
"grep_files".to_string(),
|
2025-10-07 19:33:19 +01:00
|
|
|
"list_dir".to_string(),
|
2025-10-08 11:02:50 +01:00
|
|
|
"read_file".to_string(),
|
|
|
|
|
"test_sync_tool".to_string(),
|
2025-10-05 17:10:49 +01:00
|
|
|
],
|
|
|
|
|
supports_parallel_tool_calls: true,
|
2025-10-27 18:46:30 +00:00
|
|
|
support_verbosity: true,
|
2025-10-05 17:10:49 +01:00
|
|
|
)
|
2025-10-05 17:26:04 +01:00
|
|
|
|
|
|
|
|
// Internal models.
|
2025-10-26 18:55:12 -07:00
|
|
|
} else if slug.starts_with("codex-exp-") {
|
2025-10-05 17:26:04 +01:00
|
|
|
model_family!(
|
|
|
|
|
slug, slug,
|
|
|
|
|
supports_reasoning_summaries: true,
|
|
|
|
|
reasoning_summary_format: ReasoningSummaryFormat::Experimental,
|
|
|
|
|
base_instructions: GPT_5_CODEX_INSTRUCTIONS.to_string(),
|
|
|
|
|
apply_patch_tool_type: Some(ApplyPatchToolType::Freeform),
|
2025-10-08 11:02:50 +01:00
|
|
|
experimental_supported_tools: vec![
|
|
|
|
|
"grep_files".to_string(),
|
|
|
|
|
"list_dir".to_string(),
|
|
|
|
|
"read_file".to_string(),
|
|
|
|
|
],
|
2025-10-05 17:26:04 +01:00
|
|
|
supports_parallel_tool_calls: true,
|
2025-10-27 18:46:30 +00:00
|
|
|
support_verbosity: true,
|
2025-10-05 17:26:04 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Production models.
|
2025-10-26 18:55:12 -07:00
|
|
|
} else if slug.starts_with("gpt-5-codex") || slug.starts_with("codex-") {
|
2025-09-13 16:40:54 -07:00
|
|
|
model_family!(
|
|
|
|
|
slug, slug,
|
|
|
|
|
supports_reasoning_summaries: true,
|
|
|
|
|
reasoning_summary_format: ReasoningSummaryFormat::Experimental,
|
2025-09-15 08:17:13 -07:00
|
|
|
base_instructions: GPT_5_CODEX_INSTRUCTIONS.to_string(),
|
2025-10-04 19:16:36 -07:00
|
|
|
apply_patch_tool_type: Some(ApplyPatchToolType::Freeform),
|
2025-10-27 18:46:30 +00:00
|
|
|
support_verbosity: true,
|
2025-09-13 16:40:54 -07:00
|
|
|
)
|
2025-08-07 09:07:51 -07:00
|
|
|
} else if slug.starts_with("gpt-5") {
|
2025-08-06 16:14:02 -07:00
|
|
|
model_family!(
|
2025-08-07 09:07:51 -07:00
|
|
|
slug, "gpt-5",
|
2025-08-06 16:14:02 -07:00
|
|
|
supports_reasoning_summaries: true,
|
2025-09-14 20:20:37 -05:00
|
|
|
needs_special_apply_patch_instructions: true,
|
2025-10-27 18:46:30 +00:00
|
|
|
support_verbosity: true,
|
2025-09-14 15:45:15 -07:00
|
|
|
)
|
2025-08-04 23:50:03 -07:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-14 15:45:15 -07:00
|
|
|
|
|
|
|
|
pub fn derive_default_model_family(model: &str) -> ModelFamily {
|
|
|
|
|
ModelFamily {
|
|
|
|
|
slug: model.to_string(),
|
|
|
|
|
family: model.to_string(),
|
|
|
|
|
needs_special_apply_patch_instructions: false,
|
|
|
|
|
supports_reasoning_summaries: false,
|
|
|
|
|
reasoning_summary_format: ReasoningSummaryFormat::None,
|
|
|
|
|
uses_local_shell_tool: false,
|
2025-10-05 17:10:49 +01:00
|
|
|
supports_parallel_tool_calls: false,
|
2025-09-14 15:45:15 -07:00
|
|
|
apply_patch_tool_type: None,
|
|
|
|
|
base_instructions: BASE_INSTRUCTIONS.to_string(),
|
2025-10-03 17:58:03 +01:00
|
|
|
experimental_supported_tools: Vec::new(),
|
2025-10-20 11:29:49 -07:00
|
|
|
effective_context_window_percent: 95,
|
2025-10-27 18:46:30 +00:00
|
|
|
support_verbosity: false,
|
2025-09-14 15:45:15 -07:00
|
|
|
}
|
|
|
|
|
}
|