coze_lab 0.1.48 → 0.1.50
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 -------------------------------------------------
|
|
@@ -131,10 +131,131 @@ else:
|
|
|
131
131
|
|
|
132
132
|
# --- Configuration ---
|
|
133
133
|
DEBUG = os.environ.get("CC_COZELOOP_DEBUG", "").lower() == "true"
|
|
134
|
+
_DEFAULT_WORKSPACE_ID = "7649231955045072915" # hardcoded spaceID fallback
|
|
135
|
+
|
|
136
|
+
# 实时 tool span 上报的批量间隔(秒):距上次上报不足此值则本次不发,把已结束的 tool span
|
|
137
|
+
# 攒到下次一起发(最多每 5s 一批)。终态(Stop/SubagentStop)不受限,立即收尾。
|
|
138
|
+
REALTIME_BATCH_INTERVAL = float(os.environ.get("COZELOOP_REALTIME_BATCH_INTERVAL", "5"))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# --- coze-context parsing -------------------------------------------------
|
|
142
|
+
# User messages may embed a block like:
|
|
143
|
+
# <coze-context>
|
|
144
|
+
# account_id: 0
|
|
145
|
+
# agent_id: 7644920552473395499
|
|
146
|
+
# session_id: 7644919579054997796
|
|
147
|
+
# message_id: 04dd5246-...
|
|
148
|
+
# </coze-context>
|
|
149
|
+
# We parse its key:value pairs and inject them into the trace.
|
|
150
|
+
|
|
151
|
+
_COZE_CTX_OPEN = "<coze-context>"
|
|
152
|
+
_COZE_CTX_CLOSE = "</coze-context>"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def parse_coze_context(text: str) -> Dict[str, str]:
|
|
156
|
+
"""Extract the LAST <coze-context> block's key:value pairs from text.
|
|
157
|
+
|
|
158
|
+
Returns {} if no block is present. Tag keys are prefixed with
|
|
159
|
+
'coze_' by the caller; here we return raw keys as written.
|
|
160
|
+
"""
|
|
161
|
+
if not text or _COZE_CTX_OPEN not in text:
|
|
162
|
+
return {}
|
|
163
|
+
# Take the last occurrence (latest context wins).
|
|
164
|
+
open_idx = text.rfind(_COZE_CTX_OPEN)
|
|
165
|
+
close_idx = text.find(_COZE_CTX_CLOSE, open_idx)
|
|
166
|
+
if close_idx == -1:
|
|
167
|
+
return {}
|
|
168
|
+
body = text[open_idx + len(_COZE_CTX_OPEN):close_idx]
|
|
169
|
+
# The block may arrive with real newlines, OR with literal backslash-n
|
|
170
|
+
# (e.g. when the whole message is an embedded JSON string that was never
|
|
171
|
+
# un-escaped). Normalize both forms before splitting into lines.
|
|
172
|
+
body = body.replace("\\r\\n", "\n").replace("\\n", "\n").replace("\\r", "\n")
|
|
173
|
+
result: Dict[str, str] = {}
|
|
174
|
+
for line in body.splitlines():
|
|
175
|
+
line = line.strip()
|
|
176
|
+
if not line or ":" not in line:
|
|
177
|
+
continue
|
|
178
|
+
key, _, value = line.partition(":")
|
|
179
|
+
key = key.strip()
|
|
180
|
+
value = value.strip()
|
|
181
|
+
if key:
|
|
182
|
+
result[key] = value
|
|
183
|
+
return result
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def turn_coze_context(turn: Dict[str, Any]) -> Dict[str, str]:
|
|
187
|
+
"""Extract coze-context from a grouped turn with fallbacks for Codex rollout shapes."""
|
|
188
|
+
texts = [turn.get("user_message_text", "")]
|
|
189
|
+
for msg in turn.get("input_messages", []):
|
|
190
|
+
if isinstance(msg, dict):
|
|
191
|
+
content = msg.get("content", "")
|
|
192
|
+
if isinstance(content, str):
|
|
193
|
+
texts.append(content)
|
|
194
|
+
elif isinstance(content, list):
|
|
195
|
+
texts.append(extract_message_content_text({"content": content}))
|
|
196
|
+
elif isinstance(content, dict):
|
|
197
|
+
texts.append(extract_message_content_text({"content": [content]}))
|
|
198
|
+
user_payload = turn.get("user_message")
|
|
199
|
+
if isinstance(user_payload, dict):
|
|
200
|
+
texts.append(extract_message_content_text(user_payload))
|
|
201
|
+
for text in texts:
|
|
202
|
+
ctx = parse_coze_context(text)
|
|
203
|
+
if ctx:
|
|
204
|
+
return ctx
|
|
205
|
+
return {}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# --- trace upload failure / logid capture ---------------------------------
|
|
209
|
+
def _extract_logid(msg: str) -> str:
|
|
210
|
+
"""Pull the server logid out of an SDK error message, if present.
|
|
211
|
+
|
|
212
|
+
SDK failure messages embed it as 'logid=XXXX' (sometimes within brackets).
|
|
213
|
+
"""
|
|
214
|
+
if not msg:
|
|
215
|
+
return ""
|
|
216
|
+
marker = "logid="
|
|
217
|
+
idx = msg.find(marker)
|
|
218
|
+
if idx == -1:
|
|
219
|
+
return ""
|
|
220
|
+
rest = msg[idx + len(marker):]
|
|
221
|
+
logid = []
|
|
222
|
+
for ch in rest:
|
|
223
|
+
if ch.isalnum():
|
|
224
|
+
logid.append(ch)
|
|
225
|
+
else:
|
|
226
|
+
break
|
|
227
|
+
return "".join(logid)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _make_finish_event_processor(upload_events: Optional[List[str]] = None):
|
|
231
|
+
"""Return a trace_finish_event_processor that surfaces failures + logid.
|
|
232
|
+
|
|
233
|
+
The CozeLoop SDK calls this for each flush event; on failure we print the
|
|
234
|
+
server logid to stderr so it can be handed to platform support for tracing
|
|
235
|
+
the root cause (e.g. via `bytedcli log get-logid-log <logid>`).
|
|
236
|
+
"""
|
|
237
|
+
def _processor(info):
|
|
238
|
+
try:
|
|
239
|
+
if not getattr(info, "is_event_fail", False):
|
|
240
|
+
hook_log("upload success")
|
|
241
|
+
return
|
|
242
|
+
detail = getattr(info, "detail_msg", "") or ""
|
|
243
|
+
if upload_events is not None:
|
|
244
|
+
upload_events.append(detail or "trace export failed")
|
|
245
|
+
logid = _extract_logid(detail)
|
|
246
|
+
if logid:
|
|
247
|
+
hook_log(f"upload failed logid={logid} detail={detail[:500]}")
|
|
248
|
+
print(f"[CozeLoop] 上报失败 logid={logid} (可用 bytedcli log get-logid-log {logid} 排查)", file=sys.stderr)
|
|
249
|
+
else:
|
|
250
|
+
hook_log(f"upload failed detail={detail[:500]}")
|
|
251
|
+
print(f"[CozeLoop] 上报失败: {detail[:300]}", file=sys.stderr)
|
|
252
|
+
except Exception:
|
|
253
|
+
pass
|
|
254
|
+
return _processor
|
|
134
255
|
|
|
135
256
|
|
|
136
257
|
def _normalize_api_base_url(raw: str) -> str:
|
|
137
|
-
"""Normalize CozeLoop API/
|
|
258
|
+
"""Normalize a CozeLoop API/ingest URL to the SDK api_base_url."""
|
|
138
259
|
base = (raw or "").strip().rstrip("/")
|
|
139
260
|
if not base:
|
|
140
261
|
return ""
|
|
@@ -156,11 +277,7 @@ def _normalize_api_base_url(raw: str) -> str:
|
|
|
156
277
|
|
|
157
278
|
def get_api_base_url() -> str:
|
|
158
279
|
"""Return optional CozeLoop SDK api_base_url from onboard-injected env."""
|
|
159
|
-
return _normalize_api_base_url(
|
|
160
|
-
os.environ.get("COZELOOP_API_BASE_URL")
|
|
161
|
-
or os.environ.get("OTEL_ENDPOINT")
|
|
162
|
-
or ""
|
|
163
|
-
)
|
|
280
|
+
return _normalize_api_base_url(os.environ.get("COZELOOP_API_BASE_URL") or "")
|
|
164
281
|
|
|
165
282
|
|
|
166
283
|
def _log_file_path() -> str:
|