coze_lab 0.1.49 → 0.1.51
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/index.js
CHANGED
|
@@ -669,14 +669,12 @@ function writeClaudeCodeHook(token, workspaceId, pythonCmd, configBaseDir, cloud
|
|
|
669
669
|
delete existing.env.COZELAB_ONBOARD_CLOUD;
|
|
670
670
|
}
|
|
671
671
|
const loopBaseUrl = readEnv('COZELOOP_API_BASE_URL');
|
|
672
|
-
const otelEndpoint = readEnv('OTEL_ENDPOINT');
|
|
673
672
|
if (loopBaseUrl) {
|
|
674
673
|
existing.env.COZELOOP_API_BASE_URL = loopBaseUrl;
|
|
675
|
-
|
|
676
|
-
} else if (otelEndpoint) {
|
|
677
|
-
existing.env.OTEL_ENDPOINT = otelEndpoint;
|
|
674
|
+
} else {
|
|
678
675
|
delete existing.env.COZELOOP_API_BASE_URL;
|
|
679
676
|
}
|
|
677
|
+
delete existing.env.OTEL_ENDPOINT;
|
|
680
678
|
// 切正式环境:移除历史注入的 PPE 泳道 env(旧版本曾写入 x_tt_env/x_use_ppe)
|
|
681
679
|
delete existing.env.x_tt_env;
|
|
682
680
|
delete existing.env.x_use_ppe;
|
|
@@ -733,11 +731,8 @@ function writeCodexHook(token, workspaceId, pythonCmd, codexHome, cloud, tokenSo
|
|
|
733
731
|
envLines.push(shellEnvLine('COZELOOP_HOOK_LOG', logFile));
|
|
734
732
|
envLines.push('TRACE_TO_COZELOOP=true');
|
|
735
733
|
const loopBaseUrl = readEnv('COZELOOP_API_BASE_URL');
|
|
736
|
-
const otelEndpoint = readEnv('OTEL_ENDPOINT');
|
|
737
734
|
if (loopBaseUrl) {
|
|
738
735
|
envLines.push(shellEnvLine('COZELOOP_API_BASE_URL', loopBaseUrl));
|
|
739
|
-
} else if (otelEndpoint) {
|
|
740
|
-
envLines.push(shellEnvLine('OTEL_ENDPOINT', otelEndpoint));
|
|
741
736
|
}
|
|
742
737
|
const envContent = envLines.join('\n') + '\n';
|
|
743
738
|
try {
|
package/package.json
CHANGED
|
@@ -108,7 +108,7 @@ _DEFAULT_WORKSPACE_ID = "7649231955045072915" # hardcoded spaceID fallback
|
|
|
108
108
|
|
|
109
109
|
|
|
110
110
|
def _normalize_api_base_url(raw: str) -> str:
|
|
111
|
-
"""Normalize CozeLoop API/
|
|
111
|
+
"""Normalize a CozeLoop API/ingest URL to the SDK api_base_url."""
|
|
112
112
|
base = (raw or "").strip().rstrip("/")
|
|
113
113
|
if not base:
|
|
114
114
|
return ""
|
|
@@ -130,11 +130,7 @@ def _normalize_api_base_url(raw: str) -> str:
|
|
|
130
130
|
|
|
131
131
|
def get_api_base_url() -> str:
|
|
132
132
|
"""Return optional CozeLoop SDK api_base_url from onboard-injected env."""
|
|
133
|
-
return _normalize_api_base_url(
|
|
134
|
-
os.environ.get("COZELOOP_API_BASE_URL")
|
|
135
|
-
or os.environ.get("OTEL_ENDPOINT")
|
|
136
|
-
or ""
|
|
137
|
-
)
|
|
133
|
+
return _normalize_api_base_url(os.environ.get("COZELOOP_API_BASE_URL") or "")
|
|
138
134
|
|
|
139
135
|
|
|
140
136
|
# --- coze-context parsing -------------------------------------------------
|
|
@@ -255,7 +255,7 @@ def _make_finish_event_processor(upload_events: Optional[List[str]] = None):
|
|
|
255
255
|
|
|
256
256
|
|
|
257
257
|
def _normalize_api_base_url(raw: str) -> str:
|
|
258
|
-
"""Normalize CozeLoop API/
|
|
258
|
+
"""Normalize a CozeLoop API/ingest URL to the SDK api_base_url."""
|
|
259
259
|
base = (raw or "").strip().rstrip("/")
|
|
260
260
|
if not base:
|
|
261
261
|
return ""
|
|
@@ -277,11 +277,7 @@ def _normalize_api_base_url(raw: str) -> str:
|
|
|
277
277
|
|
|
278
278
|
def get_api_base_url() -> str:
|
|
279
279
|
"""Return optional CozeLoop SDK api_base_url from onboard-injected env."""
|
|
280
|
-
return _normalize_api_base_url(
|
|
281
|
-
os.environ.get("COZELOOP_API_BASE_URL")
|
|
282
|
-
or os.environ.get("OTEL_ENDPOINT")
|
|
283
|
-
or ""
|
|
284
|
-
)
|
|
280
|
+
return _normalize_api_base_url(os.environ.get("COZELOOP_API_BASE_URL") or "")
|
|
285
281
|
|
|
286
282
|
|
|
287
283
|
def _log_file_path() -> str:
|
|
@@ -699,6 +695,32 @@ def _assistant_bounds(turn):
|
|
|
699
695
|
return min(values), max(values)
|
|
700
696
|
|
|
701
697
|
|
|
698
|
+
def extract_token_events(entries: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
699
|
+
"""扫 rollout event_msg/token_count 事件, 与 turn 完全解耦.
|
|
700
|
+
|
|
701
|
+
每条事件独立采集, 依赖 _line_number 做全局水位线幂等. 只收 last_token_usage 非空的事件
|
|
702
|
+
(空的是心跳/占位, 不代表真实模型调用). total_token_usage 一并保留供 tag 上报.
|
|
703
|
+
"""
|
|
704
|
+
events: List[Dict[str, Any]] = []
|
|
705
|
+
for entry in entries:
|
|
706
|
+
if entry.get("type") != "event_msg":
|
|
707
|
+
continue
|
|
708
|
+
payload = entry.get("payload") or {}
|
|
709
|
+
if payload.get("type") != "token_count":
|
|
710
|
+
continue
|
|
711
|
+
info = payload.get("info") or {}
|
|
712
|
+
last_usage = info.get("last_token_usage")
|
|
713
|
+
if not isinstance(last_usage, dict) or not last_usage:
|
|
714
|
+
continue
|
|
715
|
+
events.append({
|
|
716
|
+
"line": entry.get("_line_number", 0),
|
|
717
|
+
"timestamp": entry.get("timestamp"),
|
|
718
|
+
"usage": last_usage,
|
|
719
|
+
"total_usage": info.get("total_token_usage") or {},
|
|
720
|
+
})
|
|
721
|
+
return events
|
|
722
|
+
|
|
723
|
+
|
|
702
724
|
def group_messages_into_turns(entries: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
703
725
|
"""Group raw JSONL entries into conversation turns.
|
|
704
726
|
|
|
@@ -1365,7 +1387,7 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, model_n
|
|
|
1365
1387
|
# tool span 挂到固定 root、当场 flush。codex 的实时单元是 tool_call(turn 内平铺,按 call_id 关联
|
|
1366
1388
|
# result)。全局水位线 state["rt_last_tool"] 跟踪已发的 tool_call 数。
|
|
1367
1389
|
|
|
1368
|
-
def send_steps_realtime(turns, session_id, state, model_name="codex", coze_tags_override=None, is_terminal=False):
|
|
1390
|
+
def send_steps_realtime(turns, session_id, state, model_name="codex", coze_tags_override=None, is_terminal=False, token_events=None):
|
|
1369
1391
|
import cozeloop
|
|
1370
1392
|
from cozeloop.spec.tracespec import Runtime
|
|
1371
1393
|
token = get_fresh_token()
|
|
@@ -1498,6 +1520,62 @@ def send_steps_realtime(turns, session_id, state, model_name="codex", coze_tags_
|
|
|
1498
1520
|
if sent > 0:
|
|
1499
1521
|
state["rt_last_upload_ts"] = time.time()
|
|
1500
1522
|
|
|
1523
|
+
# --- token_count 独立增量, 与 tool span 同一 root 下, 按行号水位线幂等 ---
|
|
1524
|
+
# 每条 token_count 挂一个 model_usage_<line> span:
|
|
1525
|
+
# last_token_usage → set_input_tokens/set_output_tokens (SDK 官方求和字段)
|
|
1526
|
+
# total_token_usage → tags (只观察, 不参与后端聚合, 否则会重复累加)
|
|
1527
|
+
# 只在 flush 成功后推进 rt_last_token_line, 保证上报失败下次能重发.
|
|
1528
|
+
token_sent = 0
|
|
1529
|
+
last_token_line = state.get("rt_last_token_line", -1)
|
|
1530
|
+
pending_tokens = [ev for ev in (token_events or []) if ev["line"] > last_token_line]
|
|
1531
|
+
max_flushed_line = last_token_line
|
|
1532
|
+
for ev in pending_tokens:
|
|
1533
|
+
line = ev["line"]
|
|
1534
|
+
last = ev.get("usage") or {}
|
|
1535
|
+
total = ev.get("total_usage") or {}
|
|
1536
|
+
in_tk = _usage_int(last, "input_tokens")
|
|
1537
|
+
out_tk = _usage_int(last, "output_tokens")
|
|
1538
|
+
start_dt = _parse_ts_value(ev.get("timestamp"))
|
|
1539
|
+
tk_span = client.start_span(name=f"model_usage_{line}", span_type="model",
|
|
1540
|
+
start_time=start_dt, child_of=root_ctx)
|
|
1541
|
+
tk_span.set_runtime(Runtime(library="codex-cli"))
|
|
1542
|
+
try: tk_span.set_model_name(model_name)
|
|
1543
|
+
except Exception: pass
|
|
1544
|
+
if start_dt is not None:
|
|
1545
|
+
_set_finish_time_safe(tk_span, start_dt)
|
|
1546
|
+
if in_tk > 0:
|
|
1547
|
+
try: tk_span.set_input_tokens(in_tk)
|
|
1548
|
+
except Exception: pass
|
|
1549
|
+
if out_tk > 0:
|
|
1550
|
+
try: tk_span.set_output_tokens(out_tk)
|
|
1551
|
+
except Exception: pass
|
|
1552
|
+
tk_tags = {
|
|
1553
|
+
"token_event_line": line,
|
|
1554
|
+
"token_source": "codex_token_count",
|
|
1555
|
+
"cached_input_tokens": _usage_int(last, "cached_input_tokens"),
|
|
1556
|
+
"reasoning_output_tokens": _usage_int(last, "reasoning_output_tokens"),
|
|
1557
|
+
"total_tokens": _usage_int(last, "total_tokens"),
|
|
1558
|
+
}
|
|
1559
|
+
if total:
|
|
1560
|
+
tk_tags["total_input_tokens"] = _usage_int(total, "input_tokens")
|
|
1561
|
+
tk_tags["total_output_tokens"] = _usage_int(total, "output_tokens")
|
|
1562
|
+
tk_tags["total_cached_input_tokens"] = _usage_int(total, "cached_input_tokens")
|
|
1563
|
+
tk_tags["total_reasoning_output_tokens"] = _usage_int(total, "reasoning_output_tokens")
|
|
1564
|
+
tk_tags["total_all_tokens"] = _usage_int(total, "total_tokens")
|
|
1565
|
+
tk_span.set_tags(tk_tags)
|
|
1566
|
+
tk_span.finish()
|
|
1567
|
+
client.flush()
|
|
1568
|
+
if upload_events:
|
|
1569
|
+
hook_log(f"[rt] upload failed after token flush line={line} detail={upload_events[-1][:500]}")
|
|
1570
|
+
if max_flushed_line > last_token_line:
|
|
1571
|
+
state["rt_last_token_line"] = max_flushed_line
|
|
1572
|
+
return None
|
|
1573
|
+
max_flushed_line = line
|
|
1574
|
+
token_sent += 1
|
|
1575
|
+
if token_sent > 0:
|
|
1576
|
+
state["rt_last_token_line"] = max_flushed_line
|
|
1577
|
+
state["rt_last_upload_ts"] = time.time()
|
|
1578
|
+
|
|
1501
1579
|
if is_terminal:
|
|
1502
1580
|
# 收尾:补最终 assistant 输出
|
|
1503
1581
|
last_out = ""
|
|
@@ -1523,7 +1601,7 @@ def send_steps_realtime(turns, session_id, state, model_name="codex", coze_tags_
|
|
|
1523
1601
|
hook_log(f"[rt] upload failed state not advanced detail={upload_events[-1][:500]}")
|
|
1524
1602
|
return None
|
|
1525
1603
|
|
|
1526
|
-
hook_log(f"[rt] sent {sent} tool span(s), last_tool={state['rt_last_tool']}, terminal={is_terminal}")
|
|
1604
|
+
hook_log(f"[rt] sent {sent} tool span(s), {token_sent} token span(s), last_tool={state['rt_last_tool']}, last_token_line={state.get('rt_last_token_line', -1)}, terminal={is_terminal}")
|
|
1527
1605
|
return True
|
|
1528
1606
|
except Exception as e:
|
|
1529
1607
|
hook_log(f"[rt] error: {e}")
|
|
@@ -1641,7 +1719,9 @@ def main():
|
|
|
1641
1719
|
|
|
1642
1720
|
# Group entries into turns
|
|
1643
1721
|
turns = group_messages_into_turns(entries)
|
|
1644
|
-
|
|
1722
|
+
# 独立扫 token_count 事件, 与 turn 解耦, 走独立行号水位线上报.
|
|
1723
|
+
token_events = extract_token_events(entries)
|
|
1724
|
+
debug_log(f"Grouped into {len(turns)} turns, {len(token_events)} token events")
|
|
1645
1725
|
|
|
1646
1726
|
# If this is a subagent, save data for parent to include later
|
|
1647
1727
|
if is_subagent:
|
|
@@ -1681,7 +1761,8 @@ def main():
|
|
|
1681
1761
|
# 方案 B 实时:把新完成的 tool_call 作为 span 挂稳定 root,结束即可见。不节流。
|
|
1682
1762
|
ok = send_steps_realtime(turns, session_id, state, model_name=model_name,
|
|
1683
1763
|
coze_tags_override=coze_tags_override,
|
|
1684
|
-
is_terminal=is_terminal_event
|
|
1764
|
+
is_terminal=is_terminal_event,
|
|
1765
|
+
token_events=token_events)
|
|
1685
1766
|
if ok is None:
|
|
1686
1767
|
hook_log(f"realtime send failed session_id={session_id}")
|
|
1687
1768
|
debug_log("Realtime send failed, state not advanced")
|