@spendgraph/sdk 0.1.0 → 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/dist/client.d.ts +37 -0
- package/dist/client.js +48 -0
- package/dist/core/client/client.d.ts +30 -0
- package/dist/core/client/client.js +143 -0
- package/dist/core/client/errors.d.ts +25 -0
- package/dist/core/client/errors.js +31 -0
- package/dist/core/client/index.d.ts +2 -0
- package/dist/core/client/index.js +2 -0
- package/dist/core/types.d.ts +28 -0
- package/dist/core/types.js +1 -0
- package/dist/index.d.ts +9 -124
- package/dist/index.js +5 -421
- package/dist/langchain.js +8 -2
- package/dist/resources/alerts.d.ts +18 -0
- package/dist/resources/alerts.js +10 -0
- package/dist/resources/credentials.d.ts +22 -0
- package/dist/resources/credentials.js +22 -0
- package/dist/resources/events.d.ts +41 -0
- package/dist/resources/events.js +20 -0
- package/dist/resources/index.d.ts +21 -0
- package/dist/resources/index.js +12 -0
- package/dist/resources/ingest.d.ts +42 -0
- package/dist/resources/ingest.js +30 -0
- package/dist/resources/keys.d.ts +34 -0
- package/dist/resources/keys.js +20 -0
- package/dist/resources/playground.d.ts +7 -0
- package/dist/resources/playground.js +10 -0
- package/dist/resources/pricing.d.ts +49 -0
- package/dist/resources/pricing.js +42 -0
- package/dist/resources/projects.d.ts +54 -0
- package/dist/resources/projects.js +51 -0
- package/dist/resources/prompts-admin.d.ts +23 -0
- package/dist/resources/prompts-admin.js +26 -0
- package/dist/resources/prompts.d.ts +124 -0
- package/dist/resources/prompts.js +67 -0
- package/dist/resources/stats.d.ts +65 -0
- package/dist/resources/stats.js +29 -0
- package/dist/resources/tools.d.ts +66 -0
- package/dist/resources/tools.js +30 -0
- package/dist/rollout/index.d.ts +1 -0
- package/dist/rollout/index.js +1 -0
- package/dist/rollout/rollout.d.ts +75 -0
- package/dist/rollout/rollout.js +1 -0
- package/dist/schema/index.d.ts +3 -0
- package/dist/schema/index.js +2 -0
- package/dist/schema/serialize/index.d.ts +1 -0
- package/dist/schema/serialize/index.js +1 -0
- package/dist/schema/serialize/serialize.d.ts +12 -0
- package/dist/schema/serialize/serialize.js +42 -0
- package/dist/schema/types/index.d.ts +1 -0
- package/dist/schema/types/index.js +1 -0
- package/dist/schema/types/types.d.ts +58 -0
- package/dist/schema/types/types.js +1 -0
- package/dist/schema/validate/index.d.ts +1 -0
- package/dist/schema/validate/index.js +1 -0
- package/dist/schema/validate/validate.d.ts +28 -0
- package/dist/schema/validate/validate.js +108 -0
- package/dist/track/index.d.ts +2 -0
- package/dist/track/index.js +1 -0
- package/dist/track/track.d.ts +209 -0
- package/dist/track/track.js +513 -0
- package/package.json +3 -2
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
export interface SpendGraphOptions {
|
|
2
|
+
/** spendgraph API key (sg_…). Without it the SDK is a no-op. */
|
|
3
|
+
apiKey: string | undefined;
|
|
4
|
+
/** Base URL of your deployed spendgraph app, e.g. https://costs.yourco.com */
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
/** Flush at most this often (ms). Default 5000. */
|
|
7
|
+
flushIntervalMs?: number;
|
|
8
|
+
/** Flush when the buffer reaches this size. Default 20. */
|
|
9
|
+
maxBatch?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface TrackEvent {
|
|
12
|
+
model: string;
|
|
13
|
+
/** Uncached input tokens — cache tokens go in the fields below. */
|
|
14
|
+
inputTokens: number;
|
|
15
|
+
outputTokens: number;
|
|
16
|
+
/** Prompt-cache read (hit) tokens, billed at the provider's cache-read rate. */
|
|
17
|
+
cacheReadTokens?: number;
|
|
18
|
+
/** Prompt-cache write (creation) tokens. */
|
|
19
|
+
cacheWriteTokens?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Billed on their own by search-grounded providers — Perplexity Sonar Deep
|
|
22
|
+
* Research charges both at rates that are neither input nor output. Leave
|
|
23
|
+
* them unset for every other provider; OpenAI's `reasoning_tokens` are
|
|
24
|
+
* already counted inside `completion_tokens` and would be billed twice.
|
|
25
|
+
*/
|
|
26
|
+
citationTokens?: number;
|
|
27
|
+
reasoningTokens?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Client-side id: retried sends dedupe server-side.
|
|
30
|
+
* Must be 8–64 characters — shorter ids are rejected with a 422.
|
|
31
|
+
*/
|
|
32
|
+
eventId?: string;
|
|
33
|
+
/** ISO timestamp; defaults to server receive time. */
|
|
34
|
+
timestamp?: string;
|
|
35
|
+
metadata?: Record<string, string | number | boolean>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Fire-and-forget token tracking. track() never throws and never blocks;
|
|
39
|
+
* events batch in memory and flush every 5s or 20 events. Network failures
|
|
40
|
+
* retry once, then drop with one console.warn — tracking can never break
|
|
41
|
+
* the host app.
|
|
42
|
+
*/
|
|
43
|
+
export declare class SpendGraph {
|
|
44
|
+
private readonly opts;
|
|
45
|
+
private queue;
|
|
46
|
+
private timer;
|
|
47
|
+
private warned;
|
|
48
|
+
/** Events lost in the current run of failures, reset by a successful flush. */
|
|
49
|
+
private dropped;
|
|
50
|
+
/** Unpriced model ids already reported, so each is named at most once. */
|
|
51
|
+
private readonly unpricedSeen;
|
|
52
|
+
private readonly interval;
|
|
53
|
+
private readonly maxBatch;
|
|
54
|
+
/** When the pending flush was scheduled, for the suspend check in track(). */
|
|
55
|
+
private timerAt;
|
|
56
|
+
private suspendWarned;
|
|
57
|
+
private noKeyWarned;
|
|
58
|
+
private streamUsageWarned;
|
|
59
|
+
/** The flush currently on the wire, so a later flush() can wait for it. */
|
|
60
|
+
private inFlight;
|
|
61
|
+
constructor(opts: SpendGraphOptions);
|
|
62
|
+
/**
|
|
63
|
+
* The package's client, given no retries of its own.
|
|
64
|
+
*
|
|
65
|
+
* The policy in `send` stays this class's: a flush is awaited inside
|
|
66
|
+
* serverless handlers, where blocking out a minute-long rate-limit window is
|
|
67
|
+
* worse than losing the batch, and the client's backoff would do exactly that.
|
|
68
|
+
*/
|
|
69
|
+
private clientFor;
|
|
70
|
+
/** Record one LLM call. Synchronous, returns void, never throws. */
|
|
71
|
+
track(event: TrackEvent): void;
|
|
72
|
+
/**
|
|
73
|
+
* Says once that tracking is off.
|
|
74
|
+
*
|
|
75
|
+
* Running without a key stays a no-op on purpose — it keeps spendgraph out of
|
|
76
|
+
* tests and local runs without branching at every call site. But an unset
|
|
77
|
+
* SPENDGRAPH_API_KEY is indistinguishable from that choice, and it is the
|
|
78
|
+
* likeliest reason a fresh integration records nothing at all: the code is
|
|
79
|
+
* wired up correctly, the dashboard is empty, and nothing anywhere says why.
|
|
80
|
+
* Every other failure in this class warns once; this was the one that stayed
|
|
81
|
+
* quiet, which made it the hardest to find.
|
|
82
|
+
*/
|
|
83
|
+
private warnNoKey;
|
|
84
|
+
/**
|
|
85
|
+
* Notices that the runtime froze with events still buffered.
|
|
86
|
+
*
|
|
87
|
+
* Serverless platforms suspend a function once it returns rather than
|
|
88
|
+
* exiting it, so neither the flush timer nor `beforeExit` ever runs and the
|
|
89
|
+
* queue is lost without a sound — the failure Langfuse documents for Lambda
|
|
90
|
+
* and Vercel. There is no reliable flag for "am I serverless", but there is
|
|
91
|
+
* direct evidence: a pending timer whose deadline passed long ago did not
|
|
92
|
+
* fire, which only happens if the runtime stopped executing between calls.
|
|
93
|
+
*
|
|
94
|
+
* Seeing that, send the stragglers now (they survive into this invocation)
|
|
95
|
+
* and say once what the fix is. Correct callers await flush(), which clears
|
|
96
|
+
* the timer, so this never fires for them.
|
|
97
|
+
*/
|
|
98
|
+
private detectSuspendedRuntime;
|
|
99
|
+
/**
|
|
100
|
+
* Send everything buffered now. Call at the end of serverless handlers.
|
|
101
|
+
*
|
|
102
|
+
* Serialised against a flush already running, because this method is not the
|
|
103
|
+
* only thing that starts one: track() flushes by itself the moment the batch
|
|
104
|
+
* is full, and so do the timer, the suspend check and the exit hook. Those
|
|
105
|
+
* empty the queue synchronously, so an awaited flush() used to find nothing
|
|
106
|
+
* left to send and hand back an already-resolved promise while the real
|
|
107
|
+
* batch was still on the wire — and the next thing a serverless handler does
|
|
108
|
+
* after awaiting is return, freezing the runtime and losing exactly the
|
|
109
|
+
* events the await was there to save.
|
|
110
|
+
*
|
|
111
|
+
* The drain still starts synchronously — `drain()` runs as far as its first
|
|
112
|
+
* await before returning a promise, so the queue is taken during this call
|
|
113
|
+
* and not a microtask later. That ordering is load-bearing: the suspend
|
|
114
|
+
* check and the exit hook both fire a flush and then expect the buffer to be
|
|
115
|
+
* gone, and deferring it broke them.
|
|
116
|
+
*/
|
|
117
|
+
flush(): Promise<void>;
|
|
118
|
+
/** One pass: stop the timer, take the queue, put it on the wire. */
|
|
119
|
+
private drain;
|
|
120
|
+
/**
|
|
121
|
+
* Wrap an Anthropic or OpenAI client. Use the wrapped client exactly as
|
|
122
|
+
* before — token usage is read off each response and tracked automatically.
|
|
123
|
+
*
|
|
124
|
+
* Streaming is covered too:
|
|
125
|
+
* - helper streams (`anthropic.messages.stream()`,
|
|
126
|
+
* `openai.beta.chat.completions.stream()`) are tracked via their
|
|
127
|
+
* final-message promise — the stream you get back is untouched;
|
|
128
|
+
* - raw streams (`create({ stream: true })`) are tee'd: you receive one
|
|
129
|
+
* branch, usage is accumulated off the other. For OpenAI raw streams,
|
|
130
|
+
* pass `stream_options: { include_usage: true }` or there is no usage
|
|
131
|
+
* to read and the call goes untracked.
|
|
132
|
+
*/
|
|
133
|
+
wrap<T extends object>(client: T): T;
|
|
134
|
+
/**
|
|
135
|
+
* The recursive wrapper behind wrap().
|
|
136
|
+
*
|
|
137
|
+
* Two things here are deliberate and were both wrong before.
|
|
138
|
+
*
|
|
139
|
+
* `Reflect.get` is called without a receiver, so a getter runs with the real
|
|
140
|
+
* client as `this`. Passing the proxy — the obvious reading of the Proxy
|
|
141
|
+
* docs — makes any getter that touches a `#private` field throw
|
|
142
|
+
* "Cannot read private member", and the provider SDKs this is built to wrap
|
|
143
|
+
* use private fields. That turned a tracking wrapper into a crash on a
|
|
144
|
+
* client that works perfectly well unwrapped, in an SDK whose whole contract
|
|
145
|
+
* is to fail open.
|
|
146
|
+
*
|
|
147
|
+
* And each wrapper is kept, so `client.messages` and `client.messages.create`
|
|
148
|
+
* are the same object and the same function every time they are read.
|
|
149
|
+
* Rebuilding them per access allocated a proxy and a closure on every call
|
|
150
|
+
* and quietly broke any caller that holds onto a method or compares
|
|
151
|
+
* identity. The cache is per proxy and keyed by property, so a method always
|
|
152
|
+
* applies to the object it was read from, and it re-wraps if the underlying
|
|
153
|
+
* value is ever replaced.
|
|
154
|
+
*/
|
|
155
|
+
private proxy;
|
|
156
|
+
private observeResult;
|
|
157
|
+
private interceptStream;
|
|
158
|
+
/** Accumulate usage off a tee'd SSE branch (Anthropic events / OpenAI chunks). */
|
|
159
|
+
private consumeStream;
|
|
160
|
+
/**
|
|
161
|
+
* Says once that a streamed call went untracked.
|
|
162
|
+
*
|
|
163
|
+
* A raw OpenAI stream carries no usage unless the caller asks for it, so the
|
|
164
|
+
* accumulator above finishes with nothing to report and simply returns —
|
|
165
|
+
* correct, and completely silent. That is the same hole warnNoKey was added
|
|
166
|
+
* to close: the integration looks right, the dashboard stays empty for those
|
|
167
|
+
* calls, and nothing anywhere connects the two. Anthropic streams always
|
|
168
|
+
* carry usage, so in practice this names the one option that fixes it.
|
|
169
|
+
*
|
|
170
|
+
* Guarded on having seen at least one chunk, so an empty or aborted stream —
|
|
171
|
+
* where there was never anything to read — stays quiet.
|
|
172
|
+
*/
|
|
173
|
+
private warnStreamNoUsage;
|
|
174
|
+
private trackFromResponse;
|
|
175
|
+
private send;
|
|
176
|
+
/**
|
|
177
|
+
* The drop policy, which is this class's rather than the client's.
|
|
178
|
+
*
|
|
179
|
+
* 429 is the one failure the server tells you how to fix: it sends
|
|
180
|
+
* retry-after, and the window is at most a minute wide, so a batch that
|
|
181
|
+
* arrived at the end of one is deliverable a second later. Dropping it
|
|
182
|
+
* immediately threw that away and reported the least useful thing it could —
|
|
183
|
+
* "ingest returned 429" — for the one status with a documented remedy. The
|
|
184
|
+
* wait is capped because flush() is awaited inside serverless handlers, where
|
|
185
|
+
* blocking for a minute is worse than losing the batch.
|
|
186
|
+
*/
|
|
187
|
+
private onSendFailed;
|
|
188
|
+
/**
|
|
189
|
+
* Surfaces model ids the server could not price, once each.
|
|
190
|
+
*
|
|
191
|
+
* These are accepted and stored, so nothing here is an error — but they cost
|
|
192
|
+
* $0, and a dashboard reading $0 is indistinguishable from one reading
|
|
193
|
+
* "nothing happened". Naming the id in the integrator's own console is the
|
|
194
|
+
* cheapest possible moment to catch a typo or an unmapped model, and the
|
|
195
|
+
* per-id guard keeps a steady stream of the same unknown model from becoming
|
|
196
|
+
* log noise.
|
|
197
|
+
*/
|
|
198
|
+
private reportUnpriced;
|
|
199
|
+
/**
|
|
200
|
+
* Warns once per outage, with a running count of what was lost.
|
|
201
|
+
*
|
|
202
|
+
* Warning on every flush would spam a hot loop, but warning exactly once per
|
|
203
|
+
* process — the previous behaviour — hid a server-side bug that failed every
|
|
204
|
+
* full batch: one line early in a long-lived process, then silence, while the
|
|
205
|
+
* dashboard quietly undercounted. The count is what makes the silence legible
|
|
206
|
+
* when someone does go looking.
|
|
207
|
+
*/
|
|
208
|
+
private reportDropped;
|
|
209
|
+
}
|