coze_lab 0.1.40 → 0.1.41

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coze_lab",
3
- "version": "0.1.40",
3
+ "version": "0.1.41",
4
4
  "description": "Configure local AI agents (Claude Code, Codex, OpenClaw) to report traces to CozeLoop",
5
5
  "keywords": [
6
6
  "cozeloop",
@@ -1071,7 +1071,7 @@ def _build_history_messages(history_turns: List[Dict[str, Any]]) -> list:
1071
1071
 
1072
1072
  # --- CozeLoop Trace Reporting ---
1073
1073
 
1074
- def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, history_turns: Optional[List[Dict[str, Any]]] = None, retry_on_auth_failure: bool = True):
1074
+ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, history_turns: Optional[List[Dict[str, Any]]] = None, retry_on_auth_failure: bool = True, coze_tags_override: Optional[Dict[str, str]] = None):
1075
1075
  """Send conversation turns to CozeLoop.
1076
1076
 
1077
1077
  Span hierarchy:
@@ -1160,6 +1160,11 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, history
1160
1160
  t = coze_context_tags(um.get("content") if um else None)
1161
1161
  if t:
1162
1162
  coze_tags = t
1163
+ # 增量上报兜底:本批 turns 不含 <coze-context>(它只在首 turn)时,用 caller
1164
+ # 从 state 传入的 override,保证每个 turn 的 root span 都带 coze_message_id,
1165
+ # 否则后续增量 turn 的 root span 无法按 message_id 查到。
1166
+ if not coze_tags and coze_tags_override:
1167
+ coze_tags = dict(coze_tags_override)
1163
1168
  # Drop empty-valued coze_* tags: the backend pairs traces by exact tag
1164
1169
  # match (coze_message_id / coze_agent_id), where an empty string is
1165
1170
  # indistinguishable from "absent" yet still bloats the span — never
@@ -1604,7 +1609,7 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, history
1604
1609
  if new_token:
1605
1610
  os.environ["COZELOOP_API_TOKEN"] = new_token
1606
1611
  hook_log("retry upload once after forced token refresh")
1607
- return send_turns_to_cozeloop(turns, session_id, history_turns, retry_on_auth_failure=False)
1612
+ return send_turns_to_cozeloop(turns, session_id, history_turns, retry_on_auth_failure=False, coze_tags_override=coze_tags_override)
1608
1613
  return None
1609
1614
 
1610
1615
  return True
@@ -1733,6 +1738,17 @@ def main():
1733
1738
  debug_log("No coze-context found in any turn (incl. history), skipping upload.")
1734
1739
  return
1735
1740
 
1741
+ # 持久化 coze_tags 到 state:<coze-context> 只在首 turn,后续增量批次的 turns 不含它,
1742
+ # 其 root span 会缺 coze_message_id 而无法按 message_id 查到。首次解析到就存 state,
1743
+ # 后续批次作为 override 传给 send_turns,保证每个 turn 的 root span 都带 coze_* tag。
1744
+ for _t in list(turns) + list(history_turns):
1745
+ _um = (_t.get("user_message", {}).get("message", {}) or {})
1746
+ _tags = {k: v for k, v in coze_context_tags(_um.get("content")).items() if isinstance(v, str) and v.strip()}
1747
+ if _tags:
1748
+ state["coze_tags"] = _tags
1749
+ break
1750
+ coze_tags_override = state.get("coze_tags") or None
1751
+
1736
1752
  # turn 边界控制:中途事件(PostToolUse)触发时,最后一个 turn 往往仍在进行中
1737
1753
  # (后续还会追加 step)。若此刻就上报并推进其行号,同一逻辑 turn 会在下次触发时
1738
1754
  # 因缺了起始 user 消息而被拆成新的 root span。故中途事件只上报“已完成”的 turn
@@ -1746,7 +1762,7 @@ def main():
1746
1762
  return
1747
1763
 
1748
1764
  print(f"[CozeLoop] 开始上报: session={session_id}, event={hook_event}, turns={len(turns_to_send)}/{len(turns)}", file=sys.stderr)
1749
- uploaded = send_turns_to_cozeloop(turns_to_send, session_id, history_turns)
1765
+ uploaded = send_turns_to_cozeloop(turns_to_send, session_id, history_turns, coze_tags_override=coze_tags_override)
1750
1766
  if uploaded is None:
1751
1767
  debug_log("Send failed, state not advanced.")
1752
1768
  return
@@ -1077,7 +1077,8 @@ def _make_model_message(role: str, content: str = "", tool_calls: list = None,
1077
1077
 
1078
1078
  def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, model_name: str = "codex",
1079
1079
  history_context: Optional[List[Dict[str, Any]]] = None,
1080
- retry_on_auth_failure: bool = True) -> Optional[List[Dict[str, Any]]]:
1080
+ retry_on_auth_failure: bool = True,
1081
+ coze_tags_override: Optional[Dict[str, str]] = None) -> Optional[List[Dict[str, Any]]]:
1081
1082
  """Send conversation turns to CozeLoop for tracing.
