| 1 | //! Value-only projection owned by the authoritative source module. |
| 2 | //! No command, App or rendering operation is reachable from these conversions. |
| 3 | |
| 4 | use codewhale_command_contract::facets::*; |
| 5 | |
| 6 | fn bounded(value: &crate::tool_inspection::BoundedString) -> DebugBoundedString { |
| 7 | DebugBoundedString { |
| 8 | value: value.value.clone(), |
| 9 | truncated: value.truncated, |
| 10 | } |
| 11 | } |
| 12 | |
| 13 | fn evidence<T, U>( |
| 14 | value: &crate::tool_inspection::Evidence<T>, |
| 15 | convert: impl Fn(&T) -> U, |
| 16 | ) -> DebugEvidence<U> { |
| 17 | match value { |
| 18 | crate::tool_inspection::Evidence::Known { value } => DebugEvidence::Known { |
| 19 | value: convert(value), |
| 20 | }, |
| 21 | crate::tool_inspection::Evidence::Unknown { reason } => DebugEvidence::Unknown { |
| 22 | reason: reason.clone(), |
| 23 | }, |
| 24 | } |
| 25 | } |
| 26 | |
| 27 | fn bounded_list(value: &crate::tool_inspection::BoundedList) -> DebugBoundedList { |
| 28 | DebugBoundedList { |
| 29 | count: value.count, |
| 30 | rendered: value.rendered.iter().map(bounded).collect(), |
| 31 | omitted: value.omitted, |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | pub(crate) fn tool_snapshot( |
| 36 | value: &crate::tool_inspection::ToolInspectionSnapshot, |
| 37 | ) -> DebugToolSnapshot { |
| 38 | use crate::tool_inspection::{ |
| 39 | ProviderAvailability as P, ToolProvenance as R, ToolVisibility as V, TurnStopReason as S, |
| 40 | }; |
| 41 | DebugToolSnapshot { |
| 42 | schema_version: value.schema_version, |
| 43 | capture_source: value.capture_source.to_string(), |
| 44 | delivery_status: value.delivery_status.to_string(), |
| 45 | turn_id: bounded(&value.turn_id), |
| 46 | step: value.step, |
| 47 | terminal: value |
| 48 | .terminal |
| 49 | .as_ref() |
| 50 | .map(|terminal| DebugTurnStopDiagnostics { |
| 51 | status: terminal.status.map(|status| match status { |
| 52 | crate::core::events::TurnOutcomeStatus::Completed => { |
| 53 | DebugTurnOutcomeStatus::Completed |
| 54 | } |
| 55 | crate::core::events::TurnOutcomeStatus::Interrupted => { |
| 56 | DebugTurnOutcomeStatus::Interrupted |
| 57 | } |
| 58 | crate::core::events::TurnOutcomeStatus::Failed => { |
| 59 | DebugTurnOutcomeStatus::Failed |
| 60 | } |
| 61 | }), |
| 62 | reason: terminal.reason.map(|reason| match reason { |
| 63 | S::ProviderNoToolCall => DebugTurnStopReason::ProviderNoToolCall, |
| 64 | S::ProviderToolCallMissing => DebugTurnStopReason::ProviderToolCallMissing, |
| 65 | S::StepBudgetExhausted => DebugTurnStopReason::StepBudgetExhausted, |
| 66 | S::NoProgress => DebugTurnStopReason::NoProgress, |
| 67 | S::Interrupted => DebugTurnStopReason::Interrupted, |
| 68 | S::Failed => DebugTurnStopReason::Failed, |
| 69 | }), |
| 70 | effective_max_steps: terminal.effective_max_steps, |
| 71 | step_budget_source: terminal.step_budget_source.to_string(), |
| 72 | model_step_index: terminal.model_step_index, |
| 73 | model_requests_started: terminal.model_requests_started, |
| 74 | transport_retries: terminal.transport_retries, |
| 75 | transparent_stream_retries: terminal.transparent_stream_retries, |
| 76 | stream_resumes: terminal.stream_resumes, |
| 77 | reasoning_only_reprompts: terminal.reasoning_only_reprompts, |
| 78 | empty_stop_retries: terminal.empty_stop_retries, |
| 79 | soft_landing_sent: terminal.soft_landing_sent, |
| 80 | final_report_requested: terminal.final_report_requested, |
| 81 | permission_strategy_switches: terminal.permission_strategy_switches, |
| 82 | permission_denial_rounds_without_progress: terminal |
| 83 | .permission_denial_rounds_without_progress, |
| 84 | last_provider_finish_reason: terminal |
| 85 | .last_provider_finish_reason |
| 86 | .as_ref() |
| 87 | .map(bounded), |
| 88 | last_response_tool_calls: terminal.last_response_tool_calls, |
| 89 | last_response_tool_calls_suppressed: terminal.last_response_tool_calls_suppressed, |
| 90 | last_reported_input_tokens: terminal.last_reported_input_tokens, |
| 91 | route_context_window_tokens: terminal.route_context_window_tokens, |
| 92 | last_prepared_output_limit_tokens: terminal.last_prepared_output_limit_tokens, |
| 93 | automatic_compaction_attempts: terminal.automatic_compaction_attempts, |
| 94 | emergency_compaction_attempts: terminal.emergency_compaction_attempts, |
| 95 | }), |
| 96 | tools_field_present: value.tools_field_present, |
| 97 | tool_count: value.tool_count, |
| 98 | rendered_tool_count: value.rendered_tool_count, |
| 99 | omitted_tool_count: value.omitted_tool_count, |
| 100 | payload_json_bytes: value.payload_json_bytes, |
| 101 | payload_measurement_status: value.payload_measurement_status.clone(), |
| 102 | active_tool_catalog_sha256: value.active_tool_catalog_sha256.clone(), |
| 103 | unavailable_for_this_request: value |
| 104 | .unavailable_for_this_request |
| 105 | .iter() |
| 106 | .map(|item| (*item).to_string()) |
| 107 | .collect(), |
| 108 | provider: match &value.provider { |
| 109 | P::Unknown => DebugProviderAvailability::Unknown, |
| 110 | P::Available { provider, model } => DebugProviderAvailability::Available { |
| 111 | provider: provider.clone(), |
| 112 | model: model.clone(), |
| 113 | }, |
| 114 | P::Unavailable { reason } => DebugProviderAvailability::Unavailable { |
| 115 | reason: reason.clone(), |
| 116 | }, |
| 117 | }, |
| 118 | registry_facts_present: value.registry_facts_present, |
| 119 | registry_tool_count: evidence(&value.registry_tool_count, |count| *count), |
| 120 | registry_only_tools: evidence(&value.registry_only_tools, bounded_list), |
| 121 | tools: value |
| 122 | .tools |
| 123 | .iter() |
| 124 | .map(|tool| DebugToolProjection { |
| 125 | ordinal: tool.ordinal, |
| 126 | name: bounded(&tool.name), |
| 127 | tool_type: evidence(&tool.tool_type, bounded), |
| 128 | description: bounded(&tool.description), |
| 129 | input_schema_json: bounded(&tool.input_schema_json), |
| 130 | allowed_callers: evidence(&tool.allowed_callers, bounded_list), |
| 131 | defer_loading: evidence(&tool.defer_loading, |flag| *flag), |
| 132 | input_examples: evidence(&tool.input_examples, |count| DebugCountOnly { |
| 133 | count: count.count, |
| 134 | values: count.values.to_string(), |
| 135 | }), |
| 136 | strict: evidence(&tool.strict, |flag| *flag), |
| 137 | cache_control_type: evidence(&tool.cache_control_type, bounded), |
| 138 | provenance: evidence(&tool.provenance, |origin| match origin { |
| 139 | R::Builtin => DebugToolProvenance::Builtin, |
| 140 | R::Plugin => DebugToolProvenance::Plugin, |
| 141 | R::Mcp => DebugToolProvenance::Mcp, |
| 142 | R::Synthetic => DebugToolProvenance::Synthetic, |
| 143 | R::Unknown => DebugToolProvenance::Unknown, |
| 144 | }), |
| 145 | mcp_server: evidence(&tool.mcp_server, bounded), |
| 146 | capabilities: evidence(&tool.capabilities, bounded_list), |
| 147 | approval: evidence(&tool.approval, bounded), |
| 148 | model_visible: evidence(&tool.model_visible, |flag| *flag), |
| 149 | visibility: match tool.visibility { |
| 150 | V::Active => DebugToolVisibility::Active, |
| 151 | V::Deferred => DebugToolVisibility::Deferred, |
| 152 | V::InRequest => DebugToolVisibility::InRequest, |
| 153 | }, |
| 154 | }) |
| 155 | .collect(), |
| 156 | } |
| 157 | } |
| 158 |