oh-langfuse 0.1.45 → 0.1.46

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-langfuse",
3
- "version": "0.1.45",
3
+ "version": "0.1.46",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Use npm scripts to configure Claude Code / OpenCode / Codex with Langfuse tracing.",
@@ -132,6 +132,14 @@ function collectOpencodeExplicitSkillUsages(value, out = []) {
132
132
  }
133
133
  if (typeof value !== "object") return out;
134
134
 
135
+ const command = normalizeExplicitSkillCommand(
136
+ value.command ?? value.commandName ?? value.promptCommand ?? value.properties?.command ?? value.properties?.commandName,
137
+ );
138
+ if (command) {
139
+ const callId = pickString(value.commandID, value.commandId, value.id);
140
+ out.push({ name: command, skill_namespace: skillNamespace(command), detected_by: "slash_command", skill_call_id: callId });
141
+ }
142
+
135
143
  const toolName = pickString(value.tool, value.toolName, value.name).toLowerCase();
136
144
  const input = value.input ?? value.state?.input ?? value.properties?.input;
137
145
  if (toolName === "skill" && input && typeof input === "object" && !Array.isArray(input)) {
@@ -155,6 +163,10 @@ export function detectOpencodeSkillUsages(source, knownSkills = []) {
155
163
  const key = `call:${usage.skill_call_id}`;
156
164
  if (explicitSeen.has(key)) continue;
157
165
  explicitSeen.add(key);
166
+ } else if (usage.detected_by === "slash_command") {
167
+ const key = `command:${usage.name}`;
168
+ if (explicitSeen.has(key)) continue;
169
+ explicitSeen.add(key);
158
170
  }
159
171
  explicit.push(usage);
160
172
  }
@@ -177,6 +189,12 @@ function pickString(...values) {
177
189
  return "";
178
190
  }
179
191
 
