the-token-company 0.2.5 → 0.3.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/dist/anthropic.js +82 -3
- package/dist/client.d.ts +2 -1
- package/dist/client.js +44 -0
- package/dist/compress.d.ts +5 -1
- package/dist/compress.js +49 -3
- package/dist/index.d.ts +1 -1
- package/dist/types.d.ts +29 -0
- package/package.json +1 -1
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,43 @@ 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, {
|
|
105
|
+
stripServerToolResults,
|
|
106
|
+
skipToolName: webSearch ? "ttc_web_search" : undefined,
|
|
107
|
+
}),
|
|
38
108
|
};
|
|
39
109
|
}
|
|
40
110
|
if (systemAggr != null && typeof params?.system === "string" && params.system.trim()) {
|
|
41
111
|
const result = await compressor.compress(params.system, { model, aggressiveness: systemAggr });
|
|
42
112
|
params = { ...params, system: result.output };
|
|
43
113
|
}
|
|
114
|
+
// Inject search tool
|
|
115
|
+
if (webSearch) {
|
|
116
|
+
params = injectSearchTool(params);
|
|
117
|
+
}
|
|
44
118
|
stats._endTurn();
|
|
45
|
-
|
|
119
|
+
let response = await originalCreate(params, ...rest);
|
|
120
|
+
// Handle search tool loop
|
|
121
|
+
if (webSearch) {
|
|
122
|
+
response = await handleSearchLoop(response, params, originalCreate, ttcClient, rest);
|
|
123
|
+
}
|
|
124
|
+
return response;
|
|
46
125
|
};
|
|
47
126
|
client.compression = stats;
|
|
48
127
|
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";
|
|
@@ -16,5 +16,6 @@ export declare class TheTokenCompany {
|
|
|
16
16
|
aggressiveness?: number;
|
|
17
17
|
appId?: string;
|
|
18
18
|
}): Promise<CompressResult>;
|
|
19
|
+
search(query: string, options?: SearchRequestOptions): Promise<SearchResult>;
|
|
19
20
|
private parseError;
|
|
20
21
|
}
|
package/dist/client.js
CHANGED
|
@@ -69,6 +69,50 @@ export class TheTokenCompany {
|
|
|
69
69
|
compressionRatio: data.output_tokens === 0 ? 0 : data.original_input_tokens / data.output_tokens,
|
|
70
70
|
};
|
|
71
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
|
+
}
|
|
72
116
|
async parseError(response) {
|
|
73
117
|
let msg;
|
|
74
118
|
try {
|
package/dist/compress.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export declare function compressOpenAIMessages(ttc: Compressor, messages: OpenAI
|
|
|
30
30
|
interface AnthropicBlock {
|
|
31
31
|
type: string;
|
|
32
32
|
text?: string;
|
|
33
|
+
tool_use_id?: string;
|
|
33
34
|
content?: string | Array<{
|
|
34
35
|
type: string;
|
|
35
36
|
text?: string;
|
|
@@ -42,7 +43,10 @@ interface AnthropicMessage {
|
|
|
42
43
|
content?: string | AnthropicBlock[] | null;
|
|
43
44
|
[key: string]: unknown;
|
|
44
45
|
}
|
|
45
|
-
export declare function compressAnthropicMessages(ttc: Compressor, messages: AnthropicMessage[], model: string, roleAggr: Record<string, number
|
|
46
|
+
export declare function compressAnthropicMessages(ttc: Compressor, messages: AnthropicMessage[], model: string, roleAggr: Record<string, number>, options?: {
|
|
47
|
+
stripServerToolResults?: boolean;
|
|
48
|
+
skipToolName?: string;
|
|
49
|
+
}): Promise<AnthropicMessage[]>;
|
|
46
50
|
interface AISDKTextPart {
|
|
47
51
|
type: "text";
|
|
48
52
|
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,37 @@ export async function compressOpenAIMessages(ttc, messages, model, roleAggr) {
|
|
|
46
47
|
return msg;
|
|
47
48
|
}));
|
|
48
49
|
}
|
|
49
|
-
|
|
50
|
+
function collectToolUseIds(messages, toolName) {
|
|
51
|
+
const ids = new Set();
|
|
52
|
+
for (const msg of messages) {
|
|
53
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.content))
|
|
54
|
+
continue;
|
|
55
|
+
for (const block of msg.content) {
|
|
56
|
+
if (block.type === "tool_use" && block.name === toolName && block.id) {
|
|
57
|
+
ids.add(block.id);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return ids;
|
|
62
|
+
}
|
|
63
|
+
export async function compressAnthropicMessages(ttc, messages, model, roleAggr, options) {
|
|
64
|
+
const stripServerToolResults = options?.stripServerToolResults ?? false;
|
|
65
|
+
const skipIds = options?.skipToolName ? collectToolUseIds(messages, options.skipToolName) : undefined;
|
|
50
66
|
return Promise.all(messages.map(async (msg) => {
|
|
67
|
+
if (msg.role === "assistant") {
|
|
68
|
+
const assistantAggr = roleAggr["assistant"];
|
|
69
|
+
if (assistantAggr == null && !stripServerToolResults)
|
|
70
|
+
return msg;
|
|
71
|
+
if (typeof msg.content === "string" && assistantAggr != null && msg.content.trim()) {
|
|
72
|
+
const result = await ttc.compress(msg.content, { model, aggressiveness: assistantAggr });
|
|
73
|
+
return { ...msg, content: result.output };
|
|
74
|
+
}
|
|
75
|
+
if (Array.isArray(msg.content)) {
|
|
76
|
+
const blocks = await compressAssistantBlocks(ttc, msg.content, model, assistantAggr, stripServerToolResults);
|
|
77
|
+
return { ...msg, content: blocks };
|
|
78
|
+
}
|
|
79
|
+
return msg;
|
|
80
|
+
}
|
|
51
81
|
if (msg.role !== "user")
|
|
52
82
|
return msg;
|
|
53
83
|
const userAggr = roleAggr["user"];
|
|
@@ -59,19 +89,35 @@ export async function compressAnthropicMessages(ttc, messages, model, roleAggr)
|
|
|
59
89
|
return { ...msg, content: result.output };
|
|
60
90
|
}
|
|
61
91
|
if (Array.isArray(msg.content)) {
|
|
62
|
-
const blocks = await compressAnthropicBlocks(ttc, msg.content, model, userAggr, toolAggr);
|
|
92
|
+
const blocks = await compressAnthropicBlocks(ttc, msg.content, model, userAggr, toolAggr, skipIds);
|
|
63
93
|
return { ...msg, content: blocks };
|
|
64
94
|
}
|
|
65
95
|
return msg;
|
|
66
96
|
}));
|
|
67
97
|
}
|
|
68
|
-
async function
|
|
98
|
+
async function compressAssistantBlocks(ttc, blocks, model, assistantAggr, stripServerToolResults) {
|
|
99
|
+
const results = await Promise.all(blocks.map(async (block) => {
|
|
100
|
+
if (stripServerToolResults && SERVER_TOOL_BLOCK_TYPES.has(block.type)) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
if (block.type === "text" && assistantAggr != null && typeof block.text === "string" && block.text.trim()) {
|
|
104
|
+
const result = await ttc.compress(block.text, { model, aggressiveness: assistantAggr });
|
|
105
|
+
return { ...block, text: result.output };
|
|
106
|
+
}
|
|
107
|
+
return block;
|
|
108
|
+
}));
|
|
109
|
+
const filtered = results.filter((b) => b != null);
|
|
110
|
+
return filtered.length > 0 ? filtered : blocks;
|
|
111
|
+
}
|
|
112
|
+
async function compressAnthropicBlocks(ttc, blocks, model, userAggr, toolAggr, skipIds) {
|
|
69
113
|
return Promise.all(blocks.map(async (block) => {
|
|
70
114
|
if (block.type === "text" && userAggr != null && typeof block.text === "string" && block.text.trim()) {
|
|
71
115
|
const result = await ttc.compress(block.text, { model, aggressiveness: userAggr });
|
|
72
116
|
return { ...block, text: result.output };
|
|
73
117
|
}
|
|
74
118
|
if (block.type === "tool_result" && toolAggr != null) {
|
|
119
|
+
if (block.tool_use_id && skipIds?.has(block.tool_use_id))
|
|
120
|
+
return block;
|
|
75
121
|
return compressToolResult(ttc, block, model, toolAggr);
|
|
76
122
|
}
|
|
77
123
|
return block;
|
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/types.d.ts
CHANGED
|
@@ -31,9 +31,38 @@ export interface WithCompressionOptions {
|
|
|
31
31
|
compressionApiKey: string;
|
|
32
32
|
model?: string;
|
|
33
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;
|
|
34
41
|
appId?: string;
|
|
35
42
|
fetch?: typeof globalThis.fetch;
|
|
36
43
|
}
|
|
44
|
+
export interface SearchRequestOptions {
|
|
45
|
+
maxResults?: number;
|
|
46
|
+
searchDepth?: "basic" | "advanced";
|
|
47
|
+
includeRawContent?: boolean;
|
|
48
|
+
model?: string;
|
|
49
|
+
aggressiveness?: number;
|
|
50
|
+
appId?: string;
|
|
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
|
+
}
|
|
37
66
|
export interface TurnStats {
|
|
38
67
|
inputTokens: number;
|
|
39
68
|
outputTokens: number;
|