@ramarivera/coding-agent-langfuse 0.1.16 → 0.1.17

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 CHANGED
@@ -18,7 +18,18 @@ npx @ramarivera/coding-agent-langfuse@latest \
18
18
  --agents claude,codex,grok,pi,opencode \
19
19
  --endpoint https://langfuse.ai.roxasroot.net/otel/v1/traces \
20
20
  --state "$HOME/.local/state/coding-agent-langfuse/backfill-v6.json" \
21
- --batch-size 1000
21
+ --batch-size 10
22
+ ```
23
+
24
+ Run live incremental forwarding without putting inference behind a gateway:
25
+
26
+ ```sh
27
+ npx @ramarivera/coding-agent-langfuse@latest \
28
+ --agents codex \
29
+ --endpoint https://langfuse.ai.roxasroot.net/otel/v1/traces \
30
+ --state "$HOME/.local/state/coding-agent-langfuse/live-codex.json" \
31
+ --batch-size 10 \
32
+ --follow
22
33
  ```
23
34
 
24
35
  The importer is fail-fast: the first failed OTLP POST stops the run, prints the
@@ -33,6 +33,9 @@ type BackfillOptions = {
33
33
  statePath: string;
34
34
  homeDir: string;
35
35
  dryRun: boolean;
36
+ follow: boolean;
37
+ pollIntervalMs: number;
38
+ idleExitAfterMs?: number;
36
39
  limit?: number;
37
40
  sinceMs?: number;
38
41
  untilMs?: number;
@@ -50,6 +53,10 @@ type RunSummary = {
50
53
  endpoint: string;
51
54
  statePath: string;
52
55
  };
56
+ type FollowSummary = RunSummary & {
57
+ iterations: number;
58
+ follow: true;
59
+ };
53
60
  declare function parseArgs(argv: string[]): BackfillOptions;
54
61
  declare function codexEvents(homeDir: string): BackfillEvent[];
55
62
  declare function claudeEvents(homeDir: string): BackfillEvent[];
@@ -60,5 +67,6 @@ declare function fingerprint(event: BackfillEvent): string;
60
67
  declare function toOtlp(events: BackfillEvent[]): Record<string, unknown>;
61
68
  declare function discoverEvents(options: BackfillOptions): BackfillEvent[];
62
69
  declare function run(options: BackfillOptions): Promise<RunSummary>;
63
- declare function main(argv?: string[]): Promise<RunSummary>;
64
- export { type BackfillEvent, type BackfillOptions, claudeEvents, codexEvents, discoverEvents, fingerprint, grokEvents, main, opencodeEvents, parseArgs, piEvents, run, toOtlp, };
70
+ declare function follow(options: BackfillOptions): Promise<FollowSummary>;
71
+ declare function main(argv?: string[]): Promise<RunSummary | FollowSummary>;
72
+ export { type BackfillEvent, type BackfillOptions, claudeEvents, codexEvents, discoverEvents, fingerprint, follow, grokEvents, main, opencodeEvents, parseArgs, piEvents, run, toOtlp, };
package/dist/backfill.js CHANGED
@@ -21,6 +21,9 @@ Options:
21
21
  --until ISO_OR_MS Only import events before or at this timestamp
22
22
  --limit N Stop after N unsent events
23
23
  --batch-size N OTLP spans per POST (default: 50)
24
+ --follow Keep scanning and sending newly written events
25
+ --poll-interval-ms N Delay between --follow scans (default: 5000)
26
+ --idle-exit-after-ms N Stop --follow after this much time without new sends
24
27
  --dry-run Discover and dedupe without sending or mutating state
25
28
  --help Show this help
26
29
  `;
