oh-langfuse 0.1.41 → 0.1.43
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 +62 -85
- package/bin/cli.js +21 -14
- package/codex_langfuse_notify.py +139 -59
- package/langfuse_hook.py +223 -57
- package/package.json +35 -35
- package/scripts/auto-update-runtime.mjs +4 -2
- package/scripts/codex-langfuse-setup.mjs +163 -11
- package/scripts/langfuse-check.mjs +11 -5
- package/scripts/langfuse-setup.mjs +155 -12
- package/scripts/metrics-utils.mjs +134 -10
- package/scripts/opencode-langfuse-setup.mjs +118 -61
- package/scripts/real-self-verify.mjs +13 -8
package/langfuse_hook.py
CHANGED
|
@@ -10,12 +10,40 @@ import re
|
|
|
10
10
|
import sys
|
|
11
11
|
import time
|
|
12
12
|
import hashlib
|
|
13
|
-
from dataclasses import dataclass
|
|
14
|
-
from datetime import datetime, timezone
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
from typing import Any, Dict, List, Optional, Tuple
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from datetime import datetime, timezone
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
17
|
+
from urllib.parse import urlparse
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def configure_langfuse_no_proxy() -> None:
|
|
21
|
+
hosts = ["localhost", "127.0.0.1"]
|
|
22
|
+
for key in ("LANGFUSE_HOST", "LANGFUSE_BASEURL", "CC_LANGFUSE_BASE_URL"):
|
|
23
|
+
value = os.environ.get(key)
|
|
24
|
+
if not value:
|
|
25
|
+
continue
|
|
26
|
+
parsed = urlparse(value if "://" in value else f"http://{value}")
|
|
27
|
+
if parsed.hostname:
|
|
28
|
+
hosts.append(parsed.hostname)
|
|
29
|
+
if parsed.netloc:
|
|
30
|
+
hosts.append(parsed.netloc)
|
|
31
|
+
existing = []
|
|
32
|
+
for key in ("NO_PROXY", "no_proxy"):
|
|
33
|
+
existing.extend([item.strip() for item in os.environ.get(key, "").split(",") if item.strip()])
|
|
34
|
+
merged = []
|
|
35
|
+
for item in [*existing, *hosts]:
|
|
36
|
+
if item and item not in merged:
|
|
37
|
+
merged.append(item)
|
|
38
|
+
if merged:
|
|
39
|
+
value = ",".join(merged)
|
|
40
|
+
os.environ["NO_PROXY"] = value
|
|
41
|
+
os.environ["no_proxy"] = value
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
configure_langfuse_no_proxy()
|
|
45
|
+
|
|
46
|
+
# --- Langfuse import (fail-open) ---
|
|
19
47
|
try:
|
|
20
48
|
from langfuse import Langfuse, propagate_attributes
|
|
21
49
|
except Exception as e:
|
|
@@ -37,7 +65,8 @@ LOCK_FILE = STATE_DIR / "langfuse_state.lock"
|
|
|
37
65
|
|
|
38
66
|
DEBUG = os.environ.get("CC_LANGFUSE_DEBUG", "").lower() == "true"
|
|
39
67
|
MAX_CHARS = int(os.environ.get("CC_LANGFUSE_MAX_CHARS", "20000"))
|
|
40
|
-
METRICS_SCHEMA_VERSION = "1.
|
|
68
|
+
METRICS_SCHEMA_VERSION = "1.1"
|
|
69
|
+
AGENT_NAME = "claude"
|
|
41
70
|
|
|
42
71
|
# ----------------- Logging -----------------
|
|
43
72
|
def _log(level: str, message: str) -> None:
|
|
@@ -318,11 +347,17 @@ def build_interaction_metadata(
|
|
|
318
347
|
model: Optional[str],
|
|
319
348
|
user_message_count: int = 1,
|
|
320
349
|
assistant_message_count: int = 1,
|
|
350
|
+
skill_use_events: Optional[List[Dict[str, Any]]] = None,
|
|
321
351
|
) -> Dict[str, Any]:
|
|
322
352
|
tokens = normalize_token_metrics(token_metrics)
|
|
323
353
|
interaction_id = build_interaction_id(source, session_id, turn_number)
|
|
354
|
+
events = list(skill_use_events or [])
|
|
355
|
+
skill_names_all = [str(event.get("skill_name") or "") for event in events if event.get("skill_name")]
|
|
356
|
+
unique_skill_names = list(dict.fromkeys(skill_names_all))
|
|
357
|
+
effective_skill_count = len(events) if events else int(skill_use_count or 0)
|
|
324
358
|
return {
|
|
325
359
|
"source": source,
|
|
360
|
+
"agent": source,
|
|
326
361
|
"user_id": user_id or "",
|
|
327
362
|
"session_id": session_id,
|
|
328
363
|
"interaction_id": interaction_id,
|
|
@@ -332,17 +367,23 @@ def build_interaction_metadata(
|
|
|
332
367
|
"assistant_message_count": assistant_message_count,
|
|
333
368
|
"tool_call_count": int(tool_call_count or 0),
|
|
334
369
|
"tool_result_count": int(tool_result_count or 0),
|
|
335
|
-
"skill_use_count":
|
|
370
|
+
"skill_use_count": effective_skill_count,
|
|
371
|
+
"unique_skill_count": len(unique_skill_names),
|
|
372
|
+
"repeated_skill_count": max(0, effective_skill_count - len(unique_skill_names)),
|
|
336
373
|
**tokens,
|
|
337
374
|
"model": model,
|
|
338
375
|
"turn_number": int(turn_number or 0),
|
|
339
376
|
"efficiency": {
|
|
340
377
|
"tokens_per_interaction": tokens.get("total_tokens"),
|
|
341
378
|
"tool_calls_per_interaction": int(tool_call_count or 0),
|
|
342
|
-
"skills_per_interaction":
|
|
379
|
+
"skills_per_interaction": effective_skill_count,
|
|
343
380
|
"output_input_token_ratio": _ratio(tokens.get("output_tokens"), tokens.get("input_tokens")),
|
|
344
381
|
"tokens_per_tool_call": _ratio(tokens.get("total_tokens"), int(tool_call_count or 0)),
|
|
345
382
|
},
|
|
383
|
+
**({
|
|
384
|
+
"skill_names": unique_skill_names,
|
|
385
|
+
"skill_names_all": skill_names_all,
|
|
386
|
+
} if events else {}),
|
|
346
387
|
}
|
|
347
388
|
|
|
348
389
|
def discover_known_skills(extra_roots: Optional[List[Path]] = None) -> set:
|
|
@@ -367,16 +408,31 @@ def discover_known_skills(extra_roots: Optional[List[Path]] = None) -> set:
|
|
|
367
408
|
def _skill_namespace(name: str) -> str:
|
|
368
409
|
return name.split(":", 1)[0] if ":" in name else ""
|
|
369
410
|
|
|
411
|
+
def _skill_event_type(detected_by: str) -> str:
|
|
412
|
+
return "invoked" if detected_by in ("tool_call", "plugin_event", "attribution_skill", "slash_command") else "detected"
|
|
413
|
+
|
|
414
|
+
def _skill_id_segment(name: str) -> str:
|
|
415
|
+
segment = re.sub(r"[^A-Za-z0-9_.:-]+", "-", str(name or "").strip()).strip("-")
|
|
416
|
+
return (segment or "unknown")[:96]
|
|
417
|
+
|
|
370
418
|
def detect_skill_usages(tool_calls: List[Dict[str, Any]], known_skills: set) -> List[Dict[str, str]]:
|
|
371
|
-
found: Dict[str, str] =
|
|
419
|
+
found: List[Dict[str, str]] = []
|
|
420
|
+
seen_call_ids: set = set()
|
|
372
421
|
for call in tool_calls or []:
|
|
373
422
|
tool_name = str(call.get("name") or "")
|
|
423
|
+
call_id = str(call.get("id") or call.get("call_id") or call.get("callId") or call.get("tool_call_id") or call.get("toolCallId") or "").strip()
|
|
374
424
|
input_obj = call.get("input") if isinstance(call.get("input"), (dict, list, str)) else {}
|
|
375
425
|
if tool_name.lower() == "skill" and isinstance(input_obj, dict):
|
|
376
426
|
for key in ("skill_name", "skill", "name"):
|
|
377
427
|
value = input_obj.get(key)
|
|
378
428
|
if isinstance(value, str) and value.strip():
|
|
379
|
-
|
|
429
|
+
name = value.strip()
|
|
430
|
+
if call_id:
|
|
431
|
+
dedupe_key = f"call:{call_id}"
|
|
432
|
+
if dedupe_key in seen_call_ids:
|
|
433
|
+
break
|
|
434
|
+
seen_call_ids.add(dedupe_key)
|
|
435
|
+
found.append({"name": name, "skill_namespace": _skill_namespace(name), "detected_by": "tool_call", "skill_call_id": call_id})
|
|
380
436
|
break
|
|
381
437
|
try:
|
|
382
438
|
text = json.dumps(input_obj, ensure_ascii=False)
|
|
@@ -385,11 +441,143 @@ def detect_skill_usages(tool_calls: List[Dict[str, Any]], known_skills: set) ->
|
|
|
385
441
|
for match in re.finditer(r"([A-Za-z]:)?[^\"'\n\r]*[\\/]+([^\\/\"'\n\r]+)[\\/]+SKILL\.md", text, re.IGNORECASE):
|
|
386
442
|
candidate = match.group(2)
|
|
387
443
|
if candidate and (candidate in known_skills or not known_skills):
|
|
388
|
-
found
|
|
389
|
-
return
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
444
|
+
found.append({"name": candidate, "skill_namespace": _skill_namespace(candidate), "detected_by": "skill_file_path"})
|
|
445
|
+
return found
|
|
446
|
+
|
|
447
|
+
def _skill_usage(name: str, detected_by: str, skill_call_id: str = "") -> Dict[str, str]:
|
|
448
|
+
clean = str(name or "").strip().lstrip("/")
|
|
449
|
+
return {
|
|
450
|
+
"name": clean,
|
|
451
|
+
"skill_namespace": _skill_namespace(clean),
|
|
452
|
+
"detected_by": detected_by,
|
|
453
|
+
"skill_call_id": str(skill_call_id or "").strip(),
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
def _accept_skill_candidate(name: Any, known_skills: set, trusted: bool = False) -> str:
|
|
457
|
+
clean = str(name or "").strip().lstrip("/")
|
|
458
|
+
if not clean:
|
|
459
|
+
return ""
|
|
460
|
+
if trusted or not known_skills or clean in known_skills:
|
|
461
|
+
return clean
|
|
462
|
+
return ""
|
|
463
|
+
|
|
464
|
+
def _detect_skill_usages_from_text(text: str, known_skills: set) -> List[Dict[str, str]]:
|
|
465
|
+
found: List[Dict[str, str]] = []
|
|
466
|
+
if not text:
|
|
467
|
+
return found
|
|
468
|
+
|
|
469
|
+
for pattern in (
|
|
470
|
+
r"<command-name>\s*/?([^<\s]+)\s*</command-name>",
|
|
471
|
+
r"<command-message>\s*/?([^<\s]+)\s*</command-message>",
|
|
472
|
+
):
|
|
473
|
+
for match in re.finditer(pattern, text, re.IGNORECASE):
|
|
474
|
+
name = _accept_skill_candidate(match.group(1), known_skills)
|
|
475
|
+
if name:
|
|
476
|
+
found.append(_skill_usage(name, "slash_command"))
|
|
477
|
+
|
|
478
|
+
for match in re.finditer(r"Base directory for this skill:\s*([^\r\n]+)", text, re.IGNORECASE):
|
|
479
|
+
path_text = match.group(1)
|
|
480
|
+
path_match = re.search(r"[\\/](?:skills|skill)[\\/]([^\\/\"\r\n]+)", path_text, re.IGNORECASE)
|
|
481
|
+
if path_match:
|
|
482
|
+
name = _accept_skill_candidate(path_match.group(1), known_skills)
|
|
483
|
+
if name:
|
|
484
|
+
found.append(_skill_usage(name, "skill_file_path"))
|
|
485
|
+
|
|
486
|
+
return found
|
|
487
|
+
|
|
488
|
+
def _attribution_skill_from_row(row: Dict[str, Any]) -> str:
|
|
489
|
+
if not isinstance(row, dict):
|
|
490
|
+
return ""
|
|
491
|
+
value = row.get("attributionSkill") or row.get("attribution_skill")
|
|
492
|
+
if isinstance(value, str) and value.strip():
|
|
493
|
+
return value.strip()
|
|
494
|
+
message = row.get("message")
|
|
495
|
+
if isinstance(message, dict):
|
|
496
|
+
value = message.get("attributionSkill") or message.get("attribution_skill")
|
|
497
|
+
if isinstance(value, str) and value.strip():
|
|
498
|
+
return value.strip()
|
|
499
|
+
return ""
|
|
500
|
+
|
|
501
|
+
def _dedupe_turn_skill_usages(usages: List[Dict[str, str]]) -> List[Dict[str, str]]:
|
|
502
|
+
out: List[Dict[str, str]] = []
|
|
503
|
+
seen_call_ids: set = set()
|
|
504
|
+
seen_detected_names: set = set()
|
|
505
|
+
for usage in usages or []:
|
|
506
|
+
name = str(usage.get("name") or "").strip()
|
|
507
|
+
if not name:
|
|
508
|
+
continue
|
|
509
|
+
call_id = str(usage.get("skill_call_id") or "").strip()
|
|
510
|
+
if call_id:
|
|
511
|
+
key = f"call:{call_id}"
|
|
512
|
+
if key in seen_call_ids:
|
|
513
|
+
continue
|
|
514
|
+
seen_call_ids.add(key)
|
|
515
|
+
out.append(usage)
|
|
516
|
+
continue
|
|
517
|
+
|
|
518
|
+
detected_by = str(usage.get("detected_by") or "")
|
|
519
|
+
if detected_by in ("attribution_skill", "slash_command", "skill_file_path"):
|
|
520
|
+
key = f"name:{name}"
|
|
521
|
+
if key in seen_detected_names:
|
|
522
|
+
continue
|
|
523
|
+
seen_detected_names.add(key)
|
|
524
|
+
out.append(usage)
|
|
525
|
+
return out
|
|
526
|
+
|
|
527
|
+
def detect_turn_skill_usages(turn: "Turn", tool_calls: List[Dict[str, Any]], known_skills: set) -> List[Dict[str, str]]:
|
|
528
|
+
found = list(detect_skill_usages(tool_calls, known_skills))
|
|
529
|
+
rows = [turn.user_msg, *turn.assistant_msgs]
|
|
530
|
+
|
|
531
|
+
for row in rows:
|
|
532
|
+
attributed = _accept_skill_candidate(_attribution_skill_from_row(row), known_skills, trusted=True)
|
|
533
|
+
if attributed:
|
|
534
|
+
found.append(_skill_usage(attributed, "attribution_skill"))
|
|
535
|
+
found.extend(_detect_skill_usages_from_text(extract_text(get_content(row)), known_skills))
|
|
536
|
+
|
|
537
|
+
return _dedupe_turn_skill_usages(found)
|
|
538
|
+
|
|
539
|
+
def build_skill_use_events(interaction_id: str, skill_usages: List[Dict[str, str]]) -> List[Dict[str, Any]]:
|
|
540
|
+
events: List[Dict[str, Any]] = []
|
|
541
|
+
deduped: List[Dict[str, str]] = []
|
|
542
|
+
seen_call_ids: set = set()
|
|
543
|
+
for skill in skill_usages or []:
|
|
544
|
+
call_id = str(skill.get("skill_call_id") or "").strip()
|
|
545
|
+
if call_id:
|
|
546
|
+
dedupe_key = f"call:{call_id}"
|
|
547
|
+
if dedupe_key in seen_call_ids:
|
|
548
|
+
continue
|
|
549
|
+
seen_call_ids.add(dedupe_key)
|
|
550
|
+
deduped.append(skill)
|
|
551
|
+
total = len(deduped)
|
|
552
|
+
for index, skill in enumerate(deduped, start=1):
|
|
553
|
+
name = str(skill.get("name") or "").strip()
|
|
554
|
+
if not name:
|
|
555
|
+
continue
|
|
556
|
+
detected_by = str(skill.get("detected_by") or "metadata")
|
|
557
|
+
call_id = str(skill.get("skill_call_id") or "").strip()
|
|
558
|
+
events.append({
|
|
559
|
+
"skill_use_id": f"{interaction_id}:skill:{index}:{_skill_id_segment(name)}",
|
|
560
|
+
"skill_use_index": index,
|
|
561
|
+
"skill_use_count_in_interaction": total,
|
|
562
|
+
"skill_event_type": _skill_event_type(detected_by),
|
|
563
|
+
"skill_trigger": "unknown",
|
|
564
|
+
"skill_name": name,
|
|
565
|
+
"skill_use_count": 1,
|
|
566
|
+
"skill_namespace": skill.get("skill_namespace") or _skill_namespace(name),
|
|
567
|
+
"detected_by": detected_by,
|
|
568
|
+
**({"skill_call_id": call_id} if call_id else {}),
|
|
569
|
+
})
|
|
570
|
+
return events
|
|
571
|
+
|
|
572
|
+
def summarize_skill_usages(skill_usages: List[Dict[str, str]]) -> List[Dict[str, Any]]:
|
|
573
|
+
summary: Dict[str, Dict[str, Any]] = {}
|
|
574
|
+
for item in skill_usages or []:
|
|
575
|
+
name = item.get("name")
|
|
576
|
+
if not name:
|
|
577
|
+
continue
|
|
578
|
+
entry = summary.setdefault(name, {"name": name, "count": 0, "detected_by": item.get("detected_by")})
|
|
579
|
+
entry["count"] += 1
|
|
580
|
+
return list(summary.values())
|
|
393
581
|
|
|
394
582
|
def get_model(msg: Dict[str, Any]) -> str:
|
|
395
583
|
m = msg.get("message")
|
|
@@ -609,7 +797,9 @@ def emit_turn(
|
|
|
609
797
|
usage_details = get_usage(last_assistant)
|
|
610
798
|
|
|
611
799
|
tool_calls = _tool_calls_from_assistants(turn.assistant_msgs)
|
|
612
|
-
skill_usages =
|
|
800
|
+
skill_usages = detect_turn_skill_usages(turn, tool_calls, discover_known_skills())
|
|
801
|
+
interaction_id = build_interaction_id("claude", session_id, turn_num)
|
|
802
|
+
skill_use_events = build_skill_use_events(interaction_id, skill_usages)
|
|
613
803
|
interaction_meta = build_interaction_metadata(
|
|
614
804
|
"claude",
|
|
615
805
|
user_id,
|
|
@@ -618,15 +808,13 @@ def emit_turn(
|
|
|
618
808
|
usage_details,
|
|
619
809
|
len(tool_calls),
|
|
620
810
|
len(turn.tool_results_by_id),
|
|
621
|
-
len(
|
|
811
|
+
len(skill_use_events),
|
|
622
812
|
model,
|
|
623
813
|
user_message_count=1,
|
|
624
814
|
assistant_message_count=len(turn.assistant_msgs),
|
|
815
|
+
skill_use_events=skill_use_events,
|
|
625
816
|
)
|
|
626
|
-
skill_summary =
|
|
627
|
-
{"name": item["name"], "count": 1, "detected_by": item["detected_by"]}
|
|
628
|
-
for item in skill_usages
|
|
629
|
-
]
|
|
817
|
+
skill_summary = summarize_skill_usages(skill_usages)
|
|
630
818
|
|
|
631
819
|
# attach tool outputs
|
|
632
820
|
for c in tool_calls:
|
|
@@ -642,15 +830,17 @@ def emit_turn(
|
|
|
642
830
|
with propagate_attributes(
|
|
643
831
|
user_id=user_id,
|
|
644
832
|
session_id=session_id,
|
|
645
|
-
trace_name=
|
|
646
|
-
tags=[
|
|
833
|
+
trace_name="Agent Turn",
|
|
834
|
+
tags=[AGENT_NAME],
|
|
647
835
|
):
|
|
648
|
-
with langfuse.start_as_current_observation(
|
|
649
|
-
name=
|
|
836
|
+
with langfuse.start_as_current_observation(
|
|
837
|
+
name="Agent Turn",
|
|
650
838
|
input={"role": "user", "content": user_text},
|
|
839
|
+
output={"role": "assistant", "content": assistant_text},
|
|
651
840
|
metadata={
|
|
652
841
|
**interaction_meta,
|
|
653
|
-
"source":
|
|
842
|
+
"source": AGENT_NAME,
|
|
843
|
+
"agent": AGENT_NAME,
|
|
654
844
|
"session_id": session_id,
|
|
655
845
|
"turn_number": turn_num,
|
|
656
846
|
"transcript_path": str(transcript_path),
|
|
@@ -658,17 +848,9 @@ def emit_turn(
|
|
|
658
848
|
"skills": skill_summary,
|
|
659
849
|
},
|
|
660
850
|
) as trace_span:
|
|
661
|
-
with langfuse.start_as_current_observation(
|
|
662
|
-
name="AI Interaction",
|
|
663
|
-
input={"role": "user", "content": user_text},
|
|
664
|
-
output={"role": "assistant", "content": assistant_text},
|
|
665
|
-
metadata=interaction_meta,
|
|
666
|
-
):
|
|
667
|
-
pass
|
|
668
|
-
|
|
669
851
|
# LLM generation
|
|
670
852
|
with langfuse.start_as_current_observation(
|
|
671
|
-
name="
|
|
853
|
+
name="Agent Response",
|
|
672
854
|
as_type="generation",
|
|
673
855
|
model=model,
|
|
674
856
|
input={"role": "user", "content": user_text},
|
|
@@ -678,7 +860,8 @@ def emit_turn(
|
|
|
678
860
|
"assistant_text": assistant_text_meta,
|
|
679
861
|
"tool_count": len(tool_calls),
|
|
680
862
|
"usage_details": usage_details,
|
|
681
|
-
"source":
|
|
863
|
+
"source": AGENT_NAME,
|
|
864
|
+
"agent": AGENT_NAME,
|
|
682
865
|
"user_id": user_id or "",
|
|
683
866
|
"session_id": session_id,
|
|
684
867
|
"interaction_id": interaction_meta["interaction_id"],
|
|
@@ -687,24 +870,6 @@ def emit_turn(
|
|
|
687
870
|
):
|
|
688
871
|
pass
|
|
689
872
|
|
|
690
|
-
for skill in skill_usages:
|
|
691
|
-
with langfuse.start_as_current_observation(
|
|
692
|
-
name="Skill Use",
|
|
693
|
-
metadata={
|
|
694
|
-
"source": "claude",
|
|
695
|
-
"user_id": user_id or "",
|
|
696
|
-
"session_id": session_id,
|
|
697
|
-
"interaction_id": interaction_meta["interaction_id"],
|
|
698
|
-
"skill_name": skill["name"],
|
|
699
|
-
"skill_use_count": 1,
|
|
700
|
-
"skill_namespace": skill["skill_namespace"],
|
|
701
|
-
"detected_by": skill["detected_by"],
|
|
702
|
-
"turn_number": turn_num,
|
|
703
|
-
"metrics_schema_version": METRICS_SCHEMA_VERSION,
|
|
704
|
-
},
|
|
705
|
-
):
|
|
706
|
-
pass
|
|
707
|
-
|
|
708
873
|
# Tool observations
|
|
709
874
|
for tc in tool_calls:
|
|
710
875
|
in_obj = tc["input"]
|
|
@@ -715,11 +880,12 @@ def emit_turn(
|
|
|
715
880
|
in_meta = None
|
|
716
881
|
|
|
717
882
|
with langfuse.start_as_current_observation(
|
|
718
|
-
name=
|
|
883
|
+
name="Tool Call",
|
|
719
884
|
as_type="tool",
|
|
720
885
|
input=in_obj,
|
|
721
886
|
metadata={
|
|
722
|
-
"source":
|
|
887
|
+
"source": AGENT_NAME,
|
|
888
|
+
"agent": AGENT_NAME,
|
|
723
889
|
"user_id": user_id or "",
|
|
724
890
|
"session_id": session_id,
|
|
725
891
|
"interaction_id": interaction_meta["interaction_id"],
|
package/package.json
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oh-langfuse",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.43",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "Use npm scripts to configure Claude Code / OpenCode / Codex with Langfuse tracing.",
|
|
7
|
-
"engines": {
|
|
8
|
-
"node": ">=16"
|
|
9
|
-
},
|
|
10
|
-
"bin": {
|
|
6
|
+
"description": "Use npm scripts to configure Claude Code / OpenCode / Codex with Langfuse tracing.",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=16"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
11
|
"oh-langfuse": "bin/cli.js",
|
|
12
12
|
"code-tool-langfuse": "bin/cli.js"
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
|
-
"bin",
|
|
16
|
-
"scripts/auto-update-runtime.mjs",
|
|
17
|
-
"scripts/codex-langfuse-check.mjs",
|
|
18
|
-
"scripts/codex-langfuse-setup.mjs",
|
|
19
|
-
"scripts/json-utils.mjs",
|
|
20
|
-
"scripts/langfuse-check.mjs",
|
|
21
|
-
"scripts/langfuse-setup.mjs",
|
|
22
|
-
"scripts/opencode-langfuse-check.mjs",
|
|
23
|
-
"scripts/opencode-langfuse-run.mjs",
|
|
24
|
-
"scripts/opencode-langfuse-setup.mjs",
|
|
25
|
-
"scripts/resolve-opencode-cli.mjs",
|
|
26
|
-
"scripts/real-self-verify.mjs",
|
|
27
|
-
"scripts/log-filter-utils.mjs",
|
|
28
|
-
"scripts/metrics-utils.mjs",
|
|
29
|
-
"scripts/runtime-state-utils.mjs",
|
|
30
|
-
"scripts/update-langfuse-runtime.mjs",
|
|
31
|
-
"scripts/update-utils.mjs",
|
|
15
|
+
"bin",
|
|
16
|
+
"scripts/auto-update-runtime.mjs",
|
|
17
|
+
"scripts/codex-langfuse-check.mjs",
|
|
18
|
+
"scripts/codex-langfuse-setup.mjs",
|
|
19
|
+
"scripts/json-utils.mjs",
|
|
20
|
+
"scripts/langfuse-check.mjs",
|
|
21
|
+
"scripts/langfuse-setup.mjs",
|
|
22
|
+
"scripts/opencode-langfuse-check.mjs",
|
|
23
|
+
"scripts/opencode-langfuse-run.mjs",
|
|
24
|
+
"scripts/opencode-langfuse-setup.mjs",
|
|
25
|
+
"scripts/resolve-opencode-cli.mjs",
|
|
26
|
+
"scripts/real-self-verify.mjs",
|
|
27
|
+
"scripts/log-filter-utils.mjs",
|
|
28
|
+
"scripts/metrics-utils.mjs",
|
|
29
|
+
"scripts/runtime-state-utils.mjs",
|
|
30
|
+
"scripts/update-langfuse-runtime.mjs",
|
|
31
|
+
"scripts/update-utils.mjs",
|
|
32
32
|
"langfuse_hook.py",
|
|
33
|
-
"codex_langfuse_notify.py",
|
|
33
|
+
"codex_langfuse_notify.py",
|
|
34
34
|
"README.md",
|
|
35
35
|
"SELF_VERIFY.md",
|
|
36
|
-
"CODEX_LANGFUSE_PLAN.md",
|
|
36
|
+
"CODEX_LANGFUSE_PLAN.md",
|
|
37
37
|
"setup-langfuse.bat",
|
|
38
38
|
"setup-langfuse.sh"
|
|
39
39
|
],
|
|
40
|
-
"scripts": {
|
|
41
|
-
"start": "node bin/cli.js",
|
|
42
|
-
"check": "node --check bin/cli.js",
|
|
43
|
-
"test": "node --test tests/*.test.mjs",
|
|
44
|
-
"pack:check": "npm pack --dry-run",
|
|
40
|
+
"scripts": {
|
|
41
|
+
"start": "node bin/cli.js",
|
|
42
|
+
"check": "node --check bin/cli.js",
|
|
43
|
+
"test": "node --test tests/*.test.mjs",
|
|
44
|
+
"pack:check": "npm pack --dry-run",
|
|
45
45
|
"claude:setup": "node scripts/langfuse-setup.mjs",
|
|
46
46
|
"claude:check": "node scripts/langfuse-check.mjs",
|
|
47
47
|
"langfuse:setup": "node scripts/langfuse-setup.mjs",
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
"opencode:langfuse:run": "node scripts/opencode-langfuse-run.mjs",
|
|
55
55
|
"codex:setup": "node scripts/codex-langfuse-setup.mjs",
|
|
56
56
|
"codex:check": "node scripts/codex-langfuse-check.mjs",
|
|
57
|
-
"codex:langfuse:setup": "node scripts/codex-langfuse-setup.mjs",
|
|
58
|
-
"codex:langfuse:check": "node scripts/codex-langfuse-check.mjs",
|
|
59
|
-
"update": "node scripts/update-langfuse-runtime.mjs",
|
|
60
|
-
"self:verify": "node scripts/real-self-verify.mjs"
|
|
61
|
-
},
|
|
57
|
+
"codex:langfuse:setup": "node scripts/codex-langfuse-setup.mjs",
|
|
58
|
+
"codex:langfuse:check": "node scripts/codex-langfuse-check.mjs",
|
|
59
|
+
"update": "node scripts/update-langfuse-runtime.mjs",
|
|
60
|
+
"self:verify": "node scripts/real-self-verify.mjs"
|
|
61
|
+
},
|
|
62
62
|
"dependencies": {}
|
|
63
63
|
}
|
|
@@ -112,8 +112,10 @@ async function main() {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
main()
|
|
115
|
-
.then((code) =>
|
|
115
|
+
.then((code) => {
|
|
116
|
+
process.exitCode = code;
|
|
117
|
+
})
|
|
116
118
|
.catch((error) => {
|
|
117
119
|
console.error(`[WARN] oh-langfuse auto-update skipped: ${error?.message || String(error)}`);
|
|
118
|
-
process.
|
|
120
|
+
process.exitCode = 0;
|
|
119
121
|
});
|