coze_lab 0.1.18 → 0.1.20

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
@@ -4464,6 +4464,10 @@ function mergeJson(filepath, mergeFn) {
4464
4464
  return mergeFn(existing);
4465
4465
  }
4466
4466
 
4467
+ function shellEnvLine(key, value) {
4468
+ return `${key}='${String(value).replace(/'/g, `'\\''`)}'`;
4469
+ }
4470
+
4467
4471
  // writeClaudeCodeHook 配置 Claude Code 的 hook。
4468
4472
  // configBaseDir 缺省 process.cwd()(全局/项目级);传入 agent 的 workspace 则 per-agent:
4469
4473
  // settings.json + 凭证写进 <configBaseDir>/.claude,仅该 agent(以此为 cwd 启动)生效。
@@ -4565,24 +4569,29 @@ function writeCodexHook(token, workspaceId, pythonCmd, codexHome, cloud) {
4565
4569
  writeHookScript(refreshScript, readScript('shared/cozeloop_refresh.py'));
4566
4570
 
4567
4571
  // 2. Write env file with chmod 600
4568
- // 云端(cloud):不落明文 token,hook 运行时从环境变量 COZE_API_TOKEN 读取。
4569
4572
  const envLines = [
4570
- `COZELOOP_WORKSPACE_ID=${workspaceId}`,
4573
+ shellEnvLine('COZELOOP_WORKSPACE_ID', workspaceId),
4571
4574
  ];
4572
- if (!cloud) {
4573
- envLines.push(`COZELOOP_API_TOKEN=${token}`);
4575
+ if (cloud) {
4576
+ if (process.env.COZELOOP_API_TOKEN) {
4577
+ envLines.push(shellEnvLine('COZELOOP_API_TOKEN', process.env.COZELOOP_API_TOKEN));
4578
+ } else if (process.env.COZE_API_TOKEN) {
4579
+ envLines.push(shellEnvLine('COZE_API_TOKEN', process.env.COZE_API_TOKEN));
4580
+ }
4581
+ } else {
4582
+ envLines.push(shellEnvLine('COZELOOP_API_TOKEN', token));
4574
4583
  }
4575
- envLines.push(`CODEX_HOME=${home}`);
4576
- envLines.push(`COZELOOP_HOOK_LOG=${logFile}`);
4584
+ envLines.push(shellEnvLine('CODEX_HOME', home));
4585
+ envLines.push(shellEnvLine('COZELOOP_HOOK_LOG', logFile));
4577
4586
  envLines.push('TRACE_TO_COZELOOP=true');
4578
4587
  if (process.env.COZELOOP_API_BASE_URL) {
4579
- envLines.push(`COZELOOP_API_BASE_URL=${process.env.COZELOOP_API_BASE_URL}`);
4588
+ envLines.push(shellEnvLine('COZELOOP_API_BASE_URL', process.env.COZELOOP_API_BASE_URL));
4580
4589
  } else if (process.env.OTEL_ENDPOINT) {
4581
- envLines.push(`OTEL_ENDPOINT=${process.env.OTEL_ENDPOINT}`);
4590
+ envLines.push(shellEnvLine('OTEL_ENDPOINT', process.env.OTEL_ENDPOINT));
4582
4591
  }
4583
4592
  // PPE 泳道:cozeloop SDK 读这两个环境变量,自动注入 x-tt-env / x-use-ppe header
4584
- envLines.push(`x_tt_env=${PPE_TT_ENV}`);
4585
- envLines.push(`x_use_ppe=${PPE_USE_PPE}`);
4593
+ envLines.push(shellEnvLine('x_tt_env', PPE_TT_ENV));
4594
+ envLines.push(shellEnvLine('x_use_ppe', PPE_USE_PPE));
4586
4595
  const envContent = envLines.join('\n') + '\n';
4587
4596
  try {
4588
4597
  fs.writeFileSync(envFile, envContent, { mode: 0o600 });
@@ -4660,10 +4669,38 @@ function normalizeTraceAgentIds(ids) {
4660
4669
  .filter(Boolean);
4661
4670
  }
4662
4671
 
4663
- function getOpenClawEndpoint(cloud) {
4664
- return (cloud && process.env.COZELOOP_API_BASE_URL)
4665
- ? process.env.COZELOOP_API_BASE_URL.replace(/\/+$/, '') + '/v1/loop/opentelemetry'
4666
- : 'https://api.coze.cn/v1/loop/opentelemetry';
4672
+ function getCloudCozeloopApiBaseUrl() {
4673
+ const raw = process.env.COZELOOP_API_BASE_URL || process.env.OTEL_ENDPOINT || '';
4674
+ const base = raw.trim().replace(/\/+$/, '');
4675
+ if (!base) return '';
4676
+ if (base.endsWith('/v1/loop/opentelemetry/v1/traces')) {
4677
+ return base.slice(0, -'/v1/loop/opentelemetry/v1/traces'.length).replace(/\/+$/, '');
4678
+ }
4679
+ if (base.endsWith('/api/v1/loop/opentelemetry/v1/traces')) {
4680
+ return base.slice(0, -'/v1/loop/opentelemetry/v1/traces'.length).replace(/\/+$/, '');
4681
+ }
4682
+ if (base.endsWith('/v1/loop/opentelemetry')) {
4683
+ return base.slice(0, -'/v1/loop/opentelemetry'.length).replace(/\/+$/, '');
4684
+ }
4685
+ if (base.endsWith('/api/v1/loop/opentelemetry')) {
4686
+ return base.slice(0, -'/loop/opentelemetry'.length).replace(/\/+$/, '');
4687
+ }
4688
+ if (base.endsWith('/api/v1')) {
4689
+ return base.slice(0, -'/v1'.length).replace(/\/+$/, '');
4690
+ }
4691
+ return base;
4692
+ }
4693
+
4694
+ function getCozeloopApiBaseUrl(cloud) {
4695
+ return cloud ? (getCloudCozeloopApiBaseUrl() || COZE_API) : COZE_API;
4696
+ }
4697
+
4698
+ function getOtelEndpointBase(cloud) {
4699
+ return `${getCozeloopApiBaseUrl(cloud).replace(/\/+$/, '')}/v1/loop/opentelemetry`;
4700
+ }
4701
+
4702
+ function getOtelTracesUrl(cloud) {
4703
+ return `${getOtelEndpointBase(cloud).replace(/\/+$/, '')}/v1/traces`;
4667
4704
  }
4668
4705
 
4669
4706
  function applyOpenClawPluginConfig(existing, token, workspaceId, agentId, cloud) {
@@ -4685,7 +4722,7 @@ function applyOpenClawPluginConfig(existing, token, workspaceId, agentId, cloud)
4685
4722
  if (!existing.plugins.entries[PLUGIN].config) existing.plugins.entries[PLUGIN].config = {};
4686
4723
  const pcfg = existing.plugins.entries[PLUGIN].config;
4687
4724
  pcfg.authorization = `Bearer ${token}`;
4688
- pcfg.endpoint = getOpenClawEndpoint(cloud);
4725
+ pcfg.endpoint = getOtelEndpointBase(cloud);
4689
4726
  pcfg.workspaceId = workspaceId;
4690
4727
  pcfg.debug = true;
4691
4728
  // per-agent trace 放行:把当前 agentId 并入 traceAgentIds(去重、归一为小写,
@@ -4820,7 +4857,7 @@ function httpsPost(url, body, extraHeaders) {
4820
4857
  const data = JSON.stringify(body);
4821
4858
  const u = new URL(url);
4822
4859
  const req = https.request(
4823
- { hostname: u.hostname, path: u.pathname + u.search, method: 'POST',
4860
+ { hostname: u.hostname, port: u.port || undefined, path: u.pathname + u.search, method: 'POST',
4824
4861
  headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data),
4825
4862
  'x-tt-env': PPE_TT_ENV, 'x-use-ppe': PPE_USE_PPE,
4826
4863
  ...(extraHeaders || {}) } },
@@ -4841,7 +4878,7 @@ function httpsPost(url, body, extraHeaders) {
4841
4878
  // 只看 HTTP 状态码(2xx=通),不回查 trace 是否落库——回查由外部查询方完成。
4842
4879
  // pairCode 写进 span 的 pair_code attribute,供查询方按该字段过滤回查;缺省自动生成。
4843
4880
  // 不在函数内退出,退出行为交给调用方(主流程 Step 6 / 独立命令 --verify)。
4844
- async function verifyTraceReport(token, workspaceId, pairCode, baseUrl) {
4881
+ async function verifyTraceReport(token, workspaceId, pairCode, tracesUrl) {
4845
4882
  const traceId = crypto.randomBytes(16).toString('hex'); // 32 hex chars
4846
4883
  const spanId = crypto.randomBytes(8).toString('hex'); // 16 hex chars
4847
4884
  const nowNs = String(Date.now() * 1_000_000); // OTLP 要求纳秒 unix 时间(字符串)
@@ -4874,12 +4911,10 @@ async function verifyTraceReport(token, workspaceId, pairCode, baseUrl) {
4874
4911
  };
4875
4912
 
4876
4913
  let res;
4877
- // 云端:上报走 sandbox 注入的 COZELOOP_API_BASE_URL 代理(vefaas token 经它鉴权转换);
4878
- // 缺省回退到 api.coze.cn 直连。base 已去尾部斜杠。
4879
- const apiBase = (baseUrl || COZE_API).replace(/\/+$/, '');
4914
+ const url = tracesUrl || getOtelTracesUrl(false);
4880
4915
  try {
4881
4916
  res = await httpsPost(
4882
- `${apiBase}/v1/loop/opentelemetry/v1/traces`,
4917
+ url,
4883
4918
  otlpBody,
4884
4919
  { Authorization: `Bearer ${token}`, 'cozeloop-workspace-id': workspaceId },
4885
4920
  );
@@ -5230,7 +5265,7 @@ async function main() {
5230
5265
  info('验证 trace 上报链路...');
5231
5266
  const token = await getValidToken(); // 无凭证会自动走登录/刷新
5232
5267
  console.log('');
5233
- const result = await verifyTraceReport(token, WORKSPACE_ID, args.pairCode, process.env.COZELOOP_API_BASE_URL || undefined);
5268
+ const result = await verifyTraceReport(token, WORKSPACE_ID, args.pairCode, getOtelTracesUrl(false));
5234
5269
  process.exit(result.success ? 0 : 1);
5235
5270
  }
5236
5271
 
@@ -5352,7 +5387,7 @@ async function main() {
5352
5387
 
5353
5388
  // Step 5: Verify trace reporting end-to-end
5354
5389
  info('Step 5/5: 验证 trace 上报链路...');
5355
- const verifyResult = await verifyTraceReport(token, WORKSPACE_ID, args.pairCode, args.cloud ? process.env.COZELOOP_API_BASE_URL : undefined);
5390
+ const verifyResult = await verifyTraceReport(token, WORKSPACE_ID, args.pairCode, getOtelTracesUrl(args.cloud));
5356
5391
  if (verifyResult.success) {
5357
5392
  cloudResult.verify = 'ok';
5358
5393
  } else if (CLOUD_MODE) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coze_lab",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "Configure local AI agents (Claude Code, Codex, OpenClaw) to report traces to CozeLoop",
5
5
  "keywords": [
6
6
  "cozeloop",
@@ -210,8 +210,16 @@ def _refresh_token(refresh_tok: str):
210
210
 
211
211
  def _normalize_api_base_url(url: str) -> str:
212
212
  base = (url or "").strip().rstrip("/")
213
+ if base.endswith(_OTEL_SUFFIX + "/v1/traces"):
214
+ return base[:-len(_OTEL_SUFFIX + "/v1/traces")].rstrip("/")
215
+ if base.endswith("/api/v1/loop/opentelemetry/v1/traces"):
216
+ return base[:-len("/v1/loop/opentelemetry/v1/traces")].rstrip("/")
213
217
  if base.endswith(_OTEL_SUFFIX):
214
218
  return base[:-len(_OTEL_SUFFIX)].rstrip("/")
219
+ if base.endswith("/api/v1/loop/opentelemetry"):
220
+ return base[:-len("/v1/loop/opentelemetry")].rstrip("/")
221
+ if base.endswith("/api/v1"):
222
+ return base[:-len("/v1")].rstrip("/")
215
223
  return base
216
224
 
217
225
 
@@ -850,7 +858,12 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, model_n
850
858
  hook_log(f"token resolved prefix={token[:12]}...")
851
859
  print(f"[CozeLoop] Token 获取成功 ({token[:12]}...)", file=sys.stderr)
852
860
  else:
853
- hook_log("token missing")
861
+ hook_log(
862
+ "token missing "
863
+ f"has_cozeloop_token={bool(os.environ.get('COZELOOP_API_TOKEN'))} "
864
+ f"has_coze_token={bool(os.environ.get('COZE_API_TOKEN'))} "
865
+ f"api_base_url={bool(get_api_base_url())}"
866
+ )
854
867
  print("[CozeLoop] 警告: 未找到有效 Token,上报可能失败", file=sys.stderr)
855
868
  creds = _load_credentials()
856
869
  workspace_id = (creds or {}).get("workspace_id") or os.environ.get("COZELOOP_WORKSPACE_ID", "") or _DEFAULT_WORKSPACE_ID