@@ -7,6 +7,12 @@ import {
77TooltipProvider ,
88TooltipTrigger ,
99} from "components/Tooltip/Tooltip" ;
10+ import every from "lodash/every" ;
11+ import keys from "lodash/keys" ;
12+ import mapValues from "lodash/mapValues" ;
13+ import some from "lodash/some" ;
14+ import sum from "lodash/sum" ;
15+ import uniq from "lodash/uniq" ;
1016import {
1117ArrowDownIcon ,
1218ArrowUpIcon ,
@@ -21,6 +27,43 @@ type RequestLogsRowProps = {
2127interception :AIBridgeInterception ;
2228} ;
2329
30+ /**
31+ * This function merges multiple objects with the same keys into a single object.
32+ * It's super unconventional, but it's only a temporary workaround until we
33+ * structure our metadata field for rendering in the UI.
34+ *@param objects - The objects to merge.
35+ *@returns The merged object.
36+ */
37+ const magicMetadataMerge = ( ...objects :Record < string , unknown > [ ] ) :unknown => {
38+ // Filter out empty objects
39+ const nonEmptyObjects = objects . filter ( ( obj ) => keys ( obj ) . length > 0 ) ;
40+
41+ // If all objects were empty, return null
42+ if ( nonEmptyObjects . length === 0 ) return null ;
43+
44+ // Check if all objects have the same keys
45+ const keySets = nonEmptyObjects . map ( ( obj ) => keys ( obj ) . sort ( ) . join ( "," ) ) ;
46+ // If the keys are different, just instantly return the objects
47+ if ( uniq ( keySets ) . length > 1 ) return nonEmptyObjects ;
48+
49+ // Group the objects by key
50+ const grouped = mapValues ( nonEmptyObjects [ 0 ] , ( _ , key ) =>
51+ nonEmptyObjects . map ( ( obj ) => obj [ key ] ) ,
52+ ) ;
53+
54+ // Map the grouped values to a new object
55+ const result = mapValues ( grouped , ( values :unknown [ ] ) => {
56+ const allNumeric = every ( values , ( v :unknown ) => typeof v === "number" ) ;
57+ const allSame = uniq ( values ) . length === 1 ;
58+
59+ if ( allNumeric ) return sum ( values ) ;
60+ if ( allSame ) return values [ 0 ] ;
61+ return null ; // Mark conflict
62+ } ) ;
63+
64+ return some ( result , ( v :unknown ) => v === null ) ?nonEmptyObjects :result ;
65+ } ;
66+
2467export const RequestLogsRow :FC < RequestLogsRowProps > = ( { interception} ) => {
2568const [ isOpen , setIsOpen ] = useState ( false ) ;
2669
@@ -35,35 +78,8 @@ export const RequestLogsRow: FC<RequestLogsRowProps> = ({ interception }) => {
35780 ,
3679) ;
3780
38- const KEY_ANTHROPIC_READ = "cache_read_input" ;
39- const KEY_ANTHROPIC_WRITTEN = "cache_creation_input" ;
40-
41- const KEY_OPENAI_READ = "prompt_cached" ;
42-
43- // These are an unstructured metadata field of "Record<string, unknown>",
44- // so we need to check if they're numbers and if not, return 0.
45- const cachedReadTokens = interception . token_usages . reduce (
46- ( acc , tokenUsage ) =>
47- acc +
48- ( interception . provider === "anthropic"
49- ?typeof tokenUsage . metadata ?. [ KEY_ANTHROPIC_READ ] === "number"
50- ?tokenUsage . metadata ?. [ KEY_ANTHROPIC_READ ]
51- :0
52- :typeof tokenUsage . metadata ?. [ KEY_OPENAI_READ ] === "number"
53- ?tokenUsage . metadata ?. [ KEY_OPENAI_READ ]
54- :0 ) ,
55- 0 ,
56- ) ;
57- const cachedWrittenTokens = interception . token_usages . reduce (
58- ( acc , tokenUsage ) =>
59- acc +
60- ( interception . provider === "anthropic"
61- ?typeof tokenUsage . metadata ?. [ KEY_ANTHROPIC_WRITTEN ] === "number"
62- ?tokenUsage . metadata ?. [ KEY_ANTHROPIC_WRITTEN ]
63- :0
64- :// OpenAI doesn't have a cached written tokens field, so we return 0.
65- 0 ) ,
66- 0 ,
81+ const tokenUsagesMetadata = magicMetadataMerge (
82+ ...interception . token_usages . map ( ( tokenUsage ) => tokenUsage . metadata ) ,
6783) ;
6884
6985const toolCalls = interception . tool_usages . length ;
@@ -186,12 +202,6 @@ export const RequestLogsRow: FC<RequestLogsRowProps> = ({ interception }) => {
186202< dt > Output Tokens:</ dt >
187203< dd data-chromatic = "ignore" > { outputTokens } </ dd >
188204
189- < dt > Cached Read Tokens:</ dt >
190- < dd data-chromatic = "ignore" > { cachedReadTokens } </ dd >
191-
192- < dt > Cached Written Tokens:</ dt >
193- < dd data-chromatic = "ignore" > { cachedWrittenTokens } </ dd >
194-
195205< dt > Tool Calls:</ dt >
196206< dd data-chromatic = "ignore" >
197207{ interception . tool_usages . length }
@@ -246,6 +256,15 @@ export const RequestLogsRow: FC<RequestLogsRowProps> = ({ interception }) => {
246256</ div >
247257</ div >
248258) }
259+
260+ { tokenUsagesMetadata !== null && (
261+ < div className = "flex flex-col gap-2" >
262+ < div > Metadata</ div >
263+ < div className = "bg-surface-secondary rounded-md p-4" >
264+ < pre > { JSON . stringify ( tokenUsagesMetadata , null , 2 ) } </ pre >
265+ </ div >
266+ </ div >
267+ ) }
249268</ div >
250269</ TableCell >
251270</ TableRow >