@ramarivera/coding-agent-langfuse 0.1.48 → 0.1.51
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 +17 -15
- package/dist/backfill.d.ts +1 -0
- package/dist/backfill.js +37 -28
- package/dist/service.js +2 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,14 +54,14 @@ real network cause, and preserves local state so reruns resume cleanly.
|
|
|
54
54
|
## Project tags and metadata
|
|
55
55
|
|
|
56
56
|
Backfills and live followers can attach stable Langfuse dimensions based on the
|
|
57
|
-
session cwd. Use this for client/workstream dashboards
|
|
58
|
-
|
|
57
|
+
session cwd. Use this for client/workstream dashboards without guessing from
|
|
58
|
+
host names or Windows paths in the Langfuse UI.
|
|
59
59
|
|
|
60
60
|
There are two config layers:
|
|
61
61
|
|
|
62
62
|
- Global host config: `~/.config/coding-agent-langfuse/path-tags.json`
|
|
63
|
-
- Optional project-local
|
|
64
|
-
|
|
63
|
+
- Optional project-local overlays: every `.langfuse-ca.json` found while
|
|
64
|
+
walking from the session cwd up to the scanned home directory
|
|
65
65
|
|
|
66
66
|
Global config uses prefix rules:
|
|
67
67
|
|
|
@@ -69,21 +69,23 @@ Global config uses prefix rules:
|
|
|
69
69
|
{
|
|
70
70
|
"rules": [
|
|
71
71
|
{
|
|
72
|
-
"pathPrefix": "/Users/
|
|
73
|
-
"tags": ["
|
|
72
|
+
"pathPrefix": "/Users/example/dev/acme",
|
|
73
|
+
"tags": ["acme", "client:acme"],
|
|
74
74
|
"metadata": {
|
|
75
|
-
"project_group": "
|
|
76
|
-
"project_owner": "
|
|
75
|
+
"project_group": "acme",
|
|
76
|
+
"project_owner": "platform-team"
|
|
77
77
|
},
|
|
78
|
-
"projectName": "
|
|
79
|
-
"projectFolder": "
|
|
78
|
+
"projectName": "acme",
|
|
79
|
+
"projectFolder": "acme"
|
|
80
80
|
}
|
|
81
81
|
]
|
|
82
82
|
}
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
Project-local config is intentionally smaller and overrides/extends the matched
|
|
86
|
-
global rule
|
|
86
|
+
global rule. Parent overlays are applied before child overlays, so a config at
|
|
87
|
+
`~/work/client-a/.langfuse-ca.json` can tag a whole workstream while nested
|
|
88
|
+
repos add repo-specific fields:
|
|
87
89
|
|
|
88
90
|
```json
|
|
89
91
|
{
|
|
@@ -91,7 +93,7 @@ global rule:
|
|
|
91
93
|
"metadata": {
|
|
92
94
|
"service": "portal"
|
|
93
95
|
},
|
|
94
|
-
"projectName": "
|
|
96
|
+
"projectName": "acme-portal"
|
|
95
97
|
}
|
|
96
98
|
```
|
|
97
99
|
|
|
@@ -112,9 +114,9 @@ npx @ramarivera/coding-agent-langfuse@latest \
|
|
|
112
114
|
--path-tags-config "$HOME/.config/coding-agent-langfuse/path-tags.json"
|
|
113
115
|
```
|
|
114
116
|
|
|
115
|
-
Generated services
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
Generated services keep their command small and rely on the package's default
|
|
118
|
+
global config plus upward project-local discovery. Use `--path-tags-config` only
|
|
119
|
+
for one-off CLI runs that need a non-default global config path.
|
|
118
120
|
|
|
119
121
|
## Cost calculation
|
|
120
122
|
|
package/dist/backfill.d.ts
CHANGED
package/dist/backfill.js
CHANGED
|
@@ -196,7 +196,7 @@ function projectMetadata(cwd) {
|
|
|
196
196
|
projectFolder: projectName,
|
|
197
197
|
};
|
|
198
198
|
}
|
|
199
|
-
function matchPathTags(cwd, config) {
|
|
199
|
+
function matchPathTags(cwd, config, homeDir) {
|
|
200
200
|
if (!cwd)
|
|
201
201
|
return { tags: [], metadata: {} };
|
|
202
202
|
const normalizedCwd = normalizeRulePath(cwd);
|
|
@@ -218,7 +218,7 @@ function matchPathTags(cwd, config) {
|
|
|
218
218
|
const overlays = [
|
|
219
219
|
globalMatch,
|
|
220
220
|
config.project,
|
|
221
|
-
|
|
221
|
+
...loadProjectLocalOverlays(cwd, homeDir),
|
|
222
222
|
].filter((overlay) => overlay !== undefined);
|
|
223
223
|
return overlays.reduce((acc, overlay) => {
|
|
224
224
|
if (!overlay)
|
|
@@ -236,9 +236,9 @@ function matchPathTags(cwd, config) {
|
|
|
236
236
|
projectFolder: undefined,
|
|
237
237
|
});
|
|
238
238
|
}
|
|
239
|
-
function mergeProjectMetadata(cwd, config) {
|
|
239
|
+
function mergeProjectMetadata(cwd, config, homeDir) {
|
|
240
240
|
const project = projectMetadata(cwd);
|
|
241
|
-
const pathTags = matchPathTags(cwd, config);
|
|
241
|
+
const pathTags = matchPathTags(cwd, config, homeDir);
|
|
242
242
|
return {
|
|
243
243
|
...project,
|
|
244
244
|
projectName: pathTags.projectName ?? project.projectName,
|
|
@@ -528,10 +528,12 @@ function loadPathTagsConfig(path) {
|
|
|
528
528
|
});
|
|
529
529
|
return { rules, project: normalizeProjectTagOverlay(root, path) };
|
|
530
530
|
}
|
|
531
|
-
function
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
531
|
+
function loadProjectLocalOverlays(cwd, homeDir) {
|
|
532
|
+
return findProjectLocalConfigs(cwd, homeDir)
|
|
533
|
+
.map((configPath) => loadProjectLocalOverlay(configPath))
|
|
534
|
+
.filter((overlay) => overlay !== undefined);
|
|
535
|
+
}
|
|
536
|
+
function loadProjectLocalOverlay(configPath) {
|
|
535
537
|
const cached = projectLocalConfigCache.get(configPath);
|
|
536
538
|
if (projectLocalConfigCache.has(configPath))
|
|
537
539
|
return cached;
|
|
@@ -545,31 +547,26 @@ function loadProjectLocalOverlay(cwd) {
|
|
|
545
547
|
projectLocalConfigCache.set(configPath, overlay);
|
|
546
548
|
return overlay;
|
|
547
549
|
}
|
|
548
|
-
function
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
}
|
|
557
|
-
catch {
|
|
558
|
-
const parent = dirname(current);
|
|
559
|
-
if (parent === current)
|
|
560
|
-
return undefined;
|
|
561
|
-
current = parent;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
550
|
+
function findProjectLocalConfigs(cwd, homeDir) {
|
|
551
|
+
const start = nearestExistingDirectory(cwd);
|
|
552
|
+
if (!start)
|
|
553
|
+
return [];
|
|
554
|
+
const home = homeDir ? nearestExistingDirectory(homeDir) ?? homeDir : homedir();
|
|
555
|
+
const stopAtHome = isSameOrChildPath(start, home);
|
|
556
|
+
const configPaths = [];
|
|
557
|
+
let current = start;
|
|
564
558
|
while (true) {
|
|
565
559
|
const candidate = join(current, projectLocalConfigFile);
|
|
566
560
|
if (existsSync(candidate))
|
|
567
|
-
|
|
561
|
+
configPaths.push(candidate);
|
|
562
|
+
if (stopAtHome && sameNormalizedPath(current, home))
|
|
563
|
+
break;
|
|
568
564
|
const parent = dirname(current);
|
|
569
565
|
if (parent === current)
|
|
570
|
-
|
|
566
|
+
break;
|
|
571
567
|
current = parent;
|
|
572
568
|
}
|
|
569
|
+
return configPaths.reverse();
|
|
573
570
|
}
|
|
574
571
|
function normalizeProjectTagOverlay(value, sourcePath) {
|
|
575
572
|
const root = asRecord(value);
|
|
@@ -596,6 +593,15 @@ function normalizeRulePath(path) {
|
|
|
596
593
|
const normalized = path.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
597
594
|
return /^[a-z]:\//i.test(normalized) ? normalized.toLowerCase() : normalized;
|
|
598
595
|
}
|
|
596
|
+
function sameNormalizedPath(left, right) {
|
|
597
|
+
return normalizeRulePath(left) === normalizeRulePath(right);
|
|
598
|
+
}
|
|
599
|
+
function isSameOrChildPath(child, parent) {
|
|
600
|
+
const normalizedChild = normalizeRulePath(child);
|
|
601
|
+
const normalizedParent = normalizeRulePath(parent);
|
|
602
|
+
return normalizedChild === normalizedParent ||
|
|
603
|
+
normalizedChild.startsWith(`${normalizedParent}/`);
|
|
604
|
+
}
|
|
599
605
|
function loadCostCatalogFromEnv() {
|
|
600
606
|
let catalog = { ...defaultCostRates };
|
|
601
607
|
const path = process.env.CODING_AGENT_LANGFUSE_COST_RATES_PATH ??
|
|
@@ -1868,6 +1874,7 @@ function toOtlp(events, options = {}) {
|
|
|
1868
1874
|
const maxFieldBytes = options.maxFieldBytes ?? defaultMaxFieldBytes;
|
|
1869
1875
|
const costRates = options.costRates ?? loadCostCatalogFromEnv();
|
|
1870
1876
|
const pathTagsConfig = options.pathTagsConfig ?? { rules: [] };
|
|
1877
|
+
const projectConfigHomeDir = options.homeDir ?? process.env.HOME ?? homedir();
|
|
1871
1878
|
const spansByTrace = new Map();
|
|
1872
1879
|
for (const rawEvent of events) {
|
|
1873
1880
|
const event = limitEventPayload(rawEvent, maxFieldBytes);
|
|
@@ -1882,7 +1889,7 @@ function toOtlp(events, options = {}) {
|
|
|
1882
1889
|
const traceStartMs = sortedEvents[0]?.startMs ?? Date.now();
|
|
1883
1890
|
const traceEndMs = Math.max(...sortedEvents.map((event) => event.endMs ?? event.startMs + 1), traceStartMs + 1);
|
|
1884
1891
|
const shouldEmitRootSpan = sortedEvents.some((event) => event.recordId === "session");
|
|
1885
|
-
const firstProject = mergeProjectMetadata(first.cwd, pathTagsConfig);
|
|
1892
|
+
const firstProject = mergeProjectMetadata(first.cwd, pathTagsConfig, projectConfigHomeDir);
|
|
1886
1893
|
const rootAttributes = [
|
|
1887
1894
|
attr("service.name", `agent.${first.agent}`),
|
|
1888
1895
|
attr("deployment.environment", "local"),
|
|
@@ -1962,7 +1969,7 @@ function toOtlp(events, options = {}) {
|
|
|
1962
1969
|
const generation = isGenerationEvent(event);
|
|
1963
1970
|
const usage = usageDetails(event.usage);
|
|
1964
1971
|
const cost = generation ? calculateCost(event, usage, costRates) : undefined;
|
|
1965
|
-
const eventProject = mergeProjectMetadata(event.cwd, pathTagsConfig);
|
|
1972
|
+
const eventProject = mergeProjectMetadata(event.cwd, pathTagsConfig, projectConfigHomeDir);
|
|
1966
1973
|
const attributes = [
|
|
1967
1974
|
attr("service.name", `agent.${event.agent}`),
|
|
1968
1975
|
attr("deployment.environment", "local"),
|
|
@@ -2205,6 +2212,7 @@ async function run(options) {
|
|
|
2205
2212
|
maxFieldBytes: options.maxFieldBytes,
|
|
2206
2213
|
costRates: options.costRates,
|
|
2207
2214
|
pathTagsConfig: options.pathTagsConfig,
|
|
2215
|
+
homeDir: options.homeDir,
|
|
2208
2216
|
});
|
|
2209
2217
|
}
|
|
2210
2218
|
catch (error) {
|
|
@@ -2232,6 +2240,7 @@ async function run(options) {
|
|
|
2232
2240
|
maxFieldBytes: options.maxFieldBytes,
|
|
2233
2241
|
costRates: options.costRates,
|
|
2234
2242
|
pathTagsConfig: options.pathTagsConfig,
|
|
2243
|
+
homeDir: options.homeDir,
|
|
2235
2244
|
auth: options.auth,
|
|
2236
2245
|
});
|
|
2237
2246
|
for (const event of batch) {
|
package/dist/service.js
CHANGED
|
@@ -36,11 +36,11 @@ Service options:
|
|
|
36
36
|
`;
|
|
37
37
|
}
|
|
38
38
|
function parseServiceArgs(argv) {
|
|
39
|
-
const action = parseServiceAction(argv[0]);
|
|
40
39
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
41
40
|
console.log(serviceUsage());
|
|
42
41
|
process.exit(0);
|
|
43
42
|
}
|
|
43
|
+
const action = parseServiceAction(argv[0]);
|
|
44
44
|
let platform = currentServicePlatform();
|
|
45
45
|
let agents = [...allAgents];
|
|
46
46
|
let endpoint = process.env.LANGFUSE_BACKFILL_ENDPOINT ?? defaultEndpoint;
|
|
@@ -56,8 +56,7 @@ function parseServiceArgs(argv) {
|
|
|
56
56
|
process.env.LANGFUSE_BACKFILL_COST_RATES_PATH;
|
|
57
57
|
let costRatesJson = process.env.CODING_AGENT_LANGFUSE_COST_RATES_JSON ??
|
|
58
58
|
process.env.LANGFUSE_BACKFILL_COST_RATES_JSON;
|
|
59
|
-
let pathTagsConfigPath
|
|
60
|
-
process.env.LANGFUSE_BACKFILL_PATH_TAGS_CONFIG;
|
|
59
|
+
let pathTagsConfigPath;
|
|
61
60
|
let since;
|
|
62
61
|
let dryRun = false;
|
|
63
62
|
let start = true;
|