@smithers-orchestrator/observability 0.20.0 → 0.20.1

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,138 @@
1
+ /**
2
+ * @typedef {import('./agentTrace.ts').AgentFamily} AgentFamily
3
+ * @typedef {import('./agentTrace.ts').AgentTraceCapabilityProfile} AgentTraceCapabilityProfile
4
+ */
5
+
6
+ /** @type {Record<AgentFamily, AgentTraceCapabilityProfile>} */
7
+ export const agentTraceCapabilities = {
8
+ pi: {
9
+ sessionMetadata: true,
10
+ assistantTextDeltas: true,
11
+ visibleThinkingDeltas: true,
12
+ finalAssistantMessage: true,
13
+ toolExecutionStart: true,
14
+ toolExecutionUpdate: true,
15
+ toolExecutionEnd: true,
16
+ retryEvents: true,
17
+ compactionEvents: true,
18
+ rawStderrDiagnostics: true,
19
+ persistedSessionArtifact: true,
20
+ },
21
+ codex: {
22
+ sessionMetadata: false,
23
+ assistantTextDeltas: false,
24
+ visibleThinkingDeltas: false,
25
+ finalAssistantMessage: true,
26
+ toolExecutionStart: false,
27
+ toolExecutionUpdate: false,
28
+ toolExecutionEnd: false,
29
+ retryEvents: false,
30
+ compactionEvents: false,
31
+ rawStderrDiagnostics: true,
32
+ persistedSessionArtifact: false,
33
+ },
34
+ "claude-code": {
35
+ sessionMetadata: false,
36
+ assistantTextDeltas: true,
37
+ visibleThinkingDeltas: false,
38
+ finalAssistantMessage: true,
39
+ toolExecutionStart: false,
40
+ toolExecutionUpdate: false,
41
+ toolExecutionEnd: false,
42
+ retryEvents: false,
43
+ compactionEvents: false,
44
+ rawStderrDiagnostics: true,
45
+ persistedSessionArtifact: false,
46
+ },
47
+ gemini: {
48
+ sessionMetadata: false,
49
+ assistantTextDeltas: false,
50
+ visibleThinkingDeltas: false,
51
+ finalAssistantMessage: true,
52
+ toolExecutionStart: false,
53
+ toolExecutionUpdate: false,
54
+ toolExecutionEnd: false,
55
+ retryEvents: false,
56
+ compactionEvents: false,
57
+ rawStderrDiagnostics: true,
58
+ persistedSessionArtifact: false,
59
+ },
60
+ kimi: {
61
+ sessionMetadata: false,
62
+ assistantTextDeltas: false,
63
+ visibleThinkingDeltas: false,
64
+ finalAssistantMessage: true,
65
+ toolExecutionStart: false,
66
+ toolExecutionUpdate: false,
67
+ toolExecutionEnd: false,
68
+ retryEvents: false,
69
+ compactionEvents: false,
70
+ rawStderrDiagnostics: true,
71
+ persistedSessionArtifact: false,
72
+ },
73
+ openai: {
74
+ sessionMetadata: false,
75
+ assistantTextDeltas: false,
76
+ visibleThinkingDeltas: false,
77
+ finalAssistantMessage: true,
78
+ toolExecutionStart: true,
79
+ toolExecutionUpdate: false,
80
+ toolExecutionEnd: true,
81
+ retryEvents: false,
82
+ compactionEvents: false,
83
+ rawStderrDiagnostics: false,
84
+ persistedSessionArtifact: false,
85
+ },
86
+ anthropic: {
87
+ sessionMetadata: false,
88
+ assistantTextDeltas: false,
89
+ visibleThinkingDeltas: false,
90
+ finalAssistantMessage: true,
91
+ toolExecutionStart: true,
92
+ toolExecutionUpdate: false,
93
+ toolExecutionEnd: true,
94
+ retryEvents: false,
95
+ compactionEvents: false,
96
+ rawStderrDiagnostics: false,
97
+ persistedSessionArtifact: false,
98
+ },
99
+ amp: {
100
+ sessionMetadata: false,
101
+ assistantTextDeltas: false,
102
+ visibleThinkingDeltas: false,
103
+ finalAssistantMessage: true,
104
+ toolExecutionStart: true,
105
+ toolExecutionUpdate: false,
106
+ toolExecutionEnd: true,
107
+ retryEvents: false,
108
+ compactionEvents: false,
109
+ rawStderrDiagnostics: true,
110
+ persistedSessionArtifact: false,
111
+ },
112
+ forge: {
113
+ sessionMetadata: false,
114
+ assistantTextDeltas: false,
115
+ visibleThinkingDeltas: false,
116
+ finalAssistantMessage: true,
117
+ toolExecutionStart: true,
118
+ toolExecutionUpdate: false,
119
+ toolExecutionEnd: true,
120
+ retryEvents: false,
121
+ compactionEvents: false,
122
+ rawStderrDiagnostics: true,
123
+ persistedSessionArtifact: false,
124
+ },
125
+ unknown: {
126
+ sessionMetadata: false,
127
+ assistantTextDeltas: false,
128
+ visibleThinkingDeltas: false,
129
+ finalAssistantMessage: true,
130
+ toolExecutionStart: true,
131
+ toolExecutionUpdate: false,
132
+ toolExecutionEnd: true,
133
+ retryEvents: false,
134
+ compactionEvents: false,
135
+ rawStderrDiagnostics: true,
136
+ persistedSessionArtifact: false,
137
+ },
138
+ };
@@ -0,0 +1,52 @@
1
+ import {
2
+ buildOtelAttributes,
3
+ buildOtelLogRecord,
4
+ inferCanonicalSeverity,
5
+ } from "./_otelLogBuilders.js";
6
+ /**
7
+ * @typedef {import('./agentTrace.ts').CanonicalAgentTraceEvent} CanonicalAgentTraceEvent
8
+ * @typedef {import('./_otelLogBuilders.js').OtelLogRecord} OtelLogRecord
9
+ */
10
+
11
+ /**
12
+ * @param {CanonicalAgentTraceEvent} event
13
+ * @param {{ agentId?: string; model?: string }} [context]
14
+ * @returns {OtelLogRecord}
15
+ */
16
+ export function canonicalTraceEventToOtelLogRecord(event, context) {
17
+ const attributes = buildOtelAttributes({
18
+ "smithers.event.category": "agent-trace",
19
+ "smithers.trace.version": event.traceVersion,
20
+ "smithers.transcript.version": undefined,
21
+ "run.id": event.runId,
22
+ "workflow.path": event.workflowPath,
23
+ "workflow.hash": event.workflowHash,
24
+ "node.id": event.nodeId,
25
+ "node.iteration": event.iteration,
26
+ "node.attempt": event.attempt,
27
+ "agent.family": event.source.agentFamily,
28
+ "agent.id": context?.agentId,
29
+ "agent.model": context?.model,
30
+ "agent.capture_mode": event.source.captureMode,
31
+ "trace.completeness": event.traceCompleteness,
32
+ "event.kind": event.event.kind,
33
+ "event.phase": event.event.phase,
34
+ "event.sequence": event.event.sequence,
35
+ "source.raw_type": event.source.rawType,
36
+ "source.raw_event_id": event.source.rawEventId,
37
+ "source.observed": event.source.observed,
38
+ "session.row_type": undefined,
39
+ "session.row_sequence": undefined,
40
+ "session.ingest_source": undefined,
41
+ "session.observed_live": undefined,
42
+ "provider.session_id": undefined,
43
+ "provider.thread_id": undefined,
44
+ }, event.annotations);
45
+ return buildOtelLogRecord({
46
+ category: "agent-trace",
47
+ payload: event.payload,
48
+ raw: event.raw,
49
+ redaction: event.redaction,
50
+ annotations: event.annotations,
51
+ }, attributes, inferCanonicalSeverity(event));
52
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @typedef {import('./agentTrace.ts').AgentFamily} AgentFamily
3
+ */
4
+
5
+ /**
6
+ * @param {any} agent
7
+ * @returns {AgentFamily}
8
+ */
9
+ export function detectAgentFamily(agent) {
10
+ const constructorName = String(agent?.constructor?.name ?? "").toLowerCase();
11
+ const idName = String(agent?.id ?? "").toLowerCase();
12
+ const name = constructorName && constructorName !== "object"
13
+ ? `${constructorName} ${idName}`
14
+ : idName;
15
+ if (name.includes("codex")) return "codex";
16
+ if (name.includes("claude")) return "claude-code";
17
+ if (name.includes("gemini")) return "gemini";
18
+ if (name.includes("kimi")) return "kimi";
19
+ if (name.includes("openai")) return "openai";
20
+ if (name.includes("anthropic")) return "anthropic";
21
+ if (name.includes("amp")) return "amp";
22
+ if (name.includes("forge")) return "forge";
23
+ if (constructorName.includes("piagent") ||
24
+ /(?:^|[-_\s])pi(?:$|[-_\s])/.test(idName)) {
25
+ return "pi";
26
+ }
27
+ return "unknown";
28
+ }
@@ -0,0 +1,24 @@
1
+ import { detectAgentFamily } from "./detectAgentFamily.js";
2
+ /**
3
+ * @typedef {import('./agentTrace.ts').AgentCaptureMode} AgentCaptureMode
4
+ */
5
+
6
+ /**
7
+ * @param {any} agent
8
+ * @returns {AgentCaptureMode}
9
+ */
10
+ export function detectCaptureMode(agent) {
11
+ const family = detectAgentFamily(agent);
12
+ const mode = agent?.opts?.mode ?? agent?.mode;
13
+ if (family === "pi") {
14
+ if (mode === "rpc") return "rpc-events";
15
+ if (mode === "json") return "cli-json-stream";
16
+ return "cli-text";
17
+ }
18
+ if (family === "codex") return "cli-json-stream";
19
+ const outputFormat = agent?.opts?.outputFormat ?? agent?.outputFormat;
20
+ if (family === "openai" || family === "anthropic") return "sdk-events";
21
+ if (outputFormat === "stream-json") return "cli-json-stream";
22
+ if (outputFormat === "json" || agent?.opts?.json) return "cli-json";
23
+ return "cli-text";
24
+ }
@@ -0,0 +1,19 @@
1
+ import { logErrorAwait, logInfoAwait, logWarningAwait } from "./logging.js";
2
+ /**
3
+ * @typedef {import('./_otelLogBuilders.js').OtelLogRecord} OtelLogRecord
4
+ */
5
+
6
+ /**
7
+ * @param {"agent-trace" | "agent-session"} category
8
+ * @param {OtelLogRecord} record
9
+ * @returns {Promise<void>}
10
+ */
11
+ export async function emitOtelLogRecord(category, record) {
12
+ if (record.severity === "ERROR") {
13
+ await logErrorAwait(record.body, record.attributes, category);
14
+ } else if (record.severity === "WARN") {
15
+ await logWarningAwait(record.body, record.attributes, category);
16
+ } else {
17
+ await logInfoAwait(record.body, record.attributes, category);
18
+ }
19
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @typedef {import('./agentTrace.ts').CanonicalAgentTraceEventKind} CanonicalAgentTraceEventKind
3
+ * @typedef {import('./agentTrace.ts').CanonicalAgentTraceEventPhase} CanonicalAgentTraceEventPhase
4
+ */
5
+
6
+ /**
7
+ * @param {CanonicalAgentTraceEventKind} kind
8
+ * @returns {CanonicalAgentTraceEventPhase}
9
+ */
10
+ export function kindPhase(kind) {
11
+ if (kind.startsWith("session.")) return "session";
12
+ if (kind.startsWith("turn.")) return "turn";
13
+ if (kind.startsWith("message.") || kind.startsWith("assistant.")) return "message";
14
+ if (kind.startsWith("tool.")) return "tool";
15
+ if (kind.startsWith("artifact.")) return "artifact";
16
+ return "capture";
17
+ }
package/src/logging.js CHANGED
@@ -35,10 +35,9 @@ const minLevel = resolveMinLevel();
35
35
  * @param {Effect.Effect<void, never, never>} effect
36
36
  * @param {LogAnnotations} [annotations]
37
37
  * @param {string} [span]
38
- * @param {number} [level]
38
+ * @returns {Effect.Effect<void, never, never> | null}
39
39
  */
40
- function emitLog(effect, annotations, span, level = LOG_LEVEL_INFO) {
41
- if (level < minLevel) return;
40
+ function buildLogProgram(effect, annotations, span) {
42
41
  const correlationAnnotations = correlationContextToLogAnnotations(getCurrentCorrelationContext());
43
42
  const traceAnnotations = getCurrentSmithersTraceAnnotations();
44
43
  const mergedAnnotations = correlationAnnotations || traceAnnotations || annotations
@@ -55,7 +54,32 @@ function emitLog(effect, annotations, span, level = LOG_LEVEL_INFO) {
55
54
  if (span) {
56
55
  program = program.pipe(Effect.withLogSpan(span));
57
56
  }
58
- void Effect.runFork(withCurrentCorrelationContext(program));
57
+ return withCurrentCorrelationContext(program);
58
+ }
59
+
60
+ /**
61
+ * @param {Effect.Effect<void, never, never>} effect
62
+ * @param {LogAnnotations} [annotations]
63
+ * @param {string} [span]
64
+ * @param {number} [level]
65
+ */
66
+ function emitLog(effect, annotations, span, level = LOG_LEVEL_INFO) {
67
+ if (level < minLevel) return;
68
+ const program = buildLogProgram(effect, annotations, span);
69
+ if (program) void Effect.runFork(program);
70
+ }
71
+
72
+ /**
73
+ * @param {Effect.Effect<void, never, never>} effect
74
+ * @param {LogAnnotations} [annotations]
75
+ * @param {string} [span]
76
+ * @param {number} [level]
77
+ * @returns {Promise<void>}
78
+ */
79
+ async function emitLogAwait(effect, annotations, span, level = LOG_LEVEL_INFO) {
80
+ if (level < minLevel) return;
81
+ const program = buildLogProgram(effect, annotations, span);
82
+ if (program) await Effect.runPromise(program);
59
83
  }
60
84
  /**
61
85
  * @param {string} message
@@ -89,3 +113,39 @@ export function logWarning(message, annotations, span) {
89
113
  export function logError(message, annotations, span) {
90
114
  emitLog(Effect.logError(message), annotations, span, LOG_LEVEL_ERROR);
91
115
  }
116
+ /**
117
+ * @param {string} message
118
+ * @param {LogAnnotations} [annotations]
119
+ * @param {string} [span]
120
+ * @returns {Promise<void>}
121
+ */
122
+ export async function logDebugAwait(message, annotations, span) {
123
+ await emitLogAwait(Effect.logDebug(message), annotations, span, LOG_LEVEL_DEBUG);
124
+ }
125
+ /**
126
+ * @param {string} message
127
+ * @param {LogAnnotations} [annotations]
128
+ * @param {string} [span]
129
+ * @returns {Promise<void>}
130
+ */
131
+ export async function logInfoAwait(message, annotations, span) {
132
+ await emitLogAwait(Effect.logInfo(message), annotations, span, LOG_LEVEL_INFO);
133
+ }
134
+ /**
135
+ * @param {string} message
136
+ * @param {LogAnnotations} [annotations]
137
+ * @param {string} [span]
138
+ * @returns {Promise<void>}
139
+ */
140
+ export async function logWarningAwait(message, annotations, span) {
141
+ await emitLogAwait(Effect.logWarning(message), annotations, span, LOG_LEVEL_WARNING);
142
+ }
143
+ /**
144
+ * @param {string} message
145
+ * @param {LogAnnotations} [annotations]
146
+ * @param {string} [span]
147
+ * @returns {Promise<void>}
148
+ */
149
+ export async function logErrorAwait(message, annotations, span) {
150
+ await emitLogAwait(Effect.logError(message), annotations, span, LOG_LEVEL_ERROR);
151
+ }
@@ -0,0 +1,15 @@
1
+ import { normalizeStructuredEventForFamily } from "./_traceEventNormalizers.js";
2
+ /**
3
+ * @typedef {import('./agentTrace.ts').AgentFamily} AgentFamily
4
+ * @typedef {import('./_traceEventNormalizers.js').NormalizedTraceBatch} NormalizedTraceBatch
5
+ */
6
+
7
+ /**
8
+ * @param {AgentFamily} agentFamily
9
+ * @param {any} parsed
10
+ * @param {string} rawType
11
+ * @returns {NormalizedTraceBatch}
12
+ */
13
+ export function normalizeStructuredEvent(agentFamily, parsed, rawType) {
14
+ return normalizeStructuredEventForFamily(agentFamily, parsed, rawType);
15
+ }
@@ -0,0 +1,59 @@
1
+ import { agentTraceCapabilities } from "./agentTraceCapabilities.js";
2
+ /**
3
+ * @typedef {import('./agentTrace.ts').AgentFamily} AgentFamily
4
+ * @typedef {import('./agentTrace.ts').AgentCaptureMode} AgentCaptureMode
5
+ * @typedef {import('./agentTrace.ts').AgentTraceCapabilityProfile} AgentTraceCapabilityProfile
6
+ */
7
+
8
+ /**
9
+ * @param {AgentFamily} agentFamily
10
+ * @param {AgentCaptureMode} captureMode
11
+ * @returns {AgentTraceCapabilityProfile}
12
+ */
13
+ export function resolveAgentTraceCapabilities(agentFamily, captureMode) {
14
+ const base = {
15
+ ...agentTraceCapabilities[agentFamily],
16
+ // Smithers persists a canonical NDJSON trace artifact for every successful
17
+ // flush regardless of the upstream agent family.
18
+ persistedSessionArtifact: true,
19
+ };
20
+ if (captureMode === "sdk-events" || captureMode === "cli-text") {
21
+ return base;
22
+ }
23
+ if (agentFamily === "codex") {
24
+ return {
25
+ ...base,
26
+ assistantTextDeltas: captureMode === "cli-json-stream",
27
+ toolExecutionStart: captureMode === "cli-json-stream",
28
+ toolExecutionUpdate: captureMode === "cli-json-stream",
29
+ toolExecutionEnd: captureMode === "cli-json-stream",
30
+ };
31
+ }
32
+ if (agentFamily === "claude-code") {
33
+ return {
34
+ ...base,
35
+ toolExecutionStart: captureMode === "cli-json-stream",
36
+ toolExecutionUpdate: captureMode === "cli-json-stream",
37
+ toolExecutionEnd: captureMode === "cli-json-stream",
38
+ };
39
+ }
40
+ if (agentFamily === "gemini") {
41
+ return {
42
+ ...base,
43
+ assistantTextDeltas: captureMode === "cli-json-stream",
44
+ toolExecutionStart: captureMode === "cli-json-stream",
45
+ toolExecutionUpdate: captureMode === "cli-json-stream",
46
+ toolExecutionEnd: captureMode === "cli-json-stream",
47
+ };
48
+ }
49
+ if (agentFamily === "kimi") {
50
+ return {
51
+ ...base,
52
+ assistantTextDeltas: captureMode === "cli-json-stream",
53
+ toolExecutionStart: captureMode === "cli-json-stream",
54
+ toolExecutionUpdate: captureMode === "cli-json-stream",
55
+ toolExecutionEnd: captureMode === "cli-json-stream",
56
+ };
57
+ }
58
+ return base;
59
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @typedef {import('./agentTrace.ts').AgentTraceCapabilityProfile} AgentTraceCapabilityProfile
3
+ * @typedef {import('./agentTrace.ts').CanonicalAgentTraceEventKind} CanonicalAgentTraceEventKind
4
+ */
5
+
6
+ /** @type {Array<[keyof AgentTraceCapabilityProfile, CanonicalAgentTraceEventKind[]]>} */
7
+ const capabilityKindMap = [
8
+ ["sessionMetadata", ["session.start", "session.end"]],
9
+ ["assistantTextDeltas", ["assistant.text.delta"]],
10
+ ["visibleThinkingDeltas", ["assistant.thinking.delta"]],
11
+ ["finalAssistantMessage", ["assistant.message.final"]],
12
+ ["toolExecutionStart", ["tool.execution.start"]],
13
+ ["toolExecutionUpdate", ["tool.execution.update"]],
14
+ ["toolExecutionEnd", ["tool.execution.end", "tool.result"]],
15
+ ["retryEvents", ["retry.start", "retry.end"]],
16
+ ["compactionEvents", ["compaction.start", "compaction.end"]],
17
+ ["rawStderrDiagnostics", ["stderr"]],
18
+ ["persistedSessionArtifact", ["artifact.created"]],
19
+ ];
20
+
21
+ /**
22
+ * @param {AgentTraceCapabilityProfile} profile
23
+ * @returns {CanonicalAgentTraceEventKind[]}
24
+ */
25
+ export function unsupportedKindsForCapabilities(profile) {
26
+ /** @type {CanonicalAgentTraceEventKind[]} */
27
+ const kinds = [];
28
+ for (const [field, mappedKinds] of capabilityKindMap) {
29
+ if (!profile[field]) kinds.push(...mappedKinds);
30
+ }
31
+ return kinds;
32
+ }