@kuralle-agents/core 0.13.0 → 0.14.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.
Files changed (158) hide show
  1. package/README.md +4 -4
  2. package/dist/ai-sdk/uiMessageStream.d.ts +2 -2
  3. package/dist/ai-sdk/uiMessageStream.js +42 -29
  4. package/dist/capabilities/LivePromptAssembler.d.ts +1 -1
  5. package/dist/capabilities/LivePromptAssembler.js +1 -1
  6. package/dist/capabilities/index.d.ts +1 -1
  7. package/dist/eval/EvalRunner.js +4 -4
  8. package/dist/eval/simulation.js +4 -3
  9. package/dist/events/TurnHandle.d.ts +3 -3
  10. package/dist/flow/collectUntilComplete.js +4 -4
  11. package/dist/flow/degrade.js +8 -4
  12. package/dist/flow/emitInteractive.d.ts +2 -2
  13. package/dist/flow/emitInteractive.js +6 -3
  14. package/dist/flow/extraction.d.ts +2 -2
  15. package/dist/flow/extraction.js +10 -4
  16. package/dist/flow/reduceTransition.d.ts +2 -2
  17. package/dist/flow/reduceTransition.js +7 -3
  18. package/dist/flow/runFlow.js +34 -12
  19. package/dist/foundation/AgentStateController.d.ts +1 -1
  20. package/dist/foundation/ToolExecutor.d.ts +2 -2
  21. package/dist/foundation/index.d.ts +0 -11
  22. package/dist/foundation/index.js +0 -5
  23. package/dist/index.d.ts +9 -14
  24. package/dist/index.js +5 -6
  25. package/dist/outcomes/streamPart.d.ts +2 -2
  26. package/dist/outcomes/streamPart.js +2 -3
  27. package/dist/prompts/PromptBuilder.d.ts +3 -3
  28. package/dist/prompts/PromptBuilder.js +6 -6
  29. package/dist/prompts/index.d.ts +1 -1
  30. package/dist/prompts/types.d.ts +7 -2
  31. package/dist/runtime/InMemoryRetrievalCache.d.ts +1 -1
  32. package/dist/runtime/KnowledgeProvider.d.ts +3 -2
  33. package/dist/runtime/KnowledgeProvider.js +54 -30
  34. package/dist/runtime/Runtime.d.ts +3 -1
  35. package/dist/runtime/Runtime.js +84 -30
  36. package/dist/runtime/TraceRecorder.d.ts +22 -4
  37. package/dist/runtime/TraceRecorder.js +65 -29
  38. package/dist/runtime/buildAgentToolSurface.js +1 -1
  39. package/dist/runtime/channels/TextDriver.js +12 -9
  40. package/dist/runtime/channels/executeModelTool.d.ts +6 -0
  41. package/dist/runtime/channels/executeModelTool.js +70 -10
  42. package/dist/runtime/channels/extractionTurn.js +1 -1
  43. package/dist/runtime/channels/index.d.ts +0 -3
  44. package/dist/runtime/channels/index.js +0 -6
  45. package/dist/runtime/channels/streaming/hostControlSpeak.js +5 -1
  46. package/dist/runtime/channels/streaming/speakGated.js +24 -12
  47. package/dist/runtime/citations/index.d.ts +3 -3
  48. package/dist/runtime/closeRun.js +3 -2
  49. package/dist/runtime/controlFlowSignal.d.ts +24 -0
  50. package/dist/runtime/controlFlowSignal.js +29 -0
  51. package/dist/runtime/ctx.d.ts +3 -4
  52. package/dist/runtime/ctx.js +39 -5
  53. package/dist/runtime/durable/RunStore.js +1 -1
  54. package/dist/runtime/grounding/gather.d.ts +1 -1
  55. package/dist/runtime/grounding/inMemoryKnowledge.d.ts +1 -1
  56. package/dist/runtime/grounding/knowledge.d.ts +1 -1
  57. package/dist/runtime/hostLoop.js +26 -6
  58. package/dist/runtime/index.d.ts +1 -2
  59. package/dist/runtime/index.js +1 -1
  60. package/dist/runtime/outcomeMarking.d.ts +2 -2
  61. package/dist/runtime/outcomeMarking.js +2 -1
  62. package/dist/runtime/policies/agentTurn.d.ts +1 -1
  63. package/dist/runtime/runHookSafely.d.ts +3 -0
  64. package/dist/runtime/runHookSafely.js +19 -0
  65. package/dist/scheduler/index.d.ts +2 -2
  66. package/dist/scheduler/index.js +1 -1
  67. package/dist/services/TracingService.js +2 -3
  68. package/dist/skills/collectSkills.d.ts +11 -3
  69. package/dist/skills/collectSkills.js +51 -8
  70. package/dist/skills/compositeSkillStore.d.ts +26 -0
  71. package/dist/skills/compositeSkillStore.js +54 -0
  72. package/dist/skills/defineSkill.d.ts +21 -0
  73. package/dist/skills/defineSkill.js +26 -0
  74. package/dist/skills/fsSkillStore.d.ts +3 -0
  75. package/dist/skills/fsSkillStore.js +79 -0
  76. package/dist/skills/index.d.ts +4 -0
  77. package/dist/skills/index.js +4 -0
  78. package/dist/skills/parseSkillFrontmatter.d.ts +14 -0
  79. package/dist/skills/parseSkillFrontmatter.js +183 -0
  80. package/dist/skills/wireAgentSkills.d.ts +2 -1
  81. package/dist/skills/wireAgentSkills.js +2 -2
  82. package/dist/testing/mocks.d.ts +3 -3
  83. package/dist/testing/mocks.js +7 -3
  84. package/dist/tools/controlResults.d.ts +14 -0
  85. package/dist/tools/controlResults.js +15 -0
  86. package/dist/tools/effect/ToolExecutor.d.ts +6 -0
  87. package/dist/tools/effect/ToolExecutor.js +71 -35
  88. package/dist/tools/effect/defineTool.d.ts +9 -0
  89. package/dist/tools/effect/defineTool.js +1 -0
  90. package/dist/tools/effect/errors.d.ts +2 -5
  91. package/dist/tools/effect/errors.js +2 -5
  92. package/dist/tools/index.d.ts +0 -1
  93. package/dist/tools/index.js +0 -1
  94. package/dist/types/effectTool.d.ts +2 -0
  95. package/dist/types/flow.d.ts +1 -1
  96. package/dist/types/guardrails.d.ts +5 -0
  97. package/dist/types/hooks.d.ts +2 -2
  98. package/dist/types/index.d.ts +25 -16
  99. package/dist/types/index.js +8 -13
  100. package/dist/types/knowledge.d.ts +213 -0
  101. package/dist/types/processors.d.ts +0 -58
  102. package/dist/types/run-context.d.ts +3 -7
  103. package/dist/types/runtime.d.ts +3 -74
  104. package/dist/types/session.d.ts +1 -1
  105. package/dist/types/skills.d.ts +15 -1
  106. package/dist/types/stream.d.ts +133 -71
  107. package/dist/types/stream.js +34 -1
  108. package/dist/types/trace.d.ts +2 -0
  109. package/guides/AGENTS.md +3 -3
  110. package/guides/FLOWS.md +1 -1
  111. package/guides/GETTING_STARTED.md +1 -1
  112. package/guides/RUNTIME.md +20 -15
  113. package/guides/TOOLS.md +1 -1
  114. package/package.json +3 -13
  115. package/dist/foundation/AgentDefinition.d.ts +0 -17
  116. package/dist/foundation/ConversationEventLog.d.ts +0 -71
  117. package/dist/foundation/ConversationEventLog.js +0 -1
  118. package/dist/foundation/ConversationState.d.ts +0 -33
  119. package/dist/foundation/ConversationState.js +0 -1
  120. package/dist/foundation/DefaultConversationEventLog.d.ts +0 -27
  121. package/dist/foundation/DefaultConversationEventLog.js +0 -205
  122. package/dist/foundation/DefaultConversationState.d.ts +0 -36
  123. package/dist/foundation/DefaultConversationState.js +0 -103
  124. package/dist/foundation/DefaultToolExecutor.d.ts +0 -57
  125. package/dist/foundation/DefaultToolExecutor.js +0 -132
  126. package/dist/foundation/createFoundation.d.ts +0 -32
  127. package/dist/foundation/createFoundation.js +0 -33
  128. package/dist/hooks/HookRunner.d.ts +0 -37
  129. package/dist/hooks/HookRunner.js +0 -111
  130. package/dist/hooks/builtin/logging.d.ts +0 -5
  131. package/dist/hooks/builtin/logging.js +0 -98
  132. package/dist/hooks/builtin/metrics.d.ts +0 -7
  133. package/dist/hooks/builtin/metrics.js +0 -60
  134. package/dist/hooks/builtin/observability.d.ts +0 -20
  135. package/dist/hooks/builtin/observability.js +0 -572
  136. package/dist/hooks/helpers.d.ts +0 -22
  137. package/dist/hooks/helpers.js +0 -183
  138. package/dist/hooks/index.d.ts +0 -8
  139. package/dist/hooks/index.js +0 -6
  140. package/dist/realtime/RealtimeAudioClient.d.ts +0 -166
  141. package/dist/realtime/RealtimeAudioClient.js +0 -14
  142. package/dist/realtime/RealtimeSessionHandle.d.ts +0 -6
  143. package/dist/realtime/RealtimeSessionHandle.js +0 -1
  144. package/dist/realtime/index.d.ts +0 -2
  145. package/dist/realtime/index.js +0 -1
  146. package/dist/runtime/channels/VoiceDriver.d.ts +0 -31
  147. package/dist/runtime/channels/VoiceDriver.js +0 -321
  148. package/dist/runtime/channels/voiceTools.d.ts +0 -9
  149. package/dist/runtime/channels/voiceTools.js +0 -51
  150. package/dist/tools/errorHandling.d.ts +0 -35
  151. package/dist/tools/errorHandling.js +0 -145
  152. package/dist/types/voice.d.ts +0 -517
  153. package/dist/types/voice.js +0 -14
  154. package/dist/utils/isRecord.d.ts +0 -1
  155. package/dist/utils/isRecord.js +0 -3
  156. package/dist/utils/messageNormalization.d.ts +0 -2
  157. package/dist/utils/messageNormalization.js +0 -137
  158. /package/dist/{foundation/AgentDefinition.js → types/knowledge.js} +0 -0
