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.
@@ -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
+