coze_lab 0.1.21 → 0.1.23

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/README.md CHANGED
@@ -24,7 +24,7 @@ npx coze_lab --logout # Clear cached credentials
24
24
  |-----------|----------|-----------------|
25
25
  | `--agent` | ✓ (for setup) | `claude-code`, `codex`, `openclaw` |
26
26
  | `--agent-id` | — | Resolve `~/.coze/agents/<agentId>/config.json` and write per-agent config |
27
- | `--cloud` | — | Cloud mode: read token from env and emit `COZE_LAB_RESULT=...` |
27
+ | `--cloud` | — | Cloud mode: read trace token from env and emit `COZE_LAB_RESULT=...` |
28
28
  | `--codex-home` | — | Override Codex config home for non-cloud/custom runs |
29
29
  | `--login` | — | Run the Device Code login flow only |
30
30
  | `--status` | — | Print local token status (valid / expiring / expired) |
@@ -70,6 +70,12 @@ created after a new Codex turn, Codex did not load or execute the hook.
70
70
 
71
71
  OAuth tokens are stored in `~/.cozeloop/credentials.json` (mode 600).
72
72
 
73
+ In cloud mode, trace verification and hook uploads require
74
+ `COZELOOP_API_TOKEN`. `COZE_API_TOKEN` is kept only as a legacy compatibility
75
+ signal for hook injection; it is not treated as a CozeLoop SDK trace ingest
76
+ token. If cloud only provides `COZE_API_TOKEN`, onboard still writes the hook
77
+ configuration but reports `verify=fail` with `token_source=COZE_API_TOKEN`.
78
+
73
79
  **At hook execution time** (Claude Code / Codex), the Python hook script automatically:
74
80
  1. Reads `~/.cozeloop/credentials.json`
75
81
  2. If the token expires in < 10 minutes, calls the Coze refresh API
package/index.js CHANGED
@@ -17,7 +17,7 @@ const REFRESH_THRESHOLD_MS = 10 * 60 * 1000;
17
17
  // 云端(--cloud)模式:在 stdout 输出一行机器可读结果 COZE_LAB_RESULT={...},
18
18
  // 供管理后台解析判定(inject/verify/logid/message),不依赖中文文案。
19
19
  let CLOUD_MODE = false;
20
- const cloudResult = { version: PACKAGE_VERSION, inject: 'skip', verify: 'skip', logid: '', message: '' };
20
+ const cloudResult = { version: PACKAGE_VERSION, inject: 'skip', verify: 'skip', logid: '', message: '', token_source: '' };
21
21
 
22
22
  // errorBox 在云端模式下抛此异常(而非 process.exit),由 main 外层统一收尾。
23
23
  class CloudAbort extends Error {}
@@ -40,6 +40,22 @@ function emitCloudResult() {
40
40
  console.log('COZE_LAB_RESULT=' + JSON.stringify(safe));
41
41
  }
42
42
 
43
+ function readEnv(name) {
44
+ return String(process.env[name] || '').trim();
45
+ }
46
+
47
+ function getCloudTokenInfo() {
48
+ const loopToken = readEnv('COZELOOP_API_TOKEN');
49
+ if (loopToken) {
50
+ return { token: loopToken, source: 'COZELOOP_API_TOKEN', traceUsable: true };
51
+ }
52
+ const cozeToken = readEnv('COZE_API_TOKEN');
53
+ if (cozeToken) {
54
+ return { token: cozeToken, source: 'COZE_API_TOKEN', traceUsable: false };
55
+ }
56
+ return { token: '', source: '', traceUsable: false };
57
+ }
58
+
43
59
  // ─── 1. Color helpers ────────────────────────────────────────────────────────