@@ -1,572 +0,0 @@
1
- import { mkdir, writeFile } from 'node:fs/promises';
2
- import { dirname } from 'node:path';
3
- import { TracingService } from '../../services/TracingService.js';
4
- function percentile(sortedAsc, p) {
5
- if (sortedAsc.length === 0)
6
- return 0;
7
- const idx = Math.min(sortedAsc.length - 1, Math.max(0, Math.ceil((p / 100) * sortedAsc.length) - 1));
8
- return sortedAsc[idx];
9
- }
10
- function computeLatency(turns) {
11
- if (turns.length === 0) {
12
- return { avgTurnMs: 0, p50TurnMs: 0, p95TurnMs: 0, firstResponseMs: 0 };
13
- }
14
- const sorted = [...turns].sort((a, b) => a - b);
15
- const sum = turns.reduce((a, b) => a + b, 0);
16
- return {
17
- avgTurnMs: sum / turns.length,
18
- p50TurnMs: percentile(sorted, 50),
19
- p95TurnMs: percentile(sorted, 95),
20
- firstResponseMs: turns[0] ?? 0,
21
- };
22
- }
23
- function resolveExporter(config) {
24
- const exp = config?.exporter ?? 'console';
25
- if (exp === 'console') {
26
- return async (trace) => {
27
- console.log(formatConsoleSummary(trace));
28
- };
29
- }
30
- if (exp === 'json') {
31
- return async (trace) => {
32
- const out = config?.outputPath ?? `./.kuralle-traces/session-${trace.sessionId}.json`;
33
- await mkdir(dirname(out), { recursive: true });
34
- await writeFile(out, `${JSON.stringify(trace, replacerForJson, 2)}\n`, 'utf8');
35
- };
36
- }
37
- return exp;
38
- }
39
- function replacerForJson(_key, value) {
40
- if (value instanceof Error) {
41
- return { name: value.name, message: value.message, stack: value.stack };
42
- }
43
- return value;
44
- }
45
- function formatConsoleSummary(trace) {
46
- const lines = [
47
- `[Kuralle observability] session=${trace.sessionId} agent=${trace.agentId}`,
48
- ` durationMs=${trace.durationMs} success=${trace.success} turns=${trace.turnCount}`,
49
- ` tools=${trace.toolCalls.map(t => `${t.name}(${t.durationMs}ms, ok=${t.success})`).join(', ') || '(none)'}`,
50
- ` latency avgTurn=${Math.round(trace.latency.avgTurnMs)}ms p50=${Math.round(trace.latency.p50TurnMs)}ms p95=${Math.round(trace.latency.p95TurnMs)}ms firstResponse=${Math.round(trace.latency.firstResponseMs)}ms`,
51
- ];
52
- if (trace.handoffs.length) {
53
- lines.push(` handoffs=${trace.handoffs.map(h => `${h.from}->${h.to}`).join(', ')}`);
54
- }
55
- if (trace.errors.length) {
56
- lines.push(` errors=${trace.errors.map(e => e.message).join('; ')}`);
57
- }
58
- if (trace.voice) {
59
- lines.push(` voice bargeIn=${trace.voice.bargeInCount} reconfigure=${trace.voice.reconfigureCount}`);
60
- }
61
- return lines.join('\n');
62
- }
63
- function collectToolCallsFromSpans(spans) {
64
- const out = [];
65
- for (const s of spans) {
66
- if (s.name !== 'tool.call')
67
- continue;
68
- const tool = s.attributes.tool;
69
- const name = typeof tool === 'string' ? tool : typeof tool === 'number' ? String(tool) : 'unknown';
70
- const start = s.startTime;
71
- const end = s.endTime ?? start;
72
- const ok = s.status !== 'error';
73
- out.push({ name, durationMs: Math.max(0, end - start), success: ok });
74
- }
75
- return out;
76
- }
77
- function buildSessionTrace(state, session, metadata, endTime) {
78
- const startTime = state.rootSpan.startTime;
79
- const turns = state.turnDurationsMs;
80
- const toolCalls = collectToolCallsFromSpans(state.spans);
81
- const voice = state.voice;
82
- const hasVoice = voice.bargeInCount > 0 ||
83
- voice.reconfigureCount > 0 ||
84
- voice.totalAudioInBytes > 0 ||
85
- voice.totalAudioOutBytes > 0 ||
86
- voice.timeToFirstAudioMs.length > 0;
87
- const usage = state.perTurnUsage;
88
- const lastTurn = usage.length > 0 ? usage[usage.length - 1] : undefined;
89
- const peakUtil = usage.reduce((m, t) => Math.max(m, t.contextUtilization ?? 0), 0);
90
- const totalCacheRead = usage.reduce((s, t) => s + (t.cacheReadTokens ?? 0), 0);
91
- return {
92
- sessionId: session.id,
93
- agentId: session.activeAgentId ?? session.currentAgent,
94
- startTime,
95
- endTime,
96
- durationMs: Math.max(0, endTime - startTime),
97
- success: metadata.success,
98
- turnCount: metadata.turnCount ?? turns.length,
99
- toolCalls,
100
- flowTransitions: state.flowTransitions,
101
- handoffs: state.handoffs,
102
- extractionSubmissions: state.extractionSubmissions,
103
- errors: state.errors,
104
- latency: computeLatency(turns),
105
- voice: hasVoice
106
- ? {
107
- bargeInCount: voice.bargeInCount,
108
- reconfigureCount: voice.reconfigureCount,
109
- totalAudioInBytes: voice.totalAudioInBytes,
110
- totalAudioOutBytes: voice.totalAudioOutBytes,
111
- avgTimeToFirstAudioMs: voice.timeToFirstAudioMs.length > 0
112
- ? voice.timeToFirstAudioMs.reduce((a, b) => a + b, 0) / voice.timeToFirstAudioMs.length
113
- : 0,
114
- }
115
- : undefined,
116
- spans: state.spans,
117
- ...(lastTurn
118
- ? {
119
- totalInputTokens: lastTurn.cumulativeInputTokens,
120
- totalOutputTokens: lastTurn.cumulativeOutputTokens,
121
- totalTokens: lastTurn.cumulativeTotalTokens,
122
- totalCacheReadTokens: totalCacheRead,
123
- peakContextUtilization: peakUtil,
124
- perTurnUsage: usage,
125
- }
126
- : {}),
127
- };
128
- }
129
- export function createObservabilityHooks(config) {
130
- const tracer = new TracingService({
131
- serviceName: config?.serviceName ?? 'kuralle',
132
- });
133
- const exportTrace = resolveExporter(config);
134
- const metrics = config?.metrics;
135
- const includeContent = config?.includeContent ?? false;
136
- const traceStream = config?.traceStream;
137
- const emitTrace = (event) => {
138
- if (!traceStream)
139
- return;
140
- try {
141
- traceStream(event);
142
- }
143
- catch (err) {
144
- console.error('[Kuralle observability] traceStream error:', err);
145
- }
146
- };
147
- const sessionRoots = new Map();
148
- const sessionState = new Map();
149
- const toolSpans = new Map();
150
- const activeAgentSpan = new Map();
151
- /** Text-mode trace export: one debounced export per session (avoids N exports for N turns). */
152
- const TEXT_EXPORT_DEBOUNCE_MS = 3000;
153
- const pendingTextExportTimers = new Map();
154
- const pendingTextExportMeta = new Map();
155
- const cancelPendingTextExport = (sessionId) => {
156
- const t = pendingTextExportTimers.get(sessionId);
157
- if (t !== undefined) {
158
- clearTimeout(t);
159
- pendingTextExportTimers.delete(sessionId);
160
- }
161
- pendingTextExportMeta.delete(sessionId);
162
- };
163
- const ensureState = (root) => {
164
- const id = root.attributes.sessionId;
165
- const sid = typeof id === 'string' ? id : String(id ?? '');
166
- let st = sessionState.get(sid);
167
- if (!st) {
168
- st = {
169
- rootSpan: root,
170
- spans: [],
171
- turnDurationsMs: [],
172
- flowTransitions: [],
173
- handoffs: [],
174
- extractionSubmissions: [],
175
- errors: [],
176
- voice: {
177
- bargeInCount: 0,
178
- reconfigureCount: 0,
179
- totalAudioInBytes: 0,
180
- totalAudioOutBytes: 0,
181
- timeToFirstAudioMs: [],
182
- },
183
- perTurnUsage: [],
184
- };
185
- sessionState.set(sid, st);
186
- }
187
- return st;
188
- };
189
- const pushSpan = (sessionId, span) => {
190
- const root = sessionRoots.get(sessionId);
191
- if (!root)
192
- return;
193
- const st = ensureState(root);
194
- st.spans.push(span);
195
- };
196
- return {
197
- onStart: async (ctx) => {
198
- if (!ctx?.session?.id)
199
- return;
200
- const sid = ctx.session.id;
201
- if (!sessionRoots.has(sid)) {
202
- const span = tracer.startSpan('session', {
203
- sessionId: sid,
204
- agentId: ctx.agentId,
205
- service: config?.serviceName ?? 'kuralle',
206
- });
207
- sessionRoots.set(sid, span);
208
- const st = ensureState(span);
209
- st.rootSpan = span;
210
- st.spans.push(span);
211
- emitTrace({
212
- type: 'session:start',
213
- sessionId: sid,
214
- agentId: ctx.agentId,
215
- timestamp: span.startTime,
216
- });
217
- emitTrace({
218
- type: 'span:start',
219
- spanId: span.id,
220
- name: span.name,
221
- timestamp: span.startTime,
222
- attributes: { sessionId: sid, agentId: ctx.agentId },
223
- });
224
- }
225
- },
226
- onAgentStart: async (ctx, agentId) => {
227
- if (!ctx?.session?.id)
228
- return;
229
- const root = sessionRoots.get(ctx.session.id);
230
- if (!root)
231
- return;
232
- const prev = activeAgentSpan.get(ctx.session.id);
233
- if (prev) {
234
- tracer.endSpan(prev, 'success');
235
- }
236
- const span = tracer.startSpan('agent.activate', { agentId }, root.id);
237
- activeAgentSpan.set(ctx.session.id, span);
238
- pushSpan(ctx.session.id, span);
239
- emitTrace({
240
- type: 'span:start',
241
- spanId: span.id,
242
- parentId: root.id,
243
- name: span.name,
244
- timestamp: span.startTime,
245
- attributes: { agentId },
246
- });
247
- },
248
- onAgentEnd: async (ctx, _agentId) => {
249
- if (!ctx?.session?.id)
250
- return;
251
- const span = activeAgentSpan.get(ctx.session.id);
252
- if (span) {
253
- tracer.endSpan(span, 'success');
254
- const durationMs = Math.max(0, (span.endTime ?? Date.now()) - span.startTime);
255
- emitTrace({ type: 'span:end', spanId: span.id, durationMs, status: 'success' });
256
- activeAgentSpan.delete(ctx.session.id);
257
- }
258
- },
259
- onToolCall: async (ctx, call) => {
260
- if (!ctx?.session?.id)
261
- return;
262
- const root = sessionRoots.get(ctx.session.id);
263
- if (!root)
264
- return;
265
- const parent = activeAgentSpan.get(ctx.session.id) ?? root;
266
- const span = tracer.startSpan('tool.call', { tool: call.toolName, toolCallId: call.toolCallId }, parent.id);
267
- toolSpans.set(call.toolCallId, span);
268
- pushSpan(ctx.session.id, span);
269
- emitTrace({
270
- type: 'span:start',
271
- spanId: span.id,
272
- parentId: parent.id,
273
- name: 'tool.call',
274
- timestamp: span.startTime,
275
- attributes: { tool: call.toolName },
276
- });
277
- emitTrace({
278
- type: 'tool:call',
279
- toolName: call.toolName,
280
- args: call.args,
281
- timestamp: span.startTime,
282
- });
283
- },
284
- onToolResult: async (ctx, call) => {
285
- const span = toolSpans.get(call.toolCallId);
286
- if (!span)
287
- return;
288
- tracer.endSpan(span, call.success ? 'success' : 'error');
289
- const durationMs = Math.max(0, (span.endTime ?? Date.now()) - span.startTime);
290
- emitTrace({
291
- type: 'span:end',
292
- spanId: span.id,
293
- durationMs,
294
- status: call.success ? 'success' : 'error',
295
- });
296
- emitTrace({
297
- type: 'tool:result',
298
- toolName: call.toolName,
299
- durationMs,
300
- success: call.success,
301
- });
302
- toolSpans.delete(call.toolCallId);
303
- if (metrics) {
304
- const ms = (span.endTime ?? Date.now()) - span.startTime;
305
- metrics.timing('tool.duration', ms, { tool: call.toolName });
306
- }
307
- },
308
- onToolError: async (ctx, call, error) => {
309
- const span = toolSpans.get(call.toolCallId);
310
- if (span) {
311
- tracer.endSpan(span, 'error', error);
312
- const durationMs = Math.max(0, (span.endTime ?? Date.now()) - span.startTime);
313
- emitTrace({ type: 'span:end', spanId: span.id, durationMs, status: 'error' });
314
- emitTrace({
315
- type: 'tool:result',
316
- toolName: call.toolName,
317
- durationMs,
318
- success: false,
319
- });
320
- toolSpans.delete(call.toolCallId);
321
- if (metrics) {
322
- const ms = (span.endTime ?? Date.now()) - span.startTime;
323
- metrics.timing('tool.duration', ms, { tool: call.toolName });
324
- }
325
- }
326
- },
327
- onHandoff: async (ctx, from, to, reason) => {
328
- if (!ctx?.session?.id)
329
- return;
330
- const root = sessionRoots.get(ctx.session.id);
331
- if (!root)
332
- return;
333
- const st = ensureState(root);
334
- st.handoffs.push({ from, to, reason });
335
- const span = tracer.startSpan('handoff', { from, to, reason }, root.id);
336
- pushSpan(ctx.session.id, span);
337
- tracer.endSpan(span, 'success');
338
- },
339
- onError: async (ctx, error) => {
340
- if (!ctx?.session?.id)
341
- return;
342
- const root = sessionRoots.get(ctx.session.id);
343
- if (root) {
344
- tracer.addSpanEvent(root, 'error', { message: error.message });
345
- const errSpan = tracer.startSpan('error', { message: error.message }, root.id);
346
- pushSpan(ctx.session.id, errSpan);
347
- emitTrace({
348
- type: 'span:start',
349
- spanId: errSpan.id,
350
- parentId: root.id,
351
- name: 'error',
352
- timestamp: errSpan.startTime,
353
- attributes: { message: error.message },
354
- });
355
- tracer.endSpan(errSpan, 'error', error);
356
- const errDur = Math.max(0, (errSpan.endTime ?? Date.now()) - errSpan.startTime);
357
- emitTrace({ type: 'span:end', spanId: errSpan.id, durationMs: errDur, status: 'error' });
358
- }
359
- if (sessionRoots.has(ctx.session.id)) {
360
- const st = ensureState(sessionRoots.get(ctx.session.id));
361
- st.errors.push({ message: error.message, timestamp: Date.now() });
362
- }
363
- },
364
- onEnd: async (ctx, _result) => {
365
- if (!ctx?.session?.id)
366
- return;
367
- const sid = ctx.session.id;
368
- const root = sessionRoots.get(sid);
369
- if (!root)
370
- return;
371
- const duration = Date.now() - ctx.startTime;
372
- const st = ensureState(root);
373
- st.turnDurationsMs.push(duration);
374
- const agentSpan = activeAgentSpan.get(sid);
375
- if (agentSpan) {
376
- tracer.endSpan(agentSpan, 'success');
377
- activeAgentSpan.delete(sid);
378
- }
379
- tracer.addSpanEvent(root, 'turn.end', { durationMs: duration, stepCount: ctx.stepCount });
380
- if (metrics) {
381
- metrics.timing('agent.duration', duration);
382
- }
383
- // In text mode, onSessionEnd may never fire (sessions are implicit).
384
- // Debounce export so multi-turn chats do not invoke the exporter every turn.
385
- // onSessionEnd cancels this timer and emits the final trace (realtime path).
386
- pendingTextExportMeta.set(sid, {
387
- session: ctx.session,
388
- success: _result.success,
389
- agentId: ctx.agentId,
390
- });
391
- const prevTimer = pendingTextExportTimers.get(sid);
392
- if (prevTimer !== undefined) {
393
- clearTimeout(prevTimer);
394
- }
395
- pendingTextExportTimers.set(sid, setTimeout(() => {
396
- pendingTextExportTimers.delete(sid);
397
- const meta = pendingTextExportMeta.get(sid);
398
- pendingTextExportMeta.delete(sid);
399
- const rootSnap = sessionRoots.get(sid);
400
- if (!rootSnap || !meta)
401
- return;
402
- const stSnap = ensureState(rootSnap);
403
- const nowSnap = Date.now();
404
- const textTrace = buildSessionTrace(stSnap, meta.session, {
405
- success: meta.success,
406
- durationMs: Math.max(0, nowSnap - rootSnap.startTime),
407
- turnCount: stSnap.turnDurationsMs.length,
408
- lastAgentId: meta.agentId,
409
- endReason: 'completed',
410
- }, nowSnap);
411
- exportTrace(textTrace).catch((err) => {
412
- console.error('[Kuralle observability] text-mode trace export failed:', err);
413
- });
414
- }, TEXT_EXPORT_DEBOUNCE_MS));
415
- },
416
- onStreamPart: async (ctx, part) => {
417
- if (!ctx?.session?.id)
418
- return;
419
- const root = sessionRoots.get(ctx.session.id);
420
- if (!root)
421
- return;
422
- const st = ensureState(root);
423
- if (part.type === 'flow-transition') {
424
- const ts = Date.now();
425
- st.flowTransitions.push({
426
- from: part.from,
427
- to: part.to,
428
- timestamp: ts,
429
- });
430
- emitTrace({ type: 'flow:transition', from: part.from, to: part.to, timestamp: ts });
431
- const span = tracer.startSpan('flow.transition', { from: part.from, to: part.to }, root.id);
432
- pushSpan(ctx.session.id, span);
433
- emitTrace({
434
- type: 'span:start',
435
- spanId: span.id,
436
- parentId: root.id,
437
- name: span.name,
438
- timestamp: span.startTime,
439
- attributes: { from: part.from, to: part.to },
440
- });
441
- tracer.endSpan(span, 'success');
442
- const flowDur = Math.max(0, (span.endTime ?? Date.now()) - span.startTime);
443
- emitTrace({ type: 'span:end', spanId: span.id, durationMs: flowDur, status: 'success' });
444
- }
445
- if (part.type === 'interrupted') {
446
- st.voice.bargeInCount += 1;
447
- tracer.addSpanEvent(root, 'voice.barge_in', { reason: part.reason });
448
- }
449
- if (part.type === 'custom') {
450
- if (part.name === 'voice.reconfigure' || part.name === 'realtime.reconfigure') {
451
- st.voice.reconfigureCount += 1;
452
- tracer.addSpanEvent(root, 'voice.reconfigure', {});
453
- }
454
- if (part.name === 'voice.audio_in' && part.data && typeof part.data === 'object') {
455
- const bytes = part.data.bytes;
456
- if (typeof bytes === 'number')
457
- st.voice.totalAudioInBytes += bytes;
458
- }
459
- if (part.name === 'voice.audio_out' && part.data && typeof part.data === 'object') {
460
- const bytes = part.data.bytes;
461
- if (typeof bytes === 'number')
462
- st.voice.totalAudioOutBytes += bytes;
463
- }
464
- if (part.name === 'voice.time_to_first_audio' && part.data && typeof part.data === 'object') {
465
- const ms = part.data.ms;
466
- if (typeof ms === 'number')
467
- st.voice.timeToFirstAudioMs.push(ms);
468
- }
469
- if (part.name === 'flow.extraction.submission' && part.data && typeof part.data === 'object') {
470
- const d = part.data;
471
- st.extractionSubmissions.push({
472
- node: typeof d.node === 'string' ? d.node : 'unknown',
473
- fieldsAccepted: Array.isArray(d.fieldsAccepted) ? d.fieldsAccepted.map(String) : [],
474
- fieldsRejected: Array.isArray(d.fieldsRejected) ? d.fieldsRejected.map(String) : [],
475
- });
476
- }
477
- if (part.name === 'flow.extraction.update' && part.data && typeof part.data === 'object') {
478
- const d = part.data;
479
- const collected = d.collected && typeof d.collected === 'object' && d.collected !== null
480
- ? d.collected
481
- : {};
482
- const missing = Array.isArray(d.missing) ? d.missing.map(String) : [];
483
- emitTrace({
484
- type: 'extraction:update',
485
- nodeId: typeof d.nodeId === 'string' ? d.nodeId : 'unknown',
486
- collected,
487
- missing,
488
- });
489
- }
490
- }
491
- },
492
- onTokensUpdate: async (ctx, turn) => {
493
- if (!ctx?.session?.id)
494
- return;
495
- const root = sessionRoots.get(ctx.session.id);
496
- if (!root)
497
- return;
498
- const st = ensureState(root);
499
- st.perTurnUsage.push(turn);
500
- emitTrace({
501
- type: 'tokens:turn',
502
- sessionId: ctx.session.id,
503
- turn: turn.turn,
504
- nodeId: turn.nodeId,
505
- inputTokens: turn.inputTokens,
506
- outputTokens: turn.outputTokens,
507
- totalTokens: turn.totalTokens,
508
- cacheReadTokens: turn.cacheReadTokens,
509
- cumulativeTotalTokens: turn.cumulativeTotalTokens,
510
- contextUtilization: turn.contextUtilization,
511
- model: turn.model,
512
- });
513
- },
514
- onMessage: async (ctx, message) => {
515
- if (!includeContent)
516
- return;
517
- if (!ctx?.session?.id)
518
- return;
519
- const root = sessionRoots.get(ctx.session.id);
520
- if (!root)
521
- return;
522
- const text = typeof message.content === 'string'
523
- ? message.content
524
- : JSON.stringify(message.content);
525
- const preview = text.length > 500 ? `${text.slice(0, 500)}…` : text;
526
- tracer.addSpanEvent(root, 'message', { role: message.role, preview });
527
- },
528
- onSessionEnd: async (session, metadata) => {
529
- cancelPendingTextExport(session.id);
530
- const root = sessionRoots.get(session.id);
531
- if (!root)
532
- return;
533
- const st = sessionState.get(session.id);
534
- const endTime = Date.now();
535
- tracer.endSpan(root, metadata.success ? 'success' : 'error');
536
- const rootDurationMs = Math.max(0, (root.endTime ?? endTime) - root.startTime);
537
- emitTrace({
538
- type: 'span:end',
539
- spanId: root.id,
540
- durationMs: rootDurationMs,
541
- status: metadata.success ? 'success' : 'error',
542
- });
543
- const trace = buildSessionTrace(st ?? {
544
- rootSpan: root,
545
- spans: [root],
546
- turnDurationsMs: [],
547
- flowTransitions: [],
548
- handoffs: [],
549
- extractionSubmissions: [],
550
- errors: [],
551
- voice: {
552
- bargeInCount: 0,
553
- reconfigureCount: 0,
554
- totalAudioInBytes: 0,
555
- totalAudioOutBytes: 0,
556
- timeToFirstAudioMs: [],
557
- },
558
- perTurnUsage: [],
559
- }, session, metadata, endTime);
560
- sessionRoots.delete(session.id);
561
- sessionState.delete(session.id);
562
- activeAgentSpan.delete(session.id);
563
- emitTrace({
564
- type: 'session:end',
565
- sessionId: trace.sessionId,
566
- success: trace.success,
567
- durationMs: trace.durationMs,
568
- });
569
- await exportTrace(trace);
570
- },
571
- };
572
- }
@@ -1,22 +0,0 @@
1
- import type { RunContext, HarnessHooks } from '../types/index.js';
2
- import type { TracingConfig, Span, MetricsConfig, ObservabilityMetrics, SessionTelemetry } from '../types/index.js';
3
- import { TracingService } from '../services/TracingService.js';
4
- export declare function initTracing(config: TracingConfig): void;
5
- export declare function startSpan(name: string, attributes?: Record<string, string | number | boolean>, parentSpanId?: string): Span;
6
- export declare function endSpan(span: Span, status?: 'success' | 'error', error?: Error): Span;
7
- export declare function addSpanEvent(span: Span, name: string, attributes?: Record<string, string | number | boolean>): void;
8
- export declare function getCurrentSpan(): Span | undefined;
9
- /**
10
- * Creates tracing hooks.
11
- * @param service Optional TracingService instance. If not provided, uses the global singleton (legacy).
12
- */
13
- export declare function createTracingHooks(service?: TracingService): HarnessHooks;
14
- export declare function createObservabilityMetrics(config?: MetricsConfig): ObservabilityMetrics;
15
- export declare function initMetrics(metrics: ObservabilityMetrics): void;
16
- export declare function getMetrics(): ObservabilityMetrics;
17
- export declare function createObservabilityHooks(service?: ObservabilityMetrics): HarnessHooks;
18
- export declare function captureSessionTelemetry(context: RunContext, result: {
19
- success: boolean;
20
- error?: Error;
21
- }): SessionTelemetry;
22
- export declare function createTelemetryHooks(): HarnessHooks;