the-token-company 0.2.2 → 0.3.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/ai-sdk.js +1 -1
- package/dist/anthropic.js +79 -3
- package/dist/client.d.ts +3 -1
- package/dist/client.js +47 -1
- package/dist/compress.d.ts +3 -1
- package/dist/compress.js +31 -1
- package/dist/index.d.ts +1 -1
- package/dist/openai.js +1 -1
- package/dist/types.d.ts +31 -0
- package/package.json +1 -1
package/dist/ai-sdk.js
CHANGED
|
@@ -17,7 +17,7 @@ import { CompressionStats } from "./types.js";
|
|
|
17
17
|
*/
|
|
18
18
|
export function compressionMiddleware(options) {
|
|
19
19
|
const stats = new CompressionStats();
|
|
20
|
-
const compressor = new StatsTTC(new TheTokenCompany({ apiKey: options.compressionApiKey, appId: options.appId }), stats);
|
|
20
|
+
const compressor = new StatsTTC(new TheTokenCompany({ apiKey: options.compressionApiKey, appId: options.appId, fetch: options.fetch }), stats);
|
|
21
21
|
const compressionModel = options.model ?? "bear-2";
|
|
22
22
|
const roleAggr = resolveAggressiveness(options.aggressiveness ?? 0.2);
|
|
23
23
|
const middleware = {
|
package/dist/anthropic.js
CHANGED
|
@@ -1,6 +1,67 @@
|
|
|
1
1
|
import { TheTokenCompany } from "./client.js";
|
|
2
2
|
import { StatsTTC, compressAnthropicMessages, resolveAggressiveness } from "./compress.js";
|
|
3
3
|
import { CompressionStats } from "./types.js";
|
|
4
|
+
const TTC_SEARCH_TOOL = {
|
|
5
|
+
name: "ttc_web_search",
|
|
6
|
+
description: "Search the web for current information. Use this when you need up-to-date facts, prices, news, or any information that may have changed after your training cutoff.",
|
|
7
|
+
input_schema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
query: {
|
|
11
|
+
type: "string",
|
|
12
|
+
description: "The search query"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
required: ["query"]
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
function injectSearchTool(params) {
|
|
19
|
+
let tools = params.tools ? [...params.tools] : [];
|
|
20
|
+
tools = tools.filter((t) => !String(t.type ?? "").startsWith("web_search_"));
|
|
21
|
+
if (!tools.some((t) => t.name === "ttc_web_search")) {
|
|
22
|
+
tools.push(TTC_SEARCH_TOOL);
|
|
23
|
+
}
|
|
24
|
+
return { ...params, tools };
|
|
25
|
+
}
|
|
26
|
+
function hasSearchToolUse(response) {
|
|
27
|
+
if (response.stop_reason !== "tool_use")
|
|
28
|
+
return false;
|
|
29
|
+
return response.content?.some((b) => b.type === "tool_use" && b.name === "ttc_web_search");
|
|
30
|
+
}
|
|
31
|
+
function formatSearchResults(results) {
|
|
32
|
+
return results.map(r => `Source: ${r.title}\nURL: ${r.url}\n${r.content}`).join("\n\n");
|
|
33
|
+
}
|
|
34
|
+
async function handleSearchLoop(response, params, originalCreate, ttcClient, rest) {
|
|
35
|
+
while (hasSearchToolUse(response)) {
|
|
36
|
+
const messages = [...(params.messages || [])];
|
|
37
|
+
// Add assistant response
|
|
38
|
+
const assistantContent = response.content.map((b) => {
|
|
39
|
+
if (typeof b.toJSON === 'function')
|
|
40
|
+
return b.toJSON();
|
|
41
|
+
return b;
|
|
42
|
+
});
|
|
43
|
+
messages.push({ role: "assistant", content: assistantContent });
|
|
44
|
+
// Build tool results
|
|
45
|
+
const toolResults = [];
|
|
46
|
+
for (const block of response.content) {
|
|
47
|
+
if (block.type === "tool_use" && block.name === "ttc_web_search") {
|
|
48
|
+
const query = block.input?.query || "";
|
|
49
|
+
const searchResult = await ttcClient.search(query);
|
|
50
|
+
toolResults.push({
|
|
51
|
+
type: "tool_result",
|
|
52
|
+
tool_use_id: block.id,
|
|
53
|
+
content: formatSearchResults(searchResult.results),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (toolResults.length > 0) {
|
|
58
|
+
messages.push({ role: "user", content: toolResults });
|
|
59
|
+
}
|
|
60
|
+
const newParams = { ...params, messages };
|
|
61
|
+
response = await originalCreate(newParams, ...rest);
|
|
62
|
+
}
|
|
63
|
+
return response;
|
|
64
|
+
}
|
|
4
65
|
/**
|
|
5
66
|
* Wrap an Anthropic client to auto-compress non-assistant messages.
|
|
6
67
|
*
|
|
@@ -24,25 +85,40 @@ import { CompressionStats } from "./types.js";
|
|
|
24
85
|
*/
|
|
25
86
|
export function withCompression(client, options) {
|
|
26
87
|
const stats = new CompressionStats();
|
|
27
|
-
const
|
|
88
|
+
const ttcClient = new TheTokenCompany({ apiKey: options.compressionApiKey, baseUrl: options.baseUrl, appId: options.appId, fetch: options.fetch });
|
|
89
|
+
const compressor = new StatsTTC(ttcClient, stats);
|
|
28
90
|
const model = options.model ?? "bear-2";
|
|
29
91
|
const roleAggr = resolveAggressiveness(options.aggressiveness ?? 0.2);
|
|
92
|
+
if (options.compressAssistant && !("assistant" in roleAggr)) {
|
|
93
|
+
roleAggr["assistant"] = roleAggr["user"] ?? 0.2;
|
|
94
|
+
}
|
|
30
95
|
const systemAggr = roleAggr["system"];
|
|
96
|
+
const stripServerToolResults = options.stripServerToolResults ?? false;
|
|
97
|
+
const webSearch = options.webSearch ?? false;
|
|
31
98
|
const originalCreate = client.messages.create.bind(client.messages);
|
|
32
99
|
client.messages.create = async function (params, ...rest) {
|
|
33
100
|
stats._startTurn();
|
|
34
101
|
if (params?.messages) {
|
|
35
102
|
params = {
|
|
36
103
|
...params,
|
|
37
|
-
messages: await compressAnthropicMessages(compressor, params.messages, model, roleAggr),
|
|
104
|
+
messages: await compressAnthropicMessages(compressor, params.messages, model, roleAggr, { stripServerToolResults }),
|
|
38
105
|
};
|
|
39
106
|
}
|
|
40
107
|
if (systemAggr != null && typeof params?.system === "string" && params.system.trim()) {
|
|
41
108
|
const result = await compressor.compress(params.system, { model, aggressiveness: systemAggr });
|
|
42
109
|
params = { ...params, system: result.output };
|
|
43
110
|
}
|
|
111
|
+
// Inject search tool
|
|
112
|
+
if (webSearch) {
|
|
113
|
+
params = injectSearchTool(params);
|
|
114
|
+
}
|
|
44
115
|
stats._endTurn();
|
|
45
|
-
|
|
116
|
+
let response = await originalCreate(params, ...rest);
|
|
117
|
+
// Handle search tool loop
|
|
118
|
+
if (webSearch) {
|
|
119
|
+
response = await handleSearchLoop(response, params, originalCreate, ttcClient, rest);
|
|
120
|
+
}
|
|
121
|
+
return response;
|
|
46
122
|
};
|
|
47
123
|
client.compression = stats;
|
|
48
124
|
return client;
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CompressResult, TheTokenCompanyOptions } from "./types.js";
|
|
1
|
+
import type { CompressResult, SearchRequestOptions, SearchResult, TheTokenCompanyOptions } from "./types.js";
|
|
2
2
|
export declare const BEAR_1 = "bear-1";
|
|
3
3
|
export declare const BEAR_1_1 = "bear-1.1";
|
|
4
4
|
export declare const BEAR_1_2 = "bear-1.2";
|
|
@@ -9,11 +9,13 @@ export declare class TheTokenCompany {
|
|
|
9
9
|
private readonly timeout;
|
|
10
10
|
private readonly gzip;
|
|
11
11
|
private readonly appId?;
|
|
12
|
+
private readonly fetchFn;
|
|
12
13
|
constructor(options: TheTokenCompanyOptions);
|
|
13
14
|
compress(text: string, options?: {
|
|
14
15
|
model?: string;
|
|
15
16
|
aggressiveness?: number;
|
|
16
17
|
appId?: string;
|
|
17
18
|
}): Promise<CompressResult>;
|
|
19
|
+
search(query: string, options?: SearchRequestOptions): Promise<SearchResult>;
|
|
18
20
|
private parseError;
|
|
19
21
|
}
|
package/dist/client.js
CHANGED
|
@@ -14,12 +14,14 @@ export class TheTokenCompany {
|
|
|
14
14
|
timeout;
|
|
15
15
|
gzip;
|
|
16
16
|
appId;
|
|
17
|
+
fetchFn;
|
|
17
18
|
constructor(options) {
|
|
18
19
|
this.apiKey = options.apiKey;
|
|
19
20
|
this.baseUrl = (options.baseUrl ?? BASE_URL).replace(/\/$/, "");
|
|
20
21
|
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
21
22
|
this.gzip = options.gzip ?? true;
|
|
22
23
|
this.appId = options.appId;
|
|
24
|
+
this.fetchFn = options.fetch ?? globalThis.fetch;
|
|
23
25
|
}
|
|
24
26
|
async compress(text, options = {}) {
|
|
25
27
|
const { model = "bear-2", aggressiveness = 0.2 } = options;
|
|
@@ -49,7 +51,7 @@ export class TheTokenCompany {
|
|
|
49
51
|
else {
|
|
50
52
|
body = jsonBody;
|
|
51
53
|
}
|
|
52
|
-
const response = await
|
|
54
|
+
const response = await this.fetchFn(`${this.baseUrl}/v1/compress`, {
|
|
53
55
|
method: "POST",
|
|
54
56
|
headers,
|
|
55
57
|
body: body,
|
|
@@ -67,6 +69,50 @@ export class TheTokenCompany {
|
|
|
67
69
|
compressionRatio: data.output_tokens === 0 ? 0 : data.original_input_tokens / data.output_tokens,
|
|
68
70
|
};
|
|
69
71
|
}
|
|
72
|
+
async search(query, options = {}) {
|
|
73
|
+
const { maxResults = 5, searchDepth = "basic", includeRawContent = false, model = "bear-2", aggressiveness = 0.3, } = options;
|
|
74
|
+
const resolvedAppId = options.appId ?? this.appId;
|
|
75
|
+
const payload = {
|
|
76
|
+
query,
|
|
77
|
+
max_results: maxResults,
|
|
78
|
+
search_depth: searchDepth,
|
|
79
|
+
include_raw_content: includeRawContent,
|
|
80
|
+
model,
|
|
81
|
+
compression_settings: { aggressiveness },
|
|
82
|
+
...(resolvedAppId !== undefined && { app_id: resolvedAppId }),
|
|
83
|
+
};
|
|
84
|
+
const jsonBody = JSON.stringify(payload);
|
|
85
|
+
const headers = {
|
|
86
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
87
|
+
"Content-Type": "application/json",
|
|
88
|
+
};
|
|
89
|
+
let body;
|
|
90
|
+
if (this.gzip) {
|
|
91
|
+
headers["Content-Encoding"] = "gzip";
|
|
92
|
+
body = new Uint8Array(await gzipAsync(jsonBody));
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
body = jsonBody;
|
|
96
|
+
}
|
|
97
|
+
const response = await this.fetchFn(`${this.baseUrl}/v1/search`, {
|
|
98
|
+
method: "POST",
|
|
99
|
+
headers,
|
|
100
|
+
body: body,
|
|
101
|
+
signal: AbortSignal.timeout(this.timeout),
|
|
102
|
+
});
|
|
103
|
+
if (!response.ok) {
|
|
104
|
+
throw await this.parseError(response);
|
|
105
|
+
}
|
|
106
|
+
const data = (await response.json());
|
|
107
|
+
return {
|
|
108
|
+
results: data.results,
|
|
109
|
+
query: data.query,
|
|
110
|
+
searchTime: data.search_time,
|
|
111
|
+
originalInputTokens: data.original_input_tokens,
|
|
112
|
+
outputTokens: data.output_tokens,
|
|
113
|
+
tokensSaved: data.original_input_tokens - data.output_tokens,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
70
116
|
async parseError(response) {
|
|
71
117
|
let msg;
|
|
72
118
|
try {
|
package/dist/compress.d.ts
CHANGED
|
@@ -42,7 +42,9 @@ interface AnthropicMessage {
|
|
|
42
42
|
content?: string | AnthropicBlock[] | null;
|
|
43
43
|
[key: string]: unknown;
|
|
44
44
|
}
|
|
45
|
-
export declare function compressAnthropicMessages(ttc: Compressor, messages: AnthropicMessage[], model: string, roleAggr: Record<string, number
|
|
45
|
+
export declare function compressAnthropicMessages(ttc: Compressor, messages: AnthropicMessage[], model: string, roleAggr: Record<string, number>, options?: {
|
|
46
|
+
stripServerToolResults?: boolean;
|
|
47
|
+
}): Promise<AnthropicMessage[]>;
|
|
46
48
|
interface AISDKTextPart {
|
|
47
49
|
type: "text";
|
|
48
50
|
text: string;
|
package/dist/compress.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const DEFAULT_ROLES = ["user", "system", "tool"];
|
|
2
|
+
const SERVER_TOOL_BLOCK_TYPES = new Set(["web_search_tool_result", "server_tool_use"]);
|
|
2
3
|
export class StatsTTC {
|
|
3
4
|
inner;
|
|
4
5
|
stats;
|
|
@@ -46,8 +47,23 @@ export async function compressOpenAIMessages(ttc, messages, model, roleAggr) {
|
|
|
46
47
|
return msg;
|
|
47
48
|
}));
|
|
48
49
|
}
|
|
49
|
-
export async function compressAnthropicMessages(ttc, messages, model, roleAggr) {
|
|
50
|
+
export async function compressAnthropicMessages(ttc, messages, model, roleAggr, options) {
|
|
51
|
+
const stripServerToolResults = options?.stripServerToolResults ?? false;
|
|
50
52
|
return Promise.all(messages.map(async (msg) => {
|
|
53
|
+
if (msg.role === "assistant") {
|
|
54
|
+
const assistantAggr = roleAggr["assistant"];
|
|
55
|
+
if (assistantAggr == null && !stripServerToolResults)
|
|
56
|
+
return msg;
|
|
57
|
+
if (typeof msg.content === "string" && assistantAggr != null && msg.content.trim()) {
|
|
58
|
+
const result = await ttc.compress(msg.content, { model, aggressiveness: assistantAggr });
|
|
59
|
+
return { ...msg, content: result.output };
|
|
60
|
+
}
|
|
61
|
+
if (Array.isArray(msg.content)) {
|
|
62
|
+
const blocks = await compressAssistantBlocks(ttc, msg.content, model, assistantAggr, stripServerToolResults);
|
|
63
|
+
return { ...msg, content: blocks };
|
|
64
|
+
}
|
|
65
|
+
return msg;
|
|
66
|
+
}
|
|
51
67
|
if (msg.role !== "user")
|
|
52
68
|
return msg;
|
|
53
69
|
const userAggr = roleAggr["user"];
|
|
@@ -65,6 +81,20 @@ export async function compressAnthropicMessages(ttc, messages, model, roleAggr)
|
|
|
65
81
|
return msg;
|
|
66
82
|
}));
|
|
67
83
|
}
|
|
84
|
+
async function compressAssistantBlocks(ttc, blocks, model, assistantAggr, stripServerToolResults) {
|
|
85
|
+
const results = await Promise.all(blocks.map(async (block) => {
|
|
86
|
+
if (stripServerToolResults && SERVER_TOOL_BLOCK_TYPES.has(block.type)) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
if (block.type === "text" && assistantAggr != null && typeof block.text === "string" && block.text.trim()) {
|
|
90
|
+
const result = await ttc.compress(block.text, { model, aggressiveness: assistantAggr });
|
|
91
|
+
return { ...block, text: result.output };
|
|
92
|
+
}
|
|
93
|
+
return block;
|
|
94
|
+
}));
|
|
95
|
+
const filtered = results.filter((b) => b != null);
|
|
96
|
+
return filtered.length > 0 ? filtered : blocks;
|
|
97
|
+
}
|
|
68
98
|
async function compressAnthropicBlocks(ttc, blocks, model, userAggr, toolAggr) {
|
|
69
99
|
return Promise.all(blocks.map(async (block) => {
|
|
70
100
|
if (block.type === "text" && userAggr != null && typeof block.text === "string" && block.text.trim()) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { TheTokenCompany, BEAR_1, BEAR_1_1, BEAR_1_2, BEAR_2 } from "./client.js";
|
|
2
2
|
export { TheTokenCompanyError, AuthenticationError, InvalidRequestError, PaymentRequiredError, RequestTooLargeError, RateLimitError, APIError, } from "./errors.js";
|
|
3
3
|
export { CompressionStats } from "./types.js";
|
|
4
|
-
export type { CompressResult, TheTokenCompanyOptions, Aggressiveness, WithCompressionOptions, TurnStats, } from "./types.js";
|
|
4
|
+
export type { CompressResult, TheTokenCompanyOptions, Aggressiveness, WithCompressionOptions, TurnStats, SearchRequestOptions, SearchResultItem, SearchResult, } from "./types.js";
|
|
5
5
|
/**
|
|
6
6
|
* Wrap text in `<ttc_safe>` tags to protect it from compression.
|
|
7
7
|
*/
|
package/dist/openai.js
CHANGED
|
@@ -22,7 +22,7 @@ import { CompressionStats } from "./types.js";
|
|
|
22
22
|
*/
|
|
23
23
|
export function withCompression(client, options) {
|
|
24
24
|
const stats = new CompressionStats();
|
|
25
|
-
const compressor = new StatsTTC(new TheTokenCompany({ apiKey: options.compressionApiKey, appId: options.appId }), stats);
|
|
25
|
+
const compressor = new StatsTTC(new TheTokenCompany({ apiKey: options.compressionApiKey, appId: options.appId, fetch: options.fetch }), stats);
|
|
26
26
|
const model = options.model ?? "bear-2";
|
|
27
27
|
const roleAggr = resolveAggressiveness(options.aggressiveness ?? 0.2);
|
|
28
28
|
const originalCreate = client.chat.completions.create.bind(client.chat.completions);
|
package/dist/types.d.ts
CHANGED
|
@@ -24,14 +24,45 @@ export interface TheTokenCompanyOptions {
|
|
|
24
24
|
timeout?: number;
|
|
25
25
|
gzip?: boolean;
|
|
26
26
|
appId?: string;
|
|
27
|
+
fetch?: typeof globalThis.fetch;
|
|
27
28
|
}
|
|
28
29
|
export type Aggressiveness = number | Record<string, number>;
|
|
29
30
|
export interface WithCompressionOptions {
|
|
30
31
|
compressionApiKey: string;
|
|
31
32
|
model?: string;
|
|
32
33
|
aggressiveness?: Aggressiveness;
|
|
34
|
+
/** Compress text blocks in assistant messages (multi-turn web search optimization). */
|
|
35
|
+
compressAssistant?: boolean;
|
|
36
|
+
/** Strip server-side tool result blocks (e.g. web_search_tool_result) from assistant messages. Disables citations in subsequent turns. */
|
|
37
|
+
stripServerToolResults?: boolean;
|
|
38
|
+
/** Replace Anthropic's server-side web search with TTC's compressible search. */
|
|
39
|
+
webSearch?: boolean;
|
|
40
|
+
baseUrl?: string;
|
|
41
|
+
appId?: string;
|
|
42
|
+
fetch?: typeof globalThis.fetch;
|
|
43
|
+
}
|
|
44
|
+
export interface SearchRequestOptions {
|
|
45
|
+
maxResults?: number;
|
|
46
|
+
searchDepth?: "basic" | "advanced";
|
|
47
|
+
includeRawContent?: boolean;
|
|
48
|
+
model?: string;
|
|
49
|
+
aggressiveness?: number;
|
|
33
50
|
appId?: string;
|
|
34
51
|
}
|
|
52
|
+
export interface SearchResultItem {
|
|
53
|
+
url: string;
|
|
54
|
+
title: string;
|
|
55
|
+
content: string;
|
|
56
|
+
score?: number;
|
|
57
|
+
}
|
|
58
|
+
export interface SearchResult {
|
|
59
|
+
results: SearchResultItem[];
|
|
60
|
+
query: string;
|
|
61
|
+
searchTime: number;
|
|
62
|
+
originalInputTokens: number;
|
|
63
|
+
outputTokens: number;
|
|
64
|
+
tokensSaved: number;
|
|
65
|
+
}
|
|
35
66
|
export interface TurnStats {
|
|
36
67
|
inputTokens: number;
|
|
37
68
|
outputTokens: number;
|