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 +5 -0
- package/package.json +1 -1
- package/scripts/codex/cozeloop_hook.py +50 -6
- package/scripts/openclaw/dist/index.js +6 -2
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
|
@@ -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
|
-
|
|
204
|
-
|
|
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 (
|
|
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 =
|
|
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
|
-
|
|
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 "
|
|
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["
|
|
74
|
+
out["coze_" + key] = value;
|
|
71
75
|
}
|
|
72
76
|
return out;
|
|
73
77
|
}
|