@juspay/neurolink 11.18.5 → 11.20.0
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/CHANGELOG.md +3 -4
- package/dist/browser/neurolink.min.js +362 -375
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -0
- package/dist/localUsage/claudeCodeReader.d.ts +25 -0
- package/dist/localUsage/claudeCodeReader.js +222 -0
- package/dist/localUsage/codexReader.d.ts +30 -0
- package/dist/localUsage/codexReader.js +218 -0
- package/dist/localUsage/index.d.ts +18 -0
- package/dist/localUsage/index.js +46 -0
- package/dist/localUsage/localUsageReaderRegistry.d.ts +14 -0
- package/dist/localUsage/localUsageReaderRegistry.js +54 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/localUsage.d.ts +153 -0
- package/dist/types/localUsage.js +13 -0
- package/dist/utils/messageBuilder.js +0 -76
- package/package.json +2 -1
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for `src/lib/localUsage/` — reading token usage out of each AI CLI's
|
|
3
|
+
* own local session logs.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists alongside the proxy's ledger: the proxy can only account for
|
|
6
|
+
* traffic that went through it, which caps coverage at the CLIs that expose a
|
|
7
|
+
* base-URL override. Every CLI writes its own local transcript regardless, so
|
|
8
|
+
* reading those recovers the rest — and recovers history from before the proxy
|
|
9
|
+
* was ever installed. It is also an independent source of truth: a pricing
|
|
10
|
+
* defect in the proxy's own accounting cannot hide from a reader that derives
|
|
11
|
+
* cost from a different input.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Stable identifier for one CLI this subsystem can read local usage from.
|
|
15
|
+
* Kebab-case, matching `CliProxyClientConfigurator.id`'s convention — a
|
|
16
|
+
* different registry, but the same repo-wide convention for CLI identifiers.
|
|
17
|
+
*/
|
|
18
|
+
export type LocalUsageCliId = "claude-code" | "codex" | "gemini-cli" | "opencode" | "qwen-code" | "copilot-cli" | "cursor" | "amp" | "hermes" | "kiro" | "antigravity" | "grok";
|
|
19
|
+
/**
|
|
20
|
+
* How much to trust a computed cost figure.
|
|
21
|
+
*
|
|
22
|
+
* Not decoration: some CLIs are flat-rate subscriptions where a per-request
|
|
23
|
+
* cost is meaningless, and at least one publishes a byte heuristic rather than
|
|
24
|
+
* a real number. A caller must never render "heuristic" with the same
|
|
25
|
+
* confidence as "modeled", so the distinction travels with the number.
|
|
26
|
+
*/
|
|
27
|
+
export type LocalUsageCostConfidence = "modeled" | "unavailable" | "heuristic";
|
|
28
|
+
/**
|
|
29
|
+
* How a reader avoids counting the same turn twice.
|
|
30
|
+
*
|
|
31
|
+
* Metadata on the descriptor, for introspection and for the person writing the
|
|
32
|
+
* next reader — the aggregator does not branch on it.
|
|
33
|
+
*/
|
|
34
|
+
export type LocalUsageDedupStrategy = "message-id-keep-max" | "last-write-wins" | "rowid-high-water-mark" | "session-dag";
|
|
35
|
+
/** Aggregated totals for one CLI, one scan. */
|
|
36
|
+
export type LocalUsageTotals = {
|
|
37
|
+
requests: number;
|
|
38
|
+
inputTokens: number;
|
|
39
|
+
outputTokens: number;
|
|
40
|
+
cacheReadTokens: number;
|
|
41
|
+
cacheCreationTokens: number;
|
|
42
|
+
costUsd: number;
|
|
43
|
+
/**
|
|
44
|
+
* The weakest confidence contributing to `costUsd`. A totals row mixing
|
|
45
|
+
* modeled and heuristic entries must report the weaker one, otherwise the
|
|
46
|
+
* aggregate looks better-sourced than its worst input.
|
|
47
|
+
*/
|
|
48
|
+
costConfidence: LocalUsageCostConfidence;
|
|
49
|
+
/** Turns whose model had no pricing entry, so contributed 0 to costUsd. */
|
|
50
|
+
unpricedRequests: number;
|
|
51
|
+
/** Distinct model ids behind `unpricedRequests`, for diagnosis. */
|
|
52
|
+
unpricedModels: string[];
|
|
53
|
+
};
|
|
54
|
+
/** A non-fatal per-file problem, surfaced instead of aborting the scan. */
|
|
55
|
+
export type LocalUsageScanError = {
|
|
56
|
+
cliId: LocalUsageCliId;
|
|
57
|
+
filePath: string;
|
|
58
|
+
message: string;
|
|
59
|
+
};
|
|
60
|
+
/** What one reader's `scan()` returns. */
|
|
61
|
+
export type LocalUsageScanResult = {
|
|
62
|
+
cliId: LocalUsageCliId;
|
|
63
|
+
totals: LocalUsageTotals;
|
|
64
|
+
/** Files opened during this scan, after any time filter. */
|
|
65
|
+
filesScanned: number;
|
|
66
|
+
errors: LocalUsageScanError[];
|
|
67
|
+
};
|
|
68
|
+
/** Static metadata, available without constructing a reader. */
|
|
69
|
+
export type LocalUsageReaderDescriptor = {
|
|
70
|
+
id: LocalUsageCliId;
|
|
71
|
+
displayName: string;
|
|
72
|
+
/**
|
|
73
|
+
* True only for readers checked against real data on a real machine. An
|
|
74
|
+
* honesty marker, not a completeness claim — an unverified reader may still
|
|
75
|
+
* be correct, it just has not been shown to be.
|
|
76
|
+
*/
|
|
77
|
+
verified: boolean;
|
|
78
|
+
dedupStrategy: LocalUsageDedupStrategy;
|
|
79
|
+
costConfidence: LocalUsageCostConfidence;
|
|
80
|
+
/** Whether reading this CLI's store needs a SQLite binding. */
|
|
81
|
+
requiresSqlite: boolean;
|
|
82
|
+
};
|
|
83
|
+
/** Options accepted by every reader's `scan()` and by the aggregator. */
|
|
84
|
+
export type LocalUsageScanOptions = {
|
|
85
|
+
/**
|
|
86
|
+
* Only read files modified within this many days. Defaults to 30.
|
|
87
|
+
*
|
|
88
|
+
* This is a real constraint rather than a convenience: one developer machine
|
|
89
|
+
* held 17,439 transcripts totalling 9.7 GB, and an unbounded scan reads all
|
|
90
|
+
* of it on every call. Pass `Infinity` for a deliberate full history sweep.
|
|
91
|
+
*/
|
|
92
|
+
sinceDays?: number;
|
|
93
|
+
};
|
|
94
|
+
/** The contract every reader implements — one per CLI. */
|
|
95
|
+
export type LocalUsageReader = {
|
|
96
|
+
descriptor: LocalUsageReaderDescriptor;
|
|
97
|
+
/**
|
|
98
|
+
* Whether this CLI's local store appears to exist on this machine at all —
|
|
99
|
+
* the same "do not report on something never installed" discipline the proxy
|
|
100
|
+
* client configurators use before writing a config.
|
|
101
|
+
*/
|
|
102
|
+
detect: () => Promise<boolean>;
|
|
103
|
+
scan: (options?: LocalUsageScanOptions) => Promise<LocalUsageScanResult>;
|
|
104
|
+
};
|
|
105
|
+
/** Async factory stored in the registry — a reader needs no credentials, only
|
|
106
|
+
* the filesystem, so this takes no arguments. */
|
|
107
|
+
export type LocalUsageReaderFactoryFn = () => Promise<LocalUsageReader>;
|
|
108
|
+
/** One entry in the registry map. */
|
|
109
|
+
export type LocalUsageReaderRegistration = {
|
|
110
|
+
descriptor: LocalUsageReaderDescriptor;
|
|
111
|
+
factory: LocalUsageReaderFactoryFn;
|
|
112
|
+
};
|
|
113
|
+
/** A whole reader failing — not installed, or threw — so the aggregate report
|
|
114
|
+
* can carry successes and failures side by side rather than losing both. */
|
|
115
|
+
export type LocalUsageReaderFailure = {
|
|
116
|
+
cliId: LocalUsageCliId;
|
|
117
|
+
message: string;
|
|
118
|
+
};
|
|
119
|
+
/** Top-level output of scanning every registered, detected reader. */
|
|
120
|
+
export type LocalUsageAggregateReport = {
|
|
121
|
+
generatedAt: string;
|
|
122
|
+
/** Only CLIs whose store was detected AND scanned appear here. */
|
|
123
|
+
totals: Partial<Record<LocalUsageCliId, LocalUsageTotals>>;
|
|
124
|
+
/** CLIs that were registered but produced nothing, and why. */
|
|
125
|
+
failures: LocalUsageReaderFailure[];
|
|
126
|
+
/** CLIs with no local store on this machine — absent, not failed. */
|
|
127
|
+
notInstalled: LocalUsageCliId[];
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* The `message.usage` object exactly as Claude Code writes it into a
|
|
131
|
+
* transcript line — snake_case, and every field optional because older
|
|
132
|
+
* transcripts predate some of them.
|
|
133
|
+
*/
|
|
134
|
+
export type LocalUsageClaudeRawUsage = {
|
|
135
|
+
input_tokens?: number;
|
|
136
|
+
output_tokens?: number;
|
|
137
|
+
cache_read_input_tokens?: number;
|
|
138
|
+
cache_creation_input_tokens?: number;
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* One Codex rollout reduced to its session-level totals.
|
|
142
|
+
*
|
|
143
|
+
* The token figures here are the session's CUMULATIVE counter, not a sum of
|
|
144
|
+
* per-turn values — see `codexReader.ts` for why summing overstates by ~63%.
|
|
145
|
+
*/
|
|
146
|
+
export type LocalUsageCodexSessionRollup = {
|
|
147
|
+
model?: string;
|
|
148
|
+
input: number;
|
|
149
|
+
output: number;
|
|
150
|
+
cached: number;
|
|
151
|
+
/** token_count events where the cumulative total actually advanced. */
|
|
152
|
+
billableEvents: number;
|
|
153
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for `src/lib/localUsage/` — reading token usage out of each AI CLI's
|
|
3
|
+
* own local session logs.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists alongside the proxy's ledger: the proxy can only account for
|
|
6
|
+
* traffic that went through it, which caps coverage at the CLIs that expose a
|
|
7
|
+
* base-URL override. Every CLI writes its own local transcript regardless, so
|
|
8
|
+
* reading those recovers the rest — and recovers history from before the proxy
|
|
9
|
+
* was ever installed. It is also an independent source of truth: a pricing
|
|
10
|
+
* defect in the proxy's own accounting cannot hide from a reader that derives
|
|
11
|
+
* cost from a different input.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
@@ -487,82 +487,6 @@ export async function buildMessagesArray(options) {
|
|
|
487
487
|
else if ("input" in options && options.input?.text) {
|
|
488
488
|
currentPrompt = options.input.text;
|
|
489
489
|
}
|
|
490
|
-
// Process CSV files if present and inject into prompt using proper CSV parser
|
|
491
|
-
if ("input" in options && options.input) {
|
|
492
|
-
const input = options.input;
|
|
493
|
-
let csvContent = "";
|
|
494
|
-
const csvOptions = "csvOptions" in options ? options.csvOptions : undefined;
|
|
495
|
-
// Process explicit csvFiles array
|
|
496
|
-
if (input.csvFiles && input.csvFiles.length > 0) {
|
|
497
|
-
for (let i = 0; i < input.csvFiles.length; i++) {
|
|
498
|
-
const csvFile = input.csvFiles[i];
|
|
499
|
-
const filename = extractFilename(csvFile, i);
|
|
500
|
-
const filePath = typeof csvFile === "string" ? csvFile : filename;
|
|
501
|
-
try {
|
|
502
|
-
const result = await FileDetector.detectAndProcess(csvFile, {
|
|
503
|
-
allowedTypes: ["csv"],
|
|
504
|
-
csvOptions: csvOptions,
|
|
505
|
-
});
|
|
506
|
-
let csvSection = `\n\n## CSV Data from "${filename}":\n`;
|
|
507
|
-
// Add metadata from csv-parser library
|
|
508
|
-
if (result.metadata) {
|
|
509
|
-
const metadataText = formatCSVMetadata(result.metadata);
|
|
510
|
-
if (metadataText) {
|
|
511
|
-
csvSection += metadataText + `\n\n`;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
// Put the actual CSV content BEFORE the tool instructions —
|
|
515
|
-
// buildCSVToolInstructions references "the CSV data shown above"
|
|
516
|
-
// and the trailing position keeps that reference accurate.
|
|
517
|
-
// Vertex Gemini misreads CSV-only prompts as "no files attached"
|
|
518
|
-
// when the NOTE-then-data order makes the reference dangle.
|
|
519
|
-
csvSection += result.content;
|
|
520
|
-
csvSection += buildCSVToolInstructions(filePath);
|
|
521
|
-
csvContent += csvSection;
|
|
522
|
-
logger.info(`[CSV] ✅ Processed: ${filename}`, result.metadata);
|
|
523
|
-
}
|
|
524
|
-
catch (error) {
|
|
525
|
-
logger.error(`[CSV] ❌ Failed to process ${filename}:`, error);
|
|
526
|
-
csvContent += `\n\n## CSV Data Error: Failed to process "${filename}"\nReason: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
// Process unified files array (auto-detect CSV)
|
|
531
|
-
if (input.files && input.files.length > 0) {
|
|
532
|
-
for (const file of input.files) {
|
|
533
|
-
const filename = extractFilename(file);
|
|
534
|
-
try {
|
|
535
|
-
const result = await FileDetector.detectAndProcess(file, {
|
|
536
|
-
maxSize: 50 * 1024 * 1024,
|
|
537
|
-
allowedTypes: ["csv"],
|
|
538
|
-
csvOptions: csvOptions,
|
|
539
|
-
mimetypeHint: isFileWithMetadata(file) ? file.mimetype : undefined,
|
|
540
|
-
});
|
|
541
|
-
if (result.type === "csv") {
|
|
542
|
-
let csvSection = `\n\n## CSV Data from "${filename}":\n`;
|
|
543
|
-
// Add metadata from csv-parser library
|
|
544
|
-
if (result.metadata) {
|
|
545
|
-
const metadataText = formatCSVMetadata(result.metadata);
|
|
546
|
-
if (metadataText) {
|
|
547
|
-
csvSection += metadataText + `\n\n`;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
csvSection += result.content;
|
|
551
|
-
csvContent += csvSection;
|
|
552
|
-
logger.info(`[FileDetector] ✅ CSV: ${filename}`, result.metadata);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
catch (error) {
|
|
556
|
-
// Silently skip non-CSV files in auto-detect mode
|
|
557
|
-
logger.debug(`[FileDetector] Skipped ${filename}: ${error instanceof Error ? error.message : String(error)}`);
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
// Prepend CSV content to current prompt
|
|
562
|
-
if (csvContent) {
|
|
563
|
-
currentPrompt = csvContent + (currentPrompt || "");
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
490
|
if (currentPrompt?.trim()) {
|
|
567
491
|
messages.push({
|
|
568
492
|
role: "user",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.20.0",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
|
|
6
6
|
"author": {
|
|
@@ -123,6 +123,7 @@
|
|
|
123
123
|
"test:music:unit": "npx tsx test/continuous-test-suite-music-unit.ts",
|
|
124
124
|
"test:image-gen": "npx tsx test/continuous-test-suite-image-gen-extras.ts",
|
|
125
125
|
"test:credentials": "npx tsx test/continuous-test-suite-credentials.ts",
|
|
126
|
+
"test:local-usage": "npx tsx test/continuous-test-suite-local-usage.ts",
|
|
126
127
|
"test:dynamic": "npx tsx test/continuous-test-suite-dynamic.ts",
|
|
127
128
|
"test:proxy": "npx tsx test/continuous-test-suite-proxy.ts",
|
|
128
129
|
"test:codex": "npx tsx test/continuous-test-suite-codex.ts",
|