44
60
  const C = {
45
61
  reset: '\x1b[0m',
@@ -4472,8 +4488,6 @@ function shellEnvLine(key, value) {
4472
4488
  // writeClaudeCodeHook 配置 Claude Code 的 hook。
4473
4489
  // configBaseDir 缺省 process.cwd()(全局/项目级);传入 agent 的 workspace 则 per-agent:
4474
4490
  // settings.json + 凭证写进 <configBaseDir>/.claude,仅该 agent(以此为 cwd 启动)生效。
4475
- // cloud=true 时不把明文 token 写进 settings.local.json —— 云端 hook 运行时直接读
4476
- // 环境变量 COZE_API_TOKEN(见 scripts/claude-code/cozeloop_hook.py 的 get_fresh_token)。
4477
4491
  function writeClaudeCodeHook(patToken, workspaceId, pythonCmd, configBaseDir, cloud) {
4478
4492
  const hooksDir = path.join(os.homedir(), '.claude', 'hooks');
4479
4493
  const hookScript = path.join(hooksDir, 'cozeloop_hook.py');
@@ -4527,12 +4541,28 @@ function writeClaudeCodeHook(patToken, workspaceId, pythonCmd, configBaseDir, cl
4527
4541
  const localSettings = mergeJson(localSettingsPath, (existing) => {
4528
4542
  if (!existing.env) existing.env = {};
4529
4543
  existing.env.COZELOOP_WORKSPACE_ID = workspaceId;
4530
- // 云端:token 由 sandbox 注入到环境变量 COZE_API_TOKEN,hook 运行时直接读,
4531
- // 不在配置文件落明文 token。本地:写入 OAuth 拿到的 token。
4532
4544
  if (cloud) {
4533
- delete existing.env.COZELOOP_API_TOKEN;
4545
+ const loopToken = readEnv('COZELOOP_API_TOKEN');
4546
+ const cozeToken = readEnv('COZE_API_TOKEN');
4547
+ if (loopToken) {
4548
+ existing.env.COZELOOP_API_TOKEN = loopToken;
4549
+ delete existing.env.COZE_API_TOKEN;
4550
+ } else if (cozeToken) {
4551
+ existing.env.COZE_API_TOKEN = cozeToken;
4552
+ delete existing.env.COZELOOP_API_TOKEN;
4553
+ }
4534
4554
  } else {
4535
4555
  existing.env.COZELOOP_API_TOKEN = patToken;
4556
+ delete existing.env.COZE_API_TOKEN;
4557
+ }
4558
+ const loopBaseUrl = readEnv('COZELOOP_API_BASE_URL');
4559
+ const otelEndpoint = readEnv('OTEL_ENDPOINT');
4560
+ if (loopBaseUrl) {
4561
+ existing.env.COZELOOP_API_BASE_URL = loopBaseUrl;
4562
+ delete existing.env.OTEL_ENDPOINT;
4563
+ } else if (otelEndpoint) {
4564
+ existing.env.OTEL_ENDPOINT = otelEndpoint;
4565
+ delete existing.env.COZELOOP_API_BASE_URL;
4536
4566
  }
4537
4567
  // PPE 泳道:cozeloop SDK 读这两个环境变量,自动注入 x-tt-env / x-use-ppe header
4538
4568
  existing.env.x_tt_env = PPE_TT_ENV;
@@ -4574,10 +4604,12 @@ function writeCodexHook(token, workspaceId, pythonCmd, codexHome, cloud) {
4574
4604
  shellEnvLine('COZELOOP_WORKSPACE_ID', workspaceId),
4575
4605
  ];
4576
4606
  if (cloud) {
4577
- if (process.env.COZELOOP_API_TOKEN) {
4578
- envLines.push(shellEnvLine('COZELOOP_API_TOKEN', process.env.COZELOOP_API_TOKEN));
4579
- } else if (process.env.COZE_API_TOKEN) {
4580
- envLines.push(shellEnvLine('COZE_API_TOKEN', process.env.COZE_API_TOKEN));
4607
+ const loopToken = readEnv('COZELOOP_API_TOKEN');
4608
+ const cozeToken = readEnv('COZE_API_TOKEN');
4609
+ if (loopToken) {
4610
+ envLines.push(shellEnvLine('COZELOOP_API_TOKEN', loopToken));
4611
+ } else if (cozeToken) {
4612
+ envLines.push(shellEnvLine('COZE_API_TOKEN', cozeToken));
4581
4613
  }
4582
4614
  } else {
4583
4615
  envLines.push(shellEnvLine('COZELOOP_API_TOKEN', token));
@@ -4585,10 +4617,12 @@ function writeCodexHook(token, workspaceId, pythonCmd, codexHome, cloud) {
4585
4617
  envLines.push(shellEnvLine('CODEX_HOME', home));
4586
4618
  envLines.push(shellEnvLine('COZELOOP_HOOK_LOG', logFile));
4587
4619
  envLines.push('TRACE_TO_COZELOOP=true');
4588
- if (process.env.COZELOOP_API_BASE_URL) {
4589
- envLines.push(shellEnvLine('COZELOOP_API_BASE_URL', process.env.COZELOOP_API_BASE_URL));
4590
- } else if (process.env.OTEL_ENDPOINT) {
4591
- envLines.push(shellEnvLine('OTEL_ENDPOINT', process.env.OTEL_ENDPOINT));
4620
+ const loopBaseUrl = readEnv('COZELOOP_API_BASE_URL');
4621
+ const otelEndpoint = readEnv('OTEL_ENDPOINT');
4622
+ if (loopBaseUrl) {
4623
+ envLines.push(shellEnvLine('COZELOOP_API_BASE_URL', loopBaseUrl));
4624
+ } else if (otelEndpoint) {
4625
+ envLines.push(shellEnvLine('OTEL_ENDPOINT', otelEndpoint));
4592
4626
  }
4593
4627
  // PPE 泳道:cozeloop SDK 读这两个环境变量,自动注入 x-tt-env / x-use-ppe header
4594
4628
  envLines.push(shellEnvLine('x_tt_env', PPE_TT_ENV));
@@ -4875,6 +4909,105 @@ function httpsPost(url, body, extraHeaders) {
4875
4909
  });
4876
4910
  }
4877
4911
 
4912
+ function runCommand(input) {
4913
+ return new Promise((resolve) => {
4914
+ const { spawn } = require('child_process');
4915
+ const child = spawn(input.cmd, input.args || [], {
4916
+ env: input.env || process.env,
4917
+ stdio: ['pipe', 'pipe', 'pipe'],
4918
+ });
4919
+ let stdout = '';
4920
+ let stderr = '';
4921
+ let timedOut = false;
4922
+ const timer = setTimeout(() => {
4923
+ timedOut = true;
4924
+ child.kill('SIGKILL');
4925
+ }, input.timeoutMs || 30000);
4926
+ child.stdout.on('data', chunk => { stdout += chunk.toString(); });
4927
+ child.stderr.on('data', chunk => { stderr += chunk.toString(); });
4928
+ child.on('close', code => {
4929
+ clearTimeout(timer);
4930
+ resolve({ code: timedOut ? -1 : code, stdout, stderr, timedOut });
4931
+ });
4932
+ child.stdin.end(input.stdin || '');
4933
+ });
4934
+ }
4935
+
4936
+ async function verifyTraceReportViaSdk(token, workspaceId, pairCode, pythonCmd, tokenSource) {
4937
+ const pair = pairCode || crypto.randomBytes(6).toString('hex');
4938
+ const apiBase = getCozeloopApiBaseUrl(true);
4939
+ const script = `
4940
+ import json
4941
+ import os
4942
+ import sys
4943
+
4944
+ events = []
4945
+
4946
+ def finish_event(info):
4947
+ if getattr(info, "is_event_fail", False):
4948
+ events.append(getattr(info, "detail_msg", "") or "trace export failed")
4949
+
4950
+ try:
4951
+ import cozeloop
4952
+ kwargs = {
4953
+ "workspace_id": os.environ["COZELOOP_WORKSPACE_ID"],
4954
+ "api_token": os.environ["COZELOOP_API_TOKEN"],
4955
+ "upload_timeout": 30,
4956
+ "trace_finish_event_processor": finish_event,
4957
+ }
4958
+ api_base_url = os.environ.get("COZELOOP_API_BASE_URL", "").strip()
4959
+ if api_base_url:
4960
+ kwargs["api_base_url"] = api_base_url
4961
+ client = cozeloop.new_client(**kwargs)
4962
+ with client.start_span(name="cozelab-onboard-selfcheck", span_type="main", start_new_trace=True) as span:
4963
+ span.set_tags({"pair_code": os.environ.get("COZELOOP_PAIR_CODE", ""), "source": "cozelab-onboard"})
4964
+ span.set_input("cozelab-onboard selfcheck")
4965
+ span.set_output("ok")
4966
+ client.flush()
4967
+ client.close()
4968
+ if events:
4969
+ print(json.dumps({"success": False, "body": "\\n".join(events), "api_base_url": os.environ.get("COZELOOP_API_BASE_URL", ""), "token_source": os.environ.get("COZELOOP_TOKEN_SOURCE", "")}, ensure_ascii=False))
4970
+ sys.exit(1)
4971
+ print(json.dumps({"success": True, "body": "", "api_base_url": os.environ.get("COZELOOP_API_BASE_URL", ""), "token_source": os.environ.get("COZELOOP_TOKEN_SOURCE", "")}, ensure_ascii=False))
4972
+ except Exception as e:
4973
+ print(json.dumps({"success": False, "body": str(e), "api_base_url": os.environ.get("COZELOOP_API_BASE_URL", ""), "token_source": os.environ.get("COZELOOP_TOKEN_SOURCE", "")}, ensure_ascii=False))
4974
+ sys.exit(1)
4975
+ `;
4976
+ const env = {
4977
+ ...process.env,
4978
+ COZELOOP_WORKSPACE_ID: workspaceId,
4979
+ COZELOOP_API_TOKEN: token,
4980
+ COZELOOP_PAIR_CODE: pair,
4981
+ COZELOOP_TOKEN_SOURCE: tokenSource || '',
4982
+ x_tt_env: PPE_TT_ENV,
4983
+ x_use_ppe: PPE_USE_PPE,
4984
+ };
4985
+ if (apiBase) env.COZELOOP_API_BASE_URL = apiBase;
4986
+ const result = await runCommand({
4987
+ cmd: pythonCmd || 'python3',
4988
+ args: ['-c', script],
4989
+ env,
4990
+ timeoutMs: 45000,
4991
+ });
4992
+ let parsed = null;
4993
+ const out = (result.stdout || '').trim().split(/\n/).filter(Boolean).pop();
4994
+ if (out) {
4995
+ try { parsed = JSON.parse(out); } catch { /* keep null */ }
4996
+ }
4997
+ const body = parsed?.body || result.stderr || result.stdout || (result.timedOut ? 'SDK selfcheck timed out' : '');
4998
+ const success = result.code === 0 && parsed?.success === true;
4999
+ if (success) {
5000
+ ok(`trace 上报成功 (pair_code=${pair})`);
5001
+ info(`查询方可用 pair_code=${pair} 在 CozeLoop 回查确认该 trace 已落库。`);
5002
+ } else {
5003
+ warn(`trace 上报失败: SDK selfcheck exit ${result.code}`);
5004
+ info(`trace selfcheck api_base_url=${apiBase || COZE_API}, token_source=${tokenSource || 'unknown'}`);
5005
+ const snippet = String(body || '').slice(0, 300);
5006
+ if (snippet) console.log(snippet);
5007
+ }
5008
+ return { success, status: result.code || 0, body, traceId: '', pairCode: pair, apiBaseUrl: apiBase, tokenSource };
5009
+ }
5010
+
4878
5011
  // 真实发一条最小 OTLP trace 到 CozeLoop,验证上报链路是否打通。
4879
5012
  // 只看 HTTP 状态码(2xx=通),不回查 trace 是否落库——回查由外部查询方完成。
4880
5013
  // pairCode 写进 span 的 pair_code attribute,供查询方按该字段过滤回查;缺省自动生成。
@@ -5287,25 +5420,37 @@ async function main() {
5287
5420
 
5288
5421
  // Step 1: Authorize.
5289
5422
  // 云端(--cloud):token 取自 sandbox 注入的环境变量,跳过 OAuth / credentials.json。
5290
- // 云端真实变量名是 COZELOOP_API_TOKEN(兼容历史的 COZE_API_TOKEN)。
5423
+ // trace 上报需要 COZELOOP_API_TOKEN;COZE_API_TOKEN 只保留为历史兼容来源,
5424
+ // onboard 会写入 hook,但自检会明确标记它不是 CozeLoop ingest token。
5291
5425
  // 本地:load cached → refresh → device code。
5292
5426
  // 注意:workspace_id 始终用写死的 WORKSPACE_ID(团队固定上报 workspace),不读环境。
5293
5427
  let token;
5428
+ let tokenSource = '';
5429
+ let cloudTraceUsableToken = true;
5294
5430
  if (args.cloud) {
5295
- info('Step 1/5: 云端模式,从环境变量读取 COZELOOP_API_TOKEN...');
5296
- token = process.env.COZELOOP_API_TOKEN || process.env.COZE_API_TOKEN;
5431
+ info('Step 1/5: 云端模式,从环境变量读取 trace token...');
5432
+ const tokenInfo = getCloudTokenInfo();
5433
+ token = tokenInfo.token;
5434
+ tokenSource = tokenInfo.source;
5435
+ cloudTraceUsableToken = tokenInfo.traceUsable;
5297
5436
  if (!token) {
5298
5437
  errorBox([
5299
5438
  'ERROR: --cloud 模式要求环境变量 COZELOOP_API_TOKEN',
5300
5439
  '',
5301
- '云端 sandbox 应在进程环境中注入 COZELOOP_API_TOKEN(或 COZE_API_TOKEN)。',
5440
+ '云端 sandbox 应在进程环境中注入 COZELOOP_API_TOKEN',
5302
5441
  '未检测到该变量,无法配置 trace 上报。',
5303
5442
  ]);
5304
5443
  }
5305
- ok('已从环境变量读取 COZELOOP_API_TOKEN');
5444
+ cloudResult.token_source = tokenSource;
5445
+ if (cloudTraceUsableToken) {
5446
+ ok(`已从环境变量读取 ${tokenSource}`);
5447
+ } else {
5448
+ warn('仅检测到 COZE_API_TOKEN;它不是 CozeLoop SDK trace ingest token,自检预计会失败。');
5449
+ }
5306
5450
  } else {
5307
5451
  info('Step 1/5: 检查授权状态...');
5308
5452
  token = await getValidToken();
5453
+ tokenSource = 'credentials';
5309
5454
  }
5310
5455
  console.log('');
5311
5456
 
@@ -5389,7 +5534,24 @@ async function main() {
5389
5534
 
5390
5535
  // Step 5: Verify trace reporting end-to-end
5391
5536
  info('Step 5/5: 验证 trace 上报链路...');
5392
- const verifyResult = await verifyTraceReport(token, WORKSPACE_ID, args.pairCode, getOtelTracesUrl(args.cloud));
5537
+ let verifyResult;
5538
+ if (args.cloud && !cloudTraceUsableToken) {
5539
+ verifyResult = {
5540
+ success: false,
5541
+ status: 0,
5542
+ body: '云端仅提供 COZE_API_TOKEN。Coze API token 不能作为 CozeLoop Python SDK 的 COZELOOP_API_TOKEN 上报 trace;请注入具备 CozeLoop trace ingest 权限的 COZELOOP_API_TOKEN。',
5543
+ traceId: '',
5544
+ pairCode: args.pairCode || '',
5545
+ tokenSource,
5546
+ apiBaseUrl: getCozeloopApiBaseUrl(true),
5547
+ };
5548
+ warn('trace 上报跳过:缺少 COZELOOP_API_TOKEN。');
5549
+ console.log(verifyResult.body);
5550
+ } else {
5551
+ verifyResult = args.cloud
5552
+ ? await verifyTraceReportViaSdk(token, WORKSPACE_ID, args.pairCode, pythonCmd || 'python3', tokenSource)
5553
+ : await verifyTraceReport(token, WORKSPACE_ID, args.pairCode, getOtelTracesUrl(false));
5554
+ }
5393
5555
  if (verifyResult.success) {
5394
5556
  cloudResult.verify = 'ok';
5395
5557
  } else if (CLOUD_MODE) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coze_lab",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "Configure local AI agents (Claude Code, Codex, OpenClaw) to report traces to CozeLoop",
5
5
  "keywords": [
6
6
  "cozeloop",
@@ -82,6 +82,7 @@ else:
82
82
  DEBUG = os.environ.get("CC_COZELOOP_DEBUG", "").lower() == "true"
83
83
  _COZELOOP_CLIENT_ID = "46371084383473718052118955183420.app.coze"
84
84
  _COZE_API = "https://api.coze.cn"
85
+ _OTEL_SUFFIX = "/v1/loop/opentelemetry"
85
86
  _REFRESH_THRESHOLD = 10 * 60 # refresh when < 10 minutes remain
86
87
  _DEFAULT_WORKSPACE_ID = "7644910356078837760" # hardcoded spaceID fallback
87
88
 
@@ -247,8 +248,31 @@ def _refresh_token(refresh_token: str) -> Optional[str]:
247
248
  debug_log(f"Token refresh failed: {e}")
248
249
  return None
249
250
 
251
+ def _normalize_api_base_url(url: str) -> str:
252
+ base = (url or "").strip().rstrip("/")
253
+ if base.endswith(_OTEL_SUFFIX + "/v1/traces"):
254
+ return base[:-len(_OTEL_SUFFIX + "/v1/traces")].rstrip("/")
255
+ if base.endswith("/api/v1/loop/opentelemetry/v1/traces"):
256
+ return base[:-len("/v1/loop/opentelemetry/v1/traces")].rstrip("/")
257
+ if base.endswith(_OTEL_SUFFIX):
258
+ return base[:-len(_OTEL_SUFFIX)].rstrip("/")
259
+ if base.endswith("/api/v1/loop/opentelemetry"):
260
+ return base[:-len("/v1/loop/opentelemetry")].rstrip("/")
261
+ if base.endswith("/api/v1"):
262
+ return base[:-len("/v1")].rstrip("/")
263
+ return base
264
+
265
+ def get_api_base_url() -> str:
266
+ return _normalize_api_base_url(
267
+ os.environ.get("COZELOOP_API_BASE_URL", "") or os.environ.get("OTEL_ENDPOINT", "")
268
+ )
269
+
250
270
  def get_fresh_token() -> Optional[str]:
251
271
  """Return a valid access token, refreshing if needed. Falls back to env var."""
272
+ env_token = os.environ.get("COZELOOP_API_TOKEN")
273
+ env_coze_token = os.environ.get("COZE_API_TOKEN")
274
+ if env_token:
275
+ return env_token
252
276
  creds = _load_credentials()
253
277
  if creds:
254
278
  expires_at_sec = creds.get("expires_at", 0) / 1000
@@ -262,8 +286,9 @@ def get_fresh_token() -> Optional[str]:
262
286
  if new_token:
263
287
  return new_token
264
288
  debug_log("Refresh failed, falling back to env var.")
265
- # Cloud sandbox: token lives in COZE_API_TOKEN (no credentials.json / refresh).
266
- return os.environ.get("COZELOOP_API_TOKEN") or os.environ.get("COZE_API_TOKEN")
289
+ if env_coze_token:
290
+ debug_log("COZE_API_TOKEN present but ignored; CozeLoop trace upload requires COZELOOP_API_TOKEN")
291
+ return None
267
292
 
268
293
  # -------------------------------------------------------------------------
269
294
 
@@ -873,6 +898,9 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, history
873
898
  client_kwargs["workspace_id"] = workspace_id
874
899
  if token:
875
900
  client_kwargs["api_token"] = token
901
+ api_base_url = get_api_base_url()
902
+ if api_base_url:
903
+ client_kwargs["api_base_url"] = api_base_url
876
904
  client = cozeloop.new_client(**client_kwargs)
877
905
 
878
906
  try:
@@ -1448,5 +1476,3 @@ def main():
1448
1476
 
1449
1477
  if __name__ == "__main__":
1450
1478
  main()
1451
-
1452
-
@@ -230,6 +230,10 @@ def get_api_base_url() -> str:
230
230
 
231
231
 
232
232
  def get_fresh_token():
233
+ env_token = os.environ.get("COZELOOP_API_TOKEN")
234
+ env_coze_token = os.environ.get("COZE_API_TOKEN")
235
+ if env_token:
236
+ return env_token
233
237
  creds = _load_credentials()
234
238
  if creds:
235
239
  remaining = creds.get("expires_at", 0) / 1000 - time.time()
@@ -239,13 +243,8 @@ def get_fresh_token():
239
243
  new_token = _refresh_token(creds["refresh_token"])
240
244
  if new_token:
241
245
  return new_token
242
- token = os.environ.get("COZELOOP_API_TOKEN")
243
- if token:
244
- return token
245
- if get_api_base_url():
246
- return os.environ.get("COZE_API_TOKEN")
247
- if os.environ.get("COZE_API_TOKEN"):
248
- hook_log("COZE_API_TOKEN present but ignored without COZELOOP_API_BASE_URL or OTEL_ENDPOINT")
246
+ if env_coze_token:
247
+ hook_log("COZE_API_TOKEN present but ignored; CozeLoop trace upload requires COZELOOP_API_TOKEN")
249
248
  return None
250
249
  # -------------------------------------------------------------------------
251
250