mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 08:06:19 +00:00
fix(openai): scope input turns and preserve executable paths
Map input turn references with the same account identity as request metadata. Limit the workspace preset to metadata usernames and remote namespaces; preserve project names and executable paths to avoid path failures and same-parent workspace collisions. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -205,6 +205,16 @@ pub(super) fn scope_request(
|
||||
let Some(installation_id) = installation_id else {
|
||||
return;
|
||||
};
|
||||
// 输入项的轮次引用与请求 metadata 共用账号域,保留 item/call ID 和业务元数据
|
||||
if let Some(Value::Array(input)) = body.get_mut("input") {
|
||||
for item in input {
|
||||
if let Some(Value::Object(metadata)) =
|
||||
item.get_mut("internal_chat_message_metadata_passthrough")
|
||||
{
|
||||
map_fields(metadata, installation_id, &["turn_id"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 只处理官方自动 Git 上下文的关联字段,不扫描普通用户输入和工具输出
|
||||
let Ok(targets) = locate_contexts(body, true, || Ok(())) else {
|
||||
return;
|
||||
|
||||
@@ -16,11 +16,25 @@ fn payload(websocket: bool) -> ProtocolPayload {
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.clone();
|
||||
for (index, item) in input.iter_mut().enumerate() {
|
||||
let metadata = item
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.entry("internal_chat_message_metadata_passthrough")
|
||||
.or_insert_with(|| json!({}));
|
||||
metadata["turn_id"] = json!(if index == 3 {
|
||||
"source-turn"
|
||||
} else {
|
||||
"source-previous-turn"
|
||||
});
|
||||
}
|
||||
input.insert(0, json!({"role":"user", "content":[{"type":"input_text", "text":concat!(
|
||||
"<external_codex_git_state><environment_context><git>",
|
||||
r#"{"sessionId":"source-session","threadId":"source-thread","clientUserMessageId":"source-turn","path":"/workspace/project"}"#,
|
||||
"</git></environment_context></external_codex_git_state>"
|
||||
)}], "internal_chat_message_metadata_passthrough":{"content_item_kinds":["additional_content.codex_git_state"]}}));
|
||||
)}], "internal_chat_message_metadata_passthrough":{
|
||||
"turn_id":"source-turn", "content_item_kinds":["additional_content.codex_git_state"]
|
||||
}}));
|
||||
ProtocolPayload::json_object("openai", json!({
|
||||
"model":"gpt-5.4", "input":input,
|
||||
"prompt_cache_key":"custom-cache-key", "client_metadata":{
|
||||
@@ -143,7 +157,32 @@ async fn account_correlation_is_stable_across_transports_and_changes_only_with_a
|
||||
assert_eq!(full["tool_namespaces_info"]["inventory"], "body-only");
|
||||
assert_eq!(full["future"], metadata()["future"]);
|
||||
let input = body["input"].as_array().unwrap();
|
||||
assert_eq!(json!(&input[1..]), account_isolation::isolation_history());
|
||||
assert_eq!(
|
||||
input[0]["internal_chat_message_metadata_passthrough"],
|
||||
json!({"turn_id":flat["turn_id"], "content_item_kinds":["additional_content.codex_git_state"]}),
|
||||
);
|
||||
let previous_turn = &input[1]["internal_chat_message_metadata_passthrough"]["turn_id"];
|
||||
assert_scoped_id(previous_turn.as_str().unwrap(), "source-previous-turn");
|
||||
assert_ne!(previous_turn, &flat["turn_id"]);
|
||||
let mut expected_history = account_isolation::isolation_history();
|
||||
for (index, item) in expected_history
|
||||
.as_array_mut()
|
||||
.unwrap()
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
{
|
||||
let metadata = item
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.entry("internal_chat_message_metadata_passthrough")
|
||||
.or_insert_with(|| json!({}));
|
||||
metadata["turn_id"] = if index == 3 {
|
||||
flat["turn_id"].clone()
|
||||
} else {
|
||||
previous_turn.clone()
|
||||
};
|
||||
}
|
||||
assert_eq!(json!(&input[1..]), expected_history);
|
||||
let document =
|
||||
roxmltree::Document::parse(input[0]["content"][0]["text"].as_str().unwrap())
|
||||
.unwrap();
|
||||
@@ -168,7 +207,15 @@ async fn account_correlation_is_stable_across_transports_and_changes_only_with_a
|
||||
body["prompt_cache_key"].as_str().unwrap(),
|
||||
"custom-cache-key",
|
||||
);
|
||||
captures.push((flat["session_id"].clone(), body["prompt_cache_key"].clone()));
|
||||
captures.push((
|
||||
[
|
||||
flat["session_id"].clone(),
|
||||
flat["thread_id"].clone(),
|
||||
flat["turn_id"].clone(),
|
||||
],
|
||||
body["prompt_cache_key"].clone(),
|
||||
previous_turn.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
for index in [1, 2, 3, 6, 7] {
|
||||
@@ -177,6 +224,7 @@ async fn account_correlation_is_stable_across_transports_and_changes_only_with_a
|
||||
assert_eq!(captures[4], captures[5]);
|
||||
assert_ne!(captures[0].0, captures[4].0);
|
||||
assert_ne!(captures[0].1, captures[4].1);
|
||||
assert_ne!(captures[0].2, captures[4].2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
+9
-3
@@ -299,6 +299,8 @@ Responses 也不透传 `x-stainless-*`、`Origin`、`Referer`、`sec-ch-ua*` 和
|
||||
会话、线程、轮次、窗口及父子引用在出站时按账号安装身份稳定映射为 UUID;
|
||||
同账号保留引用关系,换账号使用不同标识。HTTP 请求头、正文 metadata、WS 副本及
|
||||
已识别的 Desktop 自动 Git 上下文使用一致映射。
|
||||
输入项的 `internal_chat_message_metadata_passthrough.turn_id` 同步映射,
|
||||
保留历史轮次与当前轮次的引用关系,其余输入项元数据保持不变。
|
||||
Images 的 `x-codex-image-turn-id`、turn metadata 和 Search 的会话 `id` 使用同一账号域;
|
||||
Search 只替换该 `id` 的原始字节,其余正文保持不变。
|
||||
`prompt_cache_key` 同样按账号映射,保留同账号内的缓存共享关系;未提供时不补造。
|
||||
@@ -1571,9 +1573,13 @@ JSON 路径支持 `$`、`.成员`、`["带特殊字符的键"]`、`[索引]`、`
|
||||
`$.workspaces.*.associated_remote_urls.*` 配合 `remove_field` 和 `pattern: "private-org"` 只删除命中的远端;
|
||||
`$.workspaces` 配合 `rename_key` 可改写工作区路径键名。环境文本替换必须保留 XML 包裹结构
|
||||
|
||||
管理端提供「统一工作区信息」预设,生成一条含多个通用步骤的规则。
|
||||
预设统一路径中的用户名为 `user`,工作区路径键和环境工作目录保留实际目录名并改为 `/workspace/<项目名>`,
|
||||
保留实际仓库、提交、分支、工作区标识和会话关联信息。安装身份由账号隔离逻辑处理,
|
||||
管理端提供「统一工作区信息」预设,生成一条含多个步骤的规则,仅处理 turn metadata 的 `workspaces`:
|
||||
|
||||
- `/home/<用户名>`、`/Users/<用户名>` 及 Windows 用户目录中的用户名改为 `user`,保留其余目录结构和项目名
|
||||
- Git 远端 URL 的账号或组织路径段改为 `alex`,保留协议、主机和仓库名,支持 HTTP(S)、SSH、Git 协议和 `git@host:owner/repo` 形式;多级命名空间及本地远端路径保持原值
|
||||
|
||||
提交、分支、工作区标识和会话关联信息保持原值。正文中的工作目录及其他执行路径也保持原值,
|
||||
供客户端工具使用,仍可能包含真实用户名和项目名。安装身份由账号隔离逻辑处理,
|
||||
UA、平台参数及自动 Git 上下文版本由 [OpenAI 上游客户端身份](#openai-上游客户端身份)管理。
|
||||
预设和自定义规则使用同一编辑器,步骤均可增删、修改和排序;调整预设不覆盖已保存的规则
|
||||
|
||||
|
||||
@@ -43,11 +43,9 @@ export const presets: { key: string, name: string, steps: Partial<PrivacyStep>[]
|
||||
key: 'workspace',
|
||||
name: '统一工作区信息',
|
||||
steps: [
|
||||
// 只改写附加元数据中的路径,正文路径供本地工具执行使用
|
||||
{ scope: 'turn_metadata', selector: '$.workspaces', action: 'rename_key', pattern: String.raw`(?i)^((?:[a-z]:)?[/\\](?:home|users)[/\\])[^/\\]+`, replacement: `\${1}user` },
|
||||
{ scope: 'environment_text', selector: '$', action: 'regex_replace', pattern: String.raw`(?i)((?:[a-z]:)?[/\\](?:home|users)[/\\])[^/\\<>"'\r\n]+`, replacement: `\${1}user` },
|
||||
// 路径隐藏本机目录布局,项目名和 Git 状态仍取自实际请求
|
||||
{ scope: 'turn_metadata', selector: '$.workspaces', action: 'rename_key', pattern: String.raw`(?s)^(?:[a-zA-Z]:)?[/\\](?:.*[/\\])?([^/\\]+)[/\\]*$`, replacement: `/workspace/\${1}` },
|
||||
{ scope: 'environment_text', selector: '$', action: 'regex_replace', pattern: String.raw`(<cwd>\s*)(?:[a-zA-Z]:)?[/\\](?:[^<>\r\n]*[/\\])?([^/\\<>\r\n]+?)[/\\]*\s*(</cwd>)`, replacement: `\${1}/workspace/\${2}\${3}` },
|
||||
{ scope: 'turn_metadata', selector: '$.workspaces.*.associated_remote_urls.*', action: 'regex_replace', pattern: String.raw`(?i)^((?:(?:https?|ssh|git)://[^/?#]+/|[^/@:\s]+@[^/:\s]+:))[^/?#]+/([^/?#]+)(/?)$`, replacement: `\${1}alex/\${2}\${3}` },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user