mirror of
https://github.com/openai/codex.git
synced 2026-11-12 21:20:34 +00:00
rendering
This commit is contained in:
@@ -213,6 +213,9 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codex_features::CommonFeatureConfigToml;
|
||||
use codex_features::FeatureConfigTable;
|
||||
use codex_features::FeatureToml;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use codex_features::FEATURES;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_utils_template::Template;
|
||||
use codex_utils_template::TemplateError;
|
||||
use codex_utils_template::TemplateRenderError;
|
||||
use toml::Value as TomlValue;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::context_manager::updates::build_developer_update_item;
|
||||
|
||||
use super::session::SessionConfiguration;
|
||||
use super::turn_context::TurnContext;
|
||||
|
||||
pub(super) fn render_feature_hint_messages(
|
||||
session_configuration: &SessionConfiguration,
|
||||
turn_context: &TurnContext,
|
||||
) -> Vec<ResponseItem> {
|
||||
let lockfile = match session_configuration.to_config_lockfile_toml() {
|
||||
Ok(lockfile) => lockfile,
|
||||
Err(err) => {
|
||||
warn!(error = %err, "failed to build config lock for feature hint rendering");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let Some(features_toml) = lockfile.config.features.as_ref() else {
|
||||
return Vec::new();
|
||||
};
|
||||
let template_variables = match build_template_variables(&lockfile.config) {
|
||||
Ok(variables) => variables,
|
||||
Err(err) => {
|
||||
warn!(error = %err, "failed to serialize resolved config for feature hint rendering");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
FEATURES
|
||||
.iter()
|
||||
.filter(|spec| turn_context.features.enabled(spec.id))
|
||||
.filter_map(|spec| {
|
||||
let hint = features_toml.hint(spec.key)?;
|
||||
let rendered = match render_feature_hint(hint, &template_variables) {
|
||||
Ok(rendered) => rendered,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
feature = spec.key,
|
||||
error = %err,
|
||||
"failed to render feature hint"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if rendered.is_empty() {
|
||||
return None;
|
||||
}
|
||||
build_developer_update_item(vec![rendered])
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn render_feature_hint(
|
||||
source: &str,
|
||||
template_variables: &BTreeMap<String, String>,
|
||||
) -> Result<String, TemplateError> {
|
||||
let template = Template::parse(source)?;
|
||||
let variables = template
|
||||
.placeholders()
|
||||
.map(|placeholder| {
|
||||
template_variables
|
||||
.get(placeholder)
|
||||
.map(|value| (placeholder, value.as_str()))
|
||||
.ok_or_else(|| TemplateRenderError::MissingValue {
|
||||
name: placeholder.to_string(),
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
template.render(variables).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn build_template_variables(
|
||||
config: &codex_config::config_toml::ConfigToml,
|
||||
) -> Result<BTreeMap<String, String>, toml::ser::Error> {
|
||||
let value = TomlValue::try_from(config)?;
|
||||
let mut variables = BTreeMap::new();
|
||||
flatten_toml_value(/*prefix*/ None, &value, &mut variables);
|
||||
Ok(variables)
|
||||
}
|
||||
|
||||
fn flatten_toml_value(
|
||||
prefix: Option<&str>,
|
||||
value: &TomlValue,
|
||||
variables: &mut BTreeMap<String, String>,
|
||||
) {
|
||||
match value {
|
||||
TomlValue::Table(table) => {
|
||||
for (key, value) in table {
|
||||
let key = match prefix {
|
||||
Some(prefix) => format!("{prefix}.{key}"),
|
||||
None => key.clone(),
|
||||
};
|
||||
flatten_toml_value(Some(&key), value, variables);
|
||||
}
|
||||
}
|
||||
TomlValue::String(value) => insert_template_variable(prefix, value.clone(), variables),
|
||||
TomlValue::Integer(value) => {
|
||||
insert_template_variable(prefix, value.to_string(), variables);
|
||||
}
|
||||
TomlValue::Float(value) => {
|
||||
insert_template_variable(prefix, value.to_string(), variables);
|
||||
}
|
||||
TomlValue::Boolean(value) => {
|
||||
insert_template_variable(prefix, value.to_string(), variables);
|
||||
}
|
||||
TomlValue::Datetime(value) => {
|
||||
insert_template_variable(prefix, value.to_string(), variables);
|
||||
}
|
||||
TomlValue::Array(value) => {
|
||||
insert_template_variable(
|
||||
prefix,
|
||||
TomlValue::Array(value.clone()).to_string(),
|
||||
variables,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_template_variable(
|
||||
key: Option<&str>,
|
||||
value: String,
|
||||
variables: &mut BTreeMap<String, String>,
|
||||
) {
|
||||
if let Some(key) = key {
|
||||
variables.insert(key.to_string(), value);
|
||||
}
|
||||
}
|
||||
@@ -184,6 +184,7 @@ use codex_protocol::error::Result as CodexResult;
|
||||
use codex_protocol::exec_output::StreamOutput;
|
||||
|
||||
mod config_lock;
|
||||
mod feature_hints;
|
||||
mod handlers;
|
||||
mod mcp;
|
||||
mod multi_agents;
|
||||
@@ -2540,6 +2541,7 @@ impl Session {
|
||||
collaboration_mode,
|
||||
base_instructions,
|
||||
session_source,
|
||||
session_configuration,
|
||||
) = {
|
||||
let state = self.state.lock().await;
|
||||
(
|
||||
@@ -2548,6 +2550,7 @@ impl Session {
|
||||
state.session_configuration.collaboration_mode.clone(),
|
||||
state.session_configuration.base_instructions.clone(),
|
||||
state.session_configuration.session_source.clone(),
|
||||
state.session_configuration.clone(),
|
||||
)
|
||||
};
|
||||
if let Some(model_switch_message) =
|
||||
@@ -2702,13 +2705,16 @@ impl Session {
|
||||
|
||||
let multi_agent_v2_usage_hint_text =
|
||||
multi_agents::usage_hint_text(turn_context, &session_source);
|
||||
let feature_hint_messages =
|
||||
feature_hints::render_feature_hint_messages(&session_configuration, turn_context);
|
||||
|
||||
let mut items = Vec::with_capacity(4);
|
||||
let mut items = Vec::with_capacity(4 + feature_hint_messages.len());
|
||||
if let Some(developer_message) =
|
||||
crate::context_manager::updates::build_developer_update_item(developer_sections)
|
||||
{
|
||||
items.push(developer_message);
|
||||
}
|
||||
items.extend(feature_hint_messages);
|
||||
if let Some(usage_hint_text) = multi_agent_v2_usage_hint_text
|
||||
&& let Some(usage_hint_message) =
|
||||
crate::context_manager::updates::build_developer_update_item(vec![
|
||||
|
||||
@@ -5688,6 +5688,47 @@ async fn build_initial_context_omits_multi_agent_v2_usage_hints_when_feature_dis
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn build_initial_context_renders_feature_hint_developer_message_with_resolved_config_values()
|
||||
{
|
||||
let (session, turn_context, _rx_event) = make_session_and_context_with_auth_and_config_and_rx(
|
||||
CodexAuth::from_api_key("Test API Key"),
|
||||
Vec::new(),
|
||||
|config| {
|
||||
let _ = config.features.enable(Feature::MemoryTool);
|
||||
config.agent_max_threads = Some(7);
|
||||
|
||||
let user_config_path = match config.config_layer_stack.get_user_layer() {
|
||||
Some(layer) => match &layer.name {
|
||||
codex_config::ConfigLayerSource::User { file } => file.clone(),
|
||||
other => panic!("expected user config layer, got {other:?}"),
|
||||
},
|
||||
None => panic!("expected user config layer"),
|
||||
};
|
||||
config.config_layer_stack = config.config_layer_stack.with_user_config(
|
||||
&user_config_path,
|
||||
toml::toml! {
|
||||
[features.memories]
|
||||
enabled = true
|
||||
hint = "This is a super hint {{ agents.max_threads }}"
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
let initial_context = session.build_initial_context(turn_context.as_ref()).await;
|
||||
|
||||
let developer_messages = developer_message_texts(&initial_context);
|
||||
assert!(
|
||||
developer_messages
|
||||
.iter()
|
||||
.any(|message| message.as_slice() == ["This is a super hint 7"]),
|
||||
"expected rendered feature hint developer message, got {developer_messages:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn configured_multi_agent_v2_usage_hint_texts_use_effective_enabled_feature_state() {
|
||||
let (mut session, _turn_context) =
|
||||
|
||||
Reference in New Issue
Block a user