Add code-mode trace relationships

This commit is contained in:
Albin Cassirer
2026-04-20 16:43:01 -07:00
parent 59bd3240f1
commit 066f172cd6
16 changed files with 1761 additions and 58 deletions
+1
View File
@@ -24,6 +24,7 @@ pub use runtime::ExecuteRequest;
pub use runtime::RuntimeResponse;
pub use runtime::WaitRequest;
pub use service::CodeModeService;
pub use service::CodeModeToolInvocation;
pub use service::CodeModeTurnHost;
pub use service::CodeModeTurnWorker;
+2
View File
@@ -25,6 +25,7 @@ const EXIT_SENTINEL: &str = "__codex_code_mode_exit__";
#[derive(Clone, Debug)]
pub struct ExecuteRequest {
pub cell_id: Option<String>,
pub tool_call_id: String,
pub enabled_tools: Vec<ToolDefinition>,
pub source: String,
@@ -331,6 +332,7 @@ mod tests {
fn execute_request(source: &str) -> ExecuteRequest {
ExecuteRequest {
cell_id: None,
tool_call_id: "call_1".to_string(),
enabled_tools: Vec::new(),
source: source.to_string(),
+46 -10
View File
@@ -23,12 +23,26 @@ use crate::runtime::TurnMessage;
use crate::runtime::WaitRequest;
use crate::runtime::spawn_runtime;
/// A nested tool request made by one running code-mode cell.
///
/// The model only sees the outer `exec` call. Trace reduction needs the runtime
/// cell id and runtime tool id so it can attach this nested tool to the right
/// code cell without guessing from event ordering.
#[derive(Debug)]
pub struct CodeModeToolInvocation {
/// Code-mode runtime cell that issued the request.
pub cell_id: String,
/// Per-cell tool invocation id generated by the JavaScript runtime.
pub runtime_tool_call_id: String,
pub tool_name: ToolName,
pub input: Option<JsonValue>,
}
#[async_trait]
pub trait CodeModeTurnHost: Send + Sync {
async fn invoke_tool(
&self,
tool_name: ToolName,
input: Option<JsonValue>,
invocation: CodeModeToolInvocation,
cancellation_token: CancellationToken,
) -> Result<JsonValue, String>;
@@ -76,24 +90,40 @@ impl CodeModeService {
*self.inner.stored_values.lock().await = values;
}
pub async fn execute(&self, request: ExecuteRequest) -> Result<RuntimeResponse, String> {
let cell_id = self
.inner
pub fn allocate_cell_id(&self) -> String {
self.inner
.next_cell_id
.fetch_add(1, Ordering::Relaxed)
.to_string();
.to_string()
}
pub async fn execute(&self, request: ExecuteRequest) -> Result<RuntimeResponse, String> {
let cell_id = request
.cell_id
.clone()
.unwrap_or_else(|| self.allocate_cell_id());
if self.inner.sessions.lock().await.contains_key(&cell_id) {
return Err(format!("exec cell {cell_id} already exists"));
}
let (event_tx, event_rx) = mpsc::unbounded_channel();
let (runtime_tx, runtime_terminate_handle) = spawn_runtime(request.clone(), event_tx)?;
let (control_tx, control_rx) = mpsc::unbounded_channel();
let (response_tx, response_rx) = oneshot::channel();
self.inner.sessions.lock().await.insert(
let mut sessions = self.inner.sessions.lock().await;
if sessions.contains_key(&cell_id) {
let _ = runtime_terminate_handle.terminate_execution();
return Err(format!("exec cell {cell_id} already exists"));
}
sessions.insert(
cell_id.clone(),
SessionHandle {
control_tx: control_tx.clone(),
runtime_tx: runtime_tx.clone(),
},
);
drop(sessions);
tokio::spawn(run_session_control(
Arc::clone(&self.inner),
@@ -181,9 +211,14 @@ impl CodeModeService {
let host = Arc::clone(&host);
let inner = Arc::clone(&inner);
tokio::spawn(async move {
let response = host
.invoke_tool(name, input, CancellationToken::new())
.await;
let invocation = CodeModeToolInvocation {
cell_id: cell_id.clone(),
runtime_tool_call_id: id.clone(),
tool_name: name,
input,
};
let response =
host.invoke_tool(invocation, CancellationToken::new()).await;
let runtime_tx = inner
.sessions
.lock()
@@ -490,6 +525,7 @@ mod tests {
fn execute_request(source: &str) -> ExecuteRequest {
ExecuteRequest {
cell_id: None,
tool_call_id: "call_1".to_string(),
enabled_tools: Vec::new(),
source: source.to_string(),
+2
View File
@@ -6027,6 +6027,7 @@ async fn rejects_escalated_permissions_when_policy_not_on_request() {
session: Arc::clone(&session),
turn: Arc::clone(&turn_context),
tracker: Arc::clone(&turn_diff_tracker),
source: crate::tools::context::ToolCallSource::Direct,
call_id,
tool_name: codex_tools::ToolName::plain(tool_name),
payload: ToolPayload::Function {
@@ -6105,6 +6106,7 @@ async fn unified_exec_rejects_escalated_permissions_when_policy_not_on_request()
session: Arc::clone(&session),
turn: Arc::clone(&turn_context),
tracker: Arc::clone(&tracker),
source: crate::tools::context::ToolCallSource::Direct,
call_id: "exec-call".to_string(),
tool_name: codex_tools::ToolName::plain("exec_command"),
payload: ToolPayload::Function {
@@ -143,6 +143,7 @@ async fn guardian_allows_shell_additional_permissions_requests_past_policy_valid
session: Arc::clone(&session),
turn: Arc::clone(&turn_context),
tracker: Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new())),
source: crate::tools::context::ToolCallSource::Direct,
call_id: "test-call".to_string(),
tool_name: codex_tools::ToolName::plain("shell"),
payload: ToolPayload::Function {
@@ -209,6 +210,7 @@ async fn guardian_allows_unified_exec_additional_permissions_requests_past_polic
session: Arc::clone(&session),
turn: Arc::clone(&turn_context),
tracker: Arc::clone(&tracker),
source: crate::tools::context::ToolCallSource::Direct,
call_id: "exec-call".to_string(),
tool_name: codex_tools::ToolName::plain("exec_command"),
payload: ToolPayload::Function {
@@ -322,6 +324,7 @@ async fn shell_handler_allows_sticky_turn_permissions_without_inline_request_per
session: Arc::clone(&session),
turn: Arc::clone(&turn_context),
tracker: Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new())),
source: crate::tools::context::ToolCallSource::Direct,
call_id: "sticky-turn-grant".to_string(),
tool_name: codex_tools::ToolName::plain("shell"),
payload: ToolPayload::Function {
@@ -4,6 +4,7 @@ use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload;
use crate::tools::registry::ToolHandler;
use crate::tools::registry::ToolKind;
use serde_json::json;
use super::ExecContext;
use super::PUBLIC_TOOL_NAME;
@@ -30,12 +31,34 @@ impl CodeModeExecuteHandler {
.code_mode_service
.stored_values()
.await;
// Allocate the runtime cell id before starting V8. Nested tools can be
// requested before the model-visible `exec` item has been reduced, so
// the trace needs this explicit bridge up front.
let runtime_cell_id = exec.session.services.code_mode_service.allocate_cell_id();
let invocation_payload = codex_trace::write_payload(
"code_cell_invocation",
&json!({
"cell_id": runtime_cell_id,
"model_visible_call_id": call_id,
"source_js": args.code,
"yield_time_ms": args.yield_time_ms,
"max_output_tokens": args.max_output_tokens,
}),
);
emit_code_cell_started(
&exec,
&runtime_cell_id,
&call_id,
&args.code,
invocation_payload.as_ref(),
);
let started_at = std::time::Instant::now();
let response = exec
let response = match exec
.session
.services
.code_mode_service
.execute(codex_code_mode::ExecuteRequest {
cell_id: Some(runtime_cell_id.clone()),
tool_call_id: call_id,
enabled_tools,
source: args.code,
@@ -44,13 +67,146 @@ impl CodeModeExecuteHandler {
max_output_tokens: args.max_output_tokens,
})
.await
.map_err(FunctionCallError::RespondToModel)?;
{
Ok(response) => response,
Err(err) => {
let result_payload = codex_trace::write_payload(
"code_cell_result",
&json!({ "cell_id": runtime_cell_id, "error_text": err }),
);
emit_code_cell_failed(&exec, &runtime_cell_id, result_payload.as_ref());
return Err(FunctionCallError::RespondToModel(err));
}
};
if !matches!(&response, codex_code_mode::RuntimeResponse::Yielded { .. }) {
let result_payload = codex_trace::write_payload(
"code_cell_result",
&code_cell_response_payload(&response),
);
emit_code_cell_ended(
&exec,
&runtime_cell_id,
&response,
/*model_visible_wait_call_id*/ None,
result_payload.as_ref(),
);
}
handle_runtime_response(&exec, response, args.max_output_tokens, started_at)
.await
.map_err(FunctionCallError::RespondToModel)
}
}
fn emit_code_cell_started(
exec: &ExecContext,
runtime_cell_id: &str,
model_visible_call_id: &str,
source_js: &str,
invocation_payload: Option<&codex_trace::RawPayloadRef>,
) {
tracing::event!(
target: codex_otel::OTEL_TRACE_SAFE_TARGET,
tracing::Level::INFO,
event.name = %"codex.code_cell.started",
thread.id = %exec.session.conversation_id,
turn.id = %exec.turn.sub_id,
code_cell.runtime_id = %runtime_cell_id,
model_visible_call.id = %model_visible_call_id,
code_cell.source_js = %source_js,
raw_payload.invocation.id = %invocation_payload.map(|payload| payload.raw_payload_id.as_str()).unwrap_or(""),
raw_payload.invocation.path = %invocation_payload.map(|payload| payload.path.as_str()).unwrap_or(""),
raw_payload.invocation.kind = %invocation_payload.map(|payload| payload.kind.as_str()).unwrap_or(""),
);
}
pub(super) fn emit_code_cell_ended(
exec: &ExecContext,
runtime_cell_id: &str,
response: &codex_code_mode::RuntimeResponse,
model_visible_wait_call_id: Option<&str>,
result_payload: Option<&codex_trace::RawPayloadRef>,
) {
tracing::event!(
target: codex_otel::OTEL_TRACE_SAFE_TARGET,
tracing::Level::INFO,
event.name = %"codex.code_cell.ended",
thread.id = %exec.session.conversation_id,
turn.id = %exec.turn.sub_id,
code_cell.runtime_id = %runtime_cell_id,
status = %code_cell_status(response),
model_visible_wait_call.id = %model_visible_wait_call_id.unwrap_or(""),
raw_payload.result.id = %result_payload.map(|payload| payload.raw_payload_id.as_str()).unwrap_or(""),
raw_payload.result.path = %result_payload.map(|payload| payload.path.as_str()).unwrap_or(""),
raw_payload.result.kind = %result_payload.map(|payload| payload.kind.as_str()).unwrap_or(""),
);
}
fn emit_code_cell_failed(
exec: &ExecContext,
runtime_cell_id: &str,
result_payload: Option<&codex_trace::RawPayloadRef>,
) {
tracing::event!(
target: codex_otel::OTEL_TRACE_SAFE_TARGET,
tracing::Level::INFO,
event.name = %"codex.code_cell.ended",
thread.id = %exec.session.conversation_id,
turn.id = %exec.turn.sub_id,
code_cell.runtime_id = %runtime_cell_id,
status = %"failed",
raw_payload.result.id = %result_payload.map(|payload| payload.raw_payload_id.as_str()).unwrap_or(""),
raw_payload.result.path = %result_payload.map(|payload| payload.path.as_str()).unwrap_or(""),
raw_payload.result.kind = %result_payload.map(|payload| payload.kind.as_str()).unwrap_or(""),
);
}
fn code_cell_status(response: &codex_code_mode::RuntimeResponse) -> &'static str {
match response {
codex_code_mode::RuntimeResponse::Yielded { .. } => "yielded",
codex_code_mode::RuntimeResponse::Terminated { .. } => "terminated",
codex_code_mode::RuntimeResponse::Result { error_text, .. } => {
if error_text.is_some() {
"failed"
} else {
"completed"
}
}
}
}
fn code_cell_response_payload(response: &codex_code_mode::RuntimeResponse) -> serde_json::Value {
match response {
codex_code_mode::RuntimeResponse::Yielded {
cell_id,
content_items,
} => json!({
"cell_id": cell_id,
"status": "yielded",
"content_items": content_items,
}),
codex_code_mode::RuntimeResponse::Terminated {
cell_id,
content_items,
} => json!({
"cell_id": cell_id,
"status": "terminated",
"content_items": content_items,
}),
codex_code_mode::RuntimeResponse::Result {
cell_id,
content_items,
stored_values,
error_text,
} => json!({
"cell_id": cell_id,
"status": if error_text.is_some() { "failed" } else { "completed" },
"content_items": content_items,
"stored_values": stored_values,
"error_text": error_text,
}),
}
}
impl ToolHandler for CodeModeExecuteHandler {
type Output = FunctionToolOutput;
@@ -62,6 +218,23 @@ impl ToolHandler for CodeModeExecuteHandler {
matches!(payload, ToolPayload::Custom { .. })
}
fn uses_first_class_trace_object(&self, invocation: &ToolInvocation) -> bool {
// Code-mode `exec` is represented by the first-class CodeCell runtime
// lifecycle. Emitting the generic dispatch ToolCall as well would give
// viewers two top-level objects for the same model-visible JS cell.
//
// Only suppress the generic ToolCall once parsing is known to succeed.
// Parse errors return before a CodeCell id exists, so those failures
// still need the ordinary tool trace object.
invocation.tool_name.namespace.is_none()
&& invocation.tool_name.name.as_str() == PUBLIC_TOOL_NAME
&& matches!(
&invocation.payload,
ToolPayload::Custom { input }
if codex_code_mode::parse_exec_source(input).is_ok()
)
}
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
let ToolInvocation {
session,
+22 -7
View File
@@ -7,6 +7,7 @@ use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use codex_code_mode::CodeModeToolInvocation;
use codex_code_mode::CodeModeTurnHost;
use codex_code_mode::RuntimeResponse;
use codex_protocol::models::FunctionCallOutputContentItem;
@@ -73,6 +74,10 @@ impl CodeModeService {
self.inner.replace_stored_values(values).await;
}
pub(crate) fn allocate_cell_id(&self) -> String {
self.inner.allocate_cell_id()
}
pub(crate) async fn execute(
&self,
request: codex_code_mode::ExecuteRequest,
@@ -118,15 +123,13 @@ struct CoreTurnHost {
impl CodeModeTurnHost for CoreTurnHost {
async fn invoke_tool(
&self,
tool_name: ToolName,
input: Option<JsonValue>,
invocation: CodeModeToolInvocation,
cancellation_token: CancellationToken,
) -> Result<JsonValue, String> {
call_nested_tool(
self.exec.clone(),
self.tool_runtime.clone(),
tool_name,
input,
invocation,
cancellation_token,
)
.await
@@ -298,10 +301,15 @@ async fn build_nested_router(exec: &ExecContext) -> ToolRouter {
async fn call_nested_tool(
exec: ExecContext,
tool_runtime: ToolCallRuntime,
tool_name: ToolName,
input: Option<JsonValue>,
invocation: CodeModeToolInvocation,
cancellation_token: CancellationToken,
) -> Result<JsonValue, FunctionCallError> {
let CodeModeToolInvocation {
cell_id,
runtime_tool_call_id,
tool_name,
input,
} = invocation;
if tool_name.namespace.is_none() && tool_name.name == PUBLIC_TOOL_NAME {
return Err(FunctionCallError::RespondToModel(format!(
"{PUBLIC_TOOL_NAME} cannot invoke itself"
@@ -335,7 +343,14 @@ async fn call_nested_tool(
payload,
};
let result = tool_runtime
.handle_tool_call_with_source(call, ToolCallSource::CodeMode, cancellation_token)
.handle_tool_call_with_source(
call,
ToolCallSource::CodeMode {
cell_id,
runtime_tool_call_id,
},
cancellation_token,
)
.await?;
Ok(result.code_mode_result())
}
@@ -10,6 +10,7 @@ use crate::tools::registry::ToolKind;
use super::DEFAULT_WAIT_YIELD_TIME_MS;
use super::ExecContext;
use super::WAIT_TOOL_NAME;
use super::execute_handler::emit_code_cell_ended;
use super::handle_runtime_response;
pub struct CodeModeWaitHandler;
@@ -49,6 +50,7 @@ impl ToolHandler for CodeModeWaitHandler {
let ToolInvocation {
session,
turn,
call_id,
tool_name,
payload,
..
@@ -66,12 +68,28 @@ impl ToolHandler for CodeModeWaitHandler {
.services
.code_mode_service
.wait(codex_code_mode::WaitRequest {
cell_id: args.cell_id,
cell_id: args.cell_id.clone(),
yield_time_ms: args.yield_time_ms,
terminate: args.terminate,
})
.await
.map_err(FunctionCallError::RespondToModel)?;
if !matches!(&response, codex_code_mode::RuntimeResponse::Yielded { .. })
&& !is_missing_cell_response(&response, &args.cell_id)
{
// The initial `exec` call only records a terminal end when
// it finishes immediately. Yielded cells close later via
// `wait`, so the runtime object stays live until this point.
// Duplicate waits for a removed cell return a model-visible
// error but must not rewrite an already-finished CodeCell.
emit_code_cell_ended(
&exec,
&args.cell_id,
&response,
Some(&call_id),
/*result_payload*/ None,
);
}
handle_runtime_response(&exec, response, args.max_tokens, started_at)
.await
.map_err(FunctionCallError::RespondToModel)
@@ -82,3 +100,14 @@ impl ToolHandler for CodeModeWaitHandler {
}
}
}
fn is_missing_cell_response(response: &codex_code_mode::RuntimeResponse, cell_id: &str) -> bool {
let codex_code_mode::RuntimeResponse::Result {
error_text: Some(error_text),
..
} = response
else {
return false;
};
error_text == &format!("exec cell {cell_id} not found")
}
+8 -2
View File
@@ -28,11 +28,16 @@ use tokio::sync::Mutex;
pub type SharedTurnDiffTracker = Arc<Mutex<TurnDiffTracker>>;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ToolCallSource {
Direct,
JsRepl,
CodeMode,
CodeMode {
/// Runtime cell that issued the nested tool request.
cell_id: String,
/// Code-mode's per-cell tool invocation id.
runtime_tool_call_id: String,
},
}
#[derive(Clone)]
@@ -40,6 +45,7 @@ pub struct ToolInvocation {
pub session: Arc<Session>,
pub turn: Arc<TurnContext>,
pub tracker: SharedTurnDiffTracker,
pub source: ToolCallSource,
pub call_id: String,
pub tool_name: ToolName,
pub payload: ToolPayload,
@@ -68,6 +68,7 @@ fn invocation(
session,
turn,
tracker: Arc::new(Mutex::new(TurnDiffTracker::default())),
source: crate::tools::context::ToolCallSource::Direct,
call_id: "call-1".to_string(),
tool_name: codex_tools::ToolName::plain(tool_name),
payload,
@@ -28,6 +28,7 @@ async fn multi_agent_v2_request_user_input_rejects_subagent_threads() {
session: Arc::new(session),
turn: Arc::new(turn),
tracker: Arc::new(Mutex::new(TurnDiffTracker::default())),
source: crate::tools::context::ToolCallSource::Direct,
call_id: "call-1".to_string(),
tool_name: codex_tools::ToolName::plain(REQUEST_USER_INPUT_TOOL_NAME),
payload: ToolPayload::Function {
@@ -226,6 +226,7 @@ async fn shell_pre_tool_use_payload_uses_joined_command() {
session: session.into(),
turn: turn.into(),
tracker: Arc::new(Mutex::new(TurnDiffTracker::new())),
source: crate::tools::context::ToolCallSource::Direct,
call_id: "call-41".to_string(),
tool_name: codex_tools::ToolName::plain("shell"),
payload,
@@ -251,6 +252,7 @@ async fn shell_command_pre_tool_use_payload_uses_raw_command() {
session: session.into(),
turn: turn.into(),
tracker: Arc::new(Mutex::new(TurnDiffTracker::new())),
source: crate::tools::context::ToolCallSource::Direct,
call_id: "call-42".to_string(),
tool_name: codex_tools::ToolName::plain("shell_command"),
payload,
@@ -211,6 +211,7 @@ async fn exec_command_pre_tool_use_payload_uses_raw_command() {
session: session.into(),
turn: turn.into(),
tracker: Arc::new(Mutex::new(TurnDiffTracker::new())),
source: crate::tools::context::ToolCallSource::Direct,
call_id: "call-43".to_string(),
tool_name: codex_tools::ToolName::plain("exec_command"),
payload,
@@ -234,6 +235,7 @@ async fn exec_command_pre_tool_use_payload_skips_write_stdin() {
session: session.into(),
turn: turn.into(),
tracker: Arc::new(Mutex::new(TurnDiffTracker::new())),
source: crate::tools::context::ToolCallSource::Direct,
call_id: "call-44".to_string(),
tool_name: codex_tools::ToolName::plain("write_stdin"),
payload,
+65 -9
View File
@@ -75,6 +75,10 @@ pub trait ToolHandler: Send + Sync {
None
}
fn uses_first_class_trace_object(&self, _invocation: &ToolInvocation) -> bool {
false
}
/// Perform the actual [ToolInvocation] and returns a [ToolOutput] containing
/// the final output to return to the model.
fn handle(
@@ -133,6 +137,8 @@ trait AnyToolHandler: Send + Sync {
result: &dyn ToolOutput,
) -> Option<PostToolUsePayload>;
fn uses_first_class_trace_object(&self, invocation: &ToolInvocation) -> bool;
fn handle_any<'a>(
&'a self,
invocation: ToolInvocation,
@@ -164,6 +170,10 @@ where
ToolHandler::post_tool_use_payload(self, call_id, payload, result)
}
fn uses_first_class_trace_object(&self, invocation: &ToolInvocation) -> bool {
ToolHandler::uses_first_class_trace_object(self, invocation)
}
fn handle_any<'a>(
&'a self,
invocation: ToolInvocation,
@@ -224,16 +234,23 @@ impl ToolRegistry {
) -> Result<AnyToolResult, FunctionCallError> {
let call_id_owned = invocation.call_id.clone();
let trace_tool_call_id = format!("tool:{call_id_owned}");
let invocation_payload =
codex_trace::write_payload("tool_invocation", &trace_tool_invocation(&invocation));
emit_tool_started(
&invocation,
&trace_tool_call_id,
invocation_payload.as_ref(),
);
let uses_first_class_trace_object = self
.handler(&invocation.tool_name)
.is_some_and(|handler| handler.uses_first_class_trace_object(&invocation));
if !uses_first_class_trace_object {
let invocation_payload =
codex_trace::write_payload("tool_invocation", &trace_tool_invocation(&invocation));
emit_tool_started(
&invocation,
&trace_tool_call_id,
invocation_payload.as_ref(),
);
}
let result = self.dispatch_any_inner(invocation).await;
emit_tool_ended(&trace_tool_call_id, &result);
if !uses_first_class_trace_object {
emit_tool_ended(&trace_tool_call_id, &result);
}
result
}
@@ -466,6 +483,8 @@ fn emit_tool_started(
tool_call_id: &str,
invocation_payload: Option<&codex_trace::RawPayloadRef>,
) {
let (requester_type, model_visible_call_id, runtime_cell_id, runtime_tool_call_id) =
trace_tool_source_fields(invocation);
tracing::event!(
target: codex_otel::OTEL_TRACE_SAFE_TARGET,
tracing::Level::INFO,
@@ -474,13 +493,34 @@ fn emit_tool_started(
turn.id = %invocation.turn.sub_id,
tool.call_id = %tool_call_id,
tool.name = %invocation.tool_name.display(),
model_visible_call.id = %invocation.call_id,
tool.requester.type = %requester_type,
model_visible_call.id = %model_visible_call_id,
code_cell.runtime_id = %runtime_cell_id,
code_mode_runtime_tool.id = %runtime_tool_call_id,
raw_payload.invocation.id = %invocation_payload.map(|payload| payload.raw_payload_id.as_str()).unwrap_or(""),
raw_payload.invocation.path = %invocation_payload.map(|payload| payload.path.as_str()).unwrap_or(""),
raw_payload.invocation.kind = %invocation_payload.map(|payload| payload.kind.as_str()).unwrap_or(""),
);
}
fn trace_tool_source_fields(invocation: &ToolInvocation) -> (&str, &str, &str, &str) {
match &invocation.source {
crate::tools::context::ToolCallSource::Direct => {
("model", invocation.call_id.as_str(), "", "")
}
crate::tools::context::ToolCallSource::CodeMode {
cell_id,
runtime_tool_call_id,
} => (
"code_cell",
"",
cell_id.as_str(),
runtime_tool_call_id.as_str(),
),
crate::tools::context::ToolCallSource::JsRepl => ("runtime", "", "", ""),
}
}
fn emit_tool_ended(tool_call_id: &str, result: &Result<AnyToolResult, FunctionCallError>) {
let (status, output_preview) = match result {
Ok(result) => {
@@ -541,10 +581,26 @@ fn trace_tool_invocation(invocation: &ToolInvocation) -> Value {
"call_id": &invocation.call_id,
"tool_name": invocation.tool_name.display(),
"turn_id": &invocation.turn.sub_id,
"source": trace_tool_invocation_source(invocation),
"payload": payload,
})
}
fn trace_tool_invocation_source(invocation: &ToolInvocation) -> Value {
match &invocation.source {
crate::tools::context::ToolCallSource::Direct => json!({ "type": "model" }),
crate::tools::context::ToolCallSource::CodeMode {
cell_id,
runtime_tool_call_id,
} => json!({
"type": "code_cell",
"runtime_cell_id": cell_id,
"runtime_tool_call_id": runtime_tool_call_id,
}),
crate::tools::context::ToolCallSource::JsRepl => json!({ "type": "runtime" }),
}
}
fn trace_tool_result(
result: &Result<AnyToolResult, FunctionCallError>,
status: &str,
+1
View File
@@ -285,6 +285,7 @@ impl ToolRouter {
session,
turn,
tracker,
source,
call_id,
tool_name,
payload,
File diff suppressed because it is too large Load Diff