mirror of
https://github.com/openai/codex.git
synced 2026-11-12 22:42:36 +00:00
Enable git attribution across Codex entry points (#34819)
## What changed - Install the git attribution extension in the app server, MCP server, and `codex debug prompt-input` so authenticated workspace policy controls the commit and pull request attribution instructions sent to the model. - Resolve attribution settings from the process-level ChatGPT base URL, independent of per-thread or per-tool configuration overrides. - Treat git attribution as contextual developer content when mapping model events. ## Testing - Cover policy fetch retries, workspace switches, rollbacks, cold thread resumes with legacy instructions, MCP tool calls, and prompt-debug output. GitOrigin-RevId: 57d182c432c20d7c1c6c429057b6163c9f32088d
This commit is contained in:
1 parent
4ebd976312
commit
88eb3a2b8a
21 files changed
+600
-166
No files matched your search
@@ -3,6 +3,9 @@ use std::env;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use app_test_support::ChatGptAuthFixture;
|
||||
use app_test_support::write_chatgpt_auth;
|
||||
use codex_config::types::AuthCredentialsStoreMode;
|
||||
use codex_core::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR;
|
||||
use codex_mcp_server::CodexToolCallParam;
|
||||
use codex_mcp_server::ExecApprovalElicitRequestParams;
|
||||
@@ -19,7 +22,11 @@ use rmcp::model::RequestId;
|
||||
use serde_json::json;
|
||||
use tempfile::TempDir;
|
||||
use tokio::time::timeout;
|
||||
use wiremock::Mock;
|
||||
use wiremock::MockServer;
|
||||
use wiremock::ResponseTemplate;
|
||||
use wiremock::matchers::method;
|
||||
use wiremock::matchers::path;
|
||||
|
||||
use core_test_support::skip_if_no_network;
|
||||
use mcp_test_support::McpProcess;
|
||||
@@ -366,17 +373,39 @@ async fn codex_tool_passes_base_instructions() -> anyhow::Result<()> {
|
||||
let server =
|
||||
create_mock_responses_server(vec![create_final_assistant_message_sse_response("Enjoy!")?])
|
||||
.await;
|
||||
let caller_server = MockServer::start().await;
|
||||
|
||||
// Run `codex mcp` with a specific config.toml.
|
||||
let codex_home = TempDir::new()?;
|
||||
create_config_toml(codex_home.path(), &server.uri())?;
|
||||
let mut mcp_process = McpProcess::new(codex_home.path()).await?;
|
||||
write_chatgpt_auth(
|
||||
codex_home.path(),
|
||||
ChatGptAuthFixture::new("chatgpt-token").account_id("workspace-123"),
|
||||
AuthCredentialsStoreMode::File,
|
||||
)?;
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/backend-api/wham/settings/user"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"commit_attribution_enabled": true,
|
||||
})))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
let mut mcp_process = McpProcess::new_with_env(
|
||||
codex_home.path(),
|
||||
&[("OPENAI_API_KEY", None), ("CODEX_ACCESS_TOKEN", None)],
|
||||
)
|
||||
.await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp_process.initialize()).await??;
|
||||
|
||||
// Send a "codex" tool request, which should hit the responses endpoint.
|
||||
let codex_request_id = mcp_process
|
||||
.send_codex_tool_call(CodexToolCallParam {
|
||||
prompt: "How are you?".to_string(),
|
||||
config: Some(HashMap::from([(
|
||||
"chatgpt_base_url".to_string(),
|
||||
json!(format!("{}/backend-api", caller_server.uri())),
|
||||
)])),
|
||||
base_instructions: Some("You are a helpful assistant.".to_string()),
|
||||
developer_instructions: Some("Foreshadow upcoming tool calls.".to_string()),
|
||||
..Default::default()
|
||||
@@ -412,12 +441,15 @@ async fn codex_tool_passes_base_instructions() -> anyhow::Result<()> {
|
||||
);
|
||||
|
||||
let requests = server.received_requests().await.unwrap();
|
||||
let request = requests[0].body_json::<serde_json::Value>()?;
|
||||
let request = requests
|
||||
.iter()
|
||||
.find(|request| request.url.path() == "/v1/responses")
|
||||
.expect("mock model request should be recorded")
|
||||
.body_json::<serde_json::Value>()?;
|
||||
let instructions = request["instructions"]
|
||||
.as_str()
|
||||
.expect("responses request should include instructions");
|
||||
assert!(instructions.starts_with("You are a helpful assistant."));
|
||||
|
||||
let developer_messages: Vec<&serde_json::Value> = request["input"]
|
||||
.as_array()
|
||||
.expect("responses request should include input items")
|
||||
@@ -431,6 +463,14 @@ async fn codex_tool_passes_base_instructions() -> anyhow::Result<()> {
|
||||
.filter(|span| span.get("type").and_then(serde_json::Value::as_str) == Some("input_text"))
|
||||
.filter_map(|span| span.get("text").and_then(serde_json::Value::as_str))
|
||||
.collect();
|
||||
let developer_text = developer_contents.join("\n");
|
||||
assert_eq!(
|
||||
developer_text
|
||||
.matches("Co-authored-by: Codex <noreply@openai.com>")
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
assert_eq!(developer_text.matches("Generated with Codex.").count(), 1);
|
||||
assert!(
|
||||
developer_contents
|
||||
.iter()
|
||||
@@ -441,6 +481,13 @@ async fn codex_tool_passes_base_instructions() -> anyhow::Result<()> {
|
||||
developer_contents.contains(&"Foreshadow upcoming tool calls."),
|
||||
"expected developer instructions in developer messages, got {developer_contents:?}"
|
||||
);
|
||||
let caller_requests = caller_server.received_requests().await.unwrap();
|
||||
assert!(
|
||||
caller_requests
|
||||
.iter()
|
||||
.all(|request| request.url.path() != "/backend-api/wham/settings/user"),
|
||||
"attribution settings must use the process-level base URL"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -513,6 +560,8 @@ approval_policy = "untrusted"
|
||||
sandbox_policy = "workspace-write"
|
||||
|
||||
model_provider = "mock_provider"
|
||||
chatgpt_base_url = "{server_uri}/backend-api"
|
||||
cli_auth_credentials_store = "file"
|
||||
|
||||
[model_providers.mock_provider]
|
||||
name = "Mock provider for test"
|
||||
|
||||
Reference in new issue
Block a user