1082
1083
 
1083
1084
  Span hierarchy:
@@ -1162,6 +1163,11 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, model_n
1162
1163
  t = {f"coze_{k}": v for k, v in turn_coze_context(turn).items()}
1163
1164
  if t:
1164
1165
  coze_tags = t
1166
+ # 增量上报兜底:本批 turns 不含 <coze-context>(它只在首 turn)时,用 caller 从
1167
+ # state 传入的 override,保证每个 turn 的 root span 都带 coze_message_id,否则后续
1168
+ # 增量 turn 的 root span 无法按 message_id 查到。
1169
+ if not coze_tags and coze_tags_override:
1170
+ coze_tags = dict(coze_tags_override)
1165
1171
  # Drop empty-valued coze_* tags: the backend pairs traces by exact tag
1166
1172
  # match (coze_message_id / coze_agent_id), where an empty string is
1167
1173
  # indistinguishable from "absent" yet still bloats the span — never
@@ -1539,6 +1545,7 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, model_n
1539
1545
  model_name,
1540
1546
  history_context,
1541
1547
  retry_on_auth_failure=False,
1548
+ coze_tags_override=coze_tags_override,
1542
1549
  )
1543
1550
  return None
1544
1551
 
@@ -1688,6 +1695,17 @@ def main():
1688
1695
  if not seen_ctx:
1689
1696
  state["seen_coze_context"] = True
1690
1697
 
1698
+ # 持久化 coze_tags 到 state:<coze-context> 只在首 turn,后续增量批次不含它,其 root span
1699
+ # 会缺 coze_message_id 无法按 message_id 查到。首次解析到就存 state,后续批次作为 override
1700
+ # 传给 send_turns,保证每个 turn 的 root span 都带 coze_* tag。
1701
+ if not state.get("coze_tags"):
1702
+ for _t in turns:
1703
+ _tags = {f"coze_{k}": v for k, v in turn_coze_context(_t).items() if isinstance(v, str) and v.strip()}
1704
+ if _tags:
1705
+ state["coze_tags"] = _tags
1706
+ break
1707
+ coze_tags_override = state.get("coze_tags") or None
1708
+
1691
1709
  # 节流:PostToolUse 在密集工具调用下高频触发。距上次上报不足间隔则跳过本次增量上报。
1692
1710
  # 终态事件(Stop/SubagentStop)永不被节流,保证 turn 结束时一定收尾。
1693
1711
  if not is_terminal_event:
@@ -1714,6 +1732,7 @@ def main():
1714
1732
  updated_history = send_turns_to_cozeloop(
1715
1733
  turns_to_send, session_id, model_name,
1716
1734
  history_context=history_context,
1735
+ coze_tags_override=coze_tags_override,
1717
1736
  )
1718
1737
  if updated_history is not None:
1719
1738
  # 推进 last_processed_line:终态推进到所有 entry 末行;中途保留最后一个未完成 turn,