@ramarivera/coding-agent-langfuse 0.1.43 → 0.1.44
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 +31 -5
- package/dist/backfill.d.ts +5 -0
- package/dist/backfill.js +179 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,12 +45,18 @@ records a total cost, that recorded value wins. Otherwise, the importer
|
|
|
45
45
|
calculates per-usage-type USD costs from a model catalog using rates in USD per
|
|
46
46
|
1M tokens.
|
|
47
47
|
|
|
48
|
-
The built-in catalog covers OpenAI GPT-5.5
|
|
48
|
+
The built-in catalog covers OpenAI GPT-5.5, GPT-5.4, and GPT-5.3-Codex API list
|
|
49
|
+
pricing, Anthropic Claude Opus/Sonnet 4 API list pricing, plus the toolbox/Pi
|
|
49
50
|
models already used in local configuration, including Fireworks Kimi K2.6,
|
|
50
51
|
Fireworks DeepSeek V4 Pro, MiniMax-M3, Together DeepSeek/Kimi/GLM/MiniMax, and
|
|
51
52
|
Zai GLM. `gpt-5.5` is charged at current standard API list price by default:
|
|
52
53
|
`$5.00` input, `$0.50` cached input, and `$30.00` output per 1M tokens. GPT-5.5
|
|
53
|
-
Pro defaults to `$30.00` input and `$180.00` output per 1M tokens.
|
|
54
|
+
Pro defaults to `$30.00` input and `$180.00` output per 1M tokens. Claude Opus 4
|
|
55
|
+
models default to `$15.00` input, `$1.50` cache hits, `$18.75` 5-minute cache
|
|
56
|
+
writes, `$30.00` 1-hour cache writes, and `$75.00` output per 1M tokens.
|
|
57
|
+
When a source only records a total token count without input/output/cache
|
|
58
|
+
breakdown, the importer charges that total at the model input rate and marks the
|
|
59
|
+
cost source as `calculated_total_as_input`.
|
|
54
60
|
|
|
55
61
|
Use an override only when you intentionally want a different accounting policy:
|
|
56
62
|
|
|
@@ -72,7 +78,9 @@ services. A file can be either a direct model map or `{ "rates": { ... } }`:
|
|
|
72
78
|
"input": 1,
|
|
73
79
|
"output": 2,
|
|
74
80
|
"cacheRead": 0.1,
|
|
75
|
-
"cacheWrite": 0
|
|
81
|
+
"cacheWrite": 0,
|
|
82
|
+
"cacheWrite5m": 0,
|
|
83
|
+
"cacheWrite1h": 0
|
|
76
84
|
}
|
|
77
85
|
}
|
|
78
86
|
}
|
|
@@ -138,8 +146,26 @@ npx @ramarivera/coding-agent-langfuse@latest \
|
|
|
138
146
|
--endpoint https://langfuse.ai.roxasroot.net/otel/v1/traces
|
|
139
147
|
```
|
|
140
148
|
|
|
141
|
-
Deduplication is state-file based and keyed by
|
|
142
|
-
record id. Reuse the same `--state` path for
|
|
149
|
+
Deduplication is state-file based and keyed by importer state identity, agent,
|
|
150
|
+
session id, and source record id. Reuse the same `--state` path for normal
|
|
151
|
+
incremental runs.
|
|
152
|
+
|
|
153
|
+
For an intentional repair replay, add `--force` to resend the selected window
|
|
154
|
+
even when the state file says those events were already sent:
|
|
155
|
+
|
|
156
|
+
```sh
|
|
157
|
+
npx @ramarivera/coding-agent-langfuse@latest \
|
|
158
|
+
--agents claude,codex,grok,pi,opencode \
|
|
159
|
+
--since 2026-05-01T00:00:00Z \
|
|
160
|
+
--until 2026-06-01T00:00:00Z \
|
|
161
|
+
--force \
|
|
162
|
+
--endpoint https://langfuse.ai.roxasroot.net/otel/v1/traces
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The Langfuse trace/span IDs intentionally stay pinned to the original pre-cost
|
|
166
|
+
identity, while the state-file key can advance with importer payload changes.
|
|
167
|
+
That lets cost repairs replace historical zero-cost rows instead of creating a
|
|
168
|
+
new duplicate identity for the same source event.
|
|
143
169
|
|
|
144
170
|
## Verification
|
|
145
171
|
|
package/dist/backfill.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ type Usage = {
|
|
|
6
6
|
reasoning?: number;
|
|
7
7
|
cacheRead?: number;
|
|
8
8
|
cacheWrite?: number;
|
|
9
|
+
cacheWrite5m?: number;
|
|
10
|
+
cacheWrite1h?: number;
|
|
9
11
|
total?: number;
|
|
10
12
|
cost?: number;
|
|
11
13
|
inputIncludesCache?: boolean;
|
|
@@ -34,6 +36,7 @@ type BackfillOptions = {
|
|
|
34
36
|
statePath: string;
|
|
35
37
|
homeDir: string;
|
|
36
38
|
dryRun: boolean;
|
|
39
|
+
force: boolean;
|
|
37
40
|
follow: boolean;
|
|
38
41
|
pollIntervalMs: number;
|
|
39
42
|
idleExitAfterMs?: number;
|
|
@@ -53,6 +56,8 @@ type CostRates = {
|
|
|
53
56
|
reasoning?: number;
|
|
54
57
|
cacheRead?: number;
|
|
55
58
|
cacheWrite?: number;
|
|
59
|
+
cacheWrite5m?: number;
|
|
60
|
+
cacheWrite1h?: number;
|
|
56
61
|
};
|
|
57
62
|
type CostCatalog = Record<string, CostRates>;
|
|
58
63
|
type OtlpOptions = {
|
package/dist/backfill.js
CHANGED
|
@@ -5,14 +5,23 @@ import { existsSync, mkdirSync, renameSync, readdirSync, readFileSync, statSync,
|
|
|
5
5
|
import { hostname, homedir } from "node:os";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
const allAgents = ["claude", "codex", "grok", "opencode", "pi"];
|
|
8
|
-
const
|
|
9
|
-
const
|
|
8
|
+
const importStateIdentityVersion = "v9-cost-details";
|
|
9
|
+
const importStateIdentityVersions = {
|
|
10
10
|
claude: "v12-cost-details",
|
|
11
11
|
codex: "v10-cost-details",
|
|
12
12
|
grok: "v12-cost-details",
|
|
13
13
|
opencode: "v11-cost-details",
|
|
14
14
|
pi: "v12-cost-details",
|
|
15
15
|
};
|
|
16
|
+
const langfuseIdIdentityVersion = "v8-cached-input-token-split";
|
|
17
|
+
const langfuseIdIdentityVersions = {
|
|
18
|
+
claude: "v11-tool-results",
|
|
19
|
+
codex: "v9-codex-conversation-events",
|
|
20
|
+
grok: "v11-chat-history-only",
|
|
21
|
+
opencode: "v10-opencode-message-parts",
|
|
22
|
+
pi: "v11-tool-results",
|
|
23
|
+
};
|
|
24
|
+
const importPayloadVersion = "v10-cost-details";
|
|
16
25
|
const defaultEndpoint = "https://langfuse.ai.roxasroot.net/otel/v1/traces";
|
|
17
26
|
const deadRemoteEndpoint = "http://langfuse.ai.roxasroot.net:14318/v1/traces";
|
|
18
27
|
const defaultMaxRequestBytes = 12 * 1024 * 1024;
|
|
@@ -49,11 +58,63 @@ const gpt55ProRates = {
|
|
|
49
58
|
cacheRead: 30,
|
|
50
59
|
cacheWrite: 30,
|
|
51
60
|
};
|
|
61
|
+
const gpt54Rates = {
|
|
62
|
+
input: 2.5,
|
|
63
|
+
output: 15,
|
|
64
|
+
cacheRead: 0.25,
|
|
65
|
+
cacheWrite: 2.5,
|
|
66
|
+
};
|
|
67
|
+
const gpt53CodexRates = {
|
|
68
|
+
input: 1.75,
|
|
69
|
+
output: 14,
|
|
70
|
+
cacheRead: 0.175,
|
|
71
|
+
cacheWrite: 1.75,
|
|
72
|
+
};
|
|
73
|
+
const claudeOpus4Rates = {
|
|
74
|
+
input: 15,
|
|
75
|
+
output: 75,
|
|
76
|
+
cacheRead: 1.5,
|
|
77
|
+
cacheWrite: 18.75,
|
|
78
|
+
cacheWrite5m: 18.75,
|
|
79
|
+
cacheWrite1h: 30,
|
|
80
|
+
};
|
|
81
|
+
const claudeSonnet4Rates = {
|
|
82
|
+
input: 3,
|
|
83
|
+
output: 15,
|
|
84
|
+
cacheRead: 0.3,
|
|
85
|
+
cacheWrite: 3.75,
|
|
86
|
+
cacheWrite5m: 3.75,
|
|
87
|
+
cacheWrite1h: 6,
|
|
88
|
+
};
|
|
52
89
|
const defaultCostRates = {
|
|
53
90
|
"gpt-5.5": gpt55Rates,
|
|
54
91
|
"openai/gpt-5.5": gpt55Rates,
|
|
55
92
|
"gpt-5.5-pro": gpt55ProRates,
|
|
56
93
|
"openai/gpt-5.5-pro": gpt55ProRates,
|
|
94
|
+
"gpt-5.4": gpt54Rates,
|
|
95
|
+
"openai/gpt-5.4": gpt54Rates,
|
|
96
|
+
"gpt-5.3-codex": gpt53CodexRates,
|
|
97
|
+
"openai/gpt-5.3-codex": gpt53CodexRates,
|
|
98
|
+
"claude-opus-4": claudeOpus4Rates,
|
|
99
|
+
"anthropic/claude-opus-4": claudeOpus4Rates,
|
|
100
|
+
"claude-opus-4-1": claudeOpus4Rates,
|
|
101
|
+
"anthropic/claude-opus-4-1": claudeOpus4Rates,
|
|
102
|
+
"claude-opus-4-6": claudeOpus4Rates,
|
|
103
|
+
"anthropic/claude-opus-4-6": claudeOpus4Rates,
|
|
104
|
+
"claude-opus-4-7": claudeOpus4Rates,
|
|
105
|
+
"anthropic/claude-opus-4-7": claudeOpus4Rates,
|
|
106
|
+
"claude-opus-4-8": claudeOpus4Rates,
|
|
107
|
+
"anthropic/claude-opus-4-8": claudeOpus4Rates,
|
|
108
|
+
"claude-sonnet-4": claudeSonnet4Rates,
|
|
109
|
+
"anthropic/claude-sonnet-4": claudeSonnet4Rates,
|
|
110
|
+
"claude-sonnet-4-5": claudeSonnet4Rates,
|
|
111
|
+
"anthropic/claude-sonnet-4-5": claudeSonnet4Rates,
|
|
112
|
+
"claude-sonnet-4.5": claudeSonnet4Rates,
|
|
113
|
+
"anthropic/claude-sonnet-4.5": claudeSonnet4Rates,
|
|
114
|
+
"claude-sonnet-4-6": claudeSonnet4Rates,
|
|
115
|
+
"anthropic/claude-sonnet-4-6": claudeSonnet4Rates,
|
|
116
|
+
"claude-sonnet-4.6": claudeSonnet4Rates,
|
|
117
|
+
"anthropic/claude-sonnet-4.6": claudeSonnet4Rates,
|
|
57
118
|
"accounts/fireworks/routers/kimi-k2p6-turbo": kimiFirepassRates,
|
|
58
119
|
"fireworks-firepass/accounts/fireworks/routers/kimi-k2p6-turbo": kimiFirepassRates,
|
|
59
120
|
"kimi-for-coding": kimiFirepassRates,
|
|
@@ -67,24 +128,48 @@ const defaultCostRates = {
|
|
|
67
128
|
cacheRead: 0.2,
|
|
68
129
|
cacheWrite: 0,
|
|
69
130
|
},
|
|
131
|
+
"deepseek-ai/DeepSeek-V4-Pro": {
|
|
132
|
+
input: 2.1,
|
|
133
|
+
output: 4.4,
|
|
134
|
+
cacheRead: 0.2,
|
|
135
|
+
cacheWrite: 0,
|
|
136
|
+
},
|
|
70
137
|
"together/zai-org/GLM-5.1": {
|
|
71
138
|
input: 1.4,
|
|
72
139
|
output: 4.4,
|
|
73
140
|
cacheRead: 0.2,
|
|
74
141
|
cacheWrite: 0,
|
|
75
142
|
},
|
|
143
|
+
"zai-org/GLM-5.1": {
|
|
144
|
+
input: 1.4,
|
|
145
|
+
output: 4.4,
|
|
146
|
+
cacheRead: 0.2,
|
|
147
|
+
cacheWrite: 0,
|
|
148
|
+
},
|
|
76
149
|
"together/moonshotai/Kimi-K2.6": {
|
|
77
150
|
input: 1.2,
|
|
78
151
|
output: 4.5,
|
|
79
152
|
cacheRead: 0.2,
|
|
80
153
|
cacheWrite: 0,
|
|
81
154
|
},
|
|
155
|
+
"moonshotai/Kimi-K2.6": {
|
|
156
|
+
input: 1.2,
|
|
157
|
+
output: 4.5,
|
|
158
|
+
cacheRead: 0.2,
|
|
159
|
+
cacheWrite: 0,
|
|
160
|
+
},
|
|
82
161
|
"together/MiniMaxAI/MiniMax-M2.7": {
|
|
83
162
|
input: 0.3,
|
|
84
163
|
output: 1.2,
|
|
85
164
|
cacheRead: 0.06,
|
|
86
165
|
cacheWrite: 0,
|
|
87
166
|
},
|
|
167
|
+
"MiniMax-M2.7": {
|
|
168
|
+
input: 0.3,
|
|
169
|
+
output: 1.2,
|
|
170
|
+
cacheRead: 0.06,
|
|
171
|
+
cacheWrite: 0,
|
|
172
|
+
},
|
|
88
173
|
"zai/glm-5.1": {
|
|
89
174
|
input: 1.4,
|
|
90
175
|
output: 4.4,
|
|
@@ -125,6 +210,7 @@ Options:
|
|
|
125
210
|
--poll-interval-ms N Delay between --follow scans (default: 5000)
|
|
126
211
|
--idle-exit-after-ms N Stop --follow after this much time without new sends
|
|
127
212
|
--dry-run Discover and dedupe without sending or mutating state
|
|
213
|
+
--force Resend discovered events even when present in local state
|
|
128
214
|
--help Show this help
|
|
129
215
|
`;
|
|
130
216
|
}
|
|
@@ -133,6 +219,7 @@ function parseArgs(argv) {
|
|
|
133
219
|
let statePath = process.env.LANGFUSE_BACKFILL_STATE ?? defaultStatePath;
|
|
134
220
|
let homeDir = process.env.HOME ?? homedir();
|
|
135
221
|
let dryRun = false;
|
|
222
|
+
let force = false;
|
|
136
223
|
let limit;
|
|
137
224
|
let sinceMs;
|
|
138
225
|
let untilMs;
|
|
@@ -167,6 +254,9 @@ function parseArgs(argv) {
|
|
|
167
254
|
if (arg === "--dry-run") {
|
|
168
255
|
dryRun = true;
|
|
169
256
|
}
|
|
257
|
+
else if (arg === "--force") {
|
|
258
|
+
force = true;
|
|
259
|
+
}
|
|
170
260
|
else if (arg === "--endpoint") {
|
|
171
261
|
endpoint = normalizeEndpoint(next());
|
|
172
262
|
}
|
|
@@ -261,6 +351,7 @@ function parseArgs(argv) {
|
|
|
261
351
|
statePath,
|
|
262
352
|
homeDir,
|
|
263
353
|
dryRun,
|
|
354
|
+
force,
|
|
264
355
|
follow,
|
|
265
356
|
pollIntervalMs,
|
|
266
357
|
idleExitAfterMs,
|
|
@@ -329,6 +420,14 @@ function normalizeCostRates(value, modelKey, source) {
|
|
|
329
420
|
asNumber(record.cache_write) ??
|
|
330
421
|
asNumber(record.inputCacheCreation) ??
|
|
331
422
|
asNumber(record.input_cache_creation),
|
|
423
|
+
cacheWrite5m: asNumber(record.cacheWrite5m) ??
|
|
424
|
+
asNumber(record.cache_write_5m) ??
|
|
425
|
+
asNumber(record.inputCacheCreation5m) ??
|
|
426
|
+
asNumber(record.input_cache_creation_5m),
|
|
427
|
+
cacheWrite1h: asNumber(record.cacheWrite1h) ??
|
|
428
|
+
asNumber(record.cache_write_1h) ??
|
|
429
|
+
asNumber(record.inputCacheCreation1h) ??
|
|
430
|
+
asNumber(record.input_cache_creation_1h),
|
|
332
431
|
};
|
|
333
432
|
const values = Object.entries(rates).filter(([, rate]) => rate !== undefined);
|
|
334
433
|
if (values.length === 0)
|
|
@@ -467,11 +566,27 @@ function normalizeUsage(value) {
|
|
|
467
566
|
const record = asRecord(value);
|
|
468
567
|
const nestedCost = asRecord(record.cost);
|
|
469
568
|
const cache = asRecord(record.cache);
|
|
569
|
+
const cacheCreation = asRecord(record.cache_creation);
|
|
470
570
|
const inputDetails = asRecord(record.input_tokens_details);
|
|
471
571
|
const outputDetails = asRecord(record.output_tokens_details);
|
|
472
572
|
const directInput = asNumber(record.input);
|
|
473
573
|
const aggregateInput = asNumber(record.input_tokens) ??
|
|
474
574
|
asNumber(record.prompt_tokens);
|
|
575
|
+
const cacheWrite5m = asNumber(record.cacheWrite5m) ??
|
|
576
|
+
asNumber(record.cache_write_5m) ??
|
|
577
|
+
asNumber(record.input_cache_creation_5m) ??
|
|
578
|
+
asNumber(cacheCreation.ephemeral_5m_input_tokens);
|
|
579
|
+
const cacheWrite1h = asNumber(record.cacheWrite1h) ??
|
|
580
|
+
asNumber(record.cache_write_1h) ??
|
|
581
|
+
asNumber(record.input_cache_creation_1h) ??
|
|
582
|
+
asNumber(cacheCreation.ephemeral_1h_input_tokens);
|
|
583
|
+
const untypedCacheWrite = asNumber(record.cacheWrite) ??
|
|
584
|
+
asNumber(record.cache_creation_input_tokens) ??
|
|
585
|
+
asNumber(cache.write);
|
|
586
|
+
const hasTypedCacheWrite = cacheWrite5m !== undefined || cacheWrite1h !== undefined;
|
|
587
|
+
const hasAnthropicCacheShape = hasTypedCacheWrite ||
|
|
588
|
+
asNumber(record.cache_creation_input_tokens) !== undefined ||
|
|
589
|
+
asNumber(record.cache_read_input_tokens) !== undefined;
|
|
475
590
|
const usage = {
|
|
476
591
|
input: directInput ?? aggregateInput,
|
|
477
592
|
output: asNumber(record.output) ??
|
|
@@ -487,9 +602,9 @@ function normalizeUsage(value) {
|
|
|
487
602
|
asNumber(record.cached_tokens) ??
|
|
488
603
|
asNumber(inputDetails.cached_tokens) ??
|
|
489
604
|
asNumber(cache.read),
|
|
490
|
-
cacheWrite:
|
|
491
|
-
|
|
492
|
-
|
|
605
|
+
cacheWrite: hasTypedCacheWrite ? undefined : untypedCacheWrite,
|
|
606
|
+
cacheWrite5m,
|
|
607
|
+
cacheWrite1h,
|
|
493
608
|
total: asNumber(record.totalTokens) ?? asNumber(record.total_tokens) ??
|
|
494
609
|
asNumber(record.total),
|
|
495
610
|
cost: asNumber(nestedCost.total) ??
|
|
@@ -499,14 +614,17 @@ function normalizeUsage(value) {
|
|
|
499
614
|
asNumber(record.cost),
|
|
500
615
|
};
|
|
501
616
|
if (usage.input !== undefined) {
|
|
502
|
-
usage.inputIncludesCache = directInput === undefined;
|
|
617
|
+
usage.inputIncludesCache = directInput === undefined && !hasAnthropicCacheShape;
|
|
503
618
|
}
|
|
504
619
|
if (usage.total === undefined) {
|
|
505
620
|
const cacheRead = usage.inputIncludesCache === false ? (usage.cacheRead ?? 0) : 0;
|
|
621
|
+
const cacheWrite = (usage.cacheWrite ?? 0) +
|
|
622
|
+
(usage.cacheWrite5m ?? 0) +
|
|
623
|
+
(usage.cacheWrite1h ?? 0);
|
|
506
624
|
const total = (usage.input ?? 0) +
|
|
507
625
|
(usage.output ?? 0) +
|
|
508
626
|
(usage.reasoning ?? 0) +
|
|
509
|
-
|
|
627
|
+
cacheWrite +
|
|
510
628
|
cacheRead;
|
|
511
629
|
if (total > 0)
|
|
512
630
|
usage.total = total;
|
|
@@ -529,10 +647,13 @@ function usageDetails(usage) {
|
|
|
529
647
|
return undefined;
|
|
530
648
|
const details = {};
|
|
531
649
|
const cachedInput = usage.inputIncludesCache === false ? 0 : (usage.cacheRead ?? 0);
|
|
532
|
-
const
|
|
650
|
+
const cacheWriteTotal = (usage.cacheWrite ?? 0) +
|
|
651
|
+
(usage.cacheWrite5m ?? 0) +
|
|
652
|
+
(usage.cacheWrite1h ?? 0);
|
|
653
|
+
const cacheWriteInInput = usage.inputIncludesCache === false ? 0 : cacheWriteTotal;
|
|
533
654
|
const regularInput = usage.input === undefined
|
|
534
655
|
? undefined
|
|
535
|
-
: Math.max(usage.input - cachedInput -
|
|
656
|
+
: Math.max(usage.input - cachedInput - cacheWriteInInput, 0);
|
|
536
657
|
if (regularInput !== undefined)
|
|
537
658
|
details.input = regularInput;
|
|
538
659
|
if (usage.output !== undefined)
|
|
@@ -543,6 +664,15 @@ function usageDetails(usage) {
|
|
|
543
664
|
details.input_cached_tokens = usage.cacheRead;
|
|
544
665
|
if (usage.cacheWrite !== undefined)
|
|
545
666
|
details.input_cache_creation = usage.cacheWrite;
|
|
667
|
+
if (usage.cacheWrite5m !== undefined) {
|
|
668
|
+
details.input_cache_creation_5m = usage.cacheWrite5m;
|
|
669
|
+
}
|
|
670
|
+
if (usage.cacheWrite1h !== undefined) {
|
|
671
|
+
details.input_cache_creation_1h = usage.cacheWrite1h;
|
|
672
|
+
}
|
|
673
|
+
if (usage.cacheWrite === undefined && cacheWriteTotal > 0) {
|
|
674
|
+
details.input_cache_creation = cacheWriteTotal;
|
|
675
|
+
}
|
|
546
676
|
if (usage.total !== undefined)
|
|
547
677
|
details.total = usage.total;
|
|
548
678
|
return Object.keys(details).length > 0 ? details : undefined;
|
|
@@ -565,13 +695,30 @@ function calculateCost(event, usage, costRates) {
|
|
|
565
695
|
setCostPart(details, "output", usage.output, rates.output);
|
|
566
696
|
setCostPart(details, "output_reasoning", usage.output_reasoning, rates.reasoning ?? rates.output);
|
|
567
697
|
setCostPart(details, "input_cached_tokens", usage.input_cached_tokens, rates.cacheRead ?? rates.input);
|
|
568
|
-
|
|
698
|
+
const hasTypedCacheWrite = usage.input_cache_creation_5m !== undefined ||
|
|
699
|
+
usage.input_cache_creation_1h !== undefined;
|
|
700
|
+
setCostPart(details, "input_cache_creation_5m", usage.input_cache_creation_5m, rates.cacheWrite5m ?? rates.cacheWrite ?? rates.input);
|
|
701
|
+
setCostPart(details, "input_cache_creation_1h", usage.input_cache_creation_1h, rates.cacheWrite1h ?? rates.cacheWrite ?? rates.input);
|
|
702
|
+
if (!hasTypedCacheWrite) {
|
|
703
|
+
setCostPart(details, "input_cache_creation", usage.input_cache_creation, rates.cacheWrite ?? rates.input);
|
|
704
|
+
}
|
|
705
|
+
const calculatedTotal = Object.values(details).reduce((sum, value) => sum + value, 0);
|
|
706
|
+
let source = "calculated";
|
|
707
|
+
if (calculatedTotal === 0 &&
|
|
708
|
+
usage.total !== undefined &&
|
|
709
|
+
usage.total > 0 &&
|
|
710
|
+
rates.input !== undefined) {
|
|
711
|
+
for (const key of Object.keys(details))
|
|
712
|
+
delete details[key];
|
|
713
|
+
setCostPart(details, "input", usage.total, rates.input);
|
|
714
|
+
source = "calculated_total_as_input";
|
|
715
|
+
}
|
|
569
716
|
if (Object.keys(details).length === 0)
|
|
570
717
|
return undefined;
|
|
571
718
|
details.total = roundCost(Object.values(details).reduce((sum, value) => sum + value, 0));
|
|
572
719
|
return {
|
|
573
720
|
details,
|
|
574
|
-
source
|
|
721
|
+
source,
|
|
575
722
|
modelKey,
|
|
576
723
|
rates,
|
|
577
724
|
};
|
|
@@ -1343,19 +1490,25 @@ function stableId(input) {
|
|
|
1343
1490
|
return createHash("sha256").update(input).digest("hex").slice(0, 32);
|
|
1344
1491
|
}
|
|
1345
1492
|
function importIdentity(event) {
|
|
1346
|
-
return
|
|
1493
|
+
return importStateIdentityVersions[event.agent] ?? importStateIdentityVersion;
|
|
1494
|
+
}
|
|
1495
|
+
function langfuseIdIdentity(event) {
|
|
1496
|
+
return langfuseIdIdentityVersions[event.agent] ?? langfuseIdIdentityVersion;
|
|
1347
1497
|
}
|
|
1348
1498
|
function fingerprint(event) {
|
|
1349
1499
|
return `${importIdentity(event)}:${event.agent}:${event.sessionId}:${event.recordId}`;
|
|
1350
1500
|
}
|
|
1501
|
+
function langfuseFingerprint(event) {
|
|
1502
|
+
return `${langfuseIdIdentity(event)}:${event.agent}:${event.sessionId}:${event.recordId}`;
|
|
1503
|
+
}
|
|
1351
1504
|
function traceFingerprint(event) {
|
|
1352
|
-
return `${
|
|
1505
|
+
return `${langfuseIdIdentity(event)}:${event.agent}:${event.sessionId}`;
|
|
1353
1506
|
}
|
|
1354
1507
|
function traceId(event) {
|
|
1355
1508
|
return stableId(traceFingerprint(event));
|
|
1356
1509
|
}
|
|
1357
1510
|
function spanId(event) {
|
|
1358
|
-
return stableId(
|
|
1511
|
+
return stableId(langfuseFingerprint(event)).slice(0, 16);
|
|
1359
1512
|
}
|
|
1360
1513
|
function rootSpanId(event) {
|
|
1361
1514
|
return stableId(`${traceFingerprint(event)}:root`).slice(0, 16);
|
|
@@ -1446,6 +1599,9 @@ function toOtlp(events, options = {}) {
|
|
|
1446
1599
|
attr("langfuse.trace.metadata.project_path", firstProject.projectPath),
|
|
1447
1600
|
attr("langfuse.trace.metadata.project_name", firstProject.projectName),
|
|
1448
1601
|
attr("langfuse.trace.metadata.project_folder", firstProject.projectFolder),
|
|
1602
|
+
attr("langfuse.trace.metadata.import_payload_version", importPayloadVersion),
|
|
1603
|
+
attr("langfuse.trace.metadata.import_state_identity", importIdentity(first)),
|
|
1604
|
+
attr("langfuse.trace.metadata.langfuse_id_identity", langfuseIdIdentity(first)),
|
|
1449
1605
|
attr("langfuse.observation.metadata.agent", first.agent),
|
|
1450
1606
|
attr("langfuse.observation.metadata.host", currentHost),
|
|
1451
1607
|
attr("langfuse.observation.metadata.machine", currentHost),
|
|
@@ -1456,6 +1612,9 @@ function toOtlp(events, options = {}) {
|
|
|
1456
1612
|
attr("langfuse.observation.metadata.project_path", firstProject.projectPath),
|
|
1457
1613
|
attr("langfuse.observation.metadata.project_name", firstProject.projectName),
|
|
1458
1614
|
attr("langfuse.observation.metadata.project_folder", firstProject.projectFolder),
|
|
1615
|
+
attr("langfuse.observation.metadata.import_payload_version", importPayloadVersion),
|
|
1616
|
+
attr("langfuse.observation.metadata.import_state_identity", importIdentity(first)),
|
|
1617
|
+
attr("langfuse.observation.metadata.langfuse_id_identity", langfuseIdIdentity(first)),
|
|
1459
1618
|
attr("source.path", first.sourcePath),
|
|
1460
1619
|
attr("cwd", first.cwd),
|
|
1461
1620
|
attr("project.path", firstProject.projectPath),
|
|
@@ -1507,6 +1666,9 @@ function toOtlp(events, options = {}) {
|
|
|
1507
1666
|
attr("langfuse.observation.metadata.project_folder", eventProject.projectFolder),
|
|
1508
1667
|
attr("langfuse.observation.metadata.model", modelName ?? event.model),
|
|
1509
1668
|
attr("langfuse.observation.metadata.provider", event.provider),
|
|
1669
|
+
attr("langfuse.observation.metadata.import_payload_version", importPayloadVersion),
|
|
1670
|
+
attr("langfuse.observation.metadata.import_state_identity", importIdentity(event)),
|
|
1671
|
+
attr("langfuse.observation.metadata.langfuse_id_identity", langfuseIdIdentity(event)),
|
|
1510
1672
|
attr("langfuse.observation.usage_details", generation ? usage : undefined),
|
|
1511
1673
|
attr("langfuse.observation.cost_details", cost?.details),
|
|
1512
1674
|
attr("langfuse.observation.metadata.usage_details", usage),
|
|
@@ -1677,7 +1839,9 @@ async function run(options) {
|
|
|
1677
1839
|
for (const event of events) {
|
|
1678
1840
|
discovered[event.agent] = (discovered[event.agent] ?? 0) + 1;
|
|
1679
1841
|
}
|
|
1680
|
-
const unsent =
|
|
1842
|
+
const unsent = options.force
|
|
1843
|
+
? events
|
|
1844
|
+
: events.filter((event) => state.sent[fingerprint(event)] === undefined);
|
|
1681
1845
|
const selected = options.limit === undefined
|
|
1682
1846
|
? unsent
|
|
1683
1847
|
: unsent.slice(0, options.limit);
|