Track cumulative MCP attribution across requests and thread history (#47081)

## What changed

- Record each MCP source and its first turn, including connector and plugin identifiers when available. Cover direct and Code Mode calls, including returned errors and results that are not printed.
- Persist cumulative attribution checkpoints and restore them across compaction, resume, and forks, including forks limited to recent turns.
- Attach fresh attribution to Responses API `client_metadata` under `mcp_attribution`, outside model-visible content. Restrict transmission to allowed OpenAI HTTPS destinations for both HTTP and WebSocket requests.
- Report `attribution_error` for legacy history without checkpoints, malformed or conflicting attribution, and serialized attribution exceeding 16 KiB.

## Testing

Add coverage for source deduplication, checkpoint persistence and restoration, recent-turn forks, direct and Code Mode calls, destination filtering, and the serialized size limit.

GitOrigin-RevId: fd285e152ca9e24bfa000debb20013709d67a86e
This commit is contained in:
peilin-openai
2026-09-21 18:19:07 +00:00
committed by copyberry
parent 7cec3e110c
commit ac98537678
28 changed files with 1119 additions and 73 deletions
+5 -7
View File
@@ -267,14 +267,9 @@ async fn run_compact_task_inner_impl(
let max_retries = turn_context.provider.info().stream_max_retries();
let mut retries = 0;
// Reuse one client session so turn-scoped state (sticky routing and websocket incremental
// request tracking) survives retries within this compact turn.
let mut client_session = sess.services.model_client.new_session();
// Reuse one client session so turn-scoped state (sticky routing, websocket incremental
// request tracking)
// survives retries within this compact turn.
let responses_metadata = sess
.compaction_responses_metadata(turn_context.as_ref(), compaction_metadata)
.await;
let compaction_response = loop {
// Clone is required because of the loop
let mut turn_input = history
@@ -289,6 +284,9 @@ async fn run_compact_task_inner_impl(
base_instructions: sess.get_prompt_base_instructions().await,
..Default::default()
};
let responses_metadata = sess
.compaction_responses_metadata(turn_context.as_ref(), compaction_metadata)
.await;
let attempt_result = drain_to_completed(
&sess,
turn_context.as_ref(),