coze_lab 0.1.17 → 0.1.18

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
@@ -4575,6 +4575,11 @@ function writeCodexHook(token, workspaceId, pythonCmd, codexHome, cloud) {
4575
4575
  envLines.push(`CODEX_HOME=${home}`);
4576
4576
  envLines.push(`COZELOOP_HOOK_LOG=${logFile}`);
4577
4577
  envLines.push('TRACE_TO_COZELOOP=true');
4578
+ if (process.env.COZELOOP_API_BASE_URL) {
4579
+ envLines.push(`COZELOOP_API_BASE_URL=${process.env.COZELOOP_API_BASE_URL}`);
4580
+ } else if (process.env.OTEL_ENDPOINT) {
4581
+ envLines.push(`OTEL_ENDPOINT=${process.env.OTEL_ENDPOINT}`);
4582
+ }
4578
4583
  // PPE 泳道:cozeloop SDK 读这两个环境变量,自动注入 x-tt-env / x-use-ppe header
4579
4584
  envLines.push(`x_tt_env=${PPE_TT_ENV}`);
4580
4585
  envLines.push(`x_use_ppe=${PPE_USE_PPE}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coze_lab",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "Configure local AI agents (Claude Code, Codex, OpenClaw) to report traces to CozeLoop",
5
5
  "keywords": [
6
6
  "cozeloop",
@@ -46,6 +46,7 @@ _COZELOOP_CLIENT_ID = "46371084383473718052118955183420.app.coze"
46
46
  _COZE_API = "https://api.coze.cn"
47
47
  _REFRESH_THRESHOLD = 10 * 60
48
48
  _DEFAULT_WORKSPACE_ID = "7644910356078837760" # hardcoded spaceID fallback
49
+ _OTEL_SUFFIX = "/v1/loop/opentelemetry"
49
50
 
50
51
 
51
52
  # --- coze-context parsing -------------------------------------------------
@@ -98,6 +99,22 @@ def coze_context_tags(text: str) -> Dict[str, str]:
98
99
  return {f"coze_{k}": v for k, v in parse_coze_context(text).items()}
99
100
 
100
101
 
102
+ def turn_coze_context(turn: Dict[str, Any]) -> Dict[str, str]:
103
+ """Extract coze-context from a grouped turn with fallbacks for Codex rollout shapes."""
104
+ texts = [turn.get("user_message_text", "")]
105
+ for msg in turn.get("input_messages", []):
106
+ if isinstance(msg, dict):
107
+ texts.append(str(msg.get("content", "")))
108
+ user_payload = turn.get("user_message")
109
+ if isinstance(user_payload, dict):
110
+ texts.append(extract_message_content_text(user_payload))
111
+ for text in texts:
112
+ ctx = parse_coze_context(text)
113
+ if ctx:
114
+ return ctx
115
+ return {}
116
+
117
+
101
118
  # --- trace upload failure / logid capture ---------------------------------
102
119
  def _extract_logid(msg: str) -> str:
103
120
  """Pull the server logid out of an SDK error message, if present.
@@ -190,6 +207,20 @@ def _refresh_token(refresh_tok: str):
190
207
  pass
191
208
  return None
192
209
 
210
+
211
+ def _normalize_api_base_url(url: str) -> str:
212
+ base = (url or "").strip().rstrip("/")
213
+ if base.endswith(_OTEL_SUFFIX):
214
+ return base[:-len(_OTEL_SUFFIX)].rstrip("/")
215
+ return base
216
+
217
+
218
+ def get_api_base_url() -> str:
219
+ return _normalize_api_base_url(
220
+ os.environ.get("COZELOOP_API_BASE_URL", "") or os.environ.get("OTEL_ENDPOINT", "")
221
+ )
222
+
223
+
193
224
  def get_fresh_token():
194
225
  creds = _load_credentials()
195
226
  if creds:
@@ -200,8 +231,14 @@ def get_fresh_token():
200
231
  new_token = _refresh_token(creds["refresh_token"])
201
232
  if new_token:
202
233
  return new_token
203
- # Cloud sandbox: token lives in COZE_API_TOKEN (no credentials.json / refresh).
204
- return os.environ.get("COZELOOP_API_TOKEN") or os.environ.get("COZE_API_TOKEN")
234
+ token = os.environ.get("COZELOOP_API_TOKEN")
235
+ if token:
236
+ return token
237
+ if get_api_base_url():
238
+ return os.environ.get("COZE_API_TOKEN")
239
+ if os.environ.get("COZE_API_TOKEN"):
240
+ hook_log("COZE_API_TOKEN present but ignored without COZELOOP_API_BASE_URL or OTEL_ENDPOINT")
241
+ return None
205
242
  # -------------------------------------------------------------------------
206
243
 
207
244
  # --- SDK Import ---
@@ -516,6 +553,8 @@ def is_real_user_message(payload: Dict[str, Any]) -> bool:
516
553
  if item.get("type") != "input_text":
517
554
  continue
518
555
  text = item.get("text", "")
556
+ if parse_coze_context(text):
557
+ return True
519
558
  if text.startswith("<environment_context>"):
520
559
  continue
521
560
  if text.startswith("<permissions instructions>"):
@@ -534,9 +573,10 @@ def extract_user_text(payload: Dict[str, Any]) -> str:
534
573
  for item in payload.get("content", []):
535
574
  if isinstance(item, dict) and item.get("type") == "input_text":
536
575
  text = item.get("text", "")
537
- if (not text.startswith("<environment_context>") and
576
+ if (parse_coze_context(text) or
577
+ (not text.startswith("<environment_context>") and
538
578
  not text.startswith("<permissions instructions>") and
539
- not text.startswith("<turn_aborted>")):
579
+ not text.startswith("<turn_aborted>"))):
540
580
  parts.append(text)
541
581
  return "\n".join(parts)
542
582
 
@@ -821,6 +861,10 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, model_n
821
861
  "upload_timeout": 120,
822
862
  "trace_finish_event_processor": _make_finish_event_processor(),
823
863
  }
864
+ api_base_url = get_api_base_url()
865
+ if api_base_url:
866
+ client_kwargs["api_base_url"] = api_base_url
867
+ hook_log(f"api_base_url={api_base_url}")
824
868
  if workspace_id:
825
869
  client_kwargs["workspace_id"] = workspace_id
826
870
  if token:
@@ -859,7 +903,7 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, model_n
859
903
  # Inject coze-context kv (last occurrence across turns wins).
860
904
  coze_tags = {}
861
905
  for turn in turns:
862
- t = coze_context_tags(turn.get("user_message_text", ""))
906
+ t = {f"coze_{k}": v for k, v in turn_coze_context(turn).items()}
863
907
  if t:
864
908
  coze_tags = t
865
909
  if coze_tags:
@@ -1276,7 +1320,7 @@ def main():
1276
1320
  # Send turns to CozeLoop — only if at least one turn carries coze-context.
1277
1321
  if turns:
1278
1322
  has_coze_ctx = any(
1279
- parse_coze_context(t.get("user_message_text", ""))
1323
+ turn_coze_context(t)
1280
1324
  for t in turns
1281
1325
  )
1282
1326
  if not has_coze_ctx:
@@ -22,7 +22,11 @@ function safeClone(value) {
22
22
  // --- coze-context parsing ---------------------------------------------------
23
23
  // User input may embed a <coze-context>...</coze-context> block with key:value
24
24
  // lines (agent_id, session_id, message_id, account_id, ...). Parse the LAST
25
- // block and inject the pairs (prefixed "coze.") into the root span attributes.
25
+ // block and inject the pairs (prefixed "coze_") into the root span attributes.
26
+ // NOTE: prefix MUST be "coze_" (underscore) to match the cozelab backend trace
27
+ // query field names (coze_message_id / coze_agent_id) and stay consistent with
28
+ // the claude-code / codex hooks. Do NOT use "coze." (dot) — backend Eq filter
29
+ // matches tag keys exactly and would never hit dotted keys.
26
30
  const COZE_CTX_OPEN = "<coze-context>";
27
31
  const COZE_CTX_CLOSE = "</coze-context>";
28
32
  function cozeInputToText(input) {
@@ -67,7 +71,7 @@ function parseCozeContext(input) {
67
71
  const key = line.slice(0, sep).trim();
68
72
  const value = line.slice(sep + 1).trim();
69
73
  if (key)
70
- out["coze." + key] = value;
74
+ out["coze_" + key] = value;
71
75
  }
72
76
  return out;
73
77
  }