@useautumn/gateway 0.0.1
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.md +7 -0
- package/README.md +74 -0
- package/dist/ai-sdk/index.cjs +214 -0
- package/dist/ai-sdk/index.d.cts +43 -0
- package/dist/ai-sdk/index.d.ts +43 -0
- package/dist/ai-sdk/index.js +189 -0
- package/dist/openrouter/index.cjs +327 -0
- package/dist/openrouter/index.d.cts +69 -0
- package/dist/openrouter/index.d.ts +69 -0
- package/dist/openrouter/index.js +298 -0
- package/dist/track-C73uYSG_.d.cts +47 -0
- package/dist/track-C73uYSG_.d.ts +47 -0
- package/package.json +64 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
The MIT License (MIT) Copyright (c) 2025 - present, Recase Inc.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# @useautumn/gateway
|
|
2
|
+
|
|
3
|
+
[Autumn](https://useautumn.com) adapters for AI SDKs and gateways. Wrap your model or client once and every LLM call's token usage is tracked against the customer's AI credit balance — no manual `trackTokens` calls.
|
|
4
|
+
|
|
5
|
+
Autumn converts the token counts to a dollar cost server-side using live [models.dev](https://models.dev) rates plus your configured markup, so pricing changes never require a client redeploy.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @useautumn/gateway
|
|
11
|
+
# or
|
|
12
|
+
bun add @useautumn/gateway
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Authentication: pass an `autumn` client (from `autumn-js`), or set `AUTUMN_API_KEY` (or `AUTUMN_SECRET_KEY`) in the environment and omit it.
|
|
16
|
+
|
|
17
|
+
## Vercel AI SDK
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { openai } from "@ai-sdk/openai";
|
|
21
|
+
import { generateText } from "ai";
|
|
22
|
+
import { withAutumn } from "@useautumn/gateway/ai-sdk";
|
|
23
|
+
|
|
24
|
+
const model = withAutumn({
|
|
25
|
+
model: openai("gpt-4o"),
|
|
26
|
+
customerId: "user_123",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const { text } = await generateText({ model, prompt: "Hello!" });
|
|
30
|
+
// usage tracked automatically — streaming too
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The wrapped model is a drop-in `LanguageModelV3`. Usage is read from the response (or the stream's finish chunk) and reported as `<provider>/<model>` (e.g. `anthropic/claude-sonnet-4-5`). If your provider's name doesn't match its models.dev key, override it with `providerId`.
|
|
34
|
+
|
|
35
|
+
## OpenRouter
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { OpenRouter } from "@openrouter/sdk";
|
|
39
|
+
import { withAutumn, trackingSettled } from "@useautumn/gateway/openrouter";
|
|
40
|
+
|
|
41
|
+
const client = withAutumn({
|
|
42
|
+
openRouter: new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY }),
|
|
43
|
+
customerId: "user_123",
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const result = await client.chat.send({
|
|
47
|
+
model: "openai/gpt-4o",
|
|
48
|
+
messages: [{ role: "user", content: "Hello!" }],
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The wrapper forces OpenRouter usage accounting on every request, tracks `chat.send` (streaming and non-streaming) and the responses API (including `@openrouter/agent`'s `callModel`), and reports models as `openrouter/<slug>` using the response's resolved model — router aliases like `openrouter/auto` bill against the model that actually served the request. OpenRouter's own reported charge is attached to each event as `openrouter_cost`.
|
|
53
|
+
|
|
54
|
+
Streaming usage is tracked in the background; call `await trackingSettled(client)` before reading balances that must reflect calls just made. For consumption patterns the wrapper doesn't cover, `trackOpenRouterUsage({ usage, model, customerId })` accepts both SDK camelCase and raw snake_case usage objects.
|
|
55
|
+
|
|
56
|
+
## Options
|
|
57
|
+
|
|
58
|
+
Both adapters accept the shared tracking options:
|
|
59
|
+
|
|
60
|
+
| Option | Required | Description |
|
|
61
|
+
|--------|----------|-------------|
|
|
62
|
+
| `customerId` | Yes | Autumn customer ID to attribute usage to |
|
|
63
|
+
| `autumn` | No | `autumn-js` client; falls back to `AUTUMN_API_KEY` env |
|
|
64
|
+
| `featureId` | No | Target AI credit system feature (auto-detected if you have one) |
|
|
65
|
+
| `entityId` | No | Entity for entity-scoped balances |
|
|
66
|
+
| `properties` | No | Extra properties attached to each usage event |
|
|
67
|
+
|
|
68
|
+
Tracking failures are caught and logged — they never break your AI responses.
|
|
69
|
+
|
|
70
|
+
## Docs
|
|
71
|
+
|
|
72
|
+
- [Vercel AI SDK guide](https://docs.useautumn.com/documentation/external-providers/ai-sdk)
|
|
73
|
+
- [OpenRouter guide](https://docs.useautumn.com/documentation/external-providers/openrouter)
|
|
74
|
+
- [trackTokens API reference](https://docs.useautumn.com/api-reference/balances/trackTokens)
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/ai-sdk/index.ts
|
|
21
|
+
var ai_sdk_exports = {};
|
|
22
|
+
__export(ai_sdk_exports, {
|
|
23
|
+
withAutumn: () => withAutumn
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(ai_sdk_exports);
|
|
26
|
+
var import_ai = require("ai");
|
|
27
|
+
|
|
28
|
+
// src/shared/track.ts
|
|
29
|
+
var WIRE_KEYS = {
|
|
30
|
+
customerId: "customer_id",
|
|
31
|
+
entityId: "entity_id",
|
|
32
|
+
featureId: "feature_id",
|
|
33
|
+
modelId: "model_id",
|
|
34
|
+
inputTokens: "input_tokens",
|
|
35
|
+
outputTokens: "output_tokens",
|
|
36
|
+
cacheReadTokens: "cache_read_tokens",
|
|
37
|
+
cacheWriteTokens: "cache_write_tokens",
|
|
38
|
+
audioInputTokens: "audio_input_tokens",
|
|
39
|
+
audioOutputTokens: "audio_output_tokens",
|
|
40
|
+
reasoningTokens: "reasoning_tokens"
|
|
41
|
+
};
|
|
42
|
+
var toWire = (params) => Object.fromEntries(
|
|
43
|
+
Object.entries(params).filter(([, value]) => value !== void 0).map(([key, value]) => [WIRE_KEYS[key] ?? key, value])
|
|
44
|
+
);
|
|
45
|
+
var envClient = () => {
|
|
46
|
+
const env = typeof process === "undefined" ? void 0 : process.env;
|
|
47
|
+
const secretKey = env?.AUTUMN_API_KEY ?? env?.AUTUMN_SECRET_KEY;
|
|
48
|
+
const baseUrl = env?.AUTUMN_BASE_URL ?? "https://api.useautumn.com";
|
|
49
|
+
return {
|
|
50
|
+
trackTokens: async (params) => {
|
|
51
|
+
if (!secretKey) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
"[Autumn] No autumn client was passed and AUTUMN_API_KEY is not set."
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
const response = await fetch(`${baseUrl}/v1/balances.track_tokens`, {
|
|
57
|
+
method: "POST",
|
|
58
|
+
headers: {
|
|
59
|
+
authorization: `Bearer ${secretKey}`,
|
|
60
|
+
"content-type": "application/json"
|
|
61
|
+
},
|
|
62
|
+
body: JSON.stringify(toWire(params))
|
|
63
|
+
});
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`track_tokens failed (${response.status}): ${await response.text()}`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return response.json();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
var createTracker = ({
|
|
74
|
+
autumn = envClient(),
|
|
75
|
+
customerId,
|
|
76
|
+
featureId,
|
|
77
|
+
entityId,
|
|
78
|
+
properties
|
|
79
|
+
}) => (getEvent) => trackTokenUsage({
|
|
80
|
+
autumn,
|
|
81
|
+
getParams: () => {
|
|
82
|
+
const event = getEvent();
|
|
83
|
+
return {
|
|
84
|
+
...event.pools,
|
|
85
|
+
customerId,
|
|
86
|
+
modelId: event.modelId,
|
|
87
|
+
featureId,
|
|
88
|
+
entityId,
|
|
89
|
+
properties: event.properties ?? properties
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
var trackTokenUsage = async ({
|
|
94
|
+
autumn,
|
|
95
|
+
getParams
|
|
96
|
+
}) => {
|
|
97
|
+
try {
|
|
98
|
+
const balances = autumn.balances;
|
|
99
|
+
const trackTokens = balances?.trackTokens?.bind(balances) ?? autumn.trackTokens?.bind(autumn);
|
|
100
|
+
if (!trackTokens) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
"Autumn client does not support trackTokens \u2014 upgrade @useautumn/sdk."
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
await trackTokens(getParams());
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error("[Autumn Tracking] Failed to track usage:", error);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// src/shared/usage.ts
|
|
112
|
+
var clamp = (value) => Math.max(0, value);
|
|
113
|
+
var requiredCount = (value, label, modelName) => {
|
|
114
|
+
if (value == null) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`[Autumn] ${label} token usage was not returned by the model provider (${modelName}). This provider may not support usage tracking.`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return value;
|
|
120
|
+
};
|
|
121
|
+
var poolsFromParts = (parts, modelName) => {
|
|
122
|
+
const cacheRead = parts.cacheRead ?? 0;
|
|
123
|
+
const cacheWrite = parts.cacheWrite ?? 0;
|
|
124
|
+
const reasoning = parts.reasoning ?? 0;
|
|
125
|
+
const audioInput = parts.audioInput ?? 0;
|
|
126
|
+
const textInput = parts.textInput ?? (parts.totalInput != null ? parts.totalInput - cacheRead - cacheWrite - audioInput : void 0);
|
|
127
|
+
const textOutput = parts.textOutput ?? (parts.totalOutput != null ? parts.totalOutput - reasoning : void 0);
|
|
128
|
+
return {
|
|
129
|
+
inputTokens: clamp(requiredCount(textInput, "Input", modelName)),
|
|
130
|
+
outputTokens: clamp(requiredCount(textOutput, "Output", modelName)),
|
|
131
|
+
cacheReadTokens: clamp(cacheRead),
|
|
132
|
+
cacheWriteTokens: clamp(cacheWrite),
|
|
133
|
+
reasoningTokens: clamp(reasoning),
|
|
134
|
+
...parts.audioInput !== void 0 && {
|
|
135
|
+
audioInputTokens: clamp(audioInput)
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// src/ai-sdk/usage.ts
|
|
141
|
+
var flatCount = (value) => typeof value === "number" ? value : value?.total ?? void 0;
|
|
142
|
+
var isNested = (value) => value != null && typeof value === "object";
|
|
143
|
+
var toParts = (usage) => {
|
|
144
|
+
const input = usage.inputTokens;
|
|
145
|
+
const output = usage.outputTokens;
|
|
146
|
+
if (isNested(input)) {
|
|
147
|
+
const out = isNested(output) ? output : void 0;
|
|
148
|
+
return {
|
|
149
|
+
cacheRead: input.cacheRead ?? 0,
|
|
150
|
+
cacheWrite: input.cacheWrite ?? 0,
|
|
151
|
+
reasoning: out?.reasoning ?? 0,
|
|
152
|
+
textInput: input.noCache,
|
|
153
|
+
totalInput: input.total,
|
|
154
|
+
textOutput: out?.text,
|
|
155
|
+
totalOutput: out?.total
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
cacheRead: usage.inputTokenDetails?.cacheReadTokens ?? usage.cachedInputTokens ?? 0,
|
|
160
|
+
cacheWrite: usage.inputTokenDetails?.cacheWriteTokens ?? 0,
|
|
161
|
+
reasoning: usage.outputTokenDetails?.reasoningTokens ?? usage.reasoningTokens ?? 0,
|
|
162
|
+
textInput: usage.inputTokenDetails?.noCacheTokens,
|
|
163
|
+
totalInput: typeof input === "number" ? input : flatCount(usage.promptTokens),
|
|
164
|
+
textOutput: usage.outputTokenDetails?.textTokens,
|
|
165
|
+
totalOutput: typeof output === "number" ? output : flatCount(usage.completionTokens)
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
var normalizeUsage = (usage, modelName) => poolsFromParts(toParts(usage), modelName);
|
|
169
|
+
|
|
170
|
+
// src/ai-sdk/index.ts
|
|
171
|
+
var withAutumn = ({
|
|
172
|
+
model,
|
|
173
|
+
providerId,
|
|
174
|
+
...tracking
|
|
175
|
+
}) => {
|
|
176
|
+
const modelName = `${providerId ?? model.provider}/${model.modelId}`;
|
|
177
|
+
const track = createTracker(tracking);
|
|
178
|
+
const trackUsage = (usage) => track(() => ({
|
|
179
|
+
pools: normalizeUsage(usage, modelName),
|
|
180
|
+
modelId: modelName
|
|
181
|
+
}));
|
|
182
|
+
const middleware = {
|
|
183
|
+
specificationVersion: "v3",
|
|
184
|
+
wrapGenerate: async ({ doGenerate }) => {
|
|
185
|
+
const result = await doGenerate();
|
|
186
|
+
await trackUsage(result.usage);
|
|
187
|
+
return result;
|
|
188
|
+
},
|
|
189
|
+
wrapStream: async ({ doStream }) => {
|
|
190
|
+
const { stream, ...rest } = await doStream();
|
|
191
|
+
let trackingPromise;
|
|
192
|
+
const transformStream = new TransformStream({
|
|
193
|
+
transform(chunk, controller) {
|
|
194
|
+
if (chunk.type === "finish" && chunk.usage) {
|
|
195
|
+
trackingPromise = trackUsage(chunk.usage);
|
|
196
|
+
}
|
|
197
|
+
controller.enqueue(chunk);
|
|
198
|
+
},
|
|
199
|
+
async flush() {
|
|
200
|
+
await trackingPromise;
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
return {
|
|
204
|
+
stream: stream.pipeThrough(transformStream),
|
|
205
|
+
...rest
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
return (0, import_ai.wrapLanguageModel)({ model, middleware });
|
|
210
|
+
};
|
|
211
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
212
|
+
0 && (module.exports = {
|
|
213
|
+
withAutumn
|
|
214
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { LanguageModelV3 } from '@ai-sdk/provider';
|
|
2
|
+
import { A as AutumnTrackingOptions } from '../track-C73uYSG_.cjs';
|
|
3
|
+
export { a as AutumnClient, T as TokenPools } from '../track-C73uYSG_.cjs';
|
|
4
|
+
|
|
5
|
+
type NestedTokens = {
|
|
6
|
+
total?: number | null;
|
|
7
|
+
noCache?: number | null;
|
|
8
|
+
cacheRead?: number | null;
|
|
9
|
+
cacheWrite?: number | null;
|
|
10
|
+
text?: number | null;
|
|
11
|
+
reasoning?: number | null;
|
|
12
|
+
};
|
|
13
|
+
type LegacyCount = number | {
|
|
14
|
+
total?: number | null;
|
|
15
|
+
} | null;
|
|
16
|
+
/** Lenient view over AI SDK usage shapes: nested V3 counts, flat counts with token details, and legacy prompt/completion counts. */
|
|
17
|
+
type UsageLike = {
|
|
18
|
+
inputTokens?: number | NestedTokens | null;
|
|
19
|
+
outputTokens?: number | NestedTokens | null;
|
|
20
|
+
promptTokens?: LegacyCount;
|
|
21
|
+
completionTokens?: LegacyCount;
|
|
22
|
+
inputTokenDetails?: {
|
|
23
|
+
noCacheTokens?: number | null;
|
|
24
|
+
cacheReadTokens?: number | null;
|
|
25
|
+
cacheWriteTokens?: number | null;
|
|
26
|
+
} | null;
|
|
27
|
+
outputTokenDetails?: {
|
|
28
|
+
textTokens?: number | null;
|
|
29
|
+
reasoningTokens?: number | null;
|
|
30
|
+
} | null;
|
|
31
|
+
cachedInputTokens?: number | null;
|
|
32
|
+
reasoningTokens?: number | null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type WithAutumnOptions = AutumnTrackingOptions & {
|
|
36
|
+
/** The AI SDK language model to wrap. */
|
|
37
|
+
model: LanguageModelV3;
|
|
38
|
+
/** Override the provider prefix used in the model name (e.g. "openrouter", "custom"). Falls back to `model.provider`. */
|
|
39
|
+
providerId?: string;
|
|
40
|
+
};
|
|
41
|
+
declare const withAutumn: ({ model, providerId, ...tracking }: WithAutumnOptions) => LanguageModelV3;
|
|
42
|
+
|
|
43
|
+
export { AutumnTrackingOptions, type UsageLike, type WithAutumnOptions, withAutumn };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { LanguageModelV3 } from '@ai-sdk/provider';
|
|
2
|
+
import { A as AutumnTrackingOptions } from '../track-C73uYSG_.js';
|
|
3
|
+
export { a as AutumnClient, T as TokenPools } from '../track-C73uYSG_.js';
|
|
4
|
+
|
|
5
|
+
type NestedTokens = {
|
|
6
|
+
total?: number | null;
|
|
7
|
+
noCache?: number | null;
|
|
8
|
+
cacheRead?: number | null;
|
|
9
|
+
cacheWrite?: number | null;
|
|
10
|
+
text?: number | null;
|
|
11
|
+
reasoning?: number | null;
|
|
12
|
+
};
|
|
13
|
+
type LegacyCount = number | {
|
|
14
|
+
total?: number | null;
|
|
15
|
+
} | null;
|
|
16
|
+
/** Lenient view over AI SDK usage shapes: nested V3 counts, flat counts with token details, and legacy prompt/completion counts. */
|
|
17
|
+
type UsageLike = {
|
|
18
|
+
inputTokens?: number | NestedTokens | null;
|
|
19
|
+
outputTokens?: number | NestedTokens | null;
|
|
20
|
+
promptTokens?: LegacyCount;
|
|
21
|
+
completionTokens?: LegacyCount;
|
|
22
|
+
inputTokenDetails?: {
|
|
23
|
+
noCacheTokens?: number | null;
|
|
24
|
+
cacheReadTokens?: number | null;
|
|
25
|
+
cacheWriteTokens?: number | null;
|
|
26
|
+
} | null;
|
|
27
|
+
outputTokenDetails?: {
|
|
28
|
+
textTokens?: number | null;
|
|
29
|
+
reasoningTokens?: number | null;
|
|
30
|
+
} | null;
|
|
31
|
+
cachedInputTokens?: number | null;
|
|
32
|
+
reasoningTokens?: number | null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type WithAutumnOptions = AutumnTrackingOptions & {
|
|
36
|
+
/** The AI SDK language model to wrap. */
|
|
37
|
+
model: LanguageModelV3;
|
|
38
|
+
/** Override the provider prefix used in the model name (e.g. "openrouter", "custom"). Falls back to `model.provider`. */
|
|
39
|
+
providerId?: string;
|
|
40
|
+
};
|
|
41
|
+
declare const withAutumn: ({ model, providerId, ...tracking }: WithAutumnOptions) => LanguageModelV3;
|
|
42
|
+
|
|
43
|
+
export { AutumnTrackingOptions, type UsageLike, type WithAutumnOptions, withAutumn };
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// src/ai-sdk/index.ts
|
|
2
|
+
import { wrapLanguageModel } from "ai";
|
|
3
|
+
|
|
4
|
+
// src/shared/track.ts
|
|
5
|
+
var WIRE_KEYS = {
|
|
6
|
+
customerId: "customer_id",
|
|
7
|
+
entityId: "entity_id",
|
|
8
|
+
featureId: "feature_id",
|
|
9
|
+
modelId: "model_id",
|
|
10
|
+
inputTokens: "input_tokens",
|
|
11
|
+
outputTokens: "output_tokens",
|
|
12
|
+
cacheReadTokens: "cache_read_tokens",
|
|
13
|
+
cacheWriteTokens: "cache_write_tokens",
|
|
14
|
+
audioInputTokens: "audio_input_tokens",
|
|
15
|
+
audioOutputTokens: "audio_output_tokens",
|
|
16
|
+
reasoningTokens: "reasoning_tokens"
|
|
17
|
+
};
|
|
18
|
+
var toWire = (params) => Object.fromEntries(
|
|
19
|
+
Object.entries(params).filter(([, value]) => value !== void 0).map(([key, value]) => [WIRE_KEYS[key] ?? key, value])
|
|
20
|
+
);
|
|
21
|
+
var envClient = () => {
|
|
22
|
+
const env = typeof process === "undefined" ? void 0 : process.env;
|
|
23
|
+
const secretKey = env?.AUTUMN_API_KEY ?? env?.AUTUMN_SECRET_KEY;
|
|
24
|
+
const baseUrl = env?.AUTUMN_BASE_URL ?? "https://api.useautumn.com";
|
|
25
|
+
return {
|
|
26
|
+
trackTokens: async (params) => {
|
|
27
|
+
if (!secretKey) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
"[Autumn] No autumn client was passed and AUTUMN_API_KEY is not set."
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
const response = await fetch(`${baseUrl}/v1/balances.track_tokens`, {
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: {
|
|
35
|
+
authorization: `Bearer ${secretKey}`,
|
|
36
|
+
"content-type": "application/json"
|
|
37
|
+
},
|
|
38
|
+
body: JSON.stringify(toWire(params))
|
|
39
|
+
});
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`track_tokens failed (${response.status}): ${await response.text()}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return response.json();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
var createTracker = ({
|
|
50
|
+
autumn = envClient(),
|
|
51
|
+
customerId,
|
|
52
|
+
featureId,
|
|
53
|
+
entityId,
|
|
54
|
+
properties
|
|
55
|
+
}) => (getEvent) => trackTokenUsage({
|
|
56
|
+
autumn,
|
|
57
|
+
getParams: () => {
|
|
58
|
+
const event = getEvent();
|
|
59
|
+
return {
|
|
60
|
+
...event.pools,
|
|
61
|
+
customerId,
|
|
62
|
+
modelId: event.modelId,
|
|
63
|
+
featureId,
|
|
64
|
+
entityId,
|
|
65
|
+
properties: event.properties ?? properties
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
var trackTokenUsage = async ({
|
|
70
|
+
autumn,
|
|
71
|
+
getParams
|
|
72
|
+
}) => {
|
|
73
|
+
try {
|
|
74
|
+
const balances = autumn.balances;
|
|
75
|
+
const trackTokens = balances?.trackTokens?.bind(balances) ?? autumn.trackTokens?.bind(autumn);
|
|
76
|
+
if (!trackTokens) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
"Autumn client does not support trackTokens \u2014 upgrade @useautumn/sdk."
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
await trackTokens(getParams());
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error("[Autumn Tracking] Failed to track usage:", error);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// src/shared/usage.ts
|
|
88
|
+
var clamp = (value) => Math.max(0, value);
|
|
89
|
+
var requiredCount = (value, label, modelName) => {
|
|
90
|
+
if (value == null) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`[Autumn] ${label} token usage was not returned by the model provider (${modelName}). This provider may not support usage tracking.`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
};
|
|
97
|
+
var poolsFromParts = (parts, modelName) => {
|
|
98
|
+
const cacheRead = parts.cacheRead ?? 0;
|
|
99
|
+
const cacheWrite = parts.cacheWrite ?? 0;
|
|
100
|
+
const reasoning = parts.reasoning ?? 0;
|
|
101
|
+
const audioInput = parts.audioInput ?? 0;
|
|
102
|
+
const textInput = parts.textInput ?? (parts.totalInput != null ? parts.totalInput - cacheRead - cacheWrite - audioInput : void 0);
|
|
103
|
+
const textOutput = parts.textOutput ?? (parts.totalOutput != null ? parts.totalOutput - reasoning : void 0);
|
|
104
|
+
return {
|
|
105
|
+
inputTokens: clamp(requiredCount(textInput, "Input", modelName)),
|
|
106
|
+
outputTokens: clamp(requiredCount(textOutput, "Output", modelName)),
|
|
107
|
+
cacheReadTokens: clamp(cacheRead),
|
|
108
|
+
cacheWriteTokens: clamp(cacheWrite),
|
|
109
|
+
reasoningTokens: clamp(reasoning),
|
|
110
|
+
...parts.audioInput !== void 0 && {
|
|
111
|
+
audioInputTokens: clamp(audioInput)
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// src/ai-sdk/usage.ts
|
|
117
|
+
var flatCount = (value) => typeof value === "number" ? value : value?.total ?? void 0;
|
|
118
|
+
var isNested = (value) => value != null && typeof value === "object";
|
|
119
|
+
var toParts = (usage) => {
|
|
120
|
+
const input = usage.inputTokens;
|
|
121
|
+
const output = usage.outputTokens;
|
|
122
|
+
if (isNested(input)) {
|
|
123
|
+
const out = isNested(output) ? output : void 0;
|
|
124
|
+
return {
|
|
125
|
+
cacheRead: input.cacheRead ?? 0,
|
|
126
|
+
cacheWrite: input.cacheWrite ?? 0,
|
|
127
|
+
reasoning: out?.reasoning ?? 0,
|
|
128
|
+
textInput: input.noCache,
|
|
129
|
+
totalInput: input.total,
|
|
130
|
+
textOutput: out?.text,
|
|
131
|
+
totalOutput: out?.total
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
cacheRead: usage.inputTokenDetails?.cacheReadTokens ?? usage.cachedInputTokens ?? 0,
|
|
136
|
+
cacheWrite: usage.inputTokenDetails?.cacheWriteTokens ?? 0,
|
|
137
|
+
reasoning: usage.outputTokenDetails?.reasoningTokens ?? usage.reasoningTokens ?? 0,
|
|
138
|
+
textInput: usage.inputTokenDetails?.noCacheTokens,
|
|
139
|
+
totalInput: typeof input === "number" ? input : flatCount(usage.promptTokens),
|
|
140
|
+
textOutput: usage.outputTokenDetails?.textTokens,
|
|
141
|
+
totalOutput: typeof output === "number" ? output : flatCount(usage.completionTokens)
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
var normalizeUsage = (usage, modelName) => poolsFromParts(toParts(usage), modelName);
|
|
145
|
+
|
|
146
|
+
// src/ai-sdk/index.ts
|
|
147
|
+
var withAutumn = ({
|
|
148
|
+
model,
|
|
149
|
+
providerId,
|
|
150
|
+
...tracking
|
|
151
|
+
}) => {
|
|
152
|
+
const modelName = `${providerId ?? model.provider}/${model.modelId}`;
|
|
153
|
+
const track = createTracker(tracking);
|
|
154
|
+
const trackUsage = (usage) => track(() => ({
|
|
155
|
+
pools: normalizeUsage(usage, modelName),
|
|
156
|
+
modelId: modelName
|
|
157
|
+
}));
|
|
158
|
+
const middleware = {
|
|
159
|
+
specificationVersion: "v3",
|
|
160
|
+
wrapGenerate: async ({ doGenerate }) => {
|
|
161
|
+
const result = await doGenerate();
|
|
162
|
+
await trackUsage(result.usage);
|
|
163
|
+
return result;
|
|
164
|
+
},
|
|
165
|
+
wrapStream: async ({ doStream }) => {
|
|
166
|
+
const { stream, ...rest } = await doStream();
|
|
167
|
+
let trackingPromise;
|
|
168
|
+
const transformStream = new TransformStream({
|
|
169
|
+
transform(chunk, controller) {
|
|
170
|
+
if (chunk.type === "finish" && chunk.usage) {
|
|
171
|
+
trackingPromise = trackUsage(chunk.usage);
|
|
172
|
+
}
|
|
173
|
+
controller.enqueue(chunk);
|
|
174
|
+
},
|
|
175
|
+
async flush() {
|
|
176
|
+
await trackingPromise;
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
return {
|
|
180
|
+
stream: stream.pipeThrough(transformStream),
|
|
181
|
+
...rest
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
return wrapLanguageModel({ model, middleware });
|
|
186
|
+
};
|
|
187
|
+
export {
|
|
188
|
+
withAutumn
|
|
189
|
+
};
|