@@ -34,6 +37,9 @@ function parseArgs(argv) {
34
37
  let sinceMs;
35
38
  let untilMs;
36
39
  let batchSize = 50;
40
+ let follow = false;
41
+ let pollIntervalMs = 5_000;
42
+ let idleExitAfterMs;
37
43
  const agents = new Set(allAgents);
38
44
  for (let i = 0; i < argv.length; i++) {
39
45
  const arg = argv[i];
@@ -75,6 +81,15 @@ function parseArgs(argv) {
75
81
  else if (arg === "--batch-size") {
76
82
  batchSize = Number.parseInt(next(), 10);
77
83
  }
84
+ else if (arg === "--follow") {
85
+ follow = true;
86
+ }
87
+ else if (arg === "--poll-interval-ms") {
88
+ pollIntervalMs = Number.parseInt(next(), 10);
89
+ }
90
+ else if (arg === "--idle-exit-after-ms") {
91
+ idleExitAfterMs = Number.parseInt(next(), 10);
92
+ }
78
93
  else if (arg === "--since") {
79
94
  sinceMs = parseTime(next());
80
95
  }
@@ -88,6 +103,13 @@ function parseArgs(argv) {
88
103
  if (!Number.isFinite(batchSize) || batchSize < 1) {
89
104
  throw new Error("--batch-size must be a positive integer");
90
105
  }
106
+ if (!Number.isFinite(pollIntervalMs) || pollIntervalMs < 1) {
107
+ throw new Error("--poll-interval-ms must be a positive integer");
108
+ }
109
+ if (idleExitAfterMs !== undefined &&
110
+ (!Number.isFinite(idleExitAfterMs) || idleExitAfterMs < 1)) {
111
+ throw new Error("--idle-exit-after-ms must be a positive integer");
112
+ }
91
113
  if (limit !== undefined && (!Number.isFinite(limit) || limit < 1)) {
92
114
  throw new Error("--limit must be a positive integer");
93
115
  }
@@ -97,6 +119,9 @@ function parseArgs(argv) {
97
119
  statePath,
98
120
  homeDir,
99
121
  dryRun,
122
+ follow,
123
+ pollIntervalMs,
124
+ idleExitAfterMs,
100
125
  limit,
101
126
  sinceMs,
102
127
  untilMs,
@@ -1035,8 +1060,63 @@ async function run(options) {
1035
1060
  statePath: options.statePath,
1036
1061
  };
1037
1062
  }
1063
+ function mergeSummary(base, next) {
1064
+ return {
1065
+ discovered: Object.fromEntries(allAgents.map((agent) => [
1066
+ agent,
1067
+ Math.max(base.discovered[agent] ?? 0, next.discovered[agent] ?? 0),
1068
+ ])),
1069
+ sent: base.sent + next.sent,
1070
+ skipped: next.skipped,
1071
+ failed: base.failed + next.failed,
1072
+ notAttempted: next.notAttempted,
1073
+ aborted: base.aborted || next.aborted,
1074
+ ...(next.error ? { error: next.error } : base.error ? { error: base.error } : {}),
1075
+ dryRun: base.dryRun,
1076
+ endpoint: base.endpoint,
1077
+ statePath: base.statePath,
1078
+ };
1079
+ }
1080
+ function sleep(ms) {
1081
+ return new Promise((resolve) => setTimeout(resolve, ms));
1082
+ }
1083
+ async function follow(options) {
1084
+ let iterations = 0;
1085
+ let lastSendOrStartMs = Date.now();
1086
+ let aggregate;
1087
+ while (true) {
1088
+ iterations += 1;
1089
+ const summary = await run(options);
1090
+ aggregate = aggregate === undefined ? summary : mergeSummary(aggregate, summary);
1091
+ if (summary.sent > 0)
1092
+ lastSendOrStartMs = Date.now();
1093
+ if (summary.aborted)
1094
+ break;
1095
+ if (options.idleExitAfterMs !== undefined &&
1096
+ Date.now() - lastSendOrStartMs >= options.idleExitAfterMs) {
1097
+ break;
1098
+ }
1099
+ await sleep(options.pollIntervalMs);
1100
+ }
1101
+ return {
1102
+ ...(aggregate ?? {
1103
+ discovered: Object.fromEntries(allAgents.map((agent) => [agent, 0])),
1104
+ sent: 0,
1105
+ skipped: 0,
1106
+ failed: 0,
1107
+ notAttempted: 0,
1108
+ aborted: false,
1109
+ dryRun: options.dryRun,
1110
+ endpoint: options.endpoint,
1111
+ statePath: options.statePath,
1112
+ }),
1113
+ iterations,
1114
+ follow: true,
1115
+ };
1116
+ }
1038
1117
  async function main(argv = process.argv.slice(2)) {
1039
- const summary = await run(parseArgs(argv));
1118
+ const options = parseArgs(argv);
1119
+ const summary = options.follow ? await follow(options) : await run(options);
1040
1120
  console.log(JSON.stringify(summary, null, 2));
1041
1121
  return summary;
1042
1122
  }
@@ -1050,4 +1130,4 @@ if (import.meta.url === `file://${process.argv[1]}`) {
1050
1130
  process.exit(1);
1051
1131
  }
1052
1132
  }
1053
- export { claudeEvents, codexEvents, discoverEvents, fingerprint, grokEvents, main, opencodeEvents, parseArgs, piEvents, run, toOtlp, };
1133
+ export { claudeEvents, codexEvents, discoverEvents, fingerprint, follow, grokEvents, main, opencodeEvents, parseArgs, piEvents, run, toOtlp, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ramarivera/coding-agent-langfuse",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "Universal coding-agent Langfuse backfiller and live OTLP helpers",
5
5
  "type": "module",
6
6
  "license": "MIT",