mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:40:44 +00:00
feat(diagnostics-otel): add content capture controls
Add opt-in diagnostics OTEL content capture controls, keep raw content export default-off, and guard the content-capture tests against magic truncation bounds.
This commit is contained in:
@@ -44,6 +44,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Plugins/channels: use manifest `channelConfigs` for read-only external channel discovery when no setup entry is available or setup descriptors declare runtime unnecessary. Thanks @vincentkoc.
|
||||
- TUI/dependencies: remove direct `cli-highlight` usage from the OpenClaw TUI code-block renderer, keeping themed code coloring without the extra root dependency. Thanks @vincentkoc.
|
||||
- Diagnostics/OTEL: export run, model-call, and tool-execution diagnostic lifecycle events as OTEL spans without retaining live span state. Thanks @vincentkoc.
|
||||
- Diagnostics/OTEL: accept opt-in `diagnostics.otel.captureContent` controls for future model/tool content span attributes while keeping raw content export disabled by default. Thanks @vincentkoc.
|
||||
- Providers/Anthropic Vertex: move the Vertex SDK runtime behind the bundled provider plugin so core no longer owns that provider-specific dependency. Thanks @vincentkoc.
|
||||
- Plugins/activation: expose activation plan reasons and a richer plan API so callers can inspect why a plugin was selected while preserving existing id-list activation behavior. (#70943) Thanks @vincentkoc.
|
||||
- Plugins/source metadata: expose normalized install-source facts on provider and channel catalogs so onboarding can explain npm pinning, integrity state, and local availability before runtime loads. (#70951) Thanks @vincentkoc.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
a608561acecc7cfc5f16a31b7498d7a66001f6655f5a5960a68842c59b7dcaa8 config-baseline.json
|
||||
2936d2ccf0c1e6e932a0e7c617b809e4b31dbb9a7d5afefbba29b229913b9e50 config-baseline.core.json
|
||||
52af51e35e05d0cbaa1a79fb415f2c2fe56ad5d52a62efa9cbb9c32489d517f5 config-baseline.json
|
||||
642b4e2c9891e710790313df097b4e0db75a197ec0908e9c03bdc76f5bbdf9b0 config-baseline.core.json
|
||||
22d7cd6d8279146b2d79c9531a55b80b52a2c99c81338c508104729154fdd02d config-baseline.channel.json
|
||||
d47a574045a47356e513ab308d7dcad9fa0b389f50e93c5cf0f820fab858e70e config-baseline.plugin.json
|
||||
|
||||
@@ -797,6 +797,14 @@ Notes:
|
||||
logs: false,
|
||||
sampleRate: 1.0,
|
||||
flushIntervalMs: 5000,
|
||||
captureContent: {
|
||||
enabled: false,
|
||||
inputMessages: false,
|
||||
outputMessages: false,
|
||||
toolInputs: false,
|
||||
toolOutputs: false,
|
||||
systemPrompt: false,
|
||||
},
|
||||
},
|
||||
|
||||
cacheTrace: {
|
||||
@@ -821,6 +829,7 @@ Notes:
|
||||
- `otel.traces` / `otel.metrics` / `otel.logs`: enable trace, metrics, or log export.
|
||||
- `otel.sampleRate`: trace sampling rate `0`–`1`.
|
||||
- `otel.flushIntervalMs`: periodic telemetry flush interval in ms.
|
||||
- `otel.captureContent`: opt-in raw content capture for OTEL span attributes. Defaults to off. Boolean `true` captures non-system message/tool content; the object form lets you enable `inputMessages`, `outputMessages`, `toolInputs`, `toolOutputs`, and `systemPrompt` explicitly.
|
||||
- `cacheTrace.enabled`: log cache trace snapshots for embedded runs (default: `false`).
|
||||
- `cacheTrace.filePath`: output path for cache trace JSONL (default: `$OPENCLAW_STATE_DIR/logs/cache-trace.jsonl`).
|
||||
- `cacheTrace.includeMessages` / `includePrompt` / `includeSystem`: control what is included in cache trace output (all default: `true`).
|
||||
|
||||
@@ -279,7 +279,15 @@ works with any OpenTelemetry collector/backend that accepts OTLP/HTTP.
|
||||
"metrics": true,
|
||||
"logs": true,
|
||||
"sampleRate": 0.2,
|
||||
"flushIntervalMs": 60000
|
||||
"flushIntervalMs": 60000,
|
||||
"captureContent": {
|
||||
"enabled": false,
|
||||
"inputMessages": false,
|
||||
"outputMessages": false,
|
||||
"toolInputs": false,
|
||||
"toolOutputs": false,
|
||||
"systemPrompt": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -293,6 +301,9 @@ Notes:
|
||||
counters/histograms (webhooks, queueing, session state, queue depth/wait).
|
||||
- Traces/metrics can be toggled with `traces` / `metrics` (default: on). Traces
|
||||
include model usage spans plus webhook/message processing spans when enabled.
|
||||
- Raw model/tool content is not exported by default. Use
|
||||
`diagnostics.otel.captureContent` only when your collector and retention policy
|
||||
are approved for prompt, response, tool, or system prompt text.
|
||||
- Set `headers` when your collector requires auth.
|
||||
- Environment variables supported: `OTEL_EXPORTER_OTLP_ENDPOINT`,
|
||||
`OTEL_SERVICE_NAME`, `OTEL_EXPORTER_OTLP_PROTOCOL`.
|
||||
@@ -341,8 +352,17 @@ Queues + sessions:
|
||||
|
||||
- `openclaw.model.usage`
|
||||
- `openclaw.channel`, `openclaw.provider`, `openclaw.model`
|
||||
- `openclaw.sessionKey`, `openclaw.sessionId`
|
||||
- `openclaw.tokens.*` (input/output/cache_read/cache_write/total)
|
||||
- `openclaw.run`
|
||||
- `openclaw.outcome`, `openclaw.channel`, `openclaw.provider`,
|
||||
`openclaw.model`, `openclaw.errorCategory`
|
||||
- `openclaw.model.call`
|
||||
- `gen_ai.system`, `gen_ai.request.model`, `gen_ai.operation.name`,
|
||||
`openclaw.provider`, `openclaw.model`, `openclaw.api`,
|
||||
`openclaw.transport`
|
||||
- `openclaw.tool.execution`
|
||||
- `gen_ai.tool.name`, `openclaw.toolName`, `openclaw.errorCategory`,
|
||||
`openclaw.tool.params.*`
|
||||
- `openclaw.webhook.processed`
|
||||
- `openclaw.channel`, `openclaw.webhook`, `openclaw.chatId`
|
||||
- `openclaw.webhook.error`
|
||||
@@ -350,11 +370,13 @@ Queues + sessions:
|
||||
`openclaw.error`
|
||||
- `openclaw.message.processed`
|
||||
- `openclaw.channel`, `openclaw.outcome`, `openclaw.chatId`,
|
||||
`openclaw.messageId`, `openclaw.sessionKey`, `openclaw.sessionId`,
|
||||
`openclaw.reason`
|
||||
`openclaw.messageId`, `openclaw.reason`
|
||||
- `openclaw.session.stuck`
|
||||
- `openclaw.state`, `openclaw.ageMs`, `openclaw.queueDepth`,
|
||||
`openclaw.sessionKey`, `openclaw.sessionId`
|
||||
- `openclaw.state`, `openclaw.ageMs`, `openclaw.queueDepth`
|
||||
|
||||
When content capture is explicitly enabled, model/tool spans can also include
|
||||
bounded, redacted `openclaw.content.*` attributes for the specific content
|
||||
classes you opted into.
|
||||
|
||||
### Sampling + flushing
|
||||
|
||||
|
||||
@@ -123,6 +123,8 @@ const SPAN_ID = "00f067aa0ba902b7";
|
||||
const CHILD_SPAN_ID = "1111111111111111";
|
||||
const GRANDCHILD_SPAN_ID = "2222222222222222";
|
||||
const PROTO_KEY = "__proto__";
|
||||
const MAX_TEST_OTEL_CONTENT_ATTRIBUTE_CHARS = 4096;
|
||||
const OTEL_TRUNCATED_SUFFIX_MAX_CHARS = 20;
|
||||
|
||||
function createLogger() {
|
||||
return {
|
||||
@@ -137,10 +139,13 @@ type OtelContextFlags = {
|
||||
traces?: boolean;
|
||||
metrics?: boolean;
|
||||
logs?: boolean;
|
||||
captureContent?: NonNullable<
|
||||
NonNullable<OpenClawPluginServiceContext["config"]["diagnostics"]>["otel"]
|
||||
>["captureContent"];
|
||||
};
|
||||
function createOtelContext(
|
||||
endpoint: string,
|
||||
{ traces = false, metrics = false, logs = false }: OtelContextFlags = {},
|
||||
{ traces = false, metrics = false, logs = false, captureContent }: OtelContextFlags = {},
|
||||
): OpenClawPluginServiceContext {
|
||||
return {
|
||||
config: {
|
||||
@@ -153,6 +158,7 @@ function createOtelContext(
|
||||
traces,
|
||||
metrics,
|
||||
logs,
|
||||
...(captureContent !== undefined ? { captureContent } : {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -723,6 +729,122 @@ describe("diagnostics-otel service", () => {
|
||||
await service.stop?.(ctx);
|
||||
});
|
||||
|
||||
test("does not export model or tool content unless capture is explicitly enabled", async () => {
|
||||
const service = createDiagnosticsOtelService();
|
||||
const ctx = createOtelContext(OTEL_TEST_ENDPOINT, { traces: true, metrics: true });
|
||||
await service.start(ctx);
|
||||
|
||||
emitDiagnosticEvent({
|
||||
type: "model.call.completed",
|
||||
runId: "run-1",
|
||||
callId: "call-1",
|
||||
provider: "openai",
|
||||
model: "gpt-5.4",
|
||||
durationMs: 80,
|
||||
inputMessages: ["private user prompt"],
|
||||
outputMessages: ["private model reply"],
|
||||
systemPrompt: "private system prompt",
|
||||
} as Parameters<typeof emitDiagnosticEvent>[0]);
|
||||
emitDiagnosticEvent({
|
||||
type: "tool.execution.completed",
|
||||
runId: "run-1",
|
||||
toolName: "read",
|
||||
toolCallId: "tool-1",
|
||||
durationMs: 20,
|
||||
toolInput: "private tool input",
|
||||
toolOutput: "private tool output",
|
||||
} as Parameters<typeof emitDiagnosticEvent>[0]);
|
||||
await flushDiagnosticEvents();
|
||||
|
||||
const modelCall = telemetryState.tracer.startSpan.mock.calls.find(
|
||||
(call) => call[0] === "openclaw.model.call",
|
||||
);
|
||||
const toolCall = telemetryState.tracer.startSpan.mock.calls.find(
|
||||
(call) => call[0] === "openclaw.tool.execution",
|
||||
);
|
||||
expect(modelCall?.[1]).toEqual({
|
||||
attributes: expect.not.objectContaining({
|
||||
"openclaw.content.input_messages": expect.anything(),
|
||||
"openclaw.content.output_messages": expect.anything(),
|
||||
"openclaw.content.system_prompt": expect.anything(),
|
||||
}),
|
||||
startTime: expect.any(Number),
|
||||
});
|
||||
expect(toolCall?.[1]).toEqual({
|
||||
attributes: expect.not.objectContaining({
|
||||
"openclaw.content.tool_input": expect.anything(),
|
||||
"openclaw.content.tool_output": expect.anything(),
|
||||
}),
|
||||
startTime: expect.any(Number),
|
||||
});
|
||||
await service.stop?.(ctx);
|
||||
});
|
||||
|
||||
test("exports bounded redacted content when capture fields are opted in", async () => {
|
||||
const service = createDiagnosticsOtelService();
|
||||
const ctx = createOtelContext(OTEL_TEST_ENDPOINT, {
|
||||
traces: true,
|
||||
metrics: true,
|
||||
captureContent: {
|
||||
enabled: true,
|
||||
inputMessages: true,
|
||||
outputMessages: true,
|
||||
toolInputs: true,
|
||||
toolOutputs: true,
|
||||
systemPrompt: true,
|
||||
},
|
||||
});
|
||||
await service.start(ctx);
|
||||
|
||||
emitDiagnosticEvent({
|
||||
type: "model.call.completed",
|
||||
runId: "run-1",
|
||||
callId: "call-1",
|
||||
provider: "openai",
|
||||
model: "gpt-5.4",
|
||||
durationMs: 80,
|
||||
inputMessages: ["use key sk-1234567890abcdef1234567890abcdef"], // pragma: allowlist secret
|
||||
outputMessages: ["model reply"],
|
||||
systemPrompt: "system prompt",
|
||||
} as Parameters<typeof emitDiagnosticEvent>[0]);
|
||||
emitDiagnosticEvent({
|
||||
type: "tool.execution.completed",
|
||||
runId: "run-1",
|
||||
toolName: "read",
|
||||
toolCallId: "tool-1",
|
||||
durationMs: 20,
|
||||
toolInput: "tool input",
|
||||
toolOutput: "x".repeat(6000),
|
||||
} as Parameters<typeof emitDiagnosticEvent>[0]);
|
||||
await flushDiagnosticEvents();
|
||||
|
||||
const modelCall = telemetryState.tracer.startSpan.mock.calls.find(
|
||||
(call) => call[0] === "openclaw.model.call",
|
||||
);
|
||||
const toolCall = telemetryState.tracer.startSpan.mock.calls.find(
|
||||
(call) => call[0] === "openclaw.tool.execution",
|
||||
);
|
||||
const modelAttrs = (modelCall?.[1] as { attributes?: Record<string, unknown> } | undefined)
|
||||
?.attributes;
|
||||
const toolAttrs = (toolCall?.[1] as { attributes?: Record<string, unknown> } | undefined)
|
||||
?.attributes;
|
||||
|
||||
expect(modelAttrs).toMatchObject({
|
||||
"openclaw.content.output_messages": "model reply",
|
||||
"openclaw.content.system_prompt": "system prompt",
|
||||
});
|
||||
expect(String(modelAttrs?.["openclaw.content.input_messages"])).not.toContain(
|
||||
"sk-1234567890abcdef1234567890abcdef", // pragma: allowlist secret
|
||||
);
|
||||
expect(toolAttrs).toMatchObject({
|
||||
"openclaw.content.tool_input": "tool input",
|
||||
});
|
||||
expect(String(toolAttrs?.["openclaw.content.tool_output"]).length).toBeLessThanOrEqual(
|
||||
MAX_TEST_OTEL_CONTENT_ATTRIBUTE_CHARS + OTEL_TRUNCATED_SUFFIX_MAX_CHARS,
|
||||
);
|
||||
await service.stop?.(ctx);
|
||||
});
|
||||
|
||||
test("ignores invalid diagnostic event trace parents", async () => {
|
||||
const service = createDiagnosticsOtelService();
|
||||
const ctx = createOtelContext(OTEL_TEST_ENDPOINT, { traces: true, metrics: true });
|
||||
|
||||
@@ -40,6 +40,8 @@ const DROPPED_OTEL_ATTRIBUTE_KEYS = new Set([
|
||||
"openclaw.traceId",
|
||||
]);
|
||||
const LOW_CARDINALITY_VALUE_RE = /^[A-Za-z0-9_.:-]{1,120}$/u;
|
||||
const MAX_OTEL_CONTENT_ATTRIBUTE_CHARS = 4 * 1024;
|
||||
const MAX_OTEL_CONTENT_ARRAY_ITEMS = 16;
|
||||
const MAX_OTEL_LOG_BODY_CHARS = 4 * 1024;
|
||||
const MAX_OTEL_LOG_ATTRIBUTE_COUNT = 64;
|
||||
const MAX_OTEL_LOG_ATTRIBUTE_VALUE_CHARS = 4 * 1024;
|
||||
@@ -48,6 +50,22 @@ const OTEL_LOG_RAW_ATTRIBUTE_KEY_RE = /^[A-Za-z0-9_.:-]{1,64}$/u;
|
||||
const OTEL_LOG_ATTRIBUTE_KEY_RE = /^[A-Za-z0-9_.:-]{1,96}$/u;
|
||||
const BLOCKED_OTEL_LOG_ATTRIBUTE_KEYS = new Set(["__proto__", "prototype", "constructor"]);
|
||||
|
||||
type OtelContentCapturePolicy = {
|
||||
inputMessages: boolean;
|
||||
outputMessages: boolean;
|
||||
toolInputs: boolean;
|
||||
toolOutputs: boolean;
|
||||
systemPrompt: boolean;
|
||||
};
|
||||
|
||||
const NO_CONTENT_CAPTURE: OtelContentCapturePolicy = {
|
||||
inputMessages: false,
|
||||
outputMessages: false,
|
||||
toolInputs: false,
|
||||
toolOutputs: false,
|
||||
systemPrompt: false,
|
||||
};
|
||||
|
||||
function normalizeEndpoint(endpoint?: string): string | undefined {
|
||||
const trimmed = endpoint?.trim();
|
||||
return trimmed ? trimmed.replace(/\/+$/, "") : undefined;
|
||||
@@ -119,6 +137,95 @@ function normalizeOtelLogString(value: string, maxChars: number): string {
|
||||
return redactSensitiveText(clampOtelLogText(value, maxChars));
|
||||
}
|
||||
|
||||
function resolveContentCapturePolicy(value: unknown): OtelContentCapturePolicy {
|
||||
if (value === true) {
|
||||
return {
|
||||
inputMessages: true,
|
||||
outputMessages: true,
|
||||
toolInputs: true,
|
||||
toolOutputs: true,
|
||||
systemPrompt: false,
|
||||
};
|
||||
}
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return NO_CONTENT_CAPTURE;
|
||||
}
|
||||
|
||||
const config = value as Record<string, unknown>;
|
||||
if (config.enabled !== true) {
|
||||
return NO_CONTENT_CAPTURE;
|
||||
}
|
||||
return {
|
||||
inputMessages: config.inputMessages === true,
|
||||
outputMessages: config.outputMessages === true,
|
||||
toolInputs: config.toolInputs === true,
|
||||
toolOutputs: config.toolOutputs === true,
|
||||
systemPrompt: config.systemPrompt === true,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeOtelContentValue(value: unknown): string | undefined {
|
||||
if (typeof value === "string") {
|
||||
return normalizeOtelLogString(value, MAX_OTEL_CONTENT_ATTRIBUTE_CHARS);
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
const items: string[] = [];
|
||||
for (const item of value.slice(0, MAX_OTEL_CONTENT_ARRAY_ITEMS)) {
|
||||
if (typeof item === "string") {
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
if (items.length > 0) {
|
||||
return normalizeOtelLogString(items.join("\n"), MAX_OTEL_CONTENT_ATTRIBUTE_CHARS);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function assignOtelContentAttribute(
|
||||
attributes: Record<string, string | number | boolean>,
|
||||
key: string,
|
||||
value: unknown,
|
||||
): void {
|
||||
const normalized = normalizeOtelContentValue(value);
|
||||
if (normalized) {
|
||||
attributes[key] = normalized;
|
||||
}
|
||||
}
|
||||
|
||||
function assignOtelModelContentAttributes(
|
||||
attributes: Record<string, string | number | boolean>,
|
||||
event: Record<string, unknown>,
|
||||
policy: OtelContentCapturePolicy,
|
||||
): void {
|
||||
if (policy.inputMessages) {
|
||||
assignOtelContentAttribute(attributes, "openclaw.content.input_messages", event.inputMessages);
|
||||
}
|
||||
if (policy.outputMessages) {
|
||||
assignOtelContentAttribute(
|
||||
attributes,
|
||||
"openclaw.content.output_messages",
|
||||
event.outputMessages,
|
||||
);
|
||||
}
|
||||
if (policy.systemPrompt) {
|
||||
assignOtelContentAttribute(attributes, "openclaw.content.system_prompt", event.systemPrompt);
|
||||
}
|
||||
}
|
||||
|
||||
function assignOtelToolContentAttributes(
|
||||
attributes: Record<string, string | number | boolean>,
|
||||
event: Record<string, unknown>,
|
||||
policy: OtelContentCapturePolicy,
|
||||
): void {
|
||||
if (policy.toolInputs) {
|
||||
assignOtelContentAttribute(attributes, "openclaw.content.tool_input", event.toolInput);
|
||||
}
|
||||
if (policy.toolOutputs) {
|
||||
assignOtelContentAttribute(attributes, "openclaw.content.tool_output", event.toolOutput);
|
||||
}
|
||||
}
|
||||
|
||||
function assignOtelLogAttribute(
|
||||
attributes: Record<string, string | number | boolean>,
|
||||
key: string,
|
||||
@@ -285,6 +392,7 @@ export function createDiagnosticsOtelService(): OpenClawPluginService {
|
||||
const serviceName =
|
||||
otel.serviceName?.trim() || process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE_NAME;
|
||||
const sampleRate = resolveSampleRate(otel.sampleRate);
|
||||
const contentCapturePolicy = resolveContentCapturePolicy(otel.captureContent);
|
||||
|
||||
const tracesEnabled = otel.traces !== false;
|
||||
const metricsEnabled = otel.metrics !== false;
|
||||
@@ -856,6 +964,11 @@ export function createDiagnosticsOtelService(): OpenClawPluginService {
|
||||
if (evt.transport) {
|
||||
spanAttrs["openclaw.transport"] = evt.transport;
|
||||
}
|
||||
assignOtelModelContentAttributes(
|
||||
spanAttrs,
|
||||
evt as unknown as Record<string, unknown>,
|
||||
contentCapturePolicy,
|
||||
);
|
||||
const span = spanWithDuration("openclaw.model.call", spanAttrs, evt.durationMs, {
|
||||
endTimeMs: evt.ts,
|
||||
});
|
||||
@@ -886,6 +999,11 @@ export function createDiagnosticsOtelService(): OpenClawPluginService {
|
||||
if (evt.transport) {
|
||||
spanAttrs["openclaw.transport"] = evt.transport;
|
||||
}
|
||||
assignOtelModelContentAttributes(
|
||||
spanAttrs,
|
||||
evt as unknown as Record<string, unknown>,
|
||||
contentCapturePolicy,
|
||||
);
|
||||
const span = spanWithDuration("openclaw.model.call", spanAttrs, evt.durationMs, {
|
||||
endTimeMs: evt.ts,
|
||||
});
|
||||
@@ -913,6 +1031,11 @@ export function createDiagnosticsOtelService(): OpenClawPluginService {
|
||||
...paramsSummaryAttrs(evt.paramsSummary),
|
||||
};
|
||||
addRunAttrs(spanAttrs, evt);
|
||||
assignOtelToolContentAttributes(
|
||||
spanAttrs,
|
||||
evt as unknown as Record<string, unknown>,
|
||||
contentCapturePolicy,
|
||||
);
|
||||
const span = spanWithDuration("openclaw.tool.execution", spanAttrs, evt.durationMs, {
|
||||
endTimeMs: evt.ts,
|
||||
});
|
||||
@@ -941,6 +1064,11 @@ export function createDiagnosticsOtelService(): OpenClawPluginService {
|
||||
if (evt.errorCode) {
|
||||
spanAttrs["openclaw.errorCode"] = lowCardinalityAttr(evt.errorCode, "other");
|
||||
}
|
||||
assignOtelToolContentAttributes(
|
||||
spanAttrs,
|
||||
evt as unknown as Record<string, unknown>,
|
||||
contentCapturePolicy,
|
||||
);
|
||||
const span = spanWithDuration("openclaw.tool.execution", spanAttrs, evt.durationMs, {
|
||||
endTimeMs: evt.ts,
|
||||
});
|
||||
|
||||
@@ -65,6 +65,32 @@ describe("plugins.slots.contextEngine", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("diagnostics.otel.captureContent", () => {
|
||||
it("accepts boolean and granular OTEL content capture config", () => {
|
||||
for (const captureContent of [
|
||||
true,
|
||||
false,
|
||||
{
|
||||
enabled: true,
|
||||
inputMessages: true,
|
||||
outputMessages: true,
|
||||
toolInputs: true,
|
||||
toolOutputs: true,
|
||||
systemPrompt: false,
|
||||
},
|
||||
]) {
|
||||
const result = OpenClawSchema.safeParse({
|
||||
diagnostics: {
|
||||
otel: {
|
||||
captureContent,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("auth.cooldowns auth_permanent backoff config", () => {
|
||||
it("accepts auth_permanent backoff knobs", () => {
|
||||
const result = OpenClawSchema.safeParse({
|
||||
|
||||
@@ -233,6 +233,58 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
|
||||
description:
|
||||
"Interval in milliseconds for periodic telemetry flush from buffers to the collector. Increase to reduce export chatter, or lower for faster visibility during active incident response.",
|
||||
},
|
||||
captureContent: {
|
||||
anyOf: [
|
||||
{
|
||||
type: "boolean",
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
title: "OpenTelemetry Content Capture Enabled",
|
||||
description:
|
||||
"Master switch for granular OTEL content capture fields. Keep disabled unless your collector is approved for raw prompt, response, or tool content.",
|
||||
},
|
||||
inputMessages: {
|
||||
type: "boolean",
|
||||
title: "OpenTelemetry Input Messages Capture",
|
||||
description:
|
||||
"Capture model input message text on OTEL spans when content capture is enabled.",
|
||||
},
|
||||
outputMessages: {
|
||||
type: "boolean",
|
||||
title: "OpenTelemetry Output Messages Capture",
|
||||
description:
|
||||
"Capture model output message text on OTEL spans when content capture is enabled.",
|
||||
},
|
||||
toolInputs: {
|
||||
type: "boolean",
|
||||
title: "OpenTelemetry Tool Inputs Capture",
|
||||
description:
|
||||
"Capture tool input text on OTEL spans when content capture is enabled.",
|
||||
},
|
||||
toolOutputs: {
|
||||
type: "boolean",
|
||||
title: "OpenTelemetry Tool Outputs Capture",
|
||||
description:
|
||||
"Capture tool output text on OTEL spans when content capture is enabled.",
|
||||
},
|
||||
systemPrompt: {
|
||||
type: "boolean",
|
||||
title: "OpenTelemetry System Prompt Capture",
|
||||
description:
|
||||
"Capture system prompt text on OTEL spans when content capture is enabled. This remains off unless explicitly enabled.",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
],
|
||||
title: "OpenTelemetry Content Capture",
|
||||
description:
|
||||
"Opt-in OTEL span content capture. Defaults to off; boolean true captures non-system message/tool content, while the object form lets you enable specific content classes.",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
title: "OpenTelemetry",
|
||||
@@ -23386,6 +23438,41 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
|
||||
help: "Interval in milliseconds for periodic telemetry flush from buffers to the collector. Increase to reduce export chatter, or lower for faster visibility during active incident response.",
|
||||
tags: ["observability", "performance"],
|
||||
},
|
||||
"diagnostics.otel.captureContent": {
|
||||
label: "OpenTelemetry Content Capture",
|
||||
help: "Opt-in OTEL span content capture. Defaults to off; boolean true captures non-system message/tool content, while the object form lets you enable specific content classes.",
|
||||
tags: ["observability"],
|
||||
},
|
||||
"diagnostics.otel.captureContent.enabled": {
|
||||
label: "OpenTelemetry Content Capture Enabled",
|
||||
help: "Master switch for granular OTEL content capture fields. Keep disabled unless your collector is approved for raw prompt, response, or tool content.",
|
||||
tags: ["observability"],
|
||||
},
|
||||
"diagnostics.otel.captureContent.inputMessages": {
|
||||
label: "OpenTelemetry Input Messages Capture",
|
||||
help: "Capture model input message text on OTEL spans when content capture is enabled.",
|
||||
tags: ["observability"],
|
||||
},
|
||||
"diagnostics.otel.captureContent.outputMessages": {
|
||||
label: "OpenTelemetry Output Messages Capture",
|
||||
help: "Capture model output message text on OTEL spans when content capture is enabled.",
|
||||
tags: ["observability"],
|
||||
},
|
||||
"diagnostics.otel.captureContent.toolInputs": {
|
||||
label: "OpenTelemetry Tool Inputs Capture",
|
||||
help: "Capture tool input text on OTEL spans when content capture is enabled.",
|
||||
tags: ["observability"],
|
||||
},
|
||||
"diagnostics.otel.captureContent.toolOutputs": {
|
||||
label: "OpenTelemetry Tool Outputs Capture",
|
||||
help: "Capture tool output text on OTEL spans when content capture is enabled.",
|
||||
tags: ["observability"],
|
||||
},
|
||||
"diagnostics.otel.captureContent.systemPrompt": {
|
||||
label: "OpenTelemetry System Prompt Capture",
|
||||
help: "Capture system prompt text on OTEL spans when content capture is enabled. This remains off unless explicitly enabled.",
|
||||
tags: ["observability"],
|
||||
},
|
||||
"diagnostics.cacheTrace.enabled": {
|
||||
label: "Cache Trace Enabled",
|
||||
help: "Log cache trace snapshots for embedded agent runs (default: false).",
|
||||
|
||||
@@ -548,6 +548,20 @@ export const FIELD_HELP: Record<string, string> = {
|
||||
"Trace sampling rate (0-1) controlling how much trace traffic is exported to observability backends. Lower rates reduce overhead/cost, while higher rates improve debugging fidelity.",
|
||||
"diagnostics.otel.flushIntervalMs":
|
||||
"Interval in milliseconds for periodic telemetry flush from buffers to the collector. Increase to reduce export chatter, or lower for faster visibility during active incident response.",
|
||||
"diagnostics.otel.captureContent":
|
||||
"Opt-in OTEL span content capture. Defaults to off; boolean true captures non-system message/tool content, while the object form lets you enable specific content classes.",
|
||||
"diagnostics.otel.captureContent.enabled":
|
||||
"Master switch for granular OTEL content capture fields. Keep disabled unless your collector is approved for raw prompt, response, or tool content.",
|
||||
"diagnostics.otel.captureContent.inputMessages":
|
||||
"Capture model input message text on OTEL spans when content capture is enabled.",
|
||||
"diagnostics.otel.captureContent.outputMessages":
|
||||
"Capture model output message text on OTEL spans when content capture is enabled.",
|
||||
"diagnostics.otel.captureContent.toolInputs":
|
||||
"Capture tool input text on OTEL spans when content capture is enabled.",
|
||||
"diagnostics.otel.captureContent.toolOutputs":
|
||||
"Capture tool output text on OTEL spans when content capture is enabled.",
|
||||
"diagnostics.otel.captureContent.systemPrompt":
|
||||
"Capture system prompt text on OTEL spans when content capture is enabled. This remains off unless explicitly enabled.",
|
||||
"diagnostics.cacheTrace.enabled":
|
||||
"Log cache trace snapshots for embedded agent runs (default: false).",
|
||||
"diagnostics.cacheTrace.filePath":
|
||||
|
||||
@@ -48,6 +48,13 @@ export const FIELD_LABELS: Record<string, string> = {
|
||||
"diagnostics.otel.logs": "OpenTelemetry Logs Enabled",
|
||||
"diagnostics.otel.sampleRate": "OpenTelemetry Trace Sample Rate",
|
||||
"diagnostics.otel.flushIntervalMs": "OpenTelemetry Flush Interval (ms)",
|
||||
"diagnostics.otel.captureContent": "OpenTelemetry Content Capture",
|
||||
"diagnostics.otel.captureContent.enabled": "OpenTelemetry Content Capture Enabled",
|
||||
"diagnostics.otel.captureContent.inputMessages": "OpenTelemetry Input Messages Capture",
|
||||
"diagnostics.otel.captureContent.outputMessages": "OpenTelemetry Output Messages Capture",
|
||||
"diagnostics.otel.captureContent.toolInputs": "OpenTelemetry Tool Inputs Capture",
|
||||
"diagnostics.otel.captureContent.toolOutputs": "OpenTelemetry Tool Outputs Capture",
|
||||
"diagnostics.otel.captureContent.systemPrompt": "OpenTelemetry System Prompt Capture",
|
||||
"diagnostics.cacheTrace.enabled": "Cache Trace Enabled",
|
||||
"diagnostics.cacheTrace.filePath": "Cache Trace File Path",
|
||||
"diagnostics.cacheTrace.includeMessages": "Cache Trace Include Messages",
|
||||
|
||||
@@ -244,6 +244,21 @@ export type DiagnosticsOtelConfig = {
|
||||
sampleRate?: number;
|
||||
/** Metric export interval (ms). */
|
||||
flushIntervalMs?: number;
|
||||
/**
|
||||
* Opt-in raw content capture for OTEL span attributes.
|
||||
* Boolean `true` captures non-system message/tool content; the object form
|
||||
* can enable each content class explicitly.
|
||||
*/
|
||||
captureContent?:
|
||||
| boolean
|
||||
| {
|
||||
enabled?: boolean;
|
||||
inputMessages?: boolean;
|
||||
outputMessages?: boolean;
|
||||
toolInputs?: boolean;
|
||||
toolOutputs?: boolean;
|
||||
systemPrompt?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type DiagnosticsCacheTraceConfig = {
|
||||
|
||||
@@ -299,6 +299,21 @@ export const OpenClawSchema = z
|
||||
logs: z.boolean().optional(),
|
||||
sampleRate: z.number().min(0).max(1).optional(),
|
||||
flushIntervalMs: z.number().int().nonnegative().optional(),
|
||||
captureContent: z
|
||||
.union([
|
||||
z.boolean(),
|
||||
z
|
||||
.object({
|
||||
enabled: z.boolean().optional(),
|
||||
inputMessages: z.boolean().optional(),
|
||||
outputMessages: z.boolean().optional(),
|
||||
toolInputs: z.boolean().optional(),
|
||||
toolOutputs: z.boolean().optional(),
|
||||
systemPrompt: z.boolean().optional(),
|
||||
})
|
||||
.strict(),
|
||||
])
|
||||
.optional(),
|
||||
})
|
||||
.strict()
|
||||
.optional(),
|
||||
|
||||
Reference in New Issue
Block a user