coze_lab 0.1.25 → 0.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/codex/cozeloop_hook.py +145 -42
package/package.json
CHANGED
|
@@ -640,6 +640,54 @@ def _ts_ms(dt):
|
|
|
640
640
|
return int(dt.timestamp() * 1000) if dt is not None else None
|
|
641
641
|
|
|
642
642
|
|
|
643
|
+
def _max_dt(*values):
|
|
644
|
+
result = None
|
|
645
|
+
for value in values:
|
|
646
|
+
if value is not None and (result is None or value > result):
|
|
647
|
+
result = value
|
|
648
|
+
return result
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def _parse_ts_value(value):
|
|
652
|
+
return _parse_ts({"_ts": value}) if value else None
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
def _turn_timestamps(turn):
|
|
656
|
+
values = []
|
|
657
|
+
for item in [turn.get("user_message"), *turn.get("assistant_messages", [])]:
|
|
658
|
+
dt = _parse_ts(item)
|
|
659
|
+
if dt:
|
|
660
|
+
values.append(dt)
|
|
661
|
+
for item in [*turn.get("tool_calls", []), *turn.get("tool_results", [])]:
|
|
662
|
+
dt = _parse_ts(item)
|
|
663
|
+
if dt:
|
|
664
|
+
values.append(dt)
|
|
665
|
+
for sc in turn.get("subagent_calls", []):
|
|
666
|
+
for key in ("_start_ts", "_end_ts"):
|
|
667
|
+
dt = _parse_ts_value(sc.get(key))
|
|
668
|
+
if dt:
|
|
669
|
+
values.append(dt)
|
|
670
|
+
return values
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
def _turn_bounds(turn):
|
|
674
|
+
values = _turn_timestamps(turn)
|
|
675
|
+
if not values:
|
|
676
|
+
return None, None
|
|
677
|
+
return min(values), max(values)
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
def _assistant_bounds(turn):
|
|
681
|
+
values = []
|
|
682
|
+
for item in turn.get("assistant_messages", []):
|
|
683
|
+
dt = _parse_ts(item)
|
|
684
|
+
if dt:
|
|
685
|
+
values.append(dt)
|
|
686
|
+
if not values:
|
|
687
|
+
return None, None
|
|
688
|
+
return min(values), max(values)
|
|
689
|
+
|
|
690
|
+
|
|
643
691
|
def group_messages_into_turns(entries: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
644
692
|
"""Group raw JSONL entries into conversation turns.
|
|
645
693
|
|
|
@@ -749,6 +797,8 @@ def group_messages_into_turns(entries: List[Dict[str, Any]]) -> List[Dict[str, A
|
|
|
749
797
|
"message": args.get("message", ""),
|
|
750
798
|
"model": args.get("model"),
|
|
751
799
|
"result": None,
|
|
800
|
+
"_start_ts": payload.get("_ts"),
|
|
801
|
+
"_end_ts": None,
|
|
752
802
|
}
|
|
753
803
|
current_turn["subagent_calls"].append(subagent_call)
|
|
754
804
|
pending_calls[call_id] = {"kind": "spawn", "subagent_call": subagent_call}
|
|
@@ -756,14 +806,16 @@ def group_messages_into_turns(entries: List[Dict[str, Any]]) -> List[Dict[str, A
|
|
|
756
806
|
pending_calls[call_id] = {
|
|
757
807
|
"kind": "wait",
|
|
758
808
|
"ids": args.get("ids", []),
|
|
809
|
+
"_start_ts": payload.get("_ts"),
|
|
759
810
|
}
|
|
760
811
|
else:
|
|
761
812
|
current_turn["tool_calls"].append({
|
|
762
813
|
"call_id": call_id,
|
|
763
814
|
"name": name,
|
|
764
815
|
"input": args,
|
|
816
|
+
"_ts": payload.get("_ts"),
|
|
765
817
|
})
|
|
766
|
-
pending_calls[call_id] = {"kind": "tool"}
|
|
818
|
+
pending_calls[call_id] = {"kind": "tool", "_start_ts": payload.get("_ts")}
|
|
767
819
|
|
|
768
820
|
elif item_type == "function_call_output":
|
|
769
821
|
if current_turn is None:
|
|
@@ -783,6 +835,7 @@ def group_messages_into_turns(entries: List[Dict[str, Any]]) -> List[Dict[str, A
|
|
|
783
835
|
subagent_call["nickname"] = out.get("nickname")
|
|
784
836
|
except (json.JSONDecodeError, TypeError, AttributeError):
|
|
785
837
|
pass
|
|
838
|
+
subagent_call["_end_ts"] = payload.get("_ts")
|
|
786
839
|
|
|
787
840
|
elif kind == "wait":
|
|
788
841
|
try:
|
|
@@ -795,6 +848,7 @@ def group_messages_into_turns(entries: List[Dict[str, Any]]) -> List[Dict[str, A
|
|
|
795
848
|
for sc in current_turn["subagent_calls"]:
|
|
796
849
|
if sc.get("agent_id") == agent_id and sc.get("result") is None:
|
|
797
850
|
sc["result"] = result_text
|
|
851
|
+
sc["_end_ts"] = payload.get("_ts")
|
|
798
852
|
break
|
|
799
853
|
except (json.JSONDecodeError, TypeError, AttributeError):
|
|
800
854
|
pass
|
|
@@ -803,6 +857,7 @@ def group_messages_into_turns(entries: List[Dict[str, Any]]) -> List[Dict[str, A
|
|
|
803
857
|
current_turn["tool_results"].append({
|
|
804
858
|
"call_id": call_id,
|
|
805
859
|
"output": raw_output,
|
|
860
|
+
"_ts": payload.get("_ts"),
|
|
806
861
|
})
|
|
807
862
|
|
|
808
863
|
if current_turn is not None:
|
|
@@ -888,13 +943,7 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, model_n
|
|
|
888
943
|
# 整体时间范围:所有 user/assistant payload 的真实 timestamp 极值
|
|
889
944
|
_all_ts = []
|
|
890
945
|
for _t in turns:
|
|
891
|
-
|
|
892
|
-
if _u:
|
|
893
|
-
_all_ts.append(_u)
|
|
894
|
-
for _a in _t.get("assistant_messages", []):
|
|
895
|
-
_ad = _parse_ts(_a)
|
|
896
|
-
if _ad:
|
|
897
|
-
_all_ts.append(_ad)
|
|
946
|
+
_all_ts.extend(_turn_timestamps(_t))
|
|
898
947
|
root_start_dt = min(_all_ts) if _all_ts else None
|
|
899
948
|
root_end_dt = max(_all_ts) if _all_ts else None
|
|
900
949
|
|
|
@@ -906,6 +955,7 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, model_n
|
|
|
906
955
|
"source": "codex_cli",
|
|
907
956
|
}
|
|
908
957
|
if root_start_dt is not None and root_end_dt is not None:
|
|
958
|
+
root_span.set_finish_time(root_end_dt)
|
|
909
959
|
root_tags["real_start_ms"] = _ts_ms(root_start_dt)
|
|
910
960
|
root_tags["real_end_ms"] = _ts_ms(root_end_dt)
|
|
911
961
|
root_tags["latency_ms"] = _ts_ms(root_end_dt) - _ts_ms(root_start_dt)
|
|
@@ -947,14 +997,7 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, model_n
|
|
|
947
997
|
for i, turn in enumerate(turns):
|
|
948
998
|
try:
|
|
949
999
|
# turn 真实时间:start=user payload 时间;end=最后一条 assistant payload 时间
|
|
950
|
-
turn_start_dt =
|
|
951
|
-
turn_end_dt = None
|
|
952
|
-
for _a in turn.get("assistant_messages", []):
|
|
953
|
-
_ad = _parse_ts(_a)
|
|
954
|
-
if _ad:
|
|
955
|
-
turn_end_dt = _ad
|
|
956
|
-
if turn_start_dt is None:
|
|
957
|
-
turn_start_dt = turn_end_dt
|
|
1000
|
+
turn_start_dt, turn_end_dt = _turn_bounds(turn)
|
|
958
1001
|
|
|
959
1002
|
with client.start_span(name=f"turn_{i}", span_type="main", start_time=turn_start_dt) as turn_span:
|
|
960
1003
|
turn_span.set_runtime(Runtime(library="codex-cli"))
|
|
@@ -965,6 +1008,7 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, model_n
|
|
|
965
1008
|
"source": "codex_cli",
|
|
966
1009
|
}
|
|
967
1010
|
if turn_start_dt is not None and turn_end_dt is not None:
|
|
1011
|
+
turn_span.set_finish_time(turn_end_dt)
|
|
968
1012
|
_turn_tags["real_start_ms"] = _ts_ms(turn_start_dt)
|
|
969
1013
|
_turn_tags["real_end_ms"] = _ts_ms(turn_end_dt)
|
|
970
1014
|
_turn_tags["latency_ms"] = _ts_ms(turn_end_dt) - _ts_ms(turn_start_dt)
|
|
@@ -973,21 +1017,20 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, model_n
|
|
|
973
1017
|
# --- Model span for assistant response ---
|
|
974
1018
|
if turn.get("assistant_messages"):
|
|
975
1019
|
# model span start:第一条 assistant payload 时间,回退到 turn 起点
|
|
976
|
-
_model_start_dt =
|
|
977
|
-
for _a in turn.get("assistant_messages", []):
|
|
978
|
-
_model_start_dt = _parse_ts(_a)
|
|
979
|
-
if _model_start_dt:
|
|
980
|
-
break
|
|
1020
|
+
_model_start_dt, _model_end_dt = _assistant_bounds(turn)
|
|
981
1021
|
if _model_start_dt is None:
|
|
982
1022
|
_model_start_dt = turn_start_dt
|
|
1023
|
+
if _model_end_dt is None:
|
|
1024
|
+
_model_end_dt = turn_end_dt
|
|
983
1025
|
with client.start_span(name="assistant_response", span_type="model", start_time=_model_start_dt) as model_span:
|
|
984
1026
|
model_span.set_runtime(Runtime(library="codex-cli"))
|
|
985
1027
|
model_span.set_model_name(model_name)
|
|
986
|
-
if _model_start_dt is not None and
|
|
1028
|
+
if _model_start_dt is not None and _model_end_dt is not None:
|
|
1029
|
+
model_span.set_finish_time(_model_end_dt)
|
|
987
1030
|
model_span.set_tags({
|
|
988
1031
|
"real_start_ms": _ts_ms(_model_start_dt),
|
|
989
|
-
"real_end_ms": _ts_ms(
|
|
990
|
-
"latency_ms": _ts_ms(
|
|
1032
|
+
"real_end_ms": _ts_ms(_model_end_dt),
|
|
1033
|
+
"latency_ms": _ts_ms(_model_end_dt) - _ts_ms(_model_start_dt),
|
|
991
1034
|
})
|
|
992
1035
|
|
|
993
1036
|
# Build input messages: history + current turn input
|
|
@@ -1056,17 +1099,30 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, model_n
|
|
|
1056
1099
|
# --- Tool call spans ---
|
|
1057
1100
|
for tool_call in turn.get("tool_calls", []):
|
|
1058
1101
|
tool_name = tool_call.get("name", "unknown")
|
|
1059
|
-
|
|
1102
|
+
tool_start_dt = _parse_ts(tool_call) or turn_start_dt
|
|
1103
|
+
tool_end_dt = None
|
|
1104
|
+
call_id = tool_call.get("call_id")
|
|
1105
|
+
for result in turn.get("tool_results", []):
|
|
1106
|
+
if result.get("call_id") == call_id:
|
|
1107
|
+
tool_end_dt = _parse_ts(result)
|
|
1108
|
+
break
|
|
1109
|
+
tool_finish_dt = _max_dt(tool_end_dt, tool_start_dt)
|
|
1110
|
+
with client.start_span(name=f"tool_{tool_name}", span_type="tool", start_time=tool_start_dt) as tool_span:
|
|
1060
1111
|
tool_span.set_runtime(Runtime(library="codex-cli"))
|
|
1061
|
-
|
|
1112
|
+
tool_tags = {
|
|
1062
1113
|
"tool_name": tool_name,
|
|
1063
|
-
"call_id":
|
|
1064
|
-
}
|
|
1114
|
+
"call_id": call_id,
|
|
1115
|
+
}
|
|
1116
|
+
if tool_start_dt is not None and tool_finish_dt is not None:
|
|
1117
|
+
tool_span.set_finish_time(tool_finish_dt)
|
|
1118
|
+
tool_tags["real_start_ms"] = _ts_ms(tool_start_dt)
|
|
1119
|
+
tool_tags["real_end_ms"] = _ts_ms(tool_finish_dt)
|
|
1120
|
+
tool_tags["latency_ms"] = _ts_ms(tool_finish_dt) - _ts_ms(tool_start_dt)
|
|
1121
|
+
tool_span.set_tags(tool_tags)
|
|
1065
1122
|
tool_span.set_input(
|
|
1066
1123
|
json.dumps(tool_call.get("input", {}), ensure_ascii=False)[:2000]
|
|
1067
1124
|
)
|
|
1068
1125
|
# Find matching tool result
|
|
1069
|
-
call_id = tool_call.get("call_id")
|
|
1070
1126
|
for result in turn.get("tool_results", []):
|
|
1071
1127
|
if result.get("call_id") == call_id:
|
|
1072
1128
|
output = result.get("output", "")
|
|
@@ -1079,15 +1135,24 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, model_n
|
|
|
1079
1135
|
for sc in turn.get("subagent_calls", []):
|
|
1080
1136
|
agent_id = sc.get("agent_id") or "unknown"
|
|
1081
1137
|
nickname = sc.get("nickname") or agent_id
|
|
1138
|
+
subagent_start_dt = _parse_ts_value(sc.get("_start_ts")) or turn_start_dt
|
|
1139
|
+
subagent_end_dt = _parse_ts_value(sc.get("_end_ts")) or turn_end_dt
|
|
1140
|
+
subagent_finish_dt = _max_dt(subagent_end_dt, subagent_start_dt)
|
|
1082
1141
|
|
|
1083
|
-
with client.start_span(name=f"subagent_{nickname}", span_type="agent", start_time=
|
|
1142
|
+
with client.start_span(name=f"subagent_{nickname}", span_type="agent", start_time=subagent_start_dt) as subagent_span:
|
|
1084
1143
|
subagent_span.set_runtime(Runtime(library="codex-cli"))
|
|
1085
|
-
|
|
1144
|
+
subagent_tags = {
|
|
1086
1145
|
"agent_id": agent_id,
|
|
1087
1146
|
"agent_nickname": nickname,
|
|
1088
1147
|
"agent_role": sc.get("role") or "",
|
|
1089
1148
|
"agent_model": sc.get("model") or "",
|
|
1090
|
-
}
|
|
1149
|
+
}
|
|
1150
|
+
if subagent_start_dt is not None and subagent_finish_dt is not None:
|
|
1151
|
+
subagent_span.set_finish_time(subagent_finish_dt)
|
|
1152
|
+
subagent_tags["real_start_ms"] = _ts_ms(subagent_start_dt)
|
|
1153
|
+
subagent_tags["real_end_ms"] = _ts_ms(subagent_finish_dt)
|
|
1154
|
+
subagent_tags["latency_ms"] = _ts_ms(subagent_finish_dt) - _ts_ms(subagent_start_dt)
|
|
1155
|
+
subagent_span.set_tags(subagent_tags)
|
|
1091
1156
|
subagent_span.set_input(sc.get("message", "")[:2000])
|
|
1092
1157
|
|
|
1093
1158
|
# Load and include saved subagent turn data
|
|
@@ -1097,20 +1162,45 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, model_n
|
|
|
1097
1162
|
sa_model = sa_data.get("model_name", "codex")
|
|
1098
1163
|
|
|
1099
1164
|
for si, sa_turn in enumerate(sa_turns):
|
|
1100
|
-
|
|
1165
|
+
sa_turn_start_dt, sa_turn_end_dt = _turn_bounds(sa_turn)
|
|
1166
|
+
if sa_turn_start_dt is None:
|
|
1167
|
+
sa_turn_start_dt = subagent_start_dt
|
|
1168
|
+
if sa_turn_end_dt is None:
|
|
1169
|
+
sa_turn_end_dt = sa_turn_start_dt
|
|
1170
|
+
sa_turn_finish_dt = _max_dt(sa_turn_end_dt, sa_turn_start_dt)
|
|
1171
|
+
|
|
1172
|
+
with client.start_span(name=f"turn_{si}", span_type="main", start_time=sa_turn_start_dt) as sa_turn_span:
|
|
1101
1173
|
sa_turn_span.set_runtime(Runtime(library="codex-cli"))
|
|
1102
|
-
|
|
1174
|
+
sa_turn_tags = {
|
|
1103
1175
|
"turn_index": si,
|
|
1104
1176
|
"turn_id": sa_turn.get("turn_id", ""),
|
|
1105
1177
|
"agent_name": nickname,
|
|
1106
|
-
}
|
|
1178
|
+
}
|
|
1179
|
+
if sa_turn_start_dt is not None and sa_turn_finish_dt is not None:
|
|
1180
|
+
sa_turn_span.set_finish_time(sa_turn_finish_dt)
|
|
1181
|
+
sa_turn_tags["real_start_ms"] = _ts_ms(sa_turn_start_dt)
|
|
1182
|
+
sa_turn_tags["real_end_ms"] = _ts_ms(sa_turn_finish_dt)
|
|
1183
|
+
sa_turn_tags["latency_ms"] = _ts_ms(sa_turn_finish_dt) - _ts_ms(sa_turn_start_dt)
|
|
1184
|
+
sa_turn_span.set_tags(sa_turn_tags)
|
|
1107
1185
|
|
|
1108
1186
|
# Subagent model span
|
|
1109
1187
|
if sa_turn.get("assistant_messages"):
|
|
1110
|
-
|
|
1188
|
+
sa_model_start_dt, sa_model_end_dt = _assistant_bounds(sa_turn)
|
|
1189
|
+
if sa_model_start_dt is None:
|
|
1190
|
+
sa_model_start_dt = sa_turn_start_dt
|
|
1191
|
+
if sa_model_end_dt is None:
|
|
1192
|
+
sa_model_end_dt = sa_turn_end_dt
|
|
1193
|
+
sa_model_finish_dt = _max_dt(sa_model_end_dt, sa_model_start_dt)
|
|
1194
|
+
with client.start_span(name="assistant_response", span_type="model", start_time=sa_model_start_dt) as sa_model_span:
|
|
1111
1195
|
sa_model_span.set_runtime(Runtime(library="codex-cli"))
|
|
1112
1196
|
sa_model_span.set_model_name(sa_model)
|
|
1113
|
-
|
|
1197
|
+
sa_model_tags = {"agent_name": nickname}
|
|
1198
|
+
if sa_model_start_dt is not None and sa_model_finish_dt is not None:
|
|
1199
|
+
sa_model_span.set_finish_time(sa_model_finish_dt)
|
|
1200
|
+
sa_model_tags["real_start_ms"] = _ts_ms(sa_model_start_dt)
|
|
1201
|
+
sa_model_tags["real_end_ms"] = _ts_ms(sa_model_finish_dt)
|
|
1202
|
+
sa_model_tags["latency_ms"] = _ts_ms(sa_model_finish_dt) - _ts_ms(sa_model_start_dt)
|
|
1203
|
+
sa_model_span.set_tags(sa_model_tags)
|
|
1114
1204
|
|
|
1115
1205
|
sa_input = sa_turn.get("input_messages", [])
|
|
1116
1206
|
if not sa_input:
|
|
@@ -1154,17 +1244,30 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, model_n
|
|
|
1154
1244
|
# Subagent tool spans
|
|
1155
1245
|
for sa_tc in sa_turn.get("tool_calls", []):
|
|
1156
1246
|
sa_tool_name = sa_tc.get("name", "unknown")
|
|
1157
|
-
|
|
1247
|
+
sa_tool_start_dt = _parse_ts(sa_tc) or sa_turn_start_dt
|
|
1248
|
+
sa_tool_end_dt = None
|
|
1249
|
+
sa_cid = sa_tc.get("call_id")
|
|
1250
|
+
for sa_r in sa_turn.get("tool_results", []):
|
|
1251
|
+
if sa_r.get("call_id") == sa_cid:
|
|
1252
|
+
sa_tool_end_dt = _parse_ts(sa_r)
|
|
1253
|
+
break
|
|
1254
|
+
sa_tool_finish_dt = _max_dt(sa_tool_end_dt, sa_tool_start_dt)
|
|
1255
|
+
with client.start_span(name=f"tool_{sa_tool_name}", span_type="tool", start_time=sa_tool_start_dt) as sa_tool_span:
|
|
1158
1256
|
sa_tool_span.set_runtime(Runtime(library="codex-cli"))
|
|
1159
|
-
|
|
1257
|
+
sa_tool_tags = {
|
|
1160
1258
|
"tool_name": sa_tool_name,
|
|
1161
|
-
"call_id":
|
|
1259
|
+
"call_id": sa_cid,
|
|
1162
1260
|
"agent_name": nickname,
|
|
1163
|
-
}
|
|
1261
|
+
}
|
|
1262
|
+
if sa_tool_start_dt is not None and sa_tool_finish_dt is not None:
|
|
1263
|
+
sa_tool_span.set_finish_time(sa_tool_finish_dt)
|
|
1264
|
+
sa_tool_tags["real_start_ms"] = _ts_ms(sa_tool_start_dt)
|
|
1265
|
+
sa_tool_tags["real_end_ms"] = _ts_ms(sa_tool_finish_dt)
|
|
1266
|
+
sa_tool_tags["latency_ms"] = _ts_ms(sa_tool_finish_dt) - _ts_ms(sa_tool_start_dt)
|
|
1267
|
+
sa_tool_span.set_tags(sa_tool_tags)
|
|
1164
1268
|
sa_tool_span.set_input(
|
|
1165
1269
|
json.dumps(sa_tc.get("input", {}), ensure_ascii=False)[:2000]
|
|
1166
1270
|
)
|
|
1167
|
-
sa_cid = sa_tc.get("call_id")
|
|
1168
1271
|
for sa_r in sa_turn.get("tool_results", []):
|
|
1169
1272
|
if sa_r.get("call_id") == sa_cid:
|
|
1170
1273
|
sa_out = sa_r.get("output", "")
|