the-token-company 0.2.5 → 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/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 compressor = new StatsTTC(new TheTokenCompany({ apiKey: options.compressionApiKey, appId: options.appId, fetch: options.fetch }), stats);
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
- return originalCreate(params, ...rest);
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";
@@ -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 {
@@ -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>): Promise<AnthropicMessage[]>;
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/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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "the-token-company",
3
- "version": "0.2.5",
3
+ "version": "0.3.0",
4
4
  "description": "Node.js SDK for The Token Company — compress LLM prompts to reduce costs and latency",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",