coze_lab 0.1.21 → 0.1.22
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 +115 -6
- package/package.json +1 -1
- package/scripts/claude-code/cozeloop_hook.py +35 -4
- package/scripts/codex/cozeloop_hook.py +10 -6
package/index.js
CHANGED
|
@@ -4472,8 +4472,6 @@ function shellEnvLine(key, value) {
|
|
|
4472
4472
|
// writeClaudeCodeHook 配置 Claude Code 的 hook。
|
|
4473
4473
|
// configBaseDir 缺省 process.cwd()(全局/项目级);传入 agent 的 workspace 则 per-agent:
|
|
4474
4474
|
// 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
4475
|
function writeClaudeCodeHook(patToken, workspaceId, pythonCmd, configBaseDir, cloud) {
|
|
4478
4476
|
const hooksDir = path.join(os.homedir(), '.claude', 'hooks');
|
|
4479
4477
|
const hookScript = path.join(hooksDir, 'cozeloop_hook.py');
|
|
@@ -4527,12 +4525,24 @@ function writeClaudeCodeHook(patToken, workspaceId, pythonCmd, configBaseDir, cl
|
|
|
4527
4525
|
const localSettings = mergeJson(localSettingsPath, (existing) => {
|
|
4528
4526
|
if (!existing.env) existing.env = {};
|
|
4529
4527
|
existing.env.COZELOOP_WORKSPACE_ID = workspaceId;
|
|
4530
|
-
// 云端:token 由 sandbox 注入到环境变量 COZE_API_TOKEN,hook 运行时直接读,
|
|
4531
|
-
// 不在配置文件落明文 token。本地:写入 OAuth 拿到的 token。
|
|
4532
4528
|
if (cloud) {
|
|
4533
|
-
|
|
4529
|
+
if (process.env.COZELOOP_API_TOKEN) {
|
|
4530
|
+
existing.env.COZELOOP_API_TOKEN = process.env.COZELOOP_API_TOKEN;
|
|
4531
|
+
delete existing.env.COZE_API_TOKEN;
|
|
4532
|
+
} else if (process.env.COZE_API_TOKEN) {
|
|
4533
|
+
existing.env.COZE_API_TOKEN = process.env.COZE_API_TOKEN;
|
|
4534
|
+
delete existing.env.COZELOOP_API_TOKEN;
|
|
4535
|
+
}
|
|
4534
4536
|
} else {
|
|
4535
4537
|
existing.env.COZELOOP_API_TOKEN = patToken;
|
|
4538
|
+
delete existing.env.COZE_API_TOKEN;
|
|
4539
|
+
}
|
|
4540
|
+
if (process.env.COZELOOP_API_BASE_URL) {
|
|
4541
|
+
existing.env.COZELOOP_API_BASE_URL = process.env.COZELOOP_API_BASE_URL;
|
|
4542
|
+
delete existing.env.OTEL_ENDPOINT;
|
|
4543
|
+
} else if (process.env.OTEL_ENDPOINT) {
|
|
4544
|
+
existing.env.OTEL_ENDPOINT = process.env.OTEL_ENDPOINT;
|
|
4545
|
+
delete existing.env.COZELOOP_API_BASE_URL;
|
|
4536
4546
|
}
|
|
4537
4547
|
// PPE 泳道:cozeloop SDK 读这两个环境变量,自动注入 x-tt-env / x-use-ppe header
|
|
4538
4548
|
existing.env.x_tt_env = PPE_TT_ENV;
|
|
@@ -4875,6 +4885,103 @@ function httpsPost(url, body, extraHeaders) {
|
|
|
4875
4885
|
});
|
|
4876
4886
|
}
|
|
4877
4887
|
|
|
4888
|
+
function runCommand(input) {
|
|
4889
|
+
return new Promise((resolve) => {
|
|
4890
|
+
const { spawn } = require('child_process');
|
|
4891
|
+
const child = spawn(input.cmd, input.args || [], {
|
|
4892
|
+
env: input.env || process.env,
|
|
4893
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
4894
|
+
});
|
|
4895
|
+
let stdout = '';
|
|
4896
|
+
let stderr = '';
|
|
4897
|
+
let timedOut = false;
|
|
4898
|
+
const timer = setTimeout(() => {
|
|
4899
|
+
timedOut = true;
|
|
4900
|
+
child.kill('SIGKILL');
|
|
4901
|
+
}, input.timeoutMs || 30000);
|
|
4902
|
+
child.stdout.on('data', chunk => { stdout += chunk.toString(); });
|
|
4903
|
+
child.stderr.on('data', chunk => { stderr += chunk.toString(); });
|
|
4904
|
+
child.on('close', code => {
|
|
4905
|
+
clearTimeout(timer);
|
|
4906
|
+
resolve({ code: timedOut ? -1 : code, stdout, stderr, timedOut });
|
|
4907
|
+
});
|
|
4908
|
+
child.stdin.end(input.stdin || '');
|
|
4909
|
+
});
|
|
4910
|
+
}
|
|
4911
|
+
|
|
4912
|
+
async function verifyTraceReportViaSdk(token, workspaceId, pairCode, pythonCmd) {
|
|
4913
|
+
const pair = pairCode || crypto.randomBytes(6).toString('hex');
|
|
4914
|
+
const script = `
|
|
4915
|
+
import json
|
|
4916
|
+
import os
|
|
4917
|
+
import sys
|
|
4918
|
+
|
|
4919
|
+
events = []
|
|
4920
|
+
|
|
4921
|
+
def finish_event(info):
|
|
4922
|
+
if getattr(info, "is_event_fail", False):
|
|
4923
|
+
events.append(getattr(info, "detail_msg", "") or "trace export failed")
|
|
4924
|
+
|
|
4925
|
+
try:
|
|
4926
|
+
import cozeloop
|
|
4927
|
+
kwargs = {
|
|
4928
|
+
"workspace_id": os.environ["COZELOOP_WORKSPACE_ID"],
|
|
4929
|
+
"api_token": os.environ["COZELOOP_API_TOKEN"],
|
|
4930
|
+
"upload_timeout": 30,
|
|
4931
|
+
"trace_finish_event_processor": finish_event,
|
|
4932
|
+
}
|
|
4933
|
+
api_base_url = os.environ.get("COZELOOP_API_BASE_URL", "").strip()
|
|
4934
|
+
if api_base_url:
|
|
4935
|
+
kwargs["api_base_url"] = api_base_url
|
|
4936
|
+
client = cozeloop.new_client(**kwargs)
|
|
4937
|
+
with client.start_span(name="cozelab-onboard-selfcheck", span_type="main", start_new_trace=True) as span:
|
|
4938
|
+
span.set_tags({"pair_code": os.environ.get("COZELOOP_PAIR_CODE", ""), "source": "cozelab-onboard"})
|
|
4939
|
+
span.set_input("cozelab-onboard selfcheck")
|
|
4940
|
+
span.set_output("ok")
|
|
4941
|
+
client.flush()
|
|
4942
|
+
client.close()
|
|
4943
|
+
if events:
|
|
4944
|
+
print(json.dumps({"success": False, "body": "\\n".join(events)}, ensure_ascii=False))
|
|
4945
|
+
sys.exit(1)
|
|
4946
|
+
print(json.dumps({"success": True, "body": ""}, ensure_ascii=False))
|
|
4947
|
+
except Exception as e:
|
|
4948
|
+
print(json.dumps({"success": False, "body": str(e)}, ensure_ascii=False))
|
|
4949
|
+
sys.exit(1)
|
|
4950
|
+
`;
|
|
4951
|
+
const env = {
|
|
4952
|
+
...process.env,
|
|
4953
|
+
COZELOOP_WORKSPACE_ID: workspaceId,
|
|
4954
|
+
COZELOOP_API_TOKEN: token,
|
|
4955
|
+
COZELOOP_PAIR_CODE: pair,
|
|
4956
|
+
x_tt_env: PPE_TT_ENV,
|
|
4957
|
+
x_use_ppe: PPE_USE_PPE,
|
|
4958
|
+
};
|
|
4959
|
+
const apiBase = getCozeloopApiBaseUrl(true);
|
|
4960
|
+
if (apiBase) env.COZELOOP_API_BASE_URL = apiBase;
|
|
4961
|
+
const result = await runCommand({
|
|
4962
|
+
cmd: pythonCmd || 'python3',
|
|
4963
|
+
args: ['-c', script],
|
|
4964
|
+
env,
|
|
4965
|
+
timeoutMs: 45000,
|
|
4966
|
+
});
|
|
4967
|
+
let parsed = null;
|
|
4968
|
+
const out = (result.stdout || '').trim().split(/\n/).filter(Boolean).pop();
|
|
4969
|
+
if (out) {
|
|
4970
|
+
try { parsed = JSON.parse(out); } catch { /* keep null */ }
|
|
4971
|
+
}
|
|
4972
|
+
const body = parsed?.body || result.stderr || result.stdout || (result.timedOut ? 'SDK selfcheck timed out' : '');
|
|
4973
|
+
const success = result.code === 0 && parsed?.success === true;
|
|
4974
|
+
if (success) {
|
|
4975
|
+
ok(`trace 上报成功 (pair_code=${pair})`);
|
|
4976
|
+
info(`查询方可用 pair_code=${pair} 在 CozeLoop 回查确认该 trace 已落库。`);
|
|
4977
|
+
} else {
|
|
4978
|
+
warn(`trace 上报失败: SDK selfcheck exit ${result.code}`);
|
|
4979
|
+
const snippet = String(body || '').slice(0, 300);
|
|
4980
|
+
if (snippet) console.log(snippet);
|
|
4981
|
+
}
|
|
4982
|
+
return { success, status: result.code || 0, body, traceId: '', pairCode: pair };
|
|
4983
|
+
}
|
|
4984
|
+
|
|
4878
4985
|
// 真实发一条最小 OTLP trace 到 CozeLoop,验证上报链路是否打通。
|
|
4879
4986
|
// 只看 HTTP 状态码(2xx=通),不回查 trace 是否落库——回查由外部查询方完成。
|
|
4880
4987
|
// pairCode 写进 span 的 pair_code attribute,供查询方按该字段过滤回查;缺省自动生成。
|
|
@@ -5389,7 +5496,9 @@ async function main() {
|
|
|
5389
5496
|
|
|
5390
5497
|
// Step 5: Verify trace reporting end-to-end
|
|
5391
5498
|
info('Step 5/5: 验证 trace 上报链路...');
|
|
5392
|
-
const verifyResult =
|
|
5499
|
+
const verifyResult = args.cloud
|
|
5500
|
+
? await verifyTraceReportViaSdk(token, WORKSPACE_ID, args.pairCode, pythonCmd || 'python3')
|
|
5501
|
+
: await verifyTraceReport(token, WORKSPACE_ID, args.pairCode, getOtelTracesUrl(false));
|
|
5393
5502
|
if (verifyResult.success) {
|
|
5394
5503
|
cloudResult.verify = 'ok';
|
|
5395
5504
|
} else if (CLOUD_MODE) {
|
package/package.json
CHANGED
|
@@ -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,32 @@ 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
|
+
api_base_url = get_api_base_url()
|
|
273
|
+
env_token = os.environ.get("COZELOOP_API_TOKEN")
|
|
274
|
+
env_coze_token = os.environ.get("COZE_API_TOKEN")
|
|
275
|
+
if api_base_url and (env_token or env_coze_token):
|
|
276
|
+
return env_token or env_coze_token
|
|
252
277
|
creds = _load_credentials()
|
|
253
278
|
if creds:
|
|
254
279
|
expires_at_sec = creds.get("expires_at", 0) / 1000
|
|
@@ -262,8 +287,13 @@ def get_fresh_token() -> Optional[str]:
|
|
|
262
287
|
if new_token:
|
|
263
288
|
return new_token
|
|
264
289
|
debug_log("Refresh failed, falling back to env var.")
|
|
265
|
-
|
|
266
|
-
|
|
290
|
+
if env_token:
|
|
291
|
+
return env_token
|
|
292
|
+
if api_base_url:
|
|
293
|
+
return env_coze_token
|
|
294
|
+
if env_coze_token:
|
|
295
|
+
debug_log("COZE_API_TOKEN present but ignored without COZELOOP_API_BASE_URL or OTEL_ENDPOINT")
|
|
296
|
+
return None
|
|
267
297
|
|
|
268
298
|
# -------------------------------------------------------------------------
|
|
269
299
|
|
|
@@ -873,6 +903,9 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, history
|
|
|
873
903
|
client_kwargs["workspace_id"] = workspace_id
|
|
874
904
|
if token:
|
|
875
905
|
client_kwargs["api_token"] = token
|
|
906
|
+
api_base_url = get_api_base_url()
|
|
907
|
+
if api_base_url:
|
|
908
|
+
client_kwargs["api_base_url"] = api_base_url
|
|
876
909
|
client = cozeloop.new_client(**client_kwargs)
|
|
877
910
|
|
|
878
911
|
try:
|
|
@@ -1448,5 +1481,3 @@ def main():
|
|
|
1448
1481
|
|
|
1449
1482
|
if __name__ == "__main__":
|
|
1450
1483
|
main()
|
|
1451
|
-
|
|
1452
|
-
|
|
@@ -230,6 +230,11 @@ def get_api_base_url() -> str:
|
|
|
230
230
|
|
|
231
231
|
|
|
232
232
|
def get_fresh_token():
|
|
233
|
+
api_base_url = get_api_base_url()
|
|
234
|
+
env_token = os.environ.get("COZELOOP_API_TOKEN")
|
|
235
|
+
env_coze_token = os.environ.get("COZE_API_TOKEN")
|
|
236
|
+
if api_base_url and (env_token or env_coze_token):
|
|
237
|
+
return env_token or env_coze_token
|
|
233
238
|
creds = _load_credentials()
|
|
234
239
|
if creds:
|
|
235
240
|
remaining = creds.get("expires_at", 0) / 1000 - time.time()
|
|
@@ -239,12 +244,11 @@ def get_fresh_token():
|
|
|
239
244
|
new_token = _refresh_token(creds["refresh_token"])
|
|
240
245
|
if new_token:
|
|
241
246
|
return new_token
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if os.environ.get("COZE_API_TOKEN"):
|
|
247
|
+
if env_token:
|
|
248
|
+
return env_token
|
|
249
|
+
if api_base_url:
|
|
250
|
+
return env_coze_token
|
|
251
|
+
if env_coze_token:
|
|
248
252
|
hook_log("COZE_API_TOKEN present but ignored without COZELOOP_API_BASE_URL or OTEL_ENDPOINT")
|
|
249
253
|
return None
|
|
250
254
|
# -------------------------------------------------------------------------
|