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,442 @@
|
|
|
1
|
+
import { trace, context, SpanKind, SpanStatusCode } from "@opentelemetry/api";
|
|
2
|
+
import { BasicTracerProvider, BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
3
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
4
|
+
import { Resource } from "@opentelemetry/resources";
|
|
5
|
+
import { ATTR_SERVICE_NAME, ATTR_SERVICE_INSTANCE_ID } from "@opentelemetry/semantic-conventions";
|
|
6
|
+
import { hostname } from "os";
|
|
7
|
+
import { basename, join } from "path";
|
|
8
|
+
import { createRequire } from "node:module";
|
|
9
|
+
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
10
|
+
import { homedir } from "os";
|
|
11
|
+
import https from "https";
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const { version: PLUGIN_VERSION } = require("../package.json");
|
|
15
|
+
|
|
16
|
+
// ── Token refresh helpers ─────────────────────────────────────────────────
|
|
17
|
+
const _CLIENT_ID = "56089404009908161803155625287505.app.coze";
|
|
18
|
+
const _COZE_API = "https://api.coze.cn";
|
|
19
|
+
const _REFRESH_THRESHOLD_MS = 10 * 60 * 1000;
|
|
20
|
+
const _CREDS_PATH = join(homedir(), ".cozeloop", "credentials.json");
|
|
21
|
+
|
|
22
|
+
function _loadCreds() {
|
|
23
|
+
try { return JSON.parse(readFileSync(_CREDS_PATH, "utf8")); }
|
|
24
|
+
catch { return null; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function _saveCreds(c) {
|
|
28
|
+
try {
|
|
29
|
+
mkdirSync(join(homedir(), ".cozeloop"), { recursive: true });
|
|
30
|
+
writeFileSync(_CREDS_PATH, JSON.stringify(c, null, 2), { mode: 0o600 });
|
|
31
|
+
} catch { /* non-fatal */ }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function _refreshToken(refreshTok) {
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
const body = JSON.stringify({ grant_type: "refresh_token", client_id: _CLIENT_ID, refresh_token: refreshTok });
|
|
37
|
+
const req = https.request(`${_COZE_API}/api/permission/oauth2/token`, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) },
|
|
40
|
+
}, (res) => {
|
|
41
|
+
let buf = "";
|
|
42
|
+
res.on("data", c => buf += c);
|
|
43
|
+
res.on("end", () => {
|
|
44
|
+
try {
|
|
45
|
+
const d = JSON.parse(buf);
|
|
46
|
+
if (d.access_token) {
|
|
47
|
+
const creds = {
|
|
48
|
+
access_token: d.access_token,
|
|
49
|
+
refresh_token: d.refresh_token ?? refreshTok,
|
|
50
|
+
expires_at: (d.expires_in ?? 0) * 1000, // unix timestamp in seconds
|
|
51
|
+
};
|
|
52
|
+
_saveCreds(creds);
|
|
53
|
+
resolve(creds.access_token);
|
|
54
|
+
} else { resolve(null); }
|
|
55
|
+
} catch { resolve(null); }
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
req.on("error", () => resolve(null));
|
|
59
|
+
req.setTimeout(10000, () => { req.destroy(); resolve(null); });
|
|
60
|
+
req.write(body);
|
|
61
|
+
req.end();
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function getRefreshedToken(currentAuthorization) {
|
|
66
|
+
const creds = _loadCreds();
|
|
67
|
+
if (!creds) return currentAuthorization; // no creds file, keep as-is
|
|
68
|
+
const remaining = (creds.expires_at ?? 0) - Date.now();
|
|
69
|
+
if (remaining > _REFRESH_THRESHOLD_MS) return `Bearer ${creds.access_token}`;
|
|
70
|
+
if (creds.refresh_token) {
|
|
71
|
+
const newToken = await _refreshToken(creds.refresh_token);
|
|
72
|
+
if (newToken) return `Bearer ${newToken}`;
|
|
73
|
+
}
|
|
74
|
+
return currentAuthorization; // fallback
|
|
75
|
+
}
|
|
76
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
export class CozeloopExporter {
|
|
79
|
+
config;
|
|
80
|
+
api;
|
|
81
|
+
provider = null;
|
|
82
|
+
tracer = null;
|
|
83
|
+
initialized = false;
|
|
84
|
+
initPromise = null;
|
|
85
|
+
// Per-trace context: keyed by the plugin-level rootSpanId so that
|
|
86
|
+
// concurrent or overlapping traces never stomp on each other.
|
|
87
|
+
traceContexts = new Map();
|
|
88
|
+
openSpans = new Map();
|
|
89
|
+
// Extra attributes derived from environment variables, applied to every span.
|
|
90
|
+
envAttributes = {};
|
|
91
|
+
constructor(api, config) {
|
|
92
|
+
this.api = api;
|
|
93
|
+
this.config = config;
|
|
94
|
+
this.envAttributes = this.parseEnvAttributes();
|
|
95
|
+
}
|
|
96
|
+
parseEnvAttributes() {
|
|
97
|
+
const attrs = {};
|
|
98
|
+
// COZE_PROJECT_ID -> project_id
|
|
99
|
+
const projectId = process.env.COZE_PROJECT_ID?.trim();
|
|
100
|
+
if (projectId) {
|
|
101
|
+
attrs["project_id"] = projectId;
|
|
102
|
+
}
|
|
103
|
+
// COZELOOP_UDF_TAGS -> udf_ prefixed keys
|
|
104
|
+
const tagsRaw = process.env.COZELOOP_UDF_TAGS?.trim();
|
|
105
|
+
if (tagsRaw) {
|
|
106
|
+
const pairs = tagsRaw.split(",");
|
|
107
|
+
for (const pair of pairs) {
|
|
108
|
+
const eqIdx = pair.indexOf("=");
|
|
109
|
+
if (eqIdx < 0) {
|
|
110
|
+
this.api.logger.error(`[CozeloopTrace] Invalid COZELOOP_UDF_TAGS entry (missing '='): "${pair}"`);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const key = pair.substring(0, eqIdx);
|
|
114
|
+
const value = pair.substring(eqIdx + 1);
|
|
115
|
+
// Validate: key and value must not contain '=' or ','
|
|
116
|
+
if (key.includes("=") || key.includes(",")) {
|
|
117
|
+
this.api.logger.error(`[CozeloopTrace] Invalid COZELOOP_UDF_TAGS key contains '=' or ',': "${key}"`);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (value.includes("=") || value.includes(",")) {
|
|
121
|
+
this.api.logger.error(`[CozeloopTrace] Invalid COZELOOP_UDF_TAGS value contains '=' or ',': "${value}"`);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (!key) {
|
|
125
|
+
this.api.logger.error(`[CozeloopTrace] Invalid COZELOOP_UDF_TAGS entry (empty key): "${pair}"`);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
attrs[`udf_${key}`] = value;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return attrs;
|
|
132
|
+
}
|
|
133
|
+
async refreshAuthIfNeeded() {
|
|
134
|
+
const fresh = await getRefreshedToken(this.config.authorization);
|
|
135
|
+
if (fresh && fresh !== this.config.authorization) {
|
|
136
|
+
this.api.logger.info("[CozeloopTrace] Token refreshed, re-initializing exporter...");
|
|
137
|
+
this.config.authorization = fresh;
|
|
138
|
+
// Reset so initialize() re-creates the exporter with the new token
|
|
139
|
+
this.initialized = false;
|
|
140
|
+
this.initPromise = null;
|
|
141
|
+
if (this.provider) {
|
|
142
|
+
try { await this.provider.shutdown(); } catch { /* ignore */ }
|
|
143
|
+
this.provider = null;
|
|
144
|
+
this.tracer = null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async ensureInitialized() {
|
|
149
|
+
if (this.initialized)
|
|
150
|
+
return;
|
|
151
|
+
if (this.initPromise)
|
|
152
|
+
return this.initPromise;
|
|
153
|
+
this.initPromise = this.initialize();
|
|
154
|
+
await this.initPromise;
|
|
155
|
+
}
|
|
156
|
+
async initialize() {
|
|
157
|
+
this.api.logger.info(`[CozeloopTrace] Initializing exporter...`);
|
|
158
|
+
const instanceName = this.config.serviceName || basename(process.cwd()) || "openclaw-agent";
|
|
159
|
+
const instanceId = `${instanceName}@${hostname()}:${process.pid}`;
|
|
160
|
+
const resource = new Resource({
|
|
161
|
+
[ATTR_SERVICE_NAME]: this.config.serviceName,
|
|
162
|
+
[ATTR_SERVICE_INSTANCE_ID]: instanceId,
|
|
163
|
+
"host.name": hostname(),
|
|
164
|
+
});
|
|
165
|
+
const authorization = this.config.authorization;
|
|
166
|
+
const workspaceId = this.config.workspaceId;
|
|
167
|
+
this.api.logger.info(`[CozeloopTrace] Using authorization, workspaceId=${workspaceId}, tokenLength=${authorization?.length}`);
|
|
168
|
+
const exporter = new OTLPTraceExporter({
|
|
169
|
+
url: `${this.config.endpoint}/v1/traces`,
|
|
170
|
+
headers: {
|
|
171
|
+
"Authorization": authorization,
|
|
172
|
+
"cozeloop-workspace-id": workspaceId,
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
this.provider = new BasicTracerProvider({ resource });
|
|
176
|
+
this.provider.addSpanProcessor(new BatchSpanProcessor(exporter, {
|
|
177
|
+
maxQueueSize: 100,
|
|
178
|
+
maxExportBatchSize: this.config.batchSize || 10,
|
|
179
|
+
scheduledDelayMillis: this.config.batchInterval || 5000,
|
|
180
|
+
}));
|
|
181
|
+
// Do NOT call this.provider.register() — it sets the global TracerProvider
|
|
182
|
+
// singleton, so if the plugin is activated more than once (e.g. gateway +
|
|
183
|
+
// plugins subsystem), the second instance would silently get a NOOP tracer
|
|
184
|
+
// while its hooks override those of the first instance, causing all trace
|
|
185
|
+
// operations to become no-ops. Instead, obtain the tracer directly from
|
|
186
|
+
// our own provider instance.
|
|
187
|
+
this.tracer = this.provider.getTracer("openclaw-cozeloop-trace", PLUGIN_VERSION);
|
|
188
|
+
this.initialized = true;
|
|
189
|
+
this.api.logger.info(`[CozeloopTrace] Exporter initialized with Authorization, workspaceId=${workspaceId}`);
|
|
190
|
+
}
|
|
191
|
+
async startSpan(spanData, spanId) {
|
|
192
|
+
try {
|
|
193
|
+
await this.ensureInitialized();
|
|
194
|
+
this.doStartSpan(spanData, spanId);
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
this.api.logger.error(`[CozeloopTrace] Failed to start span: ${err}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
doStartSpan(spanData, spanId) {
|
|
201
|
+
if (!this.tracer)
|
|
202
|
+
return;
|
|
203
|
+
const spanKind = this.getSpanKind(spanData.type);
|
|
204
|
+
const isRoot = !spanData.parentSpanId;
|
|
205
|
+
const isAgent = spanData.type === "agent";
|
|
206
|
+
// Resolve parent context:
|
|
207
|
+
// - Root spans: no parent, use active context.
|
|
208
|
+
// - Agent/child spans: look up traceContexts by parentSpanId (which is
|
|
209
|
+
// always the rootSpanId set by index.ts createSpan / ensureRootSpan).
|
|
210
|
+
const traceCtx = spanData.parentSpanId
|
|
211
|
+
? this.traceContexts.get(spanData.parentSpanId)
|
|
212
|
+
: undefined;
|
|
213
|
+
if (!isRoot && !traceCtx && this.config.debug) {
|
|
214
|
+
const keys = Array.from(this.traceContexts.keys());
|
|
215
|
+
this.api.logger.info(`[CozeloopTrace] doStartSpan() cannot find parent context: ` +
|
|
216
|
+
`parentSpanId=${spanData.parentSpanId}, spanName=${spanData.name}, type=${spanData.type}, ` +
|
|
217
|
+
`traceContextKeys=[${keys.join(",")}]`);
|
|
218
|
+
}
|
|
219
|
+
let parentContext;
|
|
220
|
+
if (isRoot) {
|
|
221
|
+
parentContext = context.active();
|
|
222
|
+
}
|
|
223
|
+
else if (isAgent) {
|
|
224
|
+
parentContext = traceCtx?.rootContext || context.active();
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
parentContext = traceCtx?.agentContext || traceCtx?.rootContext || context.active();
|
|
228
|
+
}
|
|
229
|
+
const runtimeTag = {
|
|
230
|
+
language: "nodejs",
|
|
231
|
+
library: "openclaw",
|
|
232
|
+
};
|
|
233
|
+
if (process.env.COZELOOP_SCENE) {
|
|
234
|
+
runtimeTag.scene = process.env.COZELOOP_SCENE;
|
|
235
|
+
}
|
|
236
|
+
const systemTagRuntime = JSON.stringify(runtimeTag);
|
|
237
|
+
const span = this.tracer.startSpan(spanData.name, {
|
|
238
|
+
kind: spanKind,
|
|
239
|
+
startTime: spanData.startTime,
|
|
240
|
+
attributes: {
|
|
241
|
+
"cozeloop.span_type": spanData.type,
|
|
242
|
+
"cozeloop.system_tag_runtime": systemTagRuntime,
|
|
243
|
+
...this.envAttributes,
|
|
244
|
+
...this.flattenAttributes(spanData.attributes),
|
|
245
|
+
},
|
|
246
|
+
}, parentContext);
|
|
247
|
+
if (isRoot) {
|
|
248
|
+
const rootContext = trace.setSpan(context.active(), span);
|
|
249
|
+
this.traceContexts.set(spanId, { rootSpan: span, rootContext });
|
|
250
|
+
if (this.config.debug) {
|
|
251
|
+
const sc = span.spanContext();
|
|
252
|
+
this.api.logger.info(`[CozeloopTrace] Created ROOT span: name=${spanData.name}, traceId=${sc.traceId}, spanId=${sc.spanId}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (isAgent && traceCtx) {
|
|
256
|
+
traceCtx.agentSpan = span;
|
|
257
|
+
traceCtx.agentContext = trace.setSpan(traceCtx.rootContext, span);
|
|
258
|
+
if (this.config.debug) {
|
|
259
|
+
const sc = span.spanContext();
|
|
260
|
+
this.api.logger.info(`[CozeloopTrace] Created AGENT span: name=${spanData.name}, traceId=${sc.traceId}, spanId=${sc.spanId}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
this.setSpanInputOutput(span, spanData);
|
|
264
|
+
this.openSpans.set(spanId, span);
|
|
265
|
+
if (this.config.debug && !isRoot && !isAgent) {
|
|
266
|
+
const spanContext = span.spanContext();
|
|
267
|
+
this.api.logger.info(`[CozeloopTrace] Started span: name=${spanData.name}, type=${spanData.type}, ` +
|
|
268
|
+
`traceId=${spanContext.traceId}, spanId=${spanContext.spanId}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
endSpanById(spanId, endTime, additionalAttrs, output, input) {
|
|
272
|
+
const span = this.openSpans.get(spanId);
|
|
273
|
+
if (!span) {
|
|
274
|
+
if (this.config.debug) {
|
|
275
|
+
this.api.logger.info(`[CozeloopTrace] Span not found for ending: spanId=${spanId}`);
|
|
276
|
+
}
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (additionalAttrs) {
|
|
280
|
+
for (const [key, value] of Object.entries(additionalAttrs)) {
|
|
281
|
+
if (value !== undefined && value !== null) {
|
|
282
|
+
span.setAttribute(key, value);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (input !== undefined) {
|
|
287
|
+
const inputStr = typeof input === "string" ? input : JSON.stringify(input);
|
|
288
|
+
span.setAttribute("cozeloop.input", inputStr.substring(0, 3200000));
|
|
289
|
+
}
|
|
290
|
+
if (output !== undefined) {
|
|
291
|
+
const outputStr = typeof output === "string" ? output : JSON.stringify(output);
|
|
292
|
+
span.setAttribute("cozeloop.output", outputStr.substring(0, 3200000));
|
|
293
|
+
}
|
|
294
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
295
|
+
span.end(endTime || Date.now());
|
|
296
|
+
this.openSpans.delete(spanId);
|
|
297
|
+
if (this.config.debug) {
|
|
298
|
+
const sc = span.spanContext();
|
|
299
|
+
this.api.logger.info(`[CozeloopTrace] Ended span: spanId=${spanId}, traceId=${sc.traceId}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async export(spanData) {
|
|
303
|
+
try {
|
|
304
|
+
await this.ensureInitialized();
|
|
305
|
+
if (!this.tracer)
|
|
306
|
+
return;
|
|
307
|
+
const spanKind = this.getSpanKind(spanData.type);
|
|
308
|
+
const isRoot = !spanData.parentSpanId;
|
|
309
|
+
const isAgent = spanData.type === "agent";
|
|
310
|
+
const traceCtx = spanData.parentSpanId
|
|
311
|
+
? this.traceContexts.get(spanData.parentSpanId)
|
|
312
|
+
: undefined;
|
|
313
|
+
if (!isRoot && !traceCtx) {
|
|
314
|
+
// Only warn for span types that are expected to be inside a trace
|
|
315
|
+
// (agent, model, tool). message/session/gateway spans may fire before
|
|
316
|
+
// the root span is created and that is normal.
|
|
317
|
+
const criticalTypes = new Set(["agent", "model", "tool"]);
|
|
318
|
+
if (criticalTypes.has(spanData.type) && this.config.debug) {
|
|
319
|
+
const keys = Array.from(this.traceContexts.keys());
|
|
320
|
+
this.api.logger.info(`[CozeloopTrace] export() cannot find parent context: ` +
|
|
321
|
+
`parentSpanId=${spanData.parentSpanId}, spanName=${spanData.name}, type=${spanData.type}, ` +
|
|
322
|
+
`traceContextKeys=[${keys.join(",")}]`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
let parentContext;
|
|
326
|
+
if (isRoot) {
|
|
327
|
+
parentContext = context.active();
|
|
328
|
+
}
|
|
329
|
+
else if (isAgent) {
|
|
330
|
+
parentContext = traceCtx?.rootContext || context.active();
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
parentContext = traceCtx?.agentContext || traceCtx?.rootContext || context.active();
|
|
334
|
+
}
|
|
335
|
+
const runtimeTag = {
|
|
336
|
+
language: "nodejs",
|
|
337
|
+
library: "openclaw",
|
|
338
|
+
};
|
|
339
|
+
if (process.env.COZELOOP_SCENE) {
|
|
340
|
+
runtimeTag.scene = process.env.COZELOOP_SCENE;
|
|
341
|
+
}
|
|
342
|
+
const systemTagRuntime = JSON.stringify(runtimeTag);
|
|
343
|
+
const span = this.tracer.startSpan(spanData.name, {
|
|
344
|
+
kind: spanKind,
|
|
345
|
+
startTime: spanData.startTime,
|
|
346
|
+
attributes: {
|
|
347
|
+
"cozeloop.span_type": spanData.type,
|
|
348
|
+
"cozeloop.system_tag_runtime": systemTagRuntime,
|
|
349
|
+
...this.envAttributes,
|
|
350
|
+
...this.flattenAttributes(spanData.attributes),
|
|
351
|
+
},
|
|
352
|
+
}, parentContext);
|
|
353
|
+
if (isRoot) {
|
|
354
|
+
const rootContext = trace.setSpan(context.active(), span);
|
|
355
|
+
const spanId = spanData.spanId || "export-root";
|
|
356
|
+
this.traceContexts.set(spanId, { rootSpan: span, rootContext });
|
|
357
|
+
if (this.config.debug) {
|
|
358
|
+
const sc = span.spanContext();
|
|
359
|
+
this.api.logger.info(`[CozeloopTrace] Created ROOT span: name=${spanData.name}, traceId=${sc.traceId}, spanId=${sc.spanId}`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
this.setSpanInputOutput(span, spanData);
|
|
363
|
+
const hasError = spanData.attributes["error"] === true || spanData.attributes["tool.error"] === true;
|
|
364
|
+
if (hasError) {
|
|
365
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
369
|
+
}
|
|
370
|
+
span.end(spanData.endTime || Date.now());
|
|
371
|
+
if (this.config.debug) {
|
|
372
|
+
const spanContext = span.spanContext();
|
|
373
|
+
this.api.logger.info(`[CozeloopTrace] Created span: name=${spanData.name}, type=${spanData.type}, ` +
|
|
374
|
+
`traceId=${spanContext.traceId}, spanId=${spanContext.spanId}, isRoot=${isRoot}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
catch (err) {
|
|
378
|
+
this.api.logger.error(`[CozeloopTrace] Failed to export span: ${err}`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
setSpanInputOutput(span, spanData) {
|
|
382
|
+
if (spanData.input !== undefined) {
|
|
383
|
+
const inputStr = typeof spanData.input === "string"
|
|
384
|
+
? spanData.input
|
|
385
|
+
: JSON.stringify(spanData.input);
|
|
386
|
+
span.setAttribute("cozeloop.input", inputStr.substring(0, 3200000));
|
|
387
|
+
}
|
|
388
|
+
if (spanData.output !== undefined) {
|
|
389
|
+
const outputStr = typeof spanData.output === "string"
|
|
390
|
+
? spanData.output
|
|
391
|
+
: JSON.stringify(spanData.output);
|
|
392
|
+
span.setAttribute("cozeloop.output", outputStr.substring(0, 3200000));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
hasTraceContext(rootSpanId) {
|
|
396
|
+
return this.traceContexts.has(rootSpanId);
|
|
397
|
+
}
|
|
398
|
+
endTrace(rootSpanId) {
|
|
399
|
+
if (rootSpanId) {
|
|
400
|
+
this.traceContexts.delete(rootSpanId);
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
this.traceContexts.clear();
|
|
404
|
+
this.openSpans.clear();
|
|
405
|
+
}
|
|
406
|
+
if (this.config.debug) {
|
|
407
|
+
this.api.logger.info(`[CozeloopTrace] Trace ended, context cleared${rootSpanId ? ` for rootSpanId=${rootSpanId}` : ' (all)'}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
getSpanKind(type) {
|
|
411
|
+
switch (type) {
|
|
412
|
+
case "entry":
|
|
413
|
+
case "gateway":
|
|
414
|
+
return SpanKind.SERVER;
|
|
415
|
+
case "model":
|
|
416
|
+
return SpanKind.CLIENT;
|
|
417
|
+
case "tool":
|
|
418
|
+
return SpanKind.CLIENT;
|
|
419
|
+
default:
|
|
420
|
+
return SpanKind.INTERNAL;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
flattenAttributes(attrs) {
|
|
424
|
+
const result = {};
|
|
425
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
426
|
+
if (value !== undefined && value !== null) {
|
|
427
|
+
result[key] = value;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
432
|
+
async flush() {
|
|
433
|
+
if (this.provider) {
|
|
434
|
+
await this.provider.forceFlush();
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
async dispose() {
|
|
438
|
+
if (this.provider) {
|
|
439
|
+
await this.provider.shutdown();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|