192
+ function normalizeExplicitSkillCommand(value) {
193
+ const command = pickString(value).replace(/^\/+/, "").trim();
194
+ if (!command) return "";
195
+ return command.split(/\s+/, 1)[0];
196
+ }
197
+
180
198
  export function countOpencodeToolActivity(source) {
181
199
  const payload = source?.properties ?? source?.body ?? source ?? {};
182
200
  const part = payload?.part ?? payload?.properties?.part ?? payload;
@@ -256,10 +256,10 @@ function getPatchedLangfuseDistIndexJs() {
256
256
  " shutdown: async () => {},",
257
257
  "});",
258
258
  "",
259
- "const eventPayload = (event) => event?.properties ?? event?.body ?? event ?? {};",
259
+ "const eventPayload = (event) => event?.properties ?? event?.data ?? event?.payload ?? event?.body ?? event ?? {};",
260
260
  "const eventPart = (event) => {",
261
261
  " const payload = eventPayload(event);",
262
- " return payload.part ?? payload.properties?.part ?? payload;",
262
+ " return payload.part ?? payload.properties?.part ?? payload.data?.part ?? payload.payload?.part ?? event?.part ?? event?.data?.part ?? event?.payload?.part ?? payload;",
263
263
  "};",
264
264
  "const pickEventString = (...values) => {",
265
265
  " for (const value of values) {",
@@ -270,7 +270,7 @@ function getPatchedLangfuseDistIndexJs() {
270
270
  "const countOpencodeToolActivity = (event) => {",
271
271
  " const payload = eventPayload(event);",
272
272
  " const part = eventPart(event);",
273
- " const eventType = pickEventString(event?.type, payload?.type);",
273
+ " const eventType = pickEventString(event?.name, event?.type, payload?.name, payload?.type);",
274
274
  " const partType = pickEventString(part?.type);",
275
275
  " const toolName = pickEventString(part?.tool, part?.toolName, part?.name, payload?.tool, payload?.toolName, payload?.name);",
276
276
  " const callId = pickEventString(part?.callID, part?.callId, payload?.callID, payload?.callId, part?.id);",
@@ -309,10 +309,15 @@ function getPatchedLangfuseDistIndexJs() {
309
309
  " return out;",
310
310
  "};",
311
311
  "const skillNamespace = (name) => String(name || '').includes(':') ? String(name).split(':', 1)[0] : '';",
312
- "const skillEventType = (detectedBy) => detectedBy === 'tool_call' || detectedBy === 'plugin_event' ? 'invoked' : 'detected';",
312
+ "const skillEventType = (detectedBy) => detectedBy === 'tool_call' || detectedBy === 'plugin_event' || detectedBy === 'slash_command' ? 'invoked' : 'detected';",
313
313
  "const skillIdSegment = (name) => String(name || 'unknown').trim().replace(/[^A-Za-z0-9_.:-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 96) || 'unknown';",
314
314
  "",
315
315
  "const escapeRegExp = (value) => String(value).replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');",
316
+ "const normalizeExplicitSkillCommand = (value) => {",
317
+ " const command = pickEventString(value).replace(/^\\/+/, '').trim();",
318
+ " if (!command) return '';",
319
+ " return command.split(/\\s+/, 1)[0];",
320
+ "};",
316
321
  "",
317
322
  "const collectStrings = (value, out = []) => {",
318
323
  " if (value == null) return out;",
@@ -341,6 +346,11 @@ function getPatchedLangfuseDistIndexJs() {
341
346
  " return out;",
342
347
  " }",
343
348
  " if (typeof value !== 'object') return out;",
349
+ " const command = normalizeExplicitSkillCommand(value.command ?? value.commandName ?? value.promptCommand ?? value.properties?.command ?? value.properties?.commandName);",
350
+ " if (command) {",
351
+ " const callId = pickEventString(value.commandID, value.commandId, value.id);",
352
+ " out.push({ name: command, skill_namespace: skillNamespace(command), detected_by: 'slash_command', skill_call_id: callId });",
353
+ " }",
344
354
  " const toolName = pickEventString(value.tool, value.toolName, value.name).toLowerCase();",
345
355
  " const input = value.input ?? value.state?.input ?? value.properties?.input;",
346
356
  " if (toolName === 'skill' && input && typeof input === 'object' && !Array.isArray(input)) {",
@@ -363,6 +373,10 @@ function getPatchedLangfuseDistIndexJs() {
363
373
  " const key = `call:${usage.skill_call_id}`;",
364
374
  " if (explicitSeen.has(key)) continue;",
365
375
  " explicitSeen.add(key);",
376
+ " } else if (usage.detected_by === 'slash_command') {",
377
+ " const key = `command:${usage.name}`;",
378
+ " if (explicitSeen.has(key)) continue;",
379
+ " explicitSeen.add(key);",
366
380
  " }",
367
381
  " explicit.push(usage);",
368
382
  " }",
@@ -378,9 +392,21 @@ function getPatchedLangfuseDistIndexJs() {
378
392
  " return found;",
379
393
  "};",
380
394
  "const uniqueSkillNames = (usages) => normalizeSkillNames((usages || []).map((usage) => usage.name));",
395
+ "const dedupeSkillUsages = (usages) => {",
396
+ " const out = [];",
397
+ " const seen = new Set();",
398
+ " for (const usage of usages || []) {",
399
+ " const key = usage.skill_call_id ? `call:${usage.skill_call_id}` : `${usage.name}:${usage.detected_by}`;",
400
+ " if (seen.has(key)) continue;",
401
+ " seen.add(key);",
402
+ " out.push(usage);",
403
+ " }",
404
+ " return out;",
405
+ "};",
381
406
  "const buildSkillUseEvents = (interactionId, skillUsages) => {",
382
- " const total = skillUsages.length;",
383
- " return skillUsages.map((usage, index) => {",
407
+ " const usages = dedupeSkillUsages(skillUsages);",
408
+ " const total = usages.length;",
409
+ " return usages.map((usage, index) => {",
384
410
  " const skillName = String(usage.name || '').trim();",
385
411
  " const detectedBy = String(usage.detected_by || 'metadata');",
386
412
  " return {",
@@ -457,6 +483,7 @@ function getPatchedLangfuseDistIndexJs() {
457
483
  " const knownSkillNames = await collectKnownSkillNames();",
458
484
  " const messageTextById = new Map();",
459
485
  " const skillUsagesByMessageId = new Map();",
486
+ " const skillUsagesBySessionId = new Map();",
460
487
  " const toolCallIdsByMessageId = new Map();",
461
488
  " const toolCallIdsBySessionId = new Map();",
462
489
  " const toolResultIdsByMessageId = new Map();",
@@ -495,12 +522,12 @@ function getPatchedLangfuseDistIndexJs() {
495
522
  " void shutdown('process.exit').finally(() => originalExit(code));",
496
523
  " });",
497
524
  "",
498
- " const rememberSkillUsages = (messageId, usages) => {",
499
- " if (!messageId || !usages.length) return;",
500
- " let list = skillUsagesByMessageId.get(messageId);",
525
+ " const rememberSkillUsages = (map, key, usages) => {",
526
+ " if (!key || !usages.length) return;",
527
+ " let list = map.get(key);",
501
528
  " if (!list) {",
502
529
  " list = [];",
503
- " skillUsagesByMessageId.set(messageId, list);",
530
+ " map.set(key, list);",
504
531
  " }",
505
532
  " const seen = new Set(list.map((usage) => usage.skill_call_id ? `call:${usage.skill_call_id}` : `${usage.name}:${usage.detected_by}`));",
506
533
  " for (const usage of usages) {",
@@ -523,11 +550,12 @@ function getPatchedLangfuseDistIndexJs() {
523
550
  " const payload = eventPayload(event);",
524
551
  " const part = eventPart(event);",
525
552
  " const partType = part?.type ?? '';",
526
- " const sessionId = pickEventString(part?.sessionID, part?.sessionId, payload?.sessionID, payload?.sessionId, payload?.session?.id);",
527
- " const messageId = pickEventString(part?.messageID, part?.messageId, payload?.messageID, payload?.messageId, payload?.message?.id);",
553
+ " const sessionId = pickEventString(part?.sessionID, part?.sessionId, payload?.sessionID, payload?.sessionId, payload?.session?.id, event?.sessionID, event?.sessionId);",
554
+ " const messageId = pickEventString(part?.messageID, part?.messageId, payload?.messageID, payload?.messageId, payload?.message?.id, event?.messageID, event?.messageId);",
528
555
  " const skillDetectionSources = [part, payload, part?.state?.input, part?.input, payload?.input, part?.state?.metadata, payload?.metadata, part?.state?.file, part?.file];",
529
556
  " const eventSkillUsages = detectOpencodeSkillUsages(skillDetectionSources, knownSkillNames);",
530
- " rememberSkillUsages(messageId, eventSkillUsages);",
557
+ " rememberSkillUsages(skillUsagesByMessageId, messageId, eventSkillUsages);",
558
+ " rememberSkillUsages(skillUsagesBySessionId, sessionId, eventSkillUsages);",
531
559
  " const toolActivity = countOpencodeToolActivity(event);",
532
560
  " rememberToolActivity(toolCallIdsByMessageId, messageId, toolActivity, 'toolCallCount');",
533
561
  " rememberToolActivity(toolCallIdsBySessionId, sessionId, toolActivity, 'toolCallCount');",
@@ -535,7 +563,9 @@ function getPatchedLangfuseDistIndexJs() {
535
563
  " rememberToolActivity(toolResultIdsBySessionId, sessionId, toolActivity, 'toolResultCount');",
536
564
  " if (partType === 'text' && messageId && typeof part.text === 'string') {",
537
565
  " messageTextById.set(messageId, part.text);",
538
- " rememberSkillUsages(messageId, detectOpencodeSkillUsages(part.text, knownSkillNames));",
566
+ " const textSkillUsages = detectOpencodeSkillUsages(part.text, knownSkillNames);",
567
+ " rememberSkillUsages(skillUsagesByMessageId, messageId, textSkillUsages);",
568
+ " rememberSkillUsages(skillUsagesBySessionId, sessionId, textSkillUsages);",
539
569
  " return;",
540
570
  " }",
541
571
  " if (partType !== 'step-finish' || !messageId || emittedMessageIds.has(messageId)) return;",
@@ -545,7 +575,7 @@ function getPatchedLangfuseDistIndexJs() {
545
575
  " const tokenAvailable = [tokenMetrics.input, tokenMetrics.output, total, tokenMetrics.cacheRead, tokenMetrics.reasoning].some((value) => value !== undefined);",
546
576
  " const span = metricsTracer.startSpan('Agent Turn');",
547
577
  " const text = messageTextById.get(messageId) || '';",
548
- " const skillUsages = [...(skillUsagesByMessageId.get(messageId) ?? [])];",
578
+ " const skillUsages = dedupeSkillUsages([...(skillUsagesByMessageId.get(messageId) ?? []), ...(skillUsagesBySessionId.get(sessionId) ?? [])]);",
549
579
  " const interactionId = `opencode:${userId || \"unknown\"}:${sessionId || \"unknown\"}:${messageId}`;",
550
580
  " const skillUseEvents = buildSkillUseEvents(interactionId, skillUsages);",
551
581
  " const skillNames = uniqueSkillNames(skillUsages);",
@@ -582,6 +612,7 @@ function getPatchedLangfuseDistIndexJs() {
582
612
  " span.end();",
583
613
  " messageTextById.delete(messageId);",
584
614
  " skillUsagesByMessageId.delete(messageId);",
615
+ " skillUsagesBySessionId.delete(sessionId);",
585
616
  " toolCallIdsByMessageId.delete(messageId);",
586
617
  " toolCallIdsBySessionId.delete(sessionId);",
587
618
  " toolResultIdsByMessageId.delete(messageId);",
@@ -595,9 +626,9 @@ function getPatchedLangfuseDistIndexJs() {
595
626
  " }",
596
627
  " },",
597
628
  " event: async ({ event }) => {",
598
- " const eventType = event?.type ?? '';",
629
+ " const eventType = pickEventString(event?.name, event?.type, eventPayload(event)?.name, eventPayload(event)?.type);",
599
630
  " recordInteractionMetric(event);",
600
- ' if (eventType === "session.idle" || eventType === "message.updated" || eventType === "message.part.updated" || eventType === "session.updated") {',
631
+ ' if (eventType === "session.idle" || eventType === "message.updated" || eventType === "message.part.updated" || eventType === "session.updated" || eventType === "session.idle.1" || eventType === "message.updated.1" || eventType === "message.part.updated.1" || eventType === "session.updated.1") {',
601
632
  " await flush(eventType);",
602
633
  " }",
603
634
  ' if (eventType === "server.instance.disposed") {',