coze_lab 0.1.20 → 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 +119 -8
- 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
|
@@ -9,6 +9,7 @@ const PPE_TT_ENV = 'ppe_cozelab';
|
|
|
9
9
|
const PPE_USE_PPE = '1';
|
|
10
10
|
const COZE_API = 'https://api.coze.cn';
|
|
11
11
|
const CREDS_PATH = require('path').join(require('os').homedir(), '.cozeloop', 'credentials.json');
|
|
12
|
+
const PACKAGE_VERSION = require('./package.json').version;
|
|
12
13
|
// Refresh when less than 10 minutes remain
|
|
13
14
|
const REFRESH_THRESHOLD_MS = 10 * 60 * 1000;
|
|
14
15
|
|
|
@@ -16,7 +17,7 @@ const REFRESH_THRESHOLD_MS = 10 * 60 * 1000;
|
|
|
16
17
|
// 云端(--cloud)模式:在 stdout 输出一行机器可读结果 COZE_LAB_RESULT={...},
|
|
17
18
|
// 供管理后台解析判定(inject/verify/logid/message),不依赖中文文案。
|
|
18
19
|
let CLOUD_MODE = false;
|
|
19
|
-
const cloudResult = { inject: 'skip', verify: 'skip', logid: '', message: '' };
|
|
20
|
+
const cloudResult = { version: PACKAGE_VERSION, inject: 'skip', verify: 'skip', logid: '', message: '' };
|
|
20
21
|
|
|
21
22
|
// errorBox 在云端模式下抛此异常(而非 process.exit),由 main 外层统一收尾。
|
|
22
23
|
class CloudAbort extends Error {}
|
|
@@ -4471,8 +4472,6 @@ function shellEnvLine(key, value) {
|
|
|
4471
4472
|
// writeClaudeCodeHook 配置 Claude Code 的 hook。
|
|
4472
4473
|
// configBaseDir 缺省 process.cwd()(全局/项目级);传入 agent 的 workspace 则 per-agent:
|
|
4473
4474
|
// settings.json + 凭证写进 <configBaseDir>/.claude,仅该 agent(以此为 cwd 启动)生效。
|
|
4474
|
-
// cloud=true 时不把明文 token 写进 settings.local.json —— 云端 hook 运行时直接读
|
|
4475
|
-
// 环境变量 COZE_API_TOKEN(见 scripts/claude-code/cozeloop_hook.py 的 get_fresh_token)。
|
|
4476
4475
|
function writeClaudeCodeHook(patToken, workspaceId, pythonCmd, configBaseDir, cloud) {
|
|
4477
4476
|
const hooksDir = path.join(os.homedir(), '.claude', 'hooks');
|
|
4478
4477
|
const hookScript = path.join(hooksDir, 'cozeloop_hook.py');
|
|
@@ -4526,12 +4525,24 @@ function writeClaudeCodeHook(patToken, workspaceId, pythonCmd, configBaseDir, cl
|
|
|
4526
4525
|
const localSettings = mergeJson(localSettingsPath, (existing) => {
|
|
4527
4526
|
if (!existing.env) existing.env = {};
|
|
4528
4527
|
existing.env.COZELOOP_WORKSPACE_ID = workspaceId;
|
|
4529
|
-
// 云端:token 由 sandbox 注入到环境变量 COZE_API_TOKEN,hook 运行时直接读,
|
|
4530
|
-
// 不在配置文件落明文 token。本地:写入 OAuth 拿到的 token。
|
|
4531
4528
|
if (cloud) {
|
|
4532
|
-
|
|
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
|
+
}
|
|
4533
4536
|
} else {
|
|
4534
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;
|
|
4535
4546
|
}
|
|
4536
4547
|
// PPE 泳道:cozeloop SDK 读这两个环境变量,自动注入 x-tt-env / x-use-ppe header
|
|
4537
4548
|
existing.env.x_tt_env = PPE_TT_ENV;
|
|
@@ -4874,6 +4885,103 @@ function httpsPost(url, body, extraHeaders) {
|
|
|
4874
4885
|
});
|
|
4875
4886
|
}
|
|
4876
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
|
+
|
|
4877
4985
|
// 真实发一条最小 OTLP trace 到 CozeLoop,验证上报链路是否打通。
|
|
4878
4986
|
// 只看 HTTP 状态码(2xx=通),不回查 trace 是否落库——回查由外部查询方完成。
|
|
4879
4987
|
// pairCode 写进 span 的 pair_code attribute,供查询方按该字段过滤回查;缺省自动生成。
|
|
@@ -5201,7 +5309,7 @@ const NEXT_STEP = {
|
|
|
5201
5309
|
|
|
5202
5310
|
async function main() {
|
|
5203
5311
|
console.log('');
|
|
5204
|
-
info(
|
|
5312
|
+
info(`CozeLoop Onboard CLI starting... (coze_lab v${PACKAGE_VERSION})`);
|
|
5205
5313
|
console.log('');
|
|
5206
5314
|
|
|
5207
5315
|
const args = validateArgs(parseArgs());
|
|
@@ -5357,6 +5465,7 @@ async function main() {
|
|
|
5357
5465
|
|
|
5358
5466
|
// Success summary(用实际写入路径,per-agent / 自定义 CODEX_HOME 时也准确)
|
|
5359
5467
|
const summaryLines = [
|
|
5468
|
+
`Onboard: coze_lab v${PACKAGE_VERSION}`,
|
|
5360
5469
|
`Agent: ${agent} v${version}`,
|
|
5361
5470
|
];
|
|
5362
5471
|
if (args.agentId) summaryLines.push(`Agent ID: ${args.agentId}`);
|
|
@@ -5387,7 +5496,9 @@ async function main() {
|
|
|
5387
5496
|
|
|
5388
5497
|
// Step 5: Verify trace reporting end-to-end
|
|
5389
5498
|
info('Step 5/5: 验证 trace 上报链路...');
|
|
5390
|
-
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));
|
|
5391
5502
|
if (verifyResult.success) {
|
|
5392
5503
|
cloudResult.verify = 'ok';
|
|
5393
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
|
# -------------------------------------------------------------------------
|