@ramarivera/coding-agent-langfuse 0.1.55 → 0.1.57
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 +27 -9
- package/dist/backfill.d.ts +16 -1
- package/dist/backfill.js +279 -32
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/service.d.ts +11 -1
- package/dist/service.js +164 -92
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -135,15 +135,27 @@ records a total cost, that recorded value wins. Otherwise, the importer
|
|
|
135
135
|
calculates per-usage-type USD costs from a model catalog using rates in USD per
|
|
136
136
|
1M tokens.
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
138
|
+
By default, the importer refreshes model pricing from `https://models.dev/api.json`
|
|
139
|
+
and caches it locally. The cache is used for both one-shot backfills and
|
|
140
|
+
`--follow` services, so long-running collectors do not hit the network for every
|
|
141
|
+
scan. The built-in catalog remains only as an offline fallback for known local
|
|
142
|
+
models; explicit `--cost-rates` and `--cost-rates-json` overrides always win.
|
|
143
|
+
|
|
144
|
+
Control the pricing cache with:
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
npx @ramarivera/coding-agent-langfuse@latest \
|
|
148
|
+
--models-dev-cache "$HOME/.cache/coding-agent-langfuse/models-dev-v1.json" \
|
|
149
|
+
--models-dev-ttl-ms 86400000
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Use `--models-dev` to force-enable the cache if an environment disabled it. Use
|
|
153
|
+
`--no-models-dev` or `CODING_AGENT_LANGFUSE_MODELS_DEV=0` for fully static
|
|
154
|
+
offline pricing. Advanced deployments can set `--models-dev-url`,
|
|
155
|
+
`CODING_AGENT_LANGFUSE_MODELS_DEV_CACHE`,
|
|
156
|
+
`CODING_AGENT_LANGFUSE_MODELS_DEV_TTL_MS`, or
|
|
157
|
+
`CODING_AGENT_LANGFUSE_MODELS_DEV_FETCH_TIMEOUT_MS`.
|
|
158
|
+
|
|
147
159
|
When a billable generation source only records a total token count without
|
|
148
160
|
input/output/cache breakdown, the importer charges that total at the model input
|
|
149
161
|
rate and marks the cost source as `calculated_total_as_input`.
|
|
@@ -228,6 +240,12 @@ with a conservative cross-platform PATH. Use `--npx-path` and `--path` when a
|
|
|
228
240
|
host keeps Node.js somewhere outside the normal npm/Homebrew/system locations,
|
|
229
241
|
including nvm, fnm, Volta, asdf, mise, or another shell manager.
|
|
230
242
|
|
|
243
|
+
The public API exposes `serviceCreators` for macOS LaunchAgent, Linux systemd
|
|
244
|
+
user units, and Windows Scheduled Task scripts, plus `agentProcessors` for
|
|
245
|
+
Claude Code, Codex, Grok, OpenCode, and Pi. These registries are the extension
|
|
246
|
+
points for adding another host service target or coding-agent history reader
|
|
247
|
+
without adding more platform or agent conditionals to the CLI internals.
|
|
248
|
+
|
|
231
249
|
## Backfill windows
|
|
232
250
|
|
|
233
251
|
Backfill only a timeframe when repairing a host or replaying a recent window:
|
package/dist/backfill.d.ts
CHANGED
|
@@ -50,6 +50,8 @@ type BackfillOptions = {
|
|
|
50
50
|
maxFieldBytes: number;
|
|
51
51
|
postDelayMs: number;
|
|
52
52
|
costRates: CostCatalog;
|
|
53
|
+
costRateOverrides: CostCatalog;
|
|
54
|
+
modelsDev: ModelsDevOptions;
|
|
53
55
|
pathTagsConfigPath?: string;
|
|
54
56
|
pathTagsConfig: PathTagsConfig;
|
|
55
57
|
};
|
|
@@ -63,6 +65,13 @@ type CostRates = {
|
|
|
63
65
|
cacheWrite1h?: number;
|
|
64
66
|
};
|
|
65
67
|
type CostCatalog = Record<string, CostRates>;
|
|
68
|
+
type ModelsDevOptions = {
|
|
69
|
+
enabled: boolean;
|
|
70
|
+
url: string;
|
|
71
|
+
cachePath: string;
|
|
72
|
+
ttlMs: number;
|
|
73
|
+
fetchTimeoutMs: number;
|
|
74
|
+
};
|
|
66
75
|
type OtlpOptions = {
|
|
67
76
|
maxFieldBytes?: number;
|
|
68
77
|
costRates?: CostCatalog;
|
|
@@ -88,6 +97,10 @@ type ProjectTagOverlay = {
|
|
|
88
97
|
projectFolder?: string;
|
|
89
98
|
sourcePath?: string;
|
|
90
99
|
};
|
|
100
|
+
type AgentProcessor = {
|
|
101
|
+
name: AgentName;
|
|
102
|
+
discover(options: BackfillOptions): BackfillEvent[];
|
|
103
|
+
};
|
|
91
104
|
type RunSummary = {
|
|
92
105
|
discovered: Record<string, number>;
|
|
93
106
|
sent: number;
|
|
@@ -107,6 +120,7 @@ type FollowSummary = RunSummary & {
|
|
|
107
120
|
declare const allAgents: AgentName[];
|
|
108
121
|
declare const defaultEndpoint = "https://langfuse.ai.roxasroot.net/otel/v1/traces";
|
|
109
122
|
declare function parseArgs(argv: string[]): BackfillOptions;
|
|
123
|
+
declare function parseModelsDevCostCatalog(value: unknown, source?: string): CostCatalog;
|
|
110
124
|
declare function codexEvents(homeDir: string, options?: {
|
|
111
125
|
sessionIds?: Set<string>;
|
|
112
126
|
sinceMs?: number;
|
|
@@ -121,8 +135,9 @@ declare function opencodeEvents(homeDir: string, options?: {
|
|
|
121
135
|
}): BackfillEvent[];
|
|
122
136
|
declare function fingerprint(event: BackfillEvent): string;
|
|
123
137
|
declare function toOtlp(events: BackfillEvent[], options?: OtlpOptions): Record<string, unknown>;
|
|
138
|
+
declare const agentProcessors: Record<AgentName, AgentProcessor>;
|
|
124
139
|
declare function discoverEvents(options: BackfillOptions): BackfillEvent[];
|
|
125
140
|
declare function run(options: BackfillOptions): Promise<RunSummary>;
|
|
126
141
|
declare function follow(options: BackfillOptions): Promise<FollowSummary>;
|
|
127
142
|
declare function main(argv?: string[]): Promise<RunSummary | FollowSummary>;
|
|
128
|
-
export { type BackfillEvent, type BackfillOptions, type AgentName, allAgents, claudeEvents, codexEvents, defaultEndpoint, discoverEvents, fingerprint, follow, grokEvents, main, opencodeEvents, parseArgs, piEvents, run, toOtlp, };
|
|
143
|
+
export { type BackfillEvent, type BackfillOptions, type AgentName, type AgentProcessor, type CostCatalog, type CostRates, type ModelsDevOptions, agentProcessors, allAgents, claudeEvents, codexEvents, defaultEndpoint, discoverEvents, fingerprint, follow, grokEvents, parseModelsDevCostCatalog, main, opencodeEvents, parseArgs, piEvents, run, toOtlp, };
|
package/dist/backfill.js
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import { closeSync, existsSync, mkdirSync, openSync, renameSync, readdirSync, readFileSync, readSync, statSync, writeFileSync, } from "node:fs";
|
|
5
|
-
import { hostname, homedir } from "node:os";
|
|
5
|
+
import { hostname, homedir, platform as osPlatform } from "node:os";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
const allAgents = ["claude", "codex", "grok", "opencode", "pi"];
|
|
8
|
-
const importStateIdentityVersion = "
|
|
8
|
+
const importStateIdentityVersion = "v10-models-dev-cost-catalog";
|
|
9
9
|
const importStateIdentityVersions = {
|
|
10
|
-
claude: "
|
|
11
|
-
codex: "
|
|
12
|
-
grok: "
|
|
13
|
-
opencode: "
|
|
14
|
-
pi: "
|
|
10
|
+
claude: "v14-models-dev-cost-catalog",
|
|
11
|
+
codex: "v13-models-dev-cost-catalog",
|
|
12
|
+
grok: "v13-models-dev-cost-catalog",
|
|
13
|
+
opencode: "v12-models-dev-cost-catalog",
|
|
14
|
+
pi: "v13-models-dev-cost-catalog",
|
|
15
15
|
};
|
|
16
16
|
const langfuseIdIdentityVersion = "v8-cached-input-token-split";
|
|
17
17
|
const langfuseIdIdentityVersions = {
|
|
@@ -21,10 +21,10 @@ const langfuseIdIdentityVersions = {
|
|
|
21
21
|
opencode: "v10-opencode-message-parts",
|
|
22
22
|
pi: "v11-tool-results",
|
|
23
23
|
};
|
|
24
|
-
const importPayloadVersion = "
|
|
24
|
+
const importPayloadVersion = "v11-models-dev-cost-catalog";
|
|
25
25
|
const importPayloadVersions = {
|
|
26
|
-
claude: "
|
|
27
|
-
codex: "
|
|
26
|
+
claude: "v12-models-dev-cost-catalog",
|
|
27
|
+
codex: "v13-models-dev-cost-catalog",
|
|
28
28
|
};
|
|
29
29
|
const defaultEndpoint = "https://langfuse.ai.roxasroot.net/otel/v1/traces";
|
|
30
30
|
const deadRemoteEndpoint = "http://langfuse.ai.roxasroot.net:14318/v1/traces";
|
|
@@ -32,6 +32,10 @@ const defaultMaxRequestBytes = 12 * 1024 * 1024;
|
|
|
32
32
|
const defaultMaxFieldBytes = 512 * 1024;
|
|
33
33
|
const defaultStatePath = join(homedir(), ".local/state/coding-agent-langfuse/backfill-v6.json");
|
|
34
34
|
const defaultPathTagsConfigPath = join(homedir(), ".config/coding-agent-langfuse/path-tags.json");
|
|
35
|
+
const defaultModelsDevUrl = "https://models.dev/api.json";
|
|
36
|
+
const defaultModelsDevTtlMs = 24 * 60 * 60 * 1000;
|
|
37
|
+
const defaultModelsDevFetchTimeoutMs = 5_000;
|
|
38
|
+
const defaultModelsDevCachePath = join(defaultCacheDir(), "models-dev-v1.json");
|
|
35
39
|
const currentHost = hostname();
|
|
36
40
|
const projectLocalConfigFile = ".langfuse-ca.json";
|
|
37
41
|
const projectLocalConfigCache = new Map();
|
|
@@ -115,6 +119,8 @@ const defaultCostRates = {
|
|
|
115
119
|
"anthropic/claude-opus-4": claudeOpus4Rates,
|
|
116
120
|
"claude-opus-4-1": claudeOpus4Rates,
|
|
117
121
|
"anthropic/claude-opus-4-1": claudeOpus4Rates,
|
|
122
|
+
"claude-opus-4-5": claudeOpus4Rates,
|
|
123
|
+
"anthropic/claude-opus-4-5": claudeOpus4Rates,
|
|
118
124
|
"claude-opus-4-6": claudeOpus4Rates,
|
|
119
125
|
"anthropic/claude-opus-4-6": claudeOpus4Rates,
|
|
120
126
|
"claude-opus-4-7": claudeOpus4Rates,
|
|
@@ -338,6 +344,11 @@ Options:
|
|
|
338
344
|
--max-request-bytes N Split OTLP POSTs below this JSON byte size (default: ${defaultMaxRequestBytes})
|
|
339
345
|
--max-field-bytes N Truncate individual input/output fields above this byte size (default: ${defaultMaxFieldBytes})
|
|
340
346
|
--post-delay-ms N Delay after each successful OTLP POST (default: 0)
|
|
347
|
+
--models-dev Enable models.dev pricing cache refresh
|
|
348
|
+
--no-models-dev Disable models.dev pricing cache refresh
|
|
349
|
+
--models-dev-url URL models.dev-compatible pricing catalog URL (default: ${defaultModelsDevUrl})
|
|
350
|
+
--models-dev-cache PATH Local pricing cache path (default: ${defaultModelsDevCachePath})
|
|
351
|
+
--models-dev-ttl-ms N Refresh models.dev cache after this many ms (default: ${defaultModelsDevTtlMs})
|
|
341
352
|
--cost-rates PATH JSON model cost-rate overrides in USD per 1M tokens
|
|
342
353
|
--cost-rates-json JSON Inline JSON model cost-rate overrides in USD per 1M tokens
|
|
343
354
|
--path-tags-config PATH JSON path-prefix rules that add Langfuse tags/metadata
|
|
@@ -370,7 +381,8 @@ function parseArgs(argv) {
|
|
|
370
381
|
let postDelayMs = Number.parseInt(process.env.LANGFUSE_BACKFILL_POST_DELAY_MS ?? "", 10);
|
|
371
382
|
if (!Number.isFinite(postDelayMs))
|
|
372
383
|
postDelayMs = 0;
|
|
373
|
-
let
|
|
384
|
+
let modelsDev = loadModelsDevOptionsFromEnv();
|
|
385
|
+
let costRateOverrides = loadCostCatalogOverridesFromEnv();
|
|
374
386
|
let pathTagsConfigPath = process.env.CODING_AGENT_LANGFUSE_PATH_TAGS_CONFIG ??
|
|
375
387
|
process.env.LANGFUSE_BACKFILL_PATH_TAGS_CONFIG ??
|
|
376
388
|
defaultPathTagsConfigPath;
|
|
@@ -434,11 +446,26 @@ function parseArgs(argv) {
|
|
|
434
446
|
else if (arg === "--post-delay-ms") {
|
|
435
447
|
postDelayMs = Number.parseInt(next(), 10);
|
|
436
448
|
}
|
|
449
|
+
else if (arg === "--models-dev") {
|
|
450
|
+
modelsDev = { ...modelsDev, enabled: true };
|
|
451
|
+
}
|
|
452
|
+
else if (arg === "--no-models-dev") {
|
|
453
|
+
modelsDev = { ...modelsDev, enabled: false };
|
|
454
|
+
}
|
|
455
|
+
else if (arg === "--models-dev-url") {
|
|
456
|
+
modelsDev = { ...modelsDev, url: next() };
|
|
457
|
+
}
|
|
458
|
+
else if (arg === "--models-dev-cache") {
|
|
459
|
+
modelsDev = { ...modelsDev, cachePath: next() };
|
|
460
|
+
}
|
|
461
|
+
else if (arg === "--models-dev-ttl-ms") {
|
|
462
|
+
modelsDev = { ...modelsDev, ttlMs: Number.parseInt(next(), 10) };
|
|
463
|
+
}
|
|
437
464
|
else if (arg === "--cost-rates") {
|
|
438
|
-
|
|
465
|
+
costRateOverrides = mergeCostCatalog(costRateOverrides, loadCostCatalogFile(next()));
|
|
439
466
|
}
|
|
440
467
|
else if (arg === "--cost-rates-json") {
|
|
441
|
-
|
|
468
|
+
costRateOverrides = mergeCostCatalog(costRateOverrides, parseCostCatalogJson(next()));
|
|
442
469
|
}
|
|
443
470
|
else if (arg === "--path-tags-config") {
|
|
444
471
|
pathTagsConfigPath = next();
|
|
@@ -481,6 +508,13 @@ function parseArgs(argv) {
|
|
|
481
508
|
if (!Number.isFinite(postDelayMs) || postDelayMs < 0) {
|
|
482
509
|
throw new Error("--post-delay-ms must be a non-negative integer");
|
|
483
510
|
}
|
|
511
|
+
if (!Number.isFinite(modelsDev.ttlMs) || modelsDev.ttlMs < 0) {
|
|
512
|
+
throw new Error("--models-dev-ttl-ms must be a non-negative integer");
|
|
513
|
+
}
|
|
514
|
+
if (!Number.isFinite(modelsDev.fetchTimeoutMs) ||
|
|
515
|
+
modelsDev.fetchTimeoutMs < 1) {
|
|
516
|
+
throw new Error("CODING_AGENT_LANGFUSE_MODELS_DEV_FETCH_TIMEOUT_MS must be a positive integer");
|
|
517
|
+
}
|
|
484
518
|
if (!Number.isFinite(pollIntervalMs) || pollIntervalMs < 1) {
|
|
485
519
|
throw new Error("--poll-interval-ms must be a positive integer");
|
|
486
520
|
}
|
|
@@ -494,6 +528,7 @@ function parseArgs(argv) {
|
|
|
494
528
|
if (auth !== undefined && auth.trim().length === 0) {
|
|
495
529
|
throw new Error("--auth must not be empty");
|
|
496
530
|
}
|
|
531
|
+
const costRates = resolveCostCatalogSync(modelsDev, costRateOverrides);
|
|
497
532
|
return {
|
|
498
533
|
agents,
|
|
499
534
|
endpoint,
|
|
@@ -514,6 +549,8 @@ function parseArgs(argv) {
|
|
|
514
549
|
maxFieldBytes,
|
|
515
550
|
postDelayMs,
|
|
516
551
|
costRates,
|
|
552
|
+
costRateOverrides,
|
|
553
|
+
modelsDev,
|
|
517
554
|
pathTagsConfigPath,
|
|
518
555
|
pathTagsConfig: loadPathTagsConfig(pathTagsConfigPath),
|
|
519
556
|
};
|
|
@@ -628,7 +665,22 @@ function isSameOrChildPath(child, parent) {
|
|
|
628
665
|
normalizedChild.startsWith(`${normalizedParent}/`);
|
|
629
666
|
}
|
|
630
667
|
function loadCostCatalogFromEnv() {
|
|
631
|
-
|
|
668
|
+
return resolveCostCatalogSync(loadModelsDevOptionsFromEnv(), loadCostCatalogOverridesFromEnv());
|
|
669
|
+
}
|
|
670
|
+
function resolveCostCatalogSync(modelsDev, overrides = {}) {
|
|
671
|
+
const modelsDevCatalog = modelsDev.enabled
|
|
672
|
+
? loadModelsDevCostCatalogFromCache(modelsDev)
|
|
673
|
+
: {};
|
|
674
|
+
return mergeCostCatalog(mergeCostCatalog(defaultCostRates, modelsDevCatalog), overrides);
|
|
675
|
+
}
|
|
676
|
+
async function resolveCostCatalog(options) {
|
|
677
|
+
const modelsDevCatalog = options.modelsDev.enabled
|
|
678
|
+
? await loadModelsDevCostCatalog(options.modelsDev)
|
|
679
|
+
: {};
|
|
680
|
+
return mergeCostCatalog(mergeCostCatalog(defaultCostRates, modelsDevCatalog), options.costRateOverrides);
|
|
681
|
+
}
|
|
682
|
+
function loadCostCatalogOverridesFromEnv() {
|
|
683
|
+
let catalog = {};
|
|
632
684
|
const path = process.env.CODING_AGENT_LANGFUSE_COST_RATES_PATH ??
|
|
633
685
|
process.env.LANGFUSE_BACKFILL_COST_RATES_PATH;
|
|
634
686
|
const inlineJson = process.env.CODING_AGENT_LANGFUSE_COST_RATES_JSON ??
|
|
@@ -639,6 +691,184 @@ function loadCostCatalogFromEnv() {
|
|
|
639
691
|
catalog = mergeCostCatalog(catalog, parseCostCatalogJson(inlineJson));
|
|
640
692
|
return catalog;
|
|
641
693
|
}
|
|
694
|
+
function loadModelsDevOptionsFromEnv() {
|
|
695
|
+
const enabledValue = process.env.CODING_AGENT_LANGFUSE_MODELS_DEV ??
|
|
696
|
+
process.env.LANGFUSE_BACKFILL_MODELS_DEV;
|
|
697
|
+
const ttlMs = Number.parseInt(process.env.CODING_AGENT_LANGFUSE_MODELS_DEV_TTL_MS ??
|
|
698
|
+
process.env.LANGFUSE_BACKFILL_MODELS_DEV_TTL_MS ??
|
|
699
|
+
`${defaultModelsDevTtlMs}`, 10);
|
|
700
|
+
const fetchTimeoutMs = Number.parseInt(process.env.CODING_AGENT_LANGFUSE_MODELS_DEV_FETCH_TIMEOUT_MS ??
|
|
701
|
+
`${defaultModelsDevFetchTimeoutMs}`, 10);
|
|
702
|
+
return {
|
|
703
|
+
enabled: !isFalseLike(enabledValue),
|
|
704
|
+
url: process.env.CODING_AGENT_LANGFUSE_MODELS_DEV_URL ??
|
|
705
|
+
process.env.LANGFUSE_BACKFILL_MODELS_DEV_URL ??
|
|
706
|
+
defaultModelsDevUrl,
|
|
707
|
+
cachePath: process.env.CODING_AGENT_LANGFUSE_MODELS_DEV_CACHE ??
|
|
708
|
+
process.env.LANGFUSE_BACKFILL_MODELS_DEV_CACHE ??
|
|
709
|
+
defaultModelsDevCachePath,
|
|
710
|
+
ttlMs,
|
|
711
|
+
fetchTimeoutMs,
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
function isFalseLike(value) {
|
|
715
|
+
return value !== undefined && /^(0|false|off|no)$/i.test(value.trim());
|
|
716
|
+
}
|
|
717
|
+
function defaultCacheDir() {
|
|
718
|
+
const xdgCacheHome = process.env.XDG_CACHE_HOME;
|
|
719
|
+
if (xdgCacheHome)
|
|
720
|
+
return join(xdgCacheHome, "coding-agent-langfuse");
|
|
721
|
+
if (osPlatform() === "darwin") {
|
|
722
|
+
return join(homedir(), "Library/Caches/coding-agent-langfuse");
|
|
723
|
+
}
|
|
724
|
+
return join(homedir(), ".cache/coding-agent-langfuse");
|
|
725
|
+
}
|
|
726
|
+
async function loadModelsDevCostCatalog(options) {
|
|
727
|
+
const cached = readModelsDevCache(options.cachePath);
|
|
728
|
+
if (cached && !modelsDevCacheExpired(cached.fetchedAt, options.ttlMs)) {
|
|
729
|
+
return cached.catalog;
|
|
730
|
+
}
|
|
731
|
+
try {
|
|
732
|
+
const rawCatalog = await fetchModelsDevCatalog(options);
|
|
733
|
+
writeModelsDevCache(options.cachePath, options.url, rawCatalog);
|
|
734
|
+
return parseModelsDevCostCatalog(rawCatalog, options.url);
|
|
735
|
+
}
|
|
736
|
+
catch (error) {
|
|
737
|
+
if (cached) {
|
|
738
|
+
console.warn(`Could not refresh models.dev pricing from ${options.url}; using cached pricing from ${options.cachePath}: ${describeError(error)}`);
|
|
739
|
+
return cached.catalog;
|
|
740
|
+
}
|
|
741
|
+
console.warn(`Could not load models.dev pricing from ${options.url}; using built-in fallback rates: ${describeError(error)}`);
|
|
742
|
+
return {};
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
function loadModelsDevCostCatalogFromCache(options) {
|
|
746
|
+
return readModelsDevCache(options.cachePath)?.catalog ?? {};
|
|
747
|
+
}
|
|
748
|
+
function readModelsDevCache(path) {
|
|
749
|
+
if (!existsSync(path))
|
|
750
|
+
return undefined;
|
|
751
|
+
try {
|
|
752
|
+
const json = readFileSync(path, "utf8");
|
|
753
|
+
const parsed = JSON.parse(json);
|
|
754
|
+
const record = asRecord(parsed);
|
|
755
|
+
const fetchedAt = asString(record.fetchedAt) ??
|
|
756
|
+
asString(record.fetched_at) ??
|
|
757
|
+
new Date(statSync(path).mtimeMs).toISOString();
|
|
758
|
+
return {
|
|
759
|
+
fetchedAt,
|
|
760
|
+
catalog: parseModelsDevCostCatalog(parsed, path),
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
catch (error) {
|
|
764
|
+
console.warn(`Ignoring invalid models.dev pricing cache ${path}: ${describeError(error)}`);
|
|
765
|
+
return undefined;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
function modelsDevCacheExpired(fetchedAt, ttlMs) {
|
|
769
|
+
if (ttlMs === 0)
|
|
770
|
+
return true;
|
|
771
|
+
const fetchedAtMs = Date.parse(fetchedAt);
|
|
772
|
+
if (!Number.isFinite(fetchedAtMs))
|
|
773
|
+
return true;
|
|
774
|
+
return Date.now() - fetchedAtMs > ttlMs;
|
|
775
|
+
}
|
|
776
|
+
async function fetchModelsDevCatalog(options) {
|
|
777
|
+
const controller = new AbortController();
|
|
778
|
+
const timeout = setTimeout(() => controller.abort(), options.fetchTimeoutMs);
|
|
779
|
+
try {
|
|
780
|
+
const response = await fetch(options.url, {
|
|
781
|
+
headers: { accept: "application/json" },
|
|
782
|
+
signal: controller.signal,
|
|
783
|
+
});
|
|
784
|
+
if (!response.ok) {
|
|
785
|
+
throw new Error(`HTTP ${response.status} ${await response.text()}`);
|
|
786
|
+
}
|
|
787
|
+
return await response.json();
|
|
788
|
+
}
|
|
789
|
+
finally {
|
|
790
|
+
clearTimeout(timeout);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
function writeModelsDevCache(path, sourceUrl, catalog) {
|
|
794
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
795
|
+
const tempPath = `${path}.${process.pid}.tmp`;
|
|
796
|
+
writeFileSync(tempPath, `${JSON.stringify({
|
|
797
|
+
version: 1,
|
|
798
|
+
sourceUrl,
|
|
799
|
+
fetchedAt: new Date().toISOString(),
|
|
800
|
+
catalog,
|
|
801
|
+
}, null, 2)}\n`);
|
|
802
|
+
renameSync(tempPath, path);
|
|
803
|
+
}
|
|
804
|
+
function parseModelsDevCostCatalog(value, source = "models.dev catalog") {
|
|
805
|
+
const root = asRecord(value);
|
|
806
|
+
const catalogRoot = asRecord(root.catalog);
|
|
807
|
+
const providerRoot = asRecord(catalogRoot.providers);
|
|
808
|
+
const providers = Object.keys(providerRoot).length > 0
|
|
809
|
+
? providerRoot
|
|
810
|
+
: Object.keys(catalogRoot).length > 0
|
|
811
|
+
? catalogRoot
|
|
812
|
+
: root;
|
|
813
|
+
const out = {};
|
|
814
|
+
for (const [providerId, rawProvider] of Object.entries(providers)) {
|
|
815
|
+
const provider = asRecord(rawProvider);
|
|
816
|
+
const models = asRecord(provider.models);
|
|
817
|
+
for (const [modelId, rawModel] of Object.entries(models)) {
|
|
818
|
+
const rates = modelsDevCostRates(rawModel, `${providerId}/${modelId}`, source);
|
|
819
|
+
if (!rates)
|
|
820
|
+
continue;
|
|
821
|
+
for (const alias of modelsDevAliases(providerId, modelId)) {
|
|
822
|
+
out[alias] = { ...(out[alias] ?? {}), ...rates };
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return out;
|
|
827
|
+
}
|
|
828
|
+
function modelsDevCostRates(rawModel, modelKey, source) {
|
|
829
|
+
const model = asRecord(rawModel);
|
|
830
|
+
const cost = asRecord(model.cost);
|
|
831
|
+
if (Object.keys(cost).length === 0)
|
|
832
|
+
return undefined;
|
|
833
|
+
const cacheWrite = asNumber(cost.cache_write);
|
|
834
|
+
return normalizeCostRates({
|
|
835
|
+
input: cost.input,
|
|
836
|
+
output: cost.output,
|
|
837
|
+
reasoning: cost.reasoning,
|
|
838
|
+
cacheRead: cost.cache_read,
|
|
839
|
+
cacheWrite,
|
|
840
|
+
cacheWrite5m: cacheWrite,
|
|
841
|
+
cacheWrite1h: cacheWrite,
|
|
842
|
+
}, modelKey, source);
|
|
843
|
+
}
|
|
844
|
+
function modelsDevAliases(providerId, modelId) {
|
|
845
|
+
const aliases = new Set([modelId, `${providerId}/${modelId}`]);
|
|
846
|
+
const canonicalProviderPrefixes = [
|
|
847
|
+
"anthropic",
|
|
848
|
+
"openai",
|
|
849
|
+
"fireworks-ai",
|
|
850
|
+
"fireworks",
|
|
851
|
+
"fireworks-firepass",
|
|
852
|
+
"opencode",
|
|
853
|
+
];
|
|
854
|
+
for (const prefix of canonicalProviderPrefixes) {
|
|
855
|
+
if (modelId.startsWith(`${prefix}/`)) {
|
|
856
|
+
aliases.add(modelId.slice(prefix.length + 1));
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (providerId === "anthropic")
|
|
860
|
+
aliases.add(`anthropic/${modelId}`);
|
|
861
|
+
if (providerId === "openai")
|
|
862
|
+
aliases.add(`openai/${modelId}`);
|
|
863
|
+
if (providerId === "fireworks-ai") {
|
|
864
|
+
aliases.add(`fireworks/${modelId}`);
|
|
865
|
+
aliases.add(`fireworks-firepass/${modelId}`);
|
|
866
|
+
}
|
|
867
|
+
if (providerId === "opencode") {
|
|
868
|
+
aliases.add(modelId);
|
|
869
|
+
}
|
|
870
|
+
return [...aliases];
|
|
871
|
+
}
|
|
642
872
|
function loadCostCatalogFile(path) {
|
|
643
873
|
return parseCostCatalogJson(readFileSync(path, "utf8"), path);
|
|
644
874
|
}
|
|
@@ -2260,32 +2490,49 @@ function describeError(error) {
|
|
|
2260
2490
|
? `${error.message} (${parts.join("; ")})`
|
|
2261
2491
|
: error.message;
|
|
2262
2492
|
}
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2493
|
+
const agentProcessors = {
|
|
2494
|
+
claude: {
|
|
2495
|
+
name: "claude",
|
|
2496
|
+
discover: (options) => claudeEvents(options.homeDir),
|
|
2497
|
+
},
|
|
2498
|
+
codex: {
|
|
2499
|
+
name: "codex",
|
|
2500
|
+
discover: (options) => codexEvents(options.homeDir, {
|
|
2501
|
+
sessionIds: options.sessionIds,
|
|
2502
|
+
sinceMs: options.sinceMs,
|
|
2269
2503
|
}),
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2504
|
+
},
|
|
2505
|
+
grok: {
|
|
2506
|
+
name: "grok",
|
|
2507
|
+
discover: (options) => grokEvents(options.homeDir),
|
|
2508
|
+
},
|
|
2509
|
+
opencode: {
|
|
2510
|
+
name: "opencode",
|
|
2511
|
+
discover: (options) => opencodeEvents(options.homeDir, {
|
|
2512
|
+
rowLimit: options.limit,
|
|
2513
|
+
sinceMs: options.sinceMs,
|
|
2514
|
+
untilMs: options.untilMs,
|
|
2275
2515
|
}),
|
|
2276
|
-
|
|
2277
|
-
|
|
2516
|
+
},
|
|
2517
|
+
pi: {
|
|
2518
|
+
name: "pi",
|
|
2519
|
+
discover: (options) => piEvents(options.homeDir),
|
|
2520
|
+
},
|
|
2521
|
+
};
|
|
2522
|
+
function discoverEvents(options) {
|
|
2278
2523
|
return allAgents
|
|
2279
2524
|
.filter((agent) => options.agents.has(agent))
|
|
2280
|
-
.flatMap((agent) =>
|
|
2525
|
+
.flatMap((agent) => agentProcessors[agent].discover(options))
|
|
2281
2526
|
.filter((event) => options.sinceMs === undefined || event.startMs >= options.sinceMs)
|
|
2282
2527
|
.filter((event) => options.untilMs === undefined || event.startMs <= options.untilMs)
|
|
2283
2528
|
.filter((event) => options.sessionIds.size === 0 || options.sessionIds.has(event.sessionId))
|
|
2284
2529
|
.sort((a, b) => a.startMs - b.startMs);
|
|
2285
2530
|
}
|
|
2286
2531
|
async function run(options) {
|
|
2532
|
+
const costRates = await resolveCostCatalog(options);
|
|
2533
|
+
const runOptions = { ...options, costRates };
|
|
2287
2534
|
const state = loadState(options.statePath);
|
|
2288
|
-
const events = discoverEvents(
|
|
2535
|
+
const events = discoverEvents(runOptions);
|
|
2289
2536
|
const discovered = Object.fromEntries(allAgents.map((agent) => [agent, 0]));
|
|
2290
2537
|
for (const event of events) {
|
|
2291
2538
|
discovered[event.agent] = (discovered[event.agent] ?? 0) + 1;
|
|
@@ -2307,7 +2554,7 @@ async function run(options) {
|
|
|
2307
2554
|
batchSize: options.batchSize,
|
|
2308
2555
|
maxRequestBytes: options.maxRequestBytes,
|
|
2309
2556
|
maxFieldBytes: options.maxFieldBytes,
|
|
2310
|
-
costRates:
|
|
2557
|
+
costRates: runOptions.costRates,
|
|
2311
2558
|
pathTagsConfig: options.pathTagsConfig,
|
|
2312
2559
|
homeDir: options.homeDir,
|
|
2313
2560
|
});
|
|
@@ -2335,7 +2582,7 @@ async function run(options) {
|
|
|
2335
2582
|
try {
|
|
2336
2583
|
await postOtlp(options.endpoint, batch, {
|
|
2337
2584
|
maxFieldBytes: options.maxFieldBytes,
|
|
2338
|
-
costRates:
|
|
2585
|
+
costRates: runOptions.costRates,
|
|
2339
2586
|
pathTagsConfig: options.pathTagsConfig,
|
|
2340
2587
|
homeDir: options.homeDir,
|
|
2341
2588
|
auth: options.auth,
|
|
@@ -2446,4 +2693,4 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
2446
2693
|
process.exit(1);
|
|
2447
2694
|
}
|
|
2448
2695
|
}
|
|
2449
|
-
export { allAgents, claudeEvents, codexEvents, defaultEndpoint, discoverEvents, fingerprint, follow, grokEvents, main, opencodeEvents, parseArgs, piEvents, run, toOtlp, };
|
|
2696
|
+
export { agentProcessors, allAgents, claudeEvents, codexEvents, defaultEndpoint, discoverEvents, fingerprint, follow, grokEvents, parseModelsDevCostCatalog, main, opencodeEvents, parseArgs, piEvents, run, toOtlp, };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { codexEvents, discoverEvents, fingerprint, parseArgs, piEvents, run, toOtlp, } from "./backfill.js";
|
|
2
|
-
export { buildServicePlan, parseServiceArgs, serviceMain, } from "./service.js";
|
|
1
|
+
export { type AgentProcessor, type CostCatalog, type CostRates, type ModelsDevOptions, agentProcessors, codexEvents, discoverEvents, fingerprint, parseModelsDevCostCatalog, parseArgs, piEvents, run, toOtlp, } from "./backfill.js";
|
|
2
|
+
export { type ServiceCreator, buildServicePlan, parseServiceArgs, serviceMain, serviceCreators, } from "./service.js";
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { codexEvents, discoverEvents, fingerprint, parseArgs, piEvents, run, toOtlp, } from "./backfill.js";
|
|
2
|
-
export { buildServicePlan, parseServiceArgs, serviceMain, } from "./service.js";
|
|
1
|
+
export { agentProcessors, codexEvents, discoverEvents, fingerprint, parseModelsDevCostCatalog, parseArgs, piEvents, run, toOtlp, } from "./backfill.js";
|
|
2
|
+
export { buildServicePlan, parseServiceArgs, serviceMain, serviceCreators, } from "./service.js";
|
package/dist/service.d.ts
CHANGED
|
@@ -13,6 +13,11 @@ type ServiceOptions = {
|
|
|
13
13
|
batchSize: number;
|
|
14
14
|
pollIntervalMs: number;
|
|
15
15
|
postDelayMs: number;
|
|
16
|
+
modelsDevEnabled: boolean;
|
|
17
|
+
modelsDevDisabled: boolean;
|
|
18
|
+
modelsDevUrl?: string;
|
|
19
|
+
modelsDevCachePath?: string;
|
|
20
|
+
modelsDevTtlMs?: number;
|
|
16
21
|
costRatesPath?: string;
|
|
17
22
|
costRatesJson?: string;
|
|
18
23
|
pathTagsConfigPath?: string;
|
|
@@ -34,9 +39,14 @@ type ServicePlan = {
|
|
|
34
39
|
uninstallCommands: string[][];
|
|
35
40
|
statusCommands: string[][];
|
|
36
41
|
};
|
|
42
|
+
type ServiceCreator = {
|
|
43
|
+
platform: ServicePlatform;
|
|
44
|
+
create(options: ServiceOptions, command: string[]): ServicePlan;
|
|
45
|
+
};
|
|
37
46
|
declare function parseServiceArgs(argv: string[]): ServiceOptions;
|
|
47
|
+
declare const serviceCreators: Record<ServicePlatform, ServiceCreator>;
|
|
38
48
|
declare function buildServicePlan(options: ServiceOptions): ServicePlan;
|
|
39
49
|
declare function serviceMain(argv?: string[]): Promise<ServicePlan>;
|
|
40
50
|
declare function redactStatusOutput(output: string): string;
|
|
41
51
|
declare function lifecycleEnv(): NodeJS.ProcessEnv;
|
|
42
|
-
export { type ServiceOptions, type ServicePlan, buildServicePlan, redactStatusOutput, parseServiceArgs, serviceMain, lifecycleEnv, };
|
|
52
|
+
export { type ServiceCreator, type ServiceOptions, type ServicePlan, buildServicePlan, serviceCreators, redactStatusOutput, parseServiceArgs, serviceMain, lifecycleEnv, };
|
package/dist/service.js
CHANGED
|
@@ -24,6 +24,11 @@ Service options:
|
|
|
24
24
|
--batch-size N OTLP spans per POST (default: 10)
|
|
25
25
|
--poll-interval-ms N Delay between --follow scans (default: 5000)
|
|
26
26
|
--post-delay-ms N Delay after each successful OTLP POST (default: 0)
|
|
27
|
+
--models-dev Enable models.dev pricing cache refresh in the follower
|
|
28
|
+
--no-models-dev Disable models.dev pricing cache refresh in the follower
|
|
29
|
+
--models-dev-url URL models.dev-compatible pricing catalog URL
|
|
30
|
+
--models-dev-cache PATH Local pricing cache path
|
|
31
|
+
--models-dev-ttl-ms N Refresh models.dev cache after this many ms
|
|
27
32
|
--cost-rates PATH JSON model cost-rate overrides in USD per 1M tokens
|
|
28
33
|
--cost-rates-json JSON Inline JSON model cost-rate overrides in USD per 1M tokens
|
|
29
34
|
--path-tags-config PATH JSON path-prefix rules that add Langfuse tags/metadata
|
|
@@ -52,6 +57,11 @@ function parseServiceArgs(argv) {
|
|
|
52
57
|
let batchSize = 10;
|
|
53
58
|
let pollIntervalMs = 5_000;
|
|
54
59
|
let postDelayMs = 0;
|
|
60
|
+
let modelsDevEnabled = false;
|
|
61
|
+
let modelsDevDisabled = false;
|
|
62
|
+
let modelsDevUrl;
|
|
63
|
+
let modelsDevCachePath;
|
|
64
|
+
let modelsDevTtlMs;
|
|
55
65
|
let costRatesPath = process.env.CODING_AGENT_LANGFUSE_COST_RATES_PATH ??
|
|
56
66
|
process.env.LANGFUSE_BACKFILL_COST_RATES_PATH;
|
|
57
67
|
let costRatesJson = process.env.CODING_AGENT_LANGFUSE_COST_RATES_JSON ??
|
|
@@ -103,6 +113,23 @@ function parseServiceArgs(argv) {
|
|
|
103
113
|
else if (arg === "--post-delay-ms") {
|
|
104
114
|
postDelayMs = parseNonNegativeInt(arg, next());
|
|
105
115
|
}
|
|
116
|
+
else if (arg === "--models-dev") {
|
|
117
|
+
modelsDevEnabled = true;
|
|
118
|
+
modelsDevDisabled = false;
|
|
119
|
+
}
|
|
120
|
+
else if (arg === "--no-models-dev") {
|
|
121
|
+
modelsDevEnabled = false;
|
|
122
|
+
modelsDevDisabled = true;
|
|
123
|
+
}
|
|
124
|
+
else if (arg === "--models-dev-url") {
|
|
125
|
+
modelsDevUrl = next();
|
|
126
|
+
}
|
|
127
|
+
else if (arg === "--models-dev-cache") {
|
|
128
|
+
modelsDevCachePath = next();
|
|
129
|
+
}
|
|
130
|
+
else if (arg === "--models-dev-ttl-ms") {
|
|
131
|
+
modelsDevTtlMs = parseNonNegativeInt(arg, next());
|
|
132
|
+
}
|
|
106
133
|
else if (arg === "--cost-rates") {
|
|
107
134
|
costRatesPath = next();
|
|
108
135
|
}
|
|
@@ -151,6 +178,11 @@ function parseServiceArgs(argv) {
|
|
|
151
178
|
batchSize,
|
|
152
179
|
pollIntervalMs,
|
|
153
180
|
postDelayMs,
|
|
181
|
+
modelsDevEnabled,
|
|
182
|
+
modelsDevDisabled,
|
|
183
|
+
modelsDevUrl,
|
|
184
|
+
modelsDevCachePath,
|
|
185
|
+
modelsDevTtlMs,
|
|
154
186
|
costRatesPath,
|
|
155
187
|
costRatesJson,
|
|
156
188
|
pathTagsConfigPath,
|
|
@@ -161,96 +193,109 @@ function parseServiceArgs(argv) {
|
|
|
161
193
|
pathEnv,
|
|
162
194
|
};
|
|
163
195
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
[
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
[
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
[
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
196
|
+
const serviceCreators = {
|
|
197
|
+
darwin: {
|
|
198
|
+
platform: "darwin",
|
|
199
|
+
create(options, command) {
|
|
200
|
+
const path = join(options.homeDir, "Library/LaunchAgents", `${options.name}.plist`);
|
|
201
|
+
return {
|
|
202
|
+
platform: options.platform,
|
|
203
|
+
action: options.action,
|
|
204
|
+
name: options.name,
|
|
205
|
+
path,
|
|
206
|
+
content: renderLaunchdPlist(options, command),
|
|
207
|
+
command,
|
|
208
|
+
postInstallCommands: options.start
|
|
209
|
+
? [
|
|
210
|
+
["launchctl", "bootstrap", `gui/${process.getuid?.() ?? 501}`, path],
|
|
211
|
+
["launchctl", "kickstart", "-k", `gui/${process.getuid?.() ?? 501}/${options.name}`],
|
|
212
|
+
]
|
|
213
|
+
: [],
|
|
214
|
+
uninstallCommands: [
|
|
215
|
+
["launchctl", "bootout", `gui/${process.getuid?.() ?? 501}`, path],
|
|
216
|
+
],
|
|
217
|
+
statusCommands: [
|
|
218
|
+
["launchctl", "print", `gui/${process.getuid?.() ?? 501}/${options.name}`],
|
|
219
|
+
],
|
|
220
|
+
};
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
linux: {
|
|
224
|
+
platform: "linux",
|
|
225
|
+
create(options, command) {
|
|
226
|
+
const path = join(options.homeDir, ".config/systemd/user", `${options.name}.service`);
|
|
227
|
+
return {
|
|
228
|
+
platform: options.platform,
|
|
229
|
+
action: options.action,
|
|
230
|
+
name: options.name,
|
|
231
|
+
path,
|
|
232
|
+
content: renderSystemdUnit(options, command),
|
|
233
|
+
command,
|
|
234
|
+
postInstallCommands: options.start
|
|
235
|
+
? [
|
|
236
|
+
["systemctl", "--user", "daemon-reload"],
|
|
237
|
+
["systemctl", "--user", "enable", "--now", `${options.name}.service`],
|
|
238
|
+
]
|
|
239
|
+
: [["systemctl", "--user", "daemon-reload"]],
|
|
240
|
+
uninstallCommands: [
|
|
241
|
+
["systemctl", "--user", "disable", "--now", `${options.name}.service`],
|
|
200
242
|
["systemctl", "--user", "daemon-reload"],
|
|
201
|
-
["systemctl", "--user", "enable", "--now", `${options.name}.service`],
|
|
202
|
-
]
|
|
203
|
-
: [["systemctl", "--user", "daemon-reload"]],
|
|
204
|
-
uninstallCommands: [
|
|
205
|
-
["systemctl", "--user", "disable", "--now", `${options.name}.service`],
|
|
206
|
-
["systemctl", "--user", "daemon-reload"],
|
|
207
|
-
],
|
|
208
|
-
statusCommands: [
|
|
209
|
-
["systemctl", "--user", "status", `${options.name}.service`],
|
|
210
|
-
],
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
const path = join(process.env.APPDATA ?? join(options.homeDir, "AppData/Roaming"), "coding-agent-langfuse", `${options.name}.ps1`);
|
|
214
|
-
const taskName = `CodingAgentLangfuse-${options.name}`;
|
|
215
|
-
return {
|
|
216
|
-
platform: options.platform,
|
|
217
|
-
action: options.action,
|
|
218
|
-
name: options.name,
|
|
219
|
-
path,
|
|
220
|
-
content: renderWindowsScript(command),
|
|
221
|
-
command,
|
|
222
|
-
postInstallCommands: options.start
|
|
223
|
-
? [
|
|
224
|
-
[
|
|
225
|
-
"powershell.exe",
|
|
226
|
-
"-NoProfile",
|
|
227
|
-
"-ExecutionPolicy",
|
|
228
|
-
"Bypass",
|
|
229
|
-
"-File",
|
|
230
|
-
path,
|
|
231
|
-
"-Install",
|
|
232
|
-
"-TaskName",
|
|
233
|
-
taskName,
|
|
234
243
|
],
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
244
|
+
statusCommands: [
|
|
245
|
+
["systemctl", "--user", "status", `${options.name}.service`],
|
|
246
|
+
],
|
|
247
|
+
};
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
win32: {
|
|
251
|
+
platform: "win32",
|
|
252
|
+
create(options, command) {
|
|
253
|
+
const path = join(process.env.APPDATA ?? join(options.homeDir, "AppData/Roaming"), "coding-agent-langfuse", `${options.name}.ps1`);
|
|
254
|
+
const taskName = `CodingAgentLangfuse-${options.name}`;
|
|
255
|
+
return {
|
|
256
|
+
platform: options.platform,
|
|
257
|
+
action: options.action,
|
|
258
|
+
name: options.name,
|
|
259
|
+
path,
|
|
260
|
+
content: renderWindowsScript(command),
|
|
261
|
+
command,
|
|
262
|
+
postInstallCommands: options.start
|
|
263
|
+
? [
|
|
264
|
+
[
|
|
265
|
+
"powershell.exe",
|
|
266
|
+
"-NoProfile",
|
|
267
|
+
"-ExecutionPolicy",
|
|
268
|
+
"Bypass",
|
|
269
|
+
"-File",
|
|
270
|
+
path,
|
|
271
|
+
"-Install",
|
|
272
|
+
"-TaskName",
|
|
273
|
+
taskName,
|
|
274
|
+
],
|
|
275
|
+
]
|
|
276
|
+
: [],
|
|
277
|
+
uninstallCommands: [
|
|
278
|
+
[
|
|
279
|
+
"powershell.exe",
|
|
280
|
+
"-NoProfile",
|
|
281
|
+
"-Command",
|
|
282
|
+
`Unregister-ScheduledTask -TaskName ${powershellString(taskName)} -Confirm:$false -ErrorAction SilentlyContinue`,
|
|
283
|
+
],
|
|
284
|
+
],
|
|
285
|
+
statusCommands: [
|
|
286
|
+
[
|
|
287
|
+
"powershell.exe",
|
|
288
|
+
"-NoProfile",
|
|
289
|
+
"-Command",
|
|
290
|
+
`Get-ScheduledTask -TaskName ${powershellString(taskName)} | Format-List *`,
|
|
291
|
+
],
|
|
292
|
+
],
|
|
293
|
+
};
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
function buildServicePlan(options) {
|
|
298
|
+
return serviceCreators[options.platform].create(options, buildFollowCommand(options));
|
|
254
299
|
}
|
|
255
300
|
async function serviceMain(argv = process.argv.slice(2)) {
|
|
256
301
|
const options = parseServiceArgs(argv);
|
|
@@ -302,6 +347,18 @@ function buildFollowCommand(options) {
|
|
|
302
347
|
];
|
|
303
348
|
if (options.since)
|
|
304
349
|
command.push("--since", options.since);
|
|
350
|
+
if (options.modelsDevEnabled)
|
|
351
|
+
command.push("--models-dev");
|
|
352
|
+
if (options.modelsDevDisabled)
|
|
353
|
+
command.push("--no-models-dev");
|
|
354
|
+
if (options.modelsDevUrl)
|
|
355
|
+
command.push("--models-dev-url", options.modelsDevUrl);
|
|
356
|
+
if (options.modelsDevCachePath) {
|
|
357
|
+
command.push("--models-dev-cache", options.modelsDevCachePath);
|
|
358
|
+
}
|
|
359
|
+
if (options.modelsDevTtlMs !== undefined) {
|
|
360
|
+
command.push("--models-dev-ttl-ms", String(options.modelsDevTtlMs));
|
|
361
|
+
}
|
|
305
362
|
if (options.costRatesPath)
|
|
306
363
|
command.push("--cost-rates", options.costRatesPath);
|
|
307
364
|
if (options.costRatesJson)
|
|
@@ -368,15 +425,30 @@ function renderWindowsScript(command) {
|
|
|
368
425
|
const commandArray = command.map(powershellString).join(", ");
|
|
369
426
|
return `param(
|
|
370
427
|
[switch]$Install,
|
|
428
|
+
[switch]$Run,
|
|
371
429
|
[string]$TaskName = "CodingAgentLangfuse"
|
|
372
430
|
)
|
|
373
431
|
|
|
374
432
|
$Command = @(${commandArray})
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
433
|
+
|
|
434
|
+
function Quote-PowerShellLiteral([string]$Value) {
|
|
435
|
+
return "'" + $Value.Replace("'", "''") + "'"
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if ($Run) {
|
|
439
|
+
& $Command[0] @($Command | Select-Object -Skip 1)
|
|
440
|
+
exit $LASTEXITCODE
|
|
441
|
+
}
|
|
378
442
|
|
|
379
443
|
if ($Install) {
|
|
444
|
+
$ScriptPath = if ($PSCommandPath) { $PSCommandPath } else { $MyInvocation.MyCommand.Path }
|
|
445
|
+
$RunCommand = "& " + (Quote-PowerShellLiteral $ScriptPath) + " -Run"
|
|
446
|
+
$EncodedRunCommand = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($RunCommand))
|
|
447
|
+
$Action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -EncodedCommand $EncodedRunCommand"
|
|
448
|
+
$Trigger = New-ScheduledTaskTrigger -AtLogOn
|
|
449
|
+
$Settings = New-ScheduledTaskSettingsSet -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -Hidden
|
|
450
|
+
|
|
451
|
+
Stop-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
|
|
380
452
|
Register-ScheduledTask -TaskName $TaskName -Action $Action -Trigger $Trigger -Settings $Settings -Force | Out-Null
|
|
381
453
|
Start-ScheduledTask -TaskName $TaskName
|
|
382
454
|
}
|
|
@@ -557,4 +629,4 @@ function escapeXml(value) {
|
|
|
557
629
|
.replaceAll('"', """)
|
|
558
630
|
.replaceAll("'", "'");
|
|
559
631
|
}
|
|
560
|
-
export { buildServicePlan, redactStatusOutput, parseServiceArgs, serviceMain, lifecycleEnv, };
|
|
632
|
+
export { buildServicePlan, serviceCreators, redactStatusOutput, parseServiceArgs, serviceMain, lifecycleEnv, };
|