the-token-company 0.1.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/README.md +156 -0
- package/dist/ai-sdk.d.ts +46 -0
- package/dist/ai-sdk.js +64 -0
- package/dist/anthropic.d.ts +30 -0
- package/dist/anthropic.js +49 -0
- package/dist/client.d.ts +17 -0
- package/dist/client.js +91 -0
- package/dist/compress.d.ts +68 -0
- package/dist/compress.js +149 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.js +44 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/openai.d.ts +30 -0
- package/dist/openai.js +42 -0
- package/dist/types.d.ts +51 -0
- package/dist/types.js +40 -0
- package/package.json +75 -0
package/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# The Token Company Node.js SDK
|
|
2
|
+
|
|
3
|
+
Compress LLM prompts to reduce costs and latency. 100K tokens compressed in ~85ms.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install the-token-company
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { TheTokenCompany } from "the-token-company";
|
|
15
|
+
|
|
16
|
+
const client = new TheTokenCompany({ apiKey: "ttc-..." });
|
|
17
|
+
const result = await client.compress("Your long prompt text here...", { model: "bear-2" });
|
|
18
|
+
|
|
19
|
+
console.log(result.output); // compressed text
|
|
20
|
+
console.log(result.tokensSaved); // tokens removed
|
|
21
|
+
console.log(result.compressionRatio); // e.g. 1.8
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## SDK wrappers
|
|
25
|
+
|
|
26
|
+
Drop-in wrappers that auto-compress all non-assistant messages before sending to your LLM. Assistant messages pass through unchanged so the provider's KV cache stays warm.
|
|
27
|
+
|
|
28
|
+
### OpenAI / OpenRouter
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import OpenAI from "openai";
|
|
32
|
+
import { withCompression } from "the-token-company/openai";
|
|
33
|
+
|
|
34
|
+
const client = withCompression(new OpenAI(), { compressionApiKey: "ttc-..." });
|
|
35
|
+
|
|
36
|
+
const response = await client.chat.completions.create({
|
|
37
|
+
model: "gpt-4o",
|
|
38
|
+
messages: [
|
|
39
|
+
{ role: "system", content: "You are a helpful assistant..." },
|
|
40
|
+
{ role: "user", content: "Summarize these results..." },
|
|
41
|
+
],
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
For OpenRouter, just set the base URL:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
const client = withCompression(
|
|
49
|
+
new OpenAI({ baseURL: "https://openrouter.ai/api/v1", apiKey: "or-..." }),
|
|
50
|
+
{ compressionApiKey: "ttc-..." }
|
|
51
|
+
);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Anthropic
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
58
|
+
import { withCompression } from "the-token-company/anthropic";
|
|
59
|
+
|
|
60
|
+
const client = withCompression(new Anthropic(), { compressionApiKey: "ttc-..." });
|
|
61
|
+
|
|
62
|
+
const response = await client.messages.create({
|
|
63
|
+
model: "claude-sonnet-4-6",
|
|
64
|
+
max_tokens: 1024,
|
|
65
|
+
system: "You are a helpful assistant...",
|
|
66
|
+
messages: [{ role: "user", content: "Summarize these results..." }],
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Both `messages` and the `system` parameter are compressed.
|
|
71
|
+
|
|
72
|
+
### Vercel AI SDK
|
|
73
|
+
|
|
74
|
+
**`withCompression()` one-liner** — wraps any AI SDK model with automatic compression:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { openai } from "@ai-sdk/openai";
|
|
78
|
+
import { generateText } from "ai";
|
|
79
|
+
import { withCompression } from "the-token-company/ai-sdk";
|
|
80
|
+
|
|
81
|
+
const model = withCompression(openai("gpt-4o"), { compressionApiKey: "ttc-..." });
|
|
82
|
+
|
|
83
|
+
const { text } = await generateText({
|
|
84
|
+
model,
|
|
85
|
+
messages: [{ role: "user", content: "Summarize these results..." }],
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Works with any provider (`@ai-sdk/openai`, `@ai-sdk/anthropic`, `@ai-sdk/google`, etc.).
|
|
90
|
+
|
|
91
|
+
**`compressionMiddleware()` for composition** — use when combining with other middleware:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { wrapLanguageModel, generateText } from "ai";
|
|
95
|
+
import { openai } from "@ai-sdk/openai";
|
|
96
|
+
import { compressionMiddleware } from "the-token-company/ai-sdk";
|
|
97
|
+
|
|
98
|
+
const model = wrapLanguageModel({
|
|
99
|
+
model: openai("gpt-4o"),
|
|
100
|
+
middleware: compressionMiddleware({ compressionApiKey: "ttc-..." }),
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Models
|
|
105
|
+
|
|
106
|
+
| Model | Description |
|
|
107
|
+
|------------|------------------------|
|
|
108
|
+
| `bear-2` | Latest, recommended |
|
|
109
|
+
| `bear-1.2` | Previous generation |
|
|
110
|
+
| `bear-1.1` | Legacy |
|
|
111
|
+
| `bear-1` | Legacy |
|
|
112
|
+
|
|
113
|
+
## Aggressiveness
|
|
114
|
+
|
|
115
|
+
Control compression intensity — a single number applies to all roles, or pass a per-role object:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
// All roles at 0.5
|
|
119
|
+
withCompression(client, { compressionApiKey: "ttc-...", aggressiveness: 0.5 });
|
|
120
|
+
|
|
121
|
+
// Per-role — only listed roles are compressed
|
|
122
|
+
withCompression(client, {
|
|
123
|
+
compressionApiKey: "ttc-...",
|
|
124
|
+
aggressiveness: { system: 0.1, user: 0.3, tool: 0.5 },
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
| Role key | OpenAI | Anthropic | AI SDK |
|
|
129
|
+
|------------|---------------------------------|--------------------------------|---------------------|
|
|
130
|
+
| `user` | `role: "user"` messages | User text content | User messages |
|
|
131
|
+
| `system` | `role: "system"` messages | `system` parameter | System messages |
|
|
132
|
+
| `tool` | `tool` + `function` messages | `tool_result` content blocks | Tool result parts |
|
|
133
|
+
|
|
134
|
+
## Gzip
|
|
135
|
+
|
|
136
|
+
Gzip compression of request payloads is on by default. Disable with:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
const client = new TheTokenCompany({ apiKey: "ttc-...", gzip: false });
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Response
|
|
143
|
+
|
|
144
|
+
`CompressResult` fields:
|
|
145
|
+
|
|
146
|
+
| Field | Type | Description |
|
|
147
|
+
|--------------------|----------|------------------------------------|
|
|
148
|
+
| `output` | `string` | Compressed text |
|
|
149
|
+
| `outputTokens` | `number` | Token count after compression |
|
|
150
|
+
| `inputTokens` | `number` | Token count before compression |
|
|
151
|
+
| `tokensSaved` | `number` | Tokens removed |
|
|
152
|
+
| `compressionRatio` | `number` | Ratio (e.g. 1.8x) |
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT
|
package/dist/ai-sdk.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { LanguageModelMiddleware } from "ai";
|
|
2
|
+
import type { LanguageModelV3 } from "@ai-sdk/provider";
|
|
3
|
+
import type { WithCompressionOptions } from "./types.js";
|
|
4
|
+
import { CompressionStats } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Create a Vercel AI SDK middleware that auto-compresses non-assistant messages.
|
|
7
|
+
*
|
|
8
|
+
* Use when composing with other middleware:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { wrapLanguageModel } from "ai";
|
|
12
|
+
* import { openai } from "@ai-sdk/openai";
|
|
13
|
+
* import { compressionMiddleware } from "the-token-company/ai-sdk";
|
|
14
|
+
*
|
|
15
|
+
* const { middleware, compression } = compressionMiddleware({ compressionApiKey: "ttc-..." });
|
|
16
|
+
* const model = wrapLanguageModel({ model: openai("gpt-4o"), middleware });
|
|
17
|
+
* // After calls: compression.totalTokensSaved
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function compressionMiddleware(options: WithCompressionOptions): {
|
|
21
|
+
middleware: LanguageModelMiddleware;
|
|
22
|
+
compression: CompressionStats;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Wrap any Vercel AI SDK language model with automatic compression.
|
|
26
|
+
*
|
|
27
|
+
* ```ts
|
|
28
|
+
* import { openai } from "@ai-sdk/openai";
|
|
29
|
+
* import { generateText } from "ai";
|
|
30
|
+
* import { withCompression } from "the-token-company/ai-sdk";
|
|
31
|
+
*
|
|
32
|
+
* const { model, compression } = withCompression(openai("gpt-4o"), { compressionApiKey: "ttc-..." });
|
|
33
|
+
*
|
|
34
|
+
* const { text } = await generateText({
|
|
35
|
+
* model,
|
|
36
|
+
* messages: [{ role: "user", content: "Summarize these results..." }],
|
|
37
|
+
* });
|
|
38
|
+
* console.log(compression.totalTokensSaved);
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* Works with any provider (`@ai-sdk/openai`, `@ai-sdk/anthropic`, `@ai-sdk/google`, etc.).
|
|
42
|
+
*/
|
|
43
|
+
export declare function withCompression(model: LanguageModelV3, options: WithCompressionOptions): {
|
|
44
|
+
model: LanguageModelV3;
|
|
45
|
+
compression: CompressionStats;
|
|
46
|
+
};
|
package/dist/ai-sdk.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { wrapLanguageModel } from "ai";
|
|
2
|
+
import { TheTokenCompany } from "./client.js";
|
|
3
|
+
import { AnalyticsTTC, compressAISDKPrompt, resolveAggressiveness } from "./compress.js";
|
|
4
|
+
import { CompressionStats } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Create a Vercel AI SDK middleware that auto-compresses non-assistant messages.
|
|
7
|
+
*
|
|
8
|
+
* Use when composing with other middleware:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { wrapLanguageModel } from "ai";
|
|
12
|
+
* import { openai } from "@ai-sdk/openai";
|
|
13
|
+
* import { compressionMiddleware } from "the-token-company/ai-sdk";
|
|
14
|
+
*
|
|
15
|
+
* const { middleware, compression } = compressionMiddleware({ compressionApiKey: "ttc-..." });
|
|
16
|
+
* const model = wrapLanguageModel({ model: openai("gpt-4o"), middleware });
|
|
17
|
+
* // After calls: compression.totalTokensSaved
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function compressionMiddleware(options) {
|
|
21
|
+
const stats = new CompressionStats();
|
|
22
|
+
const analytics = new AnalyticsTTC(new TheTokenCompany({ apiKey: options.compressionApiKey }), stats);
|
|
23
|
+
const compressionModel = options.model ?? "bear-2";
|
|
24
|
+
const roleAggr = resolveAggressiveness(options.aggressiveness ?? 0.2);
|
|
25
|
+
const middleware = {
|
|
26
|
+
specificationVersion: "v3",
|
|
27
|
+
transformParams: async ({ params }) => {
|
|
28
|
+
if (params.prompt) {
|
|
29
|
+
stats._startTurn();
|
|
30
|
+
const compressed = await compressAISDKPrompt(analytics, params.prompt, compressionModel, roleAggr);
|
|
31
|
+
stats._endTurn();
|
|
32
|
+
return { ...params, prompt: compressed };
|
|
33
|
+
}
|
|
34
|
+
return params;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
return { middleware, compression: stats };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Wrap any Vercel AI SDK language model with automatic compression.
|
|
41
|
+
*
|
|
42
|
+
* ```ts
|
|
43
|
+
* import { openai } from "@ai-sdk/openai";
|
|
44
|
+
* import { generateText } from "ai";
|
|
45
|
+
* import { withCompression } from "the-token-company/ai-sdk";
|
|
46
|
+
*
|
|
47
|
+
* const { model, compression } = withCompression(openai("gpt-4o"), { compressionApiKey: "ttc-..." });
|
|
48
|
+
*
|
|
49
|
+
* const { text } = await generateText({
|
|
50
|
+
* model,
|
|
51
|
+
* messages: [{ role: "user", content: "Summarize these results..." }],
|
|
52
|
+
* });
|
|
53
|
+
* console.log(compression.totalTokensSaved);
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* Works with any provider (`@ai-sdk/openai`, `@ai-sdk/anthropic`, `@ai-sdk/google`, etc.).
|
|
57
|
+
*/
|
|
58
|
+
export function withCompression(model, options) {
|
|
59
|
+
const { middleware, compression } = compressionMiddleware(options);
|
|
60
|
+
return {
|
|
61
|
+
model: wrapLanguageModel({ model, middleware }),
|
|
62
|
+
compression,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { WithCompressionOptions } from "./types.js";
|
|
2
|
+
import { CompressionStats } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Wrap an Anthropic client to auto-compress non-assistant messages.
|
|
5
|
+
*
|
|
6
|
+
* Compresses the `system` parameter and all non-assistant messages.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* import Anthropic from "@anthropic-ai/sdk";
|
|
10
|
+
* import { withCompression } from "the-token-company/anthropic";
|
|
11
|
+
*
|
|
12
|
+
* const client = withCompression(new Anthropic(), { compressionApiKey: "ttc-..." });
|
|
13
|
+
* const response = await client.messages.create({
|
|
14
|
+
* model: "claude-sonnet-4-6",
|
|
15
|
+
* max_tokens: 1024,
|
|
16
|
+
* system: "You are a helpful assistant...",
|
|
17
|
+
* messages: [{ role: "user", content: "..." }],
|
|
18
|
+
* });
|
|
19
|
+
* console.log(client.compression.totalTokensSaved);
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Assistant messages pass through unchanged to preserve the provider's KV cache.
|
|
23
|
+
*/
|
|
24
|
+
export declare function withCompression<T extends {
|
|
25
|
+
messages: {
|
|
26
|
+
create: Function;
|
|
27
|
+
};
|
|
28
|
+
}>(client: T, options: WithCompressionOptions): T & {
|
|
29
|
+
compression: CompressionStats;
|
|
30
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { TheTokenCompany } from "./client.js";
|
|
2
|
+
import { AnalyticsTTC, compressAnthropicMessages, resolveAggressiveness } from "./compress.js";
|
|
3
|
+
import { CompressionStats } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Wrap an Anthropic client to auto-compress non-assistant messages.
|
|
6
|
+
*
|
|
7
|
+
* Compresses the `system` parameter and all non-assistant messages.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import Anthropic from "@anthropic-ai/sdk";
|
|
11
|
+
* import { withCompression } from "the-token-company/anthropic";
|
|
12
|
+
*
|
|
13
|
+
* const client = withCompression(new Anthropic(), { compressionApiKey: "ttc-..." });
|
|
14
|
+
* const response = await client.messages.create({
|
|
15
|
+
* model: "claude-sonnet-4-6",
|
|
16
|
+
* max_tokens: 1024,
|
|
17
|
+
* system: "You are a helpful assistant...",
|
|
18
|
+
* messages: [{ role: "user", content: "..." }],
|
|
19
|
+
* });
|
|
20
|
+
* console.log(client.compression.totalTokensSaved);
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* Assistant messages pass through unchanged to preserve the provider's KV cache.
|
|
24
|
+
*/
|
|
25
|
+
export function withCompression(client, options) {
|
|
26
|
+
const stats = new CompressionStats();
|
|
27
|
+
const analytics = new AnalyticsTTC(new TheTokenCompany({ apiKey: options.compressionApiKey }), stats);
|
|
28
|
+
const model = options.model ?? "bear-2";
|
|
29
|
+
const roleAggr = resolveAggressiveness(options.aggressiveness ?? 0.2);
|
|
30
|
+
const systemAggr = roleAggr["system"];
|
|
31
|
+
const originalCreate = client.messages.create.bind(client.messages);
|
|
32
|
+
client.messages.create = async function (params, ...rest) {
|
|
33
|
+
stats._startTurn();
|
|
34
|
+
if (params?.messages) {
|
|
35
|
+
params = {
|
|
36
|
+
...params,
|
|
37
|
+
messages: await compressAnthropicMessages(analytics, params.messages, model, roleAggr),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (systemAggr != null && typeof params?.system === "string" && params.system.trim()) {
|
|
41
|
+
const result = await analytics.compress(params.system, { model, aggressiveness: systemAggr });
|
|
42
|
+
params = { ...params, system: result.output };
|
|
43
|
+
}
|
|
44
|
+
stats._endTurn();
|
|
45
|
+
return originalCreate(params, ...rest);
|
|
46
|
+
};
|
|
47
|
+
client.compression = stats;
|
|
48
|
+
return client;
|
|
49
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CompressResult, TheTokenCompanyOptions } from "./types.js";
|
|
2
|
+
export declare const BEAR_1 = "bear-1";
|
|
3
|
+
export declare const BEAR_1_1 = "bear-1.1";
|
|
4
|
+
export declare const BEAR_1_2 = "bear-1.2";
|
|
5
|
+
export declare const BEAR_2 = "bear-2";
|
|
6
|
+
export declare class TheTokenCompany {
|
|
7
|
+
private readonly baseUrl;
|
|
8
|
+
private readonly apiKey;
|
|
9
|
+
private readonly timeout;
|
|
10
|
+
private readonly gzip;
|
|
11
|
+
constructor(options: TheTokenCompanyOptions);
|
|
12
|
+
compress(text: string, options?: {
|
|
13
|
+
model?: string;
|
|
14
|
+
aggressiveness?: number;
|
|
15
|
+
}): Promise<CompressResult>;
|
|
16
|
+
private parseError;
|
|
17
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { gzip } from "node:zlib";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { APIError, AuthenticationError, InvalidRequestError, PaymentRequiredError, RateLimitError, RequestTooLargeError, } from "./errors.js";
|
|
4
|
+
const gzipAsync = promisify(gzip);
|
|
5
|
+
const BASE_URL = "https://api.thetokencompany.com";
|
|
6
|
+
const DEFAULT_TIMEOUT = 30_000;
|
|
7
|
+
export const BEAR_1 = "bear-1";
|
|
8
|
+
export const BEAR_1_1 = "bear-1.1";
|
|
9
|
+
export const BEAR_1_2 = "bear-1.2";
|
|
10
|
+
export const BEAR_2 = "bear-2";
|
|
11
|
+
export class TheTokenCompany {
|
|
12
|
+
baseUrl;
|
|
13
|
+
apiKey;
|
|
14
|
+
timeout;
|
|
15
|
+
gzip;
|
|
16
|
+
constructor(options) {
|
|
17
|
+
this.apiKey = options.apiKey;
|
|
18
|
+
this.baseUrl = (options.baseUrl ?? BASE_URL).replace(/\/$/, "");
|
|
19
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
20
|
+
this.gzip = options.gzip ?? true;
|
|
21
|
+
}
|
|
22
|
+
async compress(text, options = {}) {
|
|
23
|
+
const { model = "bear-2", aggressiveness = 0.2 } = options;
|
|
24
|
+
if (!text || !text.trim()) {
|
|
25
|
+
throw new InvalidRequestError("text cannot be empty");
|
|
26
|
+
}
|
|
27
|
+
if (aggressiveness < 0 || aggressiveness > 1) {
|
|
28
|
+
throw new InvalidRequestError("aggressiveness must be between 0.0 and 1.0");
|
|
29
|
+
}
|
|
30
|
+
const payload = {
|
|
31
|
+
model,
|
|
32
|
+
input: text,
|
|
33
|
+
compression_settings: { aggressiveness },
|
|
34
|
+
};
|
|
35
|
+
const jsonBody = JSON.stringify(payload);
|
|
36
|
+
const headers = {
|
|
37
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
38
|
+
"Content-Type": "application/json",
|
|
39
|
+
};
|
|
40
|
+
let body;
|
|
41
|
+
if (this.gzip) {
|
|
42
|
+
headers["Content-Encoding"] = "gzip";
|
|
43
|
+
body = new Uint8Array(await gzipAsync(jsonBody));
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
body = jsonBody;
|
|
47
|
+
}
|
|
48
|
+
const response = await fetch(`${this.baseUrl}/v1/compress`, {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers,
|
|
51
|
+
body: body,
|
|
52
|
+
signal: AbortSignal.timeout(this.timeout),
|
|
53
|
+
});
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
throw await this.parseError(response);
|
|
56
|
+
}
|
|
57
|
+
const data = (await response.json());
|
|
58
|
+
return {
|
|
59
|
+
output: data.output,
|
|
60
|
+
outputTokens: data.output_tokens,
|
|
61
|
+
inputTokens: data.original_input_tokens,
|
|
62
|
+
tokensSaved: data.original_input_tokens - data.output_tokens,
|
|
63
|
+
compressionRatio: data.output_tokens === 0 ? 0 : data.original_input_tokens / data.output_tokens,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async parseError(response) {
|
|
67
|
+
let msg;
|
|
68
|
+
try {
|
|
69
|
+
const body = (await response.json());
|
|
70
|
+
const detail = body.detail ?? "Unknown error";
|
|
71
|
+
msg = typeof detail === "object" ? (detail.message ?? JSON.stringify(detail)) : detail;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
msg = (await response.text()) || "Unknown error";
|
|
75
|
+
}
|
|
76
|
+
switch (response.status) {
|
|
77
|
+
case 401:
|
|
78
|
+
return new AuthenticationError(msg);
|
|
79
|
+
case 402:
|
|
80
|
+
return new PaymentRequiredError(msg);
|
|
81
|
+
case 413:
|
|
82
|
+
return new RequestTooLargeError(msg);
|
|
83
|
+
case 429:
|
|
84
|
+
return new RateLimitError(msg);
|
|
85
|
+
case 400:
|
|
86
|
+
return new InvalidRequestError(msg);
|
|
87
|
+
default:
|
|
88
|
+
return new APIError(`API error (${response.status}): ${msg}`, response.status);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { TheTokenCompany } from "./client.js";
|
|
2
|
+
import type { Aggressiveness, CompressResult } from "./types.js";
|
|
3
|
+
import { CompressionStats } from "./types.js";
|
|
4
|
+
export interface Compressor {
|
|
5
|
+
compress(text: string, options: {
|
|
6
|
+
model?: string;
|
|
7
|
+
aggressiveness?: number;
|
|
8
|
+
}): Promise<CompressResult>;
|
|
9
|
+
}
|
|
10
|
+
export declare class AnalyticsTTC implements Compressor {
|
|
11
|
+
private readonly inner;
|
|
12
|
+
readonly stats: CompressionStats;
|
|
13
|
+
constructor(inner: TheTokenCompany, stats: CompressionStats);
|
|
14
|
+
compress(text: string, options: {
|
|
15
|
+
model?: string;
|
|
16
|
+
aggressiveness?: number;
|
|
17
|
+
}): Promise<CompressResult>;
|
|
18
|
+
}
|
|
19
|
+
export declare function resolveAggressiveness(aggressiveness: Aggressiveness): Record<string, number>;
|
|
20
|
+
interface OpenAIMessage {
|
|
21
|
+
role: string;
|
|
22
|
+
content?: string | Array<{
|
|
23
|
+
type: string;
|
|
24
|
+
text?: string;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}> | null;
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
export declare function compressOpenAIMessages(ttc: Compressor, messages: OpenAIMessage[], model: string, roleAggr: Record<string, number>): Promise<OpenAIMessage[]>;
|
|
30
|
+
interface AnthropicBlock {
|
|
31
|
+
type: string;
|
|
32
|
+
text?: string;
|
|
33
|
+
content?: string | Array<{
|
|
34
|
+
type: string;
|
|
35
|
+
text?: string;
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
}>;
|
|
38
|
+
[key: string]: unknown;
|
|
39
|
+
}
|
|
40
|
+
interface AnthropicMessage {
|
|
41
|
+
role: string;
|
|
42
|
+
content?: string | AnthropicBlock[] | null;
|
|
43
|
+
[key: string]: unknown;
|
|
44
|
+
}
|
|
45
|
+
export declare function compressAnthropicMessages(ttc: Compressor, messages: AnthropicMessage[], model: string, roleAggr: Record<string, number>): Promise<AnthropicMessage[]>;
|
|
46
|
+
interface AISDKTextPart {
|
|
47
|
+
type: "text";
|
|
48
|
+
text: string;
|
|
49
|
+
[key: string]: unknown;
|
|
50
|
+
}
|
|
51
|
+
interface AISDKToolResultPart {
|
|
52
|
+
type: "tool-result";
|
|
53
|
+
toolCallId: string;
|
|
54
|
+
toolName: string;
|
|
55
|
+
result: unknown;
|
|
56
|
+
[key: string]: unknown;
|
|
57
|
+
}
|
|
58
|
+
type AISDKPart = AISDKTextPart | AISDKToolResultPart | {
|
|
59
|
+
type: string;
|
|
60
|
+
[key: string]: unknown;
|
|
61
|
+
};
|
|
62
|
+
interface AISDKMessage {
|
|
63
|
+
role: "system" | "user" | "assistant" | "tool";
|
|
64
|
+
content: string | AISDKPart[];
|
|
65
|
+
[key: string]: unknown;
|
|
66
|
+
}
|
|
67
|
+
export declare function compressAISDKPrompt(ttc: Compressor, prompt: AISDKMessage[], model: string, roleAggr: Record<string, number>): Promise<AISDKMessage[]>;
|
|
68
|
+
export {};
|
package/dist/compress.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
const DEFAULT_ROLES = ["user", "system", "tool"];
|
|
2
|
+
export class AnalyticsTTC {
|
|
3
|
+
inner;
|
|
4
|
+
stats;
|
|
5
|
+
constructor(inner, stats) {
|
|
6
|
+
this.inner = inner;
|
|
7
|
+
this.stats = stats;
|
|
8
|
+
}
|
|
9
|
+
async compress(text, options) {
|
|
10
|
+
const result = await this.inner.compress(text, options);
|
|
11
|
+
this.stats._record(result);
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function resolveAggressiveness(aggressiveness) {
|
|
16
|
+
if (typeof aggressiveness === "number") {
|
|
17
|
+
return Object.fromEntries(DEFAULT_ROLES.map((r) => [r, aggressiveness]));
|
|
18
|
+
}
|
|
19
|
+
return aggressiveness;
|
|
20
|
+
}
|
|
21
|
+
const OPENAI_TOOL_ROLES = new Set(["tool", "function"]);
|
|
22
|
+
function openaiAggr(role, roleAggr) {
|
|
23
|
+
if (OPENAI_TOOL_ROLES.has(role))
|
|
24
|
+
return roleAggr["tool"];
|
|
25
|
+
return roleAggr[role];
|
|
26
|
+
}
|
|
27
|
+
export async function compressOpenAIMessages(ttc, messages, model, roleAggr) {
|
|
28
|
+
return Promise.all(messages.map(async (msg) => {
|
|
29
|
+
const aggr = openaiAggr(msg.role, roleAggr);
|
|
30
|
+
if (aggr == null)
|
|
31
|
+
return msg;
|
|
32
|
+
if (typeof msg.content === "string" && msg.content.trim()) {
|
|
33
|
+
const result = await ttc.compress(msg.content, { model, aggressiveness: aggr });
|
|
34
|
+
return { ...msg, content: result.output };
|
|
35
|
+
}
|
|
36
|
+
if (Array.isArray(msg.content)) {
|
|
37
|
+
const blocks = await Promise.all(msg.content.map(async (block) => {
|
|
38
|
+
if (block.type === "text" && typeof block.text === "string" && block.text.trim()) {
|
|
39
|
+
const result = await ttc.compress(block.text, { model, aggressiveness: aggr });
|
|
40
|
+
return { ...block, text: result.output };
|
|
41
|
+
}
|
|
42
|
+
return block;
|
|
43
|
+
}));
|
|
44
|
+
return { ...msg, content: blocks };
|
|
45
|
+
}
|
|
46
|
+
return msg;
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
export async function compressAnthropicMessages(ttc, messages, model, roleAggr) {
|
|
50
|
+
return Promise.all(messages.map(async (msg) => {
|
|
51
|
+
if (msg.role !== "user")
|
|
52
|
+
return msg;
|
|
53
|
+
const userAggr = roleAggr["user"];
|
|
54
|
+
const toolAggr = roleAggr["tool"];
|
|
55
|
+
if (userAggr == null && toolAggr == null)
|
|
56
|
+
return msg;
|
|
57
|
+
if (typeof msg.content === "string" && userAggr != null && msg.content.trim()) {
|
|
58
|
+
const result = await ttc.compress(msg.content, { model, aggressiveness: userAggr });
|
|
59
|
+
return { ...msg, content: result.output };
|
|
60
|
+
}
|
|
61
|
+
if (Array.isArray(msg.content)) {
|
|
62
|
+
const blocks = await compressAnthropicBlocks(ttc, msg.content, model, userAggr, toolAggr);
|
|
63
|
+
return { ...msg, content: blocks };
|
|
64
|
+
}
|
|
65
|
+
return msg;
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
async function compressAnthropicBlocks(ttc, blocks, model, userAggr, toolAggr) {
|
|
69
|
+
return Promise.all(blocks.map(async (block) => {
|
|
70
|
+
if (block.type === "text" && userAggr != null && typeof block.text === "string" && block.text.trim()) {
|
|
71
|
+
const result = await ttc.compress(block.text, { model, aggressiveness: userAggr });
|
|
72
|
+
return { ...block, text: result.output };
|
|
73
|
+
}
|
|
74
|
+
if (block.type === "tool_result" && toolAggr != null) {
|
|
75
|
+
return compressToolResult(ttc, block, model, toolAggr);
|
|
76
|
+
}
|
|
77
|
+
return block;
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
async function compressToolResult(ttc, block, model, aggressiveness) {
|
|
81
|
+
if (typeof block.content === "string" && block.content.trim()) {
|
|
82
|
+
const result = await ttc.compress(block.content, { model, aggressiveness });
|
|
83
|
+
return { ...block, content: result.output };
|
|
84
|
+
}
|
|
85
|
+
if (Array.isArray(block.content)) {
|
|
86
|
+
const compressed = await Promise.all(block.content.map(async (inner) => {
|
|
87
|
+
if (inner.type === "text" && typeof inner.text === "string" && inner.text.trim()) {
|
|
88
|
+
const result = await ttc.compress(inner.text, { model, aggressiveness });
|
|
89
|
+
return { ...inner, text: result.output };
|
|
90
|
+
}
|
|
91
|
+
return inner;
|
|
92
|
+
}));
|
|
93
|
+
return { ...block, content: compressed };
|
|
94
|
+
}
|
|
95
|
+
return block;
|
|
96
|
+
}
|
|
97
|
+
export async function compressAISDKPrompt(ttc, prompt, model, roleAggr) {
|
|
98
|
+
return Promise.all(prompt.map(async (msg) => {
|
|
99
|
+
if (msg.role === "assistant")
|
|
100
|
+
return msg;
|
|
101
|
+
if (msg.role === "system") {
|
|
102
|
+
const aggr = roleAggr["system"];
|
|
103
|
+
if (aggr == null)
|
|
104
|
+
return msg;
|
|
105
|
+
if (typeof msg.content === "string" && msg.content.trim()) {
|
|
106
|
+
const result = await ttc.compress(msg.content, { model, aggressiveness: aggr });
|
|
107
|
+
return { ...msg, content: result.output };
|
|
108
|
+
}
|
|
109
|
+
return msg;
|
|
110
|
+
}
|
|
111
|
+
if (msg.role === "user") {
|
|
112
|
+
const userAggr = roleAggr["user"];
|
|
113
|
+
if (userAggr == null)
|
|
114
|
+
return msg;
|
|
115
|
+
if (typeof msg.content === "string" && msg.content.trim()) {
|
|
116
|
+
const result = await ttc.compress(msg.content, { model, aggressiveness: userAggr });
|
|
117
|
+
return { ...msg, content: result.output };
|
|
118
|
+
}
|
|
119
|
+
if (Array.isArray(msg.content)) {
|
|
120
|
+
const parts = await Promise.all(msg.content.map(async (part) => {
|
|
121
|
+
if (part.type === "text" && "text" in part && typeof part.text === "string" && part.text.trim()) {
|
|
122
|
+
const result = await ttc.compress(part.text, { model, aggressiveness: userAggr });
|
|
123
|
+
return { ...part, text: result.output };
|
|
124
|
+
}
|
|
125
|
+
return part;
|
|
126
|
+
}));
|
|
127
|
+
return { ...msg, content: parts };
|
|
128
|
+
}
|
|
129
|
+
return msg;
|
|
130
|
+
}
|
|
131
|
+
if (msg.role === "tool") {
|
|
132
|
+
const toolAggr = roleAggr["tool"];
|
|
133
|
+
if (toolAggr == null)
|
|
134
|
+
return msg;
|
|
135
|
+
if (Array.isArray(msg.content)) {
|
|
136
|
+
const parts = await Promise.all(msg.content.map(async (part) => {
|
|
137
|
+
if (part.type === "tool-result" && "result" in part && typeof part.result === "string" && part.result.trim()) {
|
|
138
|
+
const result = await ttc.compress(part.result, { model, aggressiveness: toolAggr });
|
|
139
|
+
return { ...part, result: result.output };
|
|
140
|
+
}
|
|
141
|
+
return part;
|
|
142
|
+
}));
|
|
143
|
+
return { ...msg, content: parts };
|
|
144
|
+
}
|
|
145
|
+
return msg;
|
|
146
|
+
}
|
|
147
|
+
return msg;
|
|
148
|
+
}));
|
|
149
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare class TheTokenCompanyError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
export declare class AuthenticationError extends TheTokenCompanyError {
|
|
5
|
+
constructor(message: string);
|
|
6
|
+
}
|
|
7
|
+
export declare class InvalidRequestError extends TheTokenCompanyError {
|
|
8
|
+
constructor(message: string);
|
|
9
|
+
}
|
|
10
|
+
export declare class PaymentRequiredError extends TheTokenCompanyError {
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
export declare class RequestTooLargeError extends TheTokenCompanyError {
|
|
14
|
+
constructor(message: string);
|
|
15
|
+
}
|
|
16
|
+
export declare class RateLimitError extends TheTokenCompanyError {
|
|
17
|
+
constructor(message: string);
|
|
18
|
+
}
|
|
19
|
+
export declare class APIError extends TheTokenCompanyError {
|
|
20
|
+
statusCode: number | undefined;
|
|
21
|
+
constructor(message: string, statusCode?: number);
|
|
22
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export class TheTokenCompanyError extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = "TheTokenCompanyError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export class AuthenticationError extends TheTokenCompanyError {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "AuthenticationError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class InvalidRequestError extends TheTokenCompanyError {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "InvalidRequestError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class PaymentRequiredError extends TheTokenCompanyError {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "PaymentRequiredError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export class RequestTooLargeError extends TheTokenCompanyError {
|
|
26
|
+
constructor(message) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = "RequestTooLargeError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export class RateLimitError extends TheTokenCompanyError {
|
|
32
|
+
constructor(message) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = "RateLimitError";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export class APIError extends TheTokenCompanyError {
|
|
38
|
+
statusCode;
|
|
39
|
+
constructor(message, statusCode) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = "APIError";
|
|
42
|
+
this.statusCode = statusCode;
|
|
43
|
+
}
|
|
44
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { TheTokenCompany, BEAR_1, BEAR_1_1, BEAR_1_2, BEAR_2 } from "./client.js";
|
|
2
|
+
export { TheTokenCompanyError, AuthenticationError, InvalidRequestError, PaymentRequiredError, RequestTooLargeError, RateLimitError, APIError, } from "./errors.js";
|
|
3
|
+
export { CompressionStats } from "./types.js";
|
|
4
|
+
export type { CompressResult, TheTokenCompanyOptions, Aggressiveness, WithCompressionOptions, TurnStats, } from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { TheTokenCompany, BEAR_1, BEAR_1_1, BEAR_1_2, BEAR_2 } from "./client.js";
|
|
2
|
+
export { TheTokenCompanyError, AuthenticationError, InvalidRequestError, PaymentRequiredError, RequestTooLargeError, RateLimitError, APIError, } from "./errors.js";
|
|
3
|
+
export { CompressionStats } from "./types.js";
|
package/dist/openai.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { WithCompressionOptions } from "./types.js";
|
|
2
|
+
import { CompressionStats } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Wrap an OpenAI-compatible client to auto-compress non-assistant messages.
|
|
5
|
+
*
|
|
6
|
+
* Works with `OpenAI`, OpenRouter, and any OpenAI-compatible client.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* import OpenAI from "openai";
|
|
10
|
+
* import { withCompression } from "the-token-company/openai";
|
|
11
|
+
*
|
|
12
|
+
* const client = withCompression(new OpenAI(), { compressionApiKey: "ttc-..." });
|
|
13
|
+
* const response = await client.chat.completions.create({
|
|
14
|
+
* model: "gpt-4o",
|
|
15
|
+
* messages: [{ role: "user", content: "..." }],
|
|
16
|
+
* });
|
|
17
|
+
* console.log(client.compression.totalTokensSaved);
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* Assistant messages pass through unchanged to preserve the provider's KV cache.
|
|
21
|
+
*/
|
|
22
|
+
export declare function withCompression<T extends {
|
|
23
|
+
chat: {
|
|
24
|
+
completions: {
|
|
25
|
+
create: Function;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
}>(client: T, options: WithCompressionOptions): T & {
|
|
29
|
+
compression: CompressionStats;
|
|
30
|
+
};
|
package/dist/openai.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { TheTokenCompany } from "./client.js";
|
|
2
|
+
import { AnalyticsTTC, compressOpenAIMessages, resolveAggressiveness } from "./compress.js";
|
|
3
|
+
import { CompressionStats } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Wrap an OpenAI-compatible client to auto-compress non-assistant messages.
|
|
6
|
+
*
|
|
7
|
+
* Works with `OpenAI`, OpenRouter, and any OpenAI-compatible client.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import OpenAI from "openai";
|
|
11
|
+
* import { withCompression } from "the-token-company/openai";
|
|
12
|
+
*
|
|
13
|
+
* const client = withCompression(new OpenAI(), { compressionApiKey: "ttc-..." });
|
|
14
|
+
* const response = await client.chat.completions.create({
|
|
15
|
+
* model: "gpt-4o",
|
|
16
|
+
* messages: [{ role: "user", content: "..." }],
|
|
17
|
+
* });
|
|
18
|
+
* console.log(client.compression.totalTokensSaved);
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Assistant messages pass through unchanged to preserve the provider's KV cache.
|
|
22
|
+
*/
|
|
23
|
+
export function withCompression(client, options) {
|
|
24
|
+
const stats = new CompressionStats();
|
|
25
|
+
const analytics = new AnalyticsTTC(new TheTokenCompany({ apiKey: options.compressionApiKey }), stats);
|
|
26
|
+
const model = options.model ?? "bear-2";
|
|
27
|
+
const roleAggr = resolveAggressiveness(options.aggressiveness ?? 0.2);
|
|
28
|
+
const originalCreate = client.chat.completions.create.bind(client.chat.completions);
|
|
29
|
+
client.chat.completions.create = async function (params, ...rest) {
|
|
30
|
+
if (params?.messages) {
|
|
31
|
+
stats._startTurn();
|
|
32
|
+
params = {
|
|
33
|
+
...params,
|
|
34
|
+
messages: await compressOpenAIMessages(analytics, params.messages, model, roleAggr),
|
|
35
|
+
};
|
|
36
|
+
stats._endTurn();
|
|
37
|
+
}
|
|
38
|
+
return originalCreate(params, ...rest);
|
|
39
|
+
};
|
|
40
|
+
client.compression = stats;
|
|
41
|
+
return client;
|
|
42
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export interface CompressRequest {
|
|
2
|
+
model: string;
|
|
3
|
+
input: string;
|
|
4
|
+
compression_settings: {
|
|
5
|
+
aggressiveness: number;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export interface CompressResponse {
|
|
9
|
+
output: string;
|
|
10
|
+
output_tokens: number;
|
|
11
|
+
original_input_tokens: number;
|
|
12
|
+
}
|
|
13
|
+
export interface CompressResult {
|
|
14
|
+
output: string;
|
|
15
|
+
outputTokens: number;
|
|
16
|
+
inputTokens: number;
|
|
17
|
+
tokensSaved: number;
|
|
18
|
+
compressionRatio: number;
|
|
19
|
+
}
|
|
20
|
+
export interface TheTokenCompanyOptions {
|
|
21
|
+
apiKey: string;
|
|
22
|
+
baseUrl?: string;
|
|
23
|
+
timeout?: number;
|
|
24
|
+
gzip?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export type Aggressiveness = number | Record<string, number>;
|
|
27
|
+
export interface WithCompressionOptions {
|
|
28
|
+
compressionApiKey: string;
|
|
29
|
+
model?: string;
|
|
30
|
+
aggressiveness?: Aggressiveness;
|
|
31
|
+
}
|
|
32
|
+
export interface TurnStats {
|
|
33
|
+
inputTokens: number;
|
|
34
|
+
outputTokens: number;
|
|
35
|
+
tokensSaved: number;
|
|
36
|
+
messagesCompressed: number;
|
|
37
|
+
ratio: number;
|
|
38
|
+
timestamp: number;
|
|
39
|
+
}
|
|
40
|
+
export declare class CompressionStats {
|
|
41
|
+
history: TurnStats[];
|
|
42
|
+
private _accumulator;
|
|
43
|
+
_startTurn(): void;
|
|
44
|
+
_record(result: CompressResult): void;
|
|
45
|
+
_endTurn(): void;
|
|
46
|
+
get totalInputTokens(): number;
|
|
47
|
+
get totalOutputTokens(): number;
|
|
48
|
+
get totalTokensSaved(): number;
|
|
49
|
+
get calls(): number;
|
|
50
|
+
get ratio(): number;
|
|
51
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export class CompressionStats {
|
|
2
|
+
history = [];
|
|
3
|
+
_accumulator = [];
|
|
4
|
+
_startTurn() {
|
|
5
|
+
this._accumulator = [];
|
|
6
|
+
}
|
|
7
|
+
_record(result) {
|
|
8
|
+
this._accumulator.push(result);
|
|
9
|
+
}
|
|
10
|
+
_endTurn() {
|
|
11
|
+
if (this._accumulator.length > 0) {
|
|
12
|
+
const inputTokens = this._accumulator.reduce((s, r) => s + r.inputTokens, 0);
|
|
13
|
+
const outputTokens = this._accumulator.reduce((s, r) => s + r.outputTokens, 0);
|
|
14
|
+
this.history.push({
|
|
15
|
+
inputTokens,
|
|
16
|
+
outputTokens,
|
|
17
|
+
tokensSaved: inputTokens - outputTokens,
|
|
18
|
+
messagesCompressed: this._accumulator.length,
|
|
19
|
+
ratio: outputTokens === 0 ? 0 : inputTokens / outputTokens,
|
|
20
|
+
timestamp: Date.now(),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
this._accumulator = [];
|
|
24
|
+
}
|
|
25
|
+
get totalInputTokens() {
|
|
26
|
+
return this.history.reduce((s, t) => s + t.inputTokens, 0);
|
|
27
|
+
}
|
|
28
|
+
get totalOutputTokens() {
|
|
29
|
+
return this.history.reduce((s, t) => s + t.outputTokens, 0);
|
|
30
|
+
}
|
|
31
|
+
get totalTokensSaved() {
|
|
32
|
+
return this.history.reduce((s, t) => s + t.tokensSaved, 0);
|
|
33
|
+
}
|
|
34
|
+
get calls() {
|
|
35
|
+
return this.history.length;
|
|
36
|
+
}
|
|
37
|
+
get ratio() {
|
|
38
|
+
return this.totalOutputTokens === 0 ? 0 : this.totalInputTokens / this.totalOutputTokens;
|
|
39
|
+
}
|
|
40
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "the-token-company",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Node.js SDK for The Token Company — compress LLM prompts to reduce costs and latency",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./openai": {
|
|
14
|
+
"types": "./dist/openai.d.ts",
|
|
15
|
+
"import": "./dist/openai.js"
|
|
16
|
+
},
|
|
17
|
+
"./anthropic": {
|
|
18
|
+
"types": "./dist/anthropic.d.ts",
|
|
19
|
+
"import": "./dist/anthropic.js"
|
|
20
|
+
},
|
|
21
|
+
"./ai-sdk": {
|
|
22
|
+
"types": "./dist/ai-sdk.d.ts",
|
|
23
|
+
"import": "./dist/ai-sdk.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"test:watch": "vitest",
|
|
33
|
+
"lint": "tsc --noEmit"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"llm",
|
|
37
|
+
"compression",
|
|
38
|
+
"tokens",
|
|
39
|
+
"ai",
|
|
40
|
+
"prompt-optimization",
|
|
41
|
+
"openai",
|
|
42
|
+
"anthropic",
|
|
43
|
+
"vercel-ai-sdk"
|
|
44
|
+
],
|
|
45
|
+
"author": "The Token Company <support@thetokencompany.com>",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "https://github.com/TheTokenCompany/the-token-company-node"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://thetokencompany.com",
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@ai-sdk/provider": "^3.0.10",
|
|
54
|
+
"@types/node": "^22",
|
|
55
|
+
"ai": "^6.0.195",
|
|
56
|
+
"typescript": "^5.7",
|
|
57
|
+
"vitest": "^3"
|
|
58
|
+
},
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"@anthropic-ai/sdk": ">=0.30.0",
|
|
61
|
+
"ai": ">=4.0.0",
|
|
62
|
+
"openai": ">=4.0.0"
|
|
63
|
+
},
|
|
64
|
+
"peerDependenciesMeta": {
|
|
65
|
+
"ai": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"openai": {
|
|
69
|
+
"optional": true
|
|
70
|
+
},
|
|
71
|
+
"@anthropic-ai/sdk": {
|
|
72
|
+
"optional": true
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|