(stepFields, kind, callerLookup)
| 1102 | * results can detect them. |
| 1103 | */ |
| 1104 | export function decodeCascadeStepToToolCall(stepFields, kind, callerLookup) { |
| 1105 | const decoder = STEP_BODY_DECODER[kind]; |
| 1106 | const meta = CASCADE_STEP[kind]; |
| 1107 | if (!decoder || !meta) return null; |
| 1108 | const oneof = getField(stepFields, meta.oneofField, 2); |
| 1109 | if (!oneof) return null; |
| 1110 | const decoded = decoder(oneof.value); |
| 1111 | const candidates = callerLookup?.get(kind) || []; |
| 1112 | // Pick the caller's preferred name. If they declared multiple tools that |
| 1113 | // map onto this kind (rare — e.g., both Read and read_file) we use the |
| 1114 | // first one in declaration order, which matches what they'd get from |
| 1115 | // calling the same tool twice in their own code. |
| 1116 | const callerName = candidates[0] || kind; |
| 1117 | const reverseFn = TOOL_MAP[callerName]?.reverse || identityArgs; |
| 1118 | let args; |
| 1119 | try { |
| 1120 | args = reverseFn(decoded); |
| 1121 | } catch { |
| 1122 | args = decoded; |
| 1123 | } |
| 1124 | return { |
| 1125 | name: callerName, |
| 1126 | arguments: args, |
| 1127 | cascade_kind: kind, |
| 1128 | observation: decoded, |
| 1129 | }; |
| 1130 | } |
| 1131 | |
| 1132 | /** |
| 1133 | * Claude sometimes emits its provider-native tool syntax while Cascade is in |
no test coverage detected