@cendor/tokenguard 0.2.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/LICENSE +201 -0
- package/README.md +78 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/index.d.ts +188 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +607 -0
- package/dist/index.js.map +1 -0
- package/dist/sinks.d.ts +82 -0
- package/dist/sinks.d.ts.map +1 -0
- package/dist/sinks.js +219 -0
- package/dist/sinks.js.map +1 -0
- package/package.json +58 -0
package/dist/sinks.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Sink } from '@cendor/core';
|
|
2
|
+
/** A spend row as delivered to a sink's `write`. */
|
|
3
|
+
export interface SpendEntry {
|
|
4
|
+
tags: Record<string, unknown>;
|
|
5
|
+
usd: string;
|
|
6
|
+
input_tokens: number;
|
|
7
|
+
output_tokens: number;
|
|
8
|
+
reasoning_tokens?: number;
|
|
9
|
+
model: string;
|
|
10
|
+
}
|
|
11
|
+
/** A row read back from {@link SQLiteSink.rows}: `[tags, usd, input, output, reasoning, model]`. */
|
|
12
|
+
export type SQLiteRow = [string, string, number, number, number, string];
|
|
13
|
+
/**
|
|
14
|
+
* Persist each spend row to a local SQLite database via `better-sqlite3` (synchronous; no network).
|
|
15
|
+
* `better-sqlite3` is a synchronous binding, so — unlike the Python `sqlite3` original — no
|
|
16
|
+
* cross-thread lock is needed (JS is single-threaded). `usd` is stored as TEXT (the Decimal string);
|
|
17
|
+
* `tags` as JSON with sorted keys.
|
|
18
|
+
*/
|
|
19
|
+
export declare class SQLiteSink {
|
|
20
|
+
private readonly db;
|
|
21
|
+
constructor(path: string);
|
|
22
|
+
write(entry: SpendEntry): void;
|
|
23
|
+
/** All rows: `[tags_json, usd, input_tokens, output_tokens, reasoning_tokens, model]`. */
|
|
24
|
+
rows(): SQLiteRow[];
|
|
25
|
+
close(): void;
|
|
26
|
+
}
|
|
27
|
+
/** Options for {@link QueueSink}. */
|
|
28
|
+
export interface QueueSinkOptions {
|
|
29
|
+
/** Bound the in-flight queue; when full, `write()` awaits room (back-pressure — never drops a
|
|
30
|
+
* row). `null`/omitted is unbounded. */
|
|
31
|
+
maxQueue?: number | null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Wrap any `Sink` so its writes drain off the hot path. The bus fans out to subscribers inline, so a
|
|
35
|
+
* durable sink otherwise adds its I/O latency to every model call. `QueueSink` decouples that:
|
|
36
|
+
* `write()` enqueues and returns immediately (a single async drain loop writes into the inner sink
|
|
37
|
+
* in FIFO order), and `flush()` / `close()` guarantee durability at shutdown.
|
|
38
|
+
*
|
|
39
|
+
* Node is single-threaded, so — unlike the Python daemon-thread original — this is an in-memory FIFO
|
|
40
|
+
* plus an async drain loop. Observable semantics are preserved: FIFO order, no dropped rows under
|
|
41
|
+
* bounded back-pressure (`write` returns a Promise when it must await room), `write`-after-`close`
|
|
42
|
+
* throws, idempotent `close`, inner `flush`→`close` ordering at close, and a bad inner-write does
|
|
43
|
+
* not kill the drainer.
|
|
44
|
+
*/
|
|
45
|
+
export declare class QueueSink {
|
|
46
|
+
private readonly inner;
|
|
47
|
+
private readonly maxQueue;
|
|
48
|
+
private readonly items;
|
|
49
|
+
private closed;
|
|
50
|
+
private shutdownRequested;
|
|
51
|
+
private idle;
|
|
52
|
+
private wakeup;
|
|
53
|
+
private readonly roomWaiters;
|
|
54
|
+
private idleWaiters;
|
|
55
|
+
private readonly worker;
|
|
56
|
+
constructor(inner: Sink, opts?: QueueSinkOptions);
|
|
57
|
+
private wake;
|
|
58
|
+
private resolveIdle;
|
|
59
|
+
private run;
|
|
60
|
+
/** Enqueue a record for the drain loop. Returns synchronously unless a bounded queue is full, in
|
|
61
|
+
* which case it returns a Promise that resolves once room frees (back-pressure). */
|
|
62
|
+
write(entry: unknown): void | Promise<void>;
|
|
63
|
+
/** Block until every queued record is written and the inner sink is flushed. */
|
|
64
|
+
flush(): Promise<void>;
|
|
65
|
+
/** Drain the queue, stop the drain loop, and flush + close the inner sink (idempotent). */
|
|
66
|
+
close(): Promise<void>;
|
|
67
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Emit OpenTelemetry counters per spend row if `@opentelemetry/api` is installed, else a no-op.
|
|
71
|
+
* Loading is attempted synchronously via `createRequire` to mirror Python's synchronous
|
|
72
|
+
* `from opentelemetry import metrics`; when the package is absent, `write` is a no-op. The three
|
|
73
|
+
* metric names are byte-identical to the Python original.
|
|
74
|
+
*/
|
|
75
|
+
export declare class OTelSink {
|
|
76
|
+
private tokensCounter;
|
|
77
|
+
private costCounter;
|
|
78
|
+
private reasoningCounter;
|
|
79
|
+
constructor();
|
|
80
|
+
write(entry: SpendEntry): void;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=sinks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sinks.d.ts","sourceRoot":"","sources":["../src/sinks.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAKzC,oDAAoD;AACpD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;CACf;AASD,oGAAoG;AACpG,MAAM,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAWzE;;;;;GAKG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAmB;gBAE1B,IAAI,EAAE,MAAM;IASxB,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAiB9B,0FAA0F;IAC1F,IAAI,IAAI,SAAS,EAAE;IAiBnB,KAAK,IAAI,IAAI;CAGd;AAaD,qCAAqC;AACrC,MAAM,WAAW,gBAAgB;IAC/B;4CACwC;IACxC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAO;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;IACvC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyB;IACrD,OAAO,CAAC,WAAW,CAAyB;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;gBAE3B,KAAK,EAAE,IAAI,EAAE,IAAI,GAAE,gBAAqB;IAMpD,OAAO,CAAC,IAAI;IAQZ,OAAO,CAAC,WAAW;YAML,GAAG;IAuBjB;wFACoF;IACpF,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAe3C,gFAAgF;IAC1E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAU5B,2FAA2F;IACrF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAatB,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7C;AAID;;;;;GAKG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,aAAa,CACd;IACP,OAAO,CAAC,WAAW,CACZ;IACP,OAAO,CAAC,gBAAgB,CAER;;IAehB,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;CAc/B"}
|
package/dist/sinks.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable spend sinks for tokenguard (the `@cendor/tokenguard/sinks` subpath). A sink satisfies
|
|
3
|
+
* `@cendor/core`'s `Sink` protocol (`write(entry)`; optional `flush()` / `close()`). tokenguard's
|
|
4
|
+
* default is in-memory (`report()` aggregation); attach one of these to also persist each spend row.
|
|
5
|
+
*
|
|
6
|
+
* Each `write` receives `{ tags, usd, input_tokens, output_tokens, reasoning_tokens, model }` —
|
|
7
|
+
* `usd` is the Decimal as a string (never a float), and `reasoning_tokens` is a subset of
|
|
8
|
+
* `output_tokens`.
|
|
9
|
+
*/
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
11
|
+
import Database from 'better-sqlite3';
|
|
12
|
+
/** `JSON.stringify` with top-level keys sorted (parity with Python's `json.dumps(sort_keys=True)`). */
|
|
13
|
+
function stableJson(obj) {
|
|
14
|
+
const sorted = {};
|
|
15
|
+
for (const key of Object.keys(obj).sort())
|
|
16
|
+
sorted[key] = obj[key];
|
|
17
|
+
return JSON.stringify(sorted);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Persist each spend row to a local SQLite database via `better-sqlite3` (synchronous; no network).
|
|
21
|
+
* `better-sqlite3` is a synchronous binding, so — unlike the Python `sqlite3` original — no
|
|
22
|
+
* cross-thread lock is needed (JS is single-threaded). `usd` is stored as TEXT (the Decimal string);
|
|
23
|
+
* `tags` as JSON with sorted keys.
|
|
24
|
+
*/
|
|
25
|
+
export class SQLiteSink {
|
|
26
|
+
db;
|
|
27
|
+
constructor(path) {
|
|
28
|
+
this.db = new Database(path);
|
|
29
|
+
this.db.exec('CREATE TABLE IF NOT EXISTS spend (' +
|
|
30
|
+
'tags TEXT, usd TEXT, input_tokens INTEGER, output_tokens INTEGER, ' +
|
|
31
|
+
'reasoning_tokens INTEGER, model TEXT)');
|
|
32
|
+
}
|
|
33
|
+
write(entry) {
|
|
34
|
+
this.db
|
|
35
|
+
.prepare('INSERT INTO spend ' +
|
|
36
|
+
'(tags, usd, input_tokens, output_tokens, reasoning_tokens, model) ' +
|
|
37
|
+
'VALUES (?, ?, ?, ?, ?, ?)')
|
|
38
|
+
.run(stableJson(entry.tags ?? {}), String(entry.usd), Number(entry.input_tokens), Number(entry.output_tokens), Number(entry.reasoning_tokens ?? 0), String(entry.model));
|
|
39
|
+
}
|
|
40
|
+
/** All rows: `[tags_json, usd, input_tokens, output_tokens, reasoning_tokens, model]`. */
|
|
41
|
+
rows() {
|
|
42
|
+
const raw = this.db
|
|
43
|
+
.prepare('SELECT tags, usd, input_tokens, output_tokens, reasoning_tokens, model ' +
|
|
44
|
+
'FROM spend ORDER BY rowid')
|
|
45
|
+
.all();
|
|
46
|
+
return raw.map((r) => [
|
|
47
|
+
r.tags,
|
|
48
|
+
r.usd,
|
|
49
|
+
r.input_tokens,
|
|
50
|
+
r.output_tokens,
|
|
51
|
+
r.reasoning_tokens,
|
|
52
|
+
r.model,
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
55
|
+
close() {
|
|
56
|
+
this.db.close();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// --------------------------------------------------------------------------- QueueSink
|
|
60
|
+
/** Sentinel enqueued by {@link QueueSink.close} to tell the drain loop to stop. */
|
|
61
|
+
const SHUTDOWN = Symbol('cendor.tokenguard.queuesink.shutdown');
|
|
62
|
+
function optionalMethod(obj, name) {
|
|
63
|
+
if (obj == null || typeof obj !== 'object')
|
|
64
|
+
return null;
|
|
65
|
+
const fn = obj[name];
|
|
66
|
+
return typeof fn === 'function' ? fn.bind(obj) : null;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Wrap any `Sink` so its writes drain off the hot path. The bus fans out to subscribers inline, so a
|
|
70
|
+
* durable sink otherwise adds its I/O latency to every model call. `QueueSink` decouples that:
|
|
71
|
+
* `write()` enqueues and returns immediately (a single async drain loop writes into the inner sink
|
|
72
|
+
* in FIFO order), and `flush()` / `close()` guarantee durability at shutdown.
|
|
73
|
+
*
|
|
74
|
+
* Node is single-threaded, so — unlike the Python daemon-thread original — this is an in-memory FIFO
|
|
75
|
+
* plus an async drain loop. Observable semantics are preserved: FIFO order, no dropped rows under
|
|
76
|
+
* bounded back-pressure (`write` returns a Promise when it must await room), `write`-after-`close`
|
|
77
|
+
* throws, idempotent `close`, inner `flush`→`close` ordering at close, and a bad inner-write does
|
|
78
|
+
* not kill the drainer.
|
|
79
|
+
*/
|
|
80
|
+
export class QueueSink {
|
|
81
|
+
inner;
|
|
82
|
+
maxQueue;
|
|
83
|
+
items = [];
|
|
84
|
+
closed = false;
|
|
85
|
+
shutdownRequested = false;
|
|
86
|
+
idle = false;
|
|
87
|
+
wakeup = null;
|
|
88
|
+
roomWaiters = [];
|
|
89
|
+
idleWaiters = [];
|
|
90
|
+
worker;
|
|
91
|
+
constructor(inner, opts = {}) {
|
|
92
|
+
this.inner = inner;
|
|
93
|
+
this.maxQueue = opts.maxQueue && opts.maxQueue > 0 ? opts.maxQueue : null;
|
|
94
|
+
this.worker = this.run();
|
|
95
|
+
}
|
|
96
|
+
wake() {
|
|
97
|
+
if (this.wakeup) {
|
|
98
|
+
const w = this.wakeup;
|
|
99
|
+
this.wakeup = null;
|
|
100
|
+
w();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
resolveIdle() {
|
|
104
|
+
const waiters = this.idleWaiters;
|
|
105
|
+
this.idleWaiters = [];
|
|
106
|
+
for (const w of waiters)
|
|
107
|
+
w();
|
|
108
|
+
}
|
|
109
|
+
async run() {
|
|
110
|
+
for (;;) {
|
|
111
|
+
while (this.items.length === 0) {
|
|
112
|
+
this.idle = true;
|
|
113
|
+
this.resolveIdle();
|
|
114
|
+
if (this.shutdownRequested)
|
|
115
|
+
return;
|
|
116
|
+
await new Promise((resolve) => {
|
|
117
|
+
this.wakeup = resolve;
|
|
118
|
+
});
|
|
119
|
+
this.idle = false;
|
|
120
|
+
}
|
|
121
|
+
const item = this.items.shift();
|
|
122
|
+
const rw = this.roomWaiters.shift(); // a slot freed → release one back-pressure waiter
|
|
123
|
+
if (rw)
|
|
124
|
+
rw();
|
|
125
|
+
if (item === SHUTDOWN)
|
|
126
|
+
return;
|
|
127
|
+
try {
|
|
128
|
+
await Promise.resolve(this.inner.write(item));
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// a bad row must not kill the worker — drop it and carry on
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Enqueue a record for the drain loop. Returns synchronously unless a bounded queue is full, in
|
|
136
|
+
* which case it returns a Promise that resolves once room frees (back-pressure). */
|
|
137
|
+
write(entry) {
|
|
138
|
+
if (this.closed)
|
|
139
|
+
throw new Error('QueueSink.write() after close()');
|
|
140
|
+
if (this.maxQueue !== null && this.items.length >= this.maxQueue) {
|
|
141
|
+
return new Promise((resolve) => {
|
|
142
|
+
this.roomWaiters.push(() => {
|
|
143
|
+
this.items.push(entry);
|
|
144
|
+
this.wake();
|
|
145
|
+
resolve();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
this.items.push(entry);
|
|
150
|
+
this.wake();
|
|
151
|
+
}
|
|
152
|
+
/** Block until every queued record is written and the inner sink is flushed. */
|
|
153
|
+
async flush() {
|
|
154
|
+
if (!(this.items.length === 0 && this.idle)) {
|
|
155
|
+
await new Promise((resolve) => {
|
|
156
|
+
this.idleWaiters.push(resolve);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
const innerFlush = optionalMethod(this.inner, 'flush');
|
|
160
|
+
if (innerFlush)
|
|
161
|
+
await innerFlush();
|
|
162
|
+
}
|
|
163
|
+
/** Drain the queue, stop the drain loop, and flush + close the inner sink (idempotent). */
|
|
164
|
+
async close() {
|
|
165
|
+
if (this.closed)
|
|
166
|
+
return;
|
|
167
|
+
this.closed = true;
|
|
168
|
+
this.shutdownRequested = true;
|
|
169
|
+
this.items.push(SHUTDOWN); // after all enqueued rows (FIFO) — the loop drains them, then exits
|
|
170
|
+
this.wake();
|
|
171
|
+
await this.worker;
|
|
172
|
+
for (const name of ['flush', 'close']) {
|
|
173
|
+
const fn = optionalMethod(this.inner, name);
|
|
174
|
+
if (fn)
|
|
175
|
+
await fn();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async [Symbol.asyncDispose]() {
|
|
179
|
+
await this.close();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// --------------------------------------------------------------------------- OTelSink
|
|
183
|
+
/**
|
|
184
|
+
* Emit OpenTelemetry counters per spend row if `@opentelemetry/api` is installed, else a no-op.
|
|
185
|
+
* Loading is attempted synchronously via `createRequire` to mirror Python's synchronous
|
|
186
|
+
* `from opentelemetry import metrics`; when the package is absent, `write` is a no-op. The three
|
|
187
|
+
* metric names are byte-identical to the Python original.
|
|
188
|
+
*/
|
|
189
|
+
export class OTelSink {
|
|
190
|
+
tokensCounter = null;
|
|
191
|
+
costCounter = null;
|
|
192
|
+
reasoningCounter = null;
|
|
193
|
+
constructor() {
|
|
194
|
+
try {
|
|
195
|
+
const req = createRequire(import.meta.url);
|
|
196
|
+
const otel = req('@opentelemetry/api');
|
|
197
|
+
const meter = otel.metrics.getMeter('cendor.tokenguard');
|
|
198
|
+
this.tokensCounter = meter.createCounter('gen_ai.client.token.usage');
|
|
199
|
+
this.costCounter = meter.createCounter('gen_ai.client.cost.usd');
|
|
200
|
+
this.reasoningCounter = meter.createCounter('gen_ai.client.reasoning.token.usage');
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// OpenTelemetry not installed — stay in no-op mode
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
write(entry) {
|
|
207
|
+
if (this.tokensCounter === null ||
|
|
208
|
+
this.costCounter === null ||
|
|
209
|
+
this.reasoningCounter === null) {
|
|
210
|
+
return; // OTel not installed — silently skip
|
|
211
|
+
}
|
|
212
|
+
const attrs = { model: entry.model ?? '' };
|
|
213
|
+
// reasoning is a subset of output — reported as its own counter, not added into the total.
|
|
214
|
+
this.tokensCounter.add(Number(entry.input_tokens) + Number(entry.output_tokens), attrs);
|
|
215
|
+
this.reasoningCounter.add(Number(entry.reasoning_tokens ?? 0), attrs);
|
|
216
|
+
this.costCounter.add(Number(entry.usd), attrs);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=sinks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sinks.js","sourceRoot":"","sources":["../src/sinks.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cendor/tokenguard",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Pre-flight cost caps + per-tag spend attribution for LLM calls. The TypeScript port of cendor.tokenguard.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./sinks": {
|
|
15
|
+
"types": "./dist/sinks.d.ts",
|
|
16
|
+
"default": "./dist/sinks.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"sideEffects": [
|
|
24
|
+
"./dist/index.js"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@cendor/core": "^0.2.0"
|
|
31
|
+
},
|
|
32
|
+
"optionalDependencies": {
|
|
33
|
+
"better-sqlite3": "^12.11.1"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@opentelemetry/api": ">=1"
|
|
37
|
+
},
|
|
38
|
+
"peerDependenciesMeta": {
|
|
39
|
+
"@opentelemetry/api": {
|
|
40
|
+
"optional": true
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"llm",
|
|
45
|
+
"budget",
|
|
46
|
+
"cost",
|
|
47
|
+
"governance",
|
|
48
|
+
"cendor"
|
|
49
|
+
],
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/cendorhq/cendor-libs-js.git",
|
|
53
|
+
"directory": "packages/tokenguard"
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
57
|
+
}
|
|
58
|
+
}
|