coze_lab 0.1.0
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 +75 -0
- package/index.js +4996 -0
- package/package.json +35 -0
- package/scripts/claude-code/cozeloop_hook.py +1303 -0
- package/scripts/codex/cozeloop_hook.py +1051 -0
- package/scripts/openclaw/dist/cozeloop-exporter.js +442 -0
- package/scripts/openclaw/dist/index.js +1315 -0
- package/scripts/openclaw/dist/span-manager.js +77 -0
- package/scripts/openclaw/dist/types.js +1 -0
- package/scripts/openclaw/openclaw.plugin.json +55 -0
- package/scripts/openclaw/package.json +45 -0
- package/scripts/shared/cozeloop_refresh.py +53 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
function generateId(length = 16) {
|
|
2
|
+
const chars = "0123456789abcdef";
|
|
3
|
+
let result = "";
|
|
4
|
+
for (let i = 0; i < length; i++) {
|
|
5
|
+
result += chars[Math.floor(Math.random() * chars.length)];
|
|
6
|
+
}
|
|
7
|
+
return result;
|
|
8
|
+
}
|
|
9
|
+
export class SpanManager {
|
|
10
|
+
activeSpans = new Map();
|
|
11
|
+
sessionTraceMap = new Map();
|
|
12
|
+
turnSpanMap = new Map();
|
|
13
|
+
generateTraceId() {
|
|
14
|
+
return generateId(32);
|
|
15
|
+
}
|
|
16
|
+
generateSpanId() {
|
|
17
|
+
return generateId(16);
|
|
18
|
+
}
|
|
19
|
+
getOrCreateTraceId(sessionId) {
|
|
20
|
+
let traceId = this.sessionTraceMap.get(sessionId);
|
|
21
|
+
if (!traceId) {
|
|
22
|
+
traceId = this.generateTraceId();
|
|
23
|
+
this.sessionTraceMap.set(sessionId, traceId);
|
|
24
|
+
}
|
|
25
|
+
return traceId;
|
|
26
|
+
}
|
|
27
|
+
startSpan(sessionId, name, type, attributes = {}, input, parentSpanId) {
|
|
28
|
+
const traceId = this.getOrCreateTraceId(sessionId);
|
|
29
|
+
const spanId = this.generateSpanId();
|
|
30
|
+
const span = {
|
|
31
|
+
name,
|
|
32
|
+
type,
|
|
33
|
+
startTime: Date.now(),
|
|
34
|
+
attributes: {
|
|
35
|
+
...attributes,
|
|
36
|
+
"session.id": sessionId,
|
|
37
|
+
},
|
|
38
|
+
input,
|
|
39
|
+
parentSpanId,
|
|
40
|
+
traceId,
|
|
41
|
+
spanId,
|
|
42
|
+
};
|
|
43
|
+
this.activeSpans.set(spanId, span);
|
|
44
|
+
return span;
|
|
45
|
+
}
|
|
46
|
+
endSpan(spanId, output, additionalAttributes) {
|
|
47
|
+
const span = this.activeSpans.get(spanId);
|
|
48
|
+
if (!span)
|
|
49
|
+
return undefined;
|
|
50
|
+
span.endTime = Date.now();
|
|
51
|
+
span.output = output;
|
|
52
|
+
if (additionalAttributes) {
|
|
53
|
+
Object.assign(span.attributes, additionalAttributes);
|
|
54
|
+
}
|
|
55
|
+
this.activeSpans.delete(spanId);
|
|
56
|
+
return span;
|
|
57
|
+
}
|
|
58
|
+
getSpan(spanId) {
|
|
59
|
+
return this.activeSpans.get(spanId);
|
|
60
|
+
}
|
|
61
|
+
setTurnSpan(turnId, spanId) {
|
|
62
|
+
this.turnSpanMap.set(turnId, spanId);
|
|
63
|
+
}
|
|
64
|
+
getTurnSpanId(turnId) {
|
|
65
|
+
return this.turnSpanMap.get(turnId);
|
|
66
|
+
}
|
|
67
|
+
clearSession(sessionId) {
|
|
68
|
+
this.sessionTraceMap.delete(sessionId);
|
|
69
|
+
for (const [turnId, spanId] of this.turnSpanMap.entries()) {
|
|
70
|
+
const span = this.activeSpans.get(spanId);
|
|
71
|
+
if (span && span.attributes["session.id"] === sessionId) {
|
|
72
|
+
this.turnSpanMap.delete(turnId);
|
|
73
|
+
this.activeSpans.delete(spanId);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "openclaw-cozeloop-trace",
|
|
3
|
+
"name": "OpenClaw CozeLoop Trace",
|
|
4
|
+
"version": "0.1.12",
|
|
5
|
+
"description": "Report OpenClaw execution traces to CozeLoop via OpenTelemetry",
|
|
6
|
+
"type": "plugin",
|
|
7
|
+
"entry": "./dist/index.js",
|
|
8
|
+
"configSchema": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"endpoint": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"default": "https://api.coze.cn/v1/loop/opentelemetry",
|
|
14
|
+
"description": "CozeLoop OTLP endpoint URL"
|
|
15
|
+
},
|
|
16
|
+
"authorization": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"default": "",
|
|
19
|
+
"description": "Authorization header value"
|
|
20
|
+
},
|
|
21
|
+
"workspaceId": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"default": "",
|
|
24
|
+
"description": "Cozeloop workspace ID"
|
|
25
|
+
},
|
|
26
|
+
"serviceName": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"default": "openclaw-agent",
|
|
29
|
+
"description": "Service name for traces"
|
|
30
|
+
},
|
|
31
|
+
"debug": {
|
|
32
|
+
"type": "boolean",
|
|
33
|
+
"default": false,
|
|
34
|
+
"description": "Enable debug logging"
|
|
35
|
+
},
|
|
36
|
+
"batchSize": {
|
|
37
|
+
"type": "number",
|
|
38
|
+
"default": 10,
|
|
39
|
+
"description": "Number of spans to buffer before sending"
|
|
40
|
+
},
|
|
41
|
+
"batchInterval": {
|
|
42
|
+
"type": "number",
|
|
43
|
+
"default": 5000,
|
|
44
|
+
"description": "Maximum time (ms) to wait before sending buffered spans"
|
|
45
|
+
},
|
|
46
|
+
"enabledHooks": {
|
|
47
|
+
"type": "array",
|
|
48
|
+
"items": {
|
|
49
|
+
"type": "string"
|
|
50
|
+
},
|
|
51
|
+
"description": "List of hooks to enable (if not set, all hooks are enabled)"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cozeloop/openclaw-cozeloop-trace",
|
|
3
|
+
"version": "0.1.12",
|
|
4
|
+
"description": "OpenClaw Plugin for reporting traces to CozeLoop via OpenTelemetry",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"openclaw": {
|
|
11
|
+
"extensions": [
|
|
12
|
+
"./dist/index.js"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"dev": "tsc --watch",
|
|
18
|
+
"clean": "rm -rf dist",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"openclaw.plugin.json"
|
|
24
|
+
],
|
|
25
|
+
"keywords": [
|
|
26
|
+
"openclaw",
|
|
27
|
+
"plugin",
|
|
28
|
+
"cozeloop",
|
|
29
|
+
"tracing",
|
|
30
|
+
"opentelemetry"
|
|
31
|
+
],
|
|
32
|
+
"author": "",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^20.0.0",
|
|
36
|
+
"typescript": "^5.0.0"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@opentelemetry/api": "^1.7.0",
|
|
40
|
+
"@opentelemetry/exporter-trace-otlp-proto": "^0.48.0",
|
|
41
|
+
"@opentelemetry/resources": "^1.22.0",
|
|
42
|
+
"@opentelemetry/sdk-trace-base": "^1.22.0",
|
|
43
|
+
"@opentelemetry/semantic-conventions": "^1.22.0"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
CozeLoop token refresh hook — runs at SessionStart.
|
|
4
|
+
Checks credentials.json and refreshes the token if expiring soon.
|
|
5
|
+
Writes the fresh token back so subsequent Stop hooks pick it up.
|
|
6
|
+
"""
|
|
7
|
+
import json, os, sys, time, urllib.request
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
CLIENT_ID = "56089404009908161803155625287505.app.coze"
|
|
11
|
+
COZE_API = "https://api.coze.cn"
|
|
12
|
+
THRESHOLD = 10 * 60 # 10 minutes
|
|
13
|
+
CREDS = Path.home() / ".cozeloop" / "credentials.json"
|
|
14
|
+
|
|
15
|
+
def load():
|
|
16
|
+
try: return json.loads(CREDS.read_text())
|
|
17
|
+
except: return None
|
|
18
|
+
|
|
19
|
+
def save(c):
|
|
20
|
+
CREDS.parent.mkdir(parents=True, exist_ok=True)
|
|
21
|
+
CREDS.write_text(json.dumps(c, indent=2))
|
|
22
|
+
os.chmod(CREDS, 0o600)
|
|
23
|
+
|
|
24
|
+
def refresh(rt):
|
|
25
|
+
try:
|
|
26
|
+
body = json.dumps({"grant_type":"refresh_token","client_id":CLIENT_ID,"refresh_token":rt}).encode()
|
|
27
|
+
req = urllib.request.Request(f"{COZE_API}/api/permission/oauth2/token",
|
|
28
|
+
data=body, headers={"Content-Type":"application/json"})
|
|
29
|
+
with urllib.request.urlopen(req, timeout=10) as r:
|
|
30
|
+
d = json.loads(r.read())
|
|
31
|
+
if d.get("access_token"):
|
|
32
|
+
save({"access_token":d["access_token"],
|
|
33
|
+
"refresh_token":d.get("refresh_token",rt),
|
|
34
|
+
"expires_at":d.get("expires_in",0)*1000})
|
|
35
|
+
return True
|
|
36
|
+
except Exception as e:
|
|
37
|
+
print(f"[cozeloop_refresh] refresh failed: {e}", file=sys.stderr)
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
def main():
|
|
41
|
+
creds = load()
|
|
42
|
+
if not creds: return
|
|
43
|
+
remaining = (creds.get("expires_at",0)/1000) - time.time()
|
|
44
|
+
if remaining > THRESHOLD: return # still fresh
|
|
45
|
+
print(f"[cozeloop_refresh] token expiring in {int(remaining)}s, refreshing...", file=sys.stderr)
|
|
46
|
+
if creds.get("refresh_token"):
|
|
47
|
+
if refresh(creds["refresh_token"]):
|
|
48
|
+
print("[cozeloop_refresh] token refreshed OK", file=sys.stderr)
|
|
49
|
+
else:
|
|
50
|
+
print("[cozeloop_refresh] refresh failed, token may expire soon", file=sys.stderr)
|
|
51
|
+
|
|
52
|
+
main()
|
|
53
|
+
|