headroom-ai 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 +108 -0
- package/dist/adapters/anthropic.cjs +146 -0
- package/dist/adapters/anthropic.cjs.map +1 -0
- package/dist/adapters/anthropic.d.cts +29 -0
- package/dist/adapters/anthropic.d.ts +29 -0
- package/dist/adapters/anthropic.js +144 -0
- package/dist/adapters/anthropic.js.map +1 -0
- package/dist/adapters/openai.cjs +41 -0
- package/dist/adapters/openai.cjs.map +1 -0
- package/dist/adapters/openai.d.cts +31 -0
- package/dist/adapters/openai.d.ts +31 -0
- package/dist/adapters/openai.js +39 -0
- package/dist/adapters/openai.js.map +1 -0
- package/dist/adapters/vercel-ai.cjs +35 -0
- package/dist/adapters/vercel-ai.cjs.map +1 -0
- package/dist/adapters/vercel-ai.d.cts +35 -0
- package/dist/adapters/vercel-ai.d.ts +35 -0
- package/dist/adapters/vercel-ai.js +32 -0
- package/dist/adapters/vercel-ai.js.map +1 -0
- package/dist/chunk-3N25JEWK.cjs +538 -0
- package/dist/chunk-3N25JEWK.cjs.map +1 -0
- package/dist/chunk-YTTW7S2Q.js +526 -0
- package/dist/chunk-YTTW7S2Q.js.map +1 -0
- package/dist/index.cjs +44 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +66 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/types-DQtcLXq3.d.cts +87 -0
- package/dist/types-DQtcLXq3.d.ts +87 -0
- package/package.json +98 -0
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# headroom-ai
|
|
2
|
+
|
|
3
|
+
Compress LLM context. Save tokens. Fit more into every request.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install headroom-ai
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { compress } from 'headroom-ai';
|
|
15
|
+
|
|
16
|
+
const result = await compress(messages, { model: 'gpt-4o' });
|
|
17
|
+
console.log(`Saved ${result.tokensSaved} tokens (${((1 - result.compressionRatio) * 100).toFixed(0)}%)`);
|
|
18
|
+
|
|
19
|
+
// Use compressed messages with any LLM client
|
|
20
|
+
const response = await openai.chat.completions.create({
|
|
21
|
+
model: 'gpt-4o',
|
|
22
|
+
messages: result.messages,
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Requires a running Headroom proxy (`headroom proxy`) or Headroom Cloud API key.
|
|
27
|
+
|
|
28
|
+
## Framework Adapters
|
|
29
|
+
|
|
30
|
+
### Vercel AI SDK
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { headroomMiddleware } from 'headroom-ai/vercel-ai';
|
|
34
|
+
import { wrapLanguageModel, generateText } from 'ai';
|
|
35
|
+
import { openai } from '@ai-sdk/openai';
|
|
36
|
+
|
|
37
|
+
const model = wrapLanguageModel({
|
|
38
|
+
model: openai('gpt-4o'),
|
|
39
|
+
middleware: headroomMiddleware(),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const { text } = await generateText({ model, messages });
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### OpenAI SDK
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { withHeadroom } from 'headroom-ai/openai';
|
|
49
|
+
import OpenAI from 'openai';
|
|
50
|
+
|
|
51
|
+
const client = withHeadroom(new OpenAI());
|
|
52
|
+
const response = await client.chat.completions.create({
|
|
53
|
+
model: 'gpt-4o',
|
|
54
|
+
messages: longConversation,
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Anthropic SDK
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { withHeadroom } from 'headroom-ai/anthropic';
|
|
62
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
63
|
+
|
|
64
|
+
const client = withHeadroom(new Anthropic());
|
|
65
|
+
const response = await client.messages.create({
|
|
66
|
+
model: 'claude-sonnet-4-5-20250929',
|
|
67
|
+
messages: longConversation,
|
|
68
|
+
max_tokens: 1024,
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Configuration
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { compress } from 'headroom-ai';
|
|
76
|
+
|
|
77
|
+
const result = await compress(messages, {
|
|
78
|
+
model: 'gpt-4o',
|
|
79
|
+
baseUrl: 'http://localhost:8787', // or https://api.headroom.ai
|
|
80
|
+
apiKey: 'hr_...', // for Headroom Cloud
|
|
81
|
+
timeout: 30000, // ms
|
|
82
|
+
fallback: true, // return uncompressed if proxy is down (default)
|
|
83
|
+
retries: 1, // retry on transient failures (default)
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Or use environment variables:
|
|
88
|
+
- `HEADROOM_BASE_URL` — proxy/cloud URL
|
|
89
|
+
- `HEADROOM_API_KEY` — Cloud API key
|
|
90
|
+
|
|
91
|
+
## Reusable Client
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { HeadroomClient } from 'headroom-ai';
|
|
95
|
+
|
|
96
|
+
const client = new HeadroomClient({
|
|
97
|
+
baseUrl: 'http://localhost:8787',
|
|
98
|
+
apiKey: 'hr_...',
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Reuse across many calls
|
|
102
|
+
const r1 = await client.compress(messages1, { model: 'gpt-4o' });
|
|
103
|
+
const r2 = await client.compress(messages2, { model: 'gpt-4o' });
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
Apache-2.0
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunk3N25JEWK_cjs = require('../chunk-3N25JEWK.cjs');
|
|
4
|
+
|
|
5
|
+
// src/adapters/anthropic.ts
|
|
6
|
+
function anthropicToOpenAI(messages) {
|
|
7
|
+
const result = [];
|
|
8
|
+
for (const msg of messages) {
|
|
9
|
+
if (msg.role === "user") {
|
|
10
|
+
if (typeof msg.content === "string") {
|
|
11
|
+
result.push({ role: "user", content: msg.content });
|
|
12
|
+
} else if (Array.isArray(msg.content)) {
|
|
13
|
+
const toolResults = msg.content.filter(
|
|
14
|
+
(b) => b.type === "tool_result"
|
|
15
|
+
);
|
|
16
|
+
const textBlocks = msg.content.filter((b) => b.type === "text");
|
|
17
|
+
if (textBlocks.length > 0) {
|
|
18
|
+
result.push({
|
|
19
|
+
role: "user",
|
|
20
|
+
content: textBlocks.map((b) => b.text).join("\n")
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
for (const tr of toolResults) {
|
|
24
|
+
result.push({
|
|
25
|
+
role: "tool",
|
|
26
|
+
content: typeof tr.content === "string" ? tr.content : JSON.stringify(tr.content),
|
|
27
|
+
tool_call_id: tr.tool_use_id
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (msg.role === "assistant") {
|
|
34
|
+
if (typeof msg.content === "string") {
|
|
35
|
+
result.push({ role: "assistant", content: msg.content });
|
|
36
|
+
} else if (Array.isArray(msg.content)) {
|
|
37
|
+
const textBlocks = msg.content.filter((b) => b.type === "text");
|
|
38
|
+
const toolUseBlocks = msg.content.filter(
|
|
39
|
+
(b) => b.type === "tool_use"
|
|
40
|
+
);
|
|
41
|
+
const content = textBlocks.length > 0 ? textBlocks.map((b) => b.text).join("\n") : null;
|
|
42
|
+
const openaiMsg = { role: "assistant", content };
|
|
43
|
+
if (toolUseBlocks.length > 0) {
|
|
44
|
+
openaiMsg.tool_calls = toolUseBlocks.map(
|
|
45
|
+
(b) => ({
|
|
46
|
+
id: b.id,
|
|
47
|
+
type: "function",
|
|
48
|
+
function: {
|
|
49
|
+
name: b.name,
|
|
50
|
+
arguments: JSON.stringify(b.input)
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
result.push(openaiMsg);
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
function openAIToAnthropic(messages) {
|
|
63
|
+
const result = [];
|
|
64
|
+
for (const msg of messages) {
|
|
65
|
+
if (msg.role === "user") {
|
|
66
|
+
if (typeof msg.content === "string") {
|
|
67
|
+
result.push({ role: "user", content: msg.content });
|
|
68
|
+
} else if (Array.isArray(msg.content)) {
|
|
69
|
+
result.push({
|
|
70
|
+
role: "user",
|
|
71
|
+
content: msg.content.map((p) => {
|
|
72
|
+
if (p.type === "text") return { type: "text", text: p.text };
|
|
73
|
+
return { type: "text", text: "" };
|
|
74
|
+
})
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (msg.role === "assistant") {
|
|
80
|
+
const blocks = [];
|
|
81
|
+
if (msg.content) blocks.push({ type: "text", text: msg.content });
|
|
82
|
+
if (msg.tool_calls) {
|
|
83
|
+
for (const tc of msg.tool_calls) {
|
|
84
|
+
blocks.push({
|
|
85
|
+
type: "tool_use",
|
|
86
|
+
id: tc.id,
|
|
87
|
+
name: tc.function.name,
|
|
88
|
+
input: JSON.parse(tc.function.arguments)
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
result.push({
|
|
93
|
+
role: "assistant",
|
|
94
|
+
content: blocks.length === 1 && blocks[0].type === "text" ? blocks[0].text : blocks
|
|
95
|
+
});
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (msg.role === "tool") {
|
|
99
|
+
result.push({
|
|
100
|
+
role: "user",
|
|
101
|
+
content: [
|
|
102
|
+
{
|
|
103
|
+
type: "tool_result",
|
|
104
|
+
tool_use_id: msg.tool_call_id,
|
|
105
|
+
content: msg.content
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
});
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (msg.role === "system") {
|
|
112
|
+
result.push({ role: "user", content: msg.content });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
function withHeadroom(client, options = {}) {
|
|
118
|
+
const originalCreate = client.messages.create.bind(client.messages);
|
|
119
|
+
const compressedCreate = async (params) => {
|
|
120
|
+
const messages = params.messages;
|
|
121
|
+
const model = options.model ?? params.model ?? "claude-sonnet-4-5-20250929";
|
|
122
|
+
const openaiMessages = anthropicToOpenAI(messages);
|
|
123
|
+
const result = await chunk3N25JEWK_cjs.compress(openaiMessages, { ...options, model });
|
|
124
|
+
const anthropicMessages = result.compressed ? openAIToAnthropic(result.messages) : messages;
|
|
125
|
+
return originalCreate({
|
|
126
|
+
...params,
|
|
127
|
+
messages: anthropicMessages
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
const messagesProxy = new Proxy(client.messages, {
|
|
131
|
+
get(target, prop) {
|
|
132
|
+
if (prop === "create") return compressedCreate;
|
|
133
|
+
return target[prop];
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
return new Proxy(client, {
|
|
137
|
+
get(target, prop) {
|
|
138
|
+
if (prop === "messages") return messagesProxy;
|
|
139
|
+
return target[prop];
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
exports.withHeadroom = withHeadroom;
|
|
145
|
+
//# sourceMappingURL=anthropic.cjs.map
|
|
146
|
+
//# sourceMappingURL=anthropic.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/anthropic.ts"],"names":["compress"],"mappings":"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","file":"anthropic.cjs","sourcesContent":["import { compress } from \"../compress.js\";\nimport type { CompressOptions, OpenAIMessage } from \"../types.js\";\nimport type { AssistantMessage, ToolCall } from \"../types.js\";\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\ninterface AnthropicLike {\n messages: {\n create: (params: any) => any;\n };\n [key: string]: any;\n}\n\n/**\n * Convert Anthropic messages to OpenAI format for compression.\n *\n * Anthropic format:\n * { role: 'user' | 'assistant', content: string | ContentBlock[] }\n * ContentBlock = { type: 'text', text } | { type: 'tool_use', ... } | { type: 'tool_result', ... }\n */\nfunction anthropicToOpenAI(messages: any[]): OpenAIMessage[] {\n const result: OpenAIMessage[] = [];\n\n for (const msg of messages) {\n if (msg.role === \"user\") {\n if (typeof msg.content === \"string\") {\n result.push({ role: \"user\", content: msg.content });\n } else if (Array.isArray(msg.content)) {\n const toolResults = msg.content.filter(\n (b: any) => b.type === \"tool_result\",\n );\n const textBlocks = msg.content.filter((b: any) => b.type === \"text\");\n\n if (textBlocks.length > 0) {\n result.push({\n role: \"user\",\n content: textBlocks.map((b: any) => b.text).join(\"\\n\"),\n });\n }\n for (const tr of toolResults) {\n result.push({\n role: \"tool\",\n content:\n typeof tr.content === \"string\"\n ? tr.content\n : JSON.stringify(tr.content),\n tool_call_id: tr.tool_use_id,\n });\n }\n }\n continue;\n }\n\n if (msg.role === \"assistant\") {\n if (typeof msg.content === \"string\") {\n result.push({ role: \"assistant\", content: msg.content });\n } else if (Array.isArray(msg.content)) {\n const textBlocks = msg.content.filter((b: any) => b.type === \"text\");\n const toolUseBlocks = msg.content.filter(\n (b: any) => b.type === \"tool_use\",\n );\n\n const content =\n textBlocks.length > 0\n ? textBlocks.map((b: any) => b.text).join(\"\\n\")\n : null;\n\n const openaiMsg: AssistantMessage = { role: \"assistant\", content };\n if (toolUseBlocks.length > 0) {\n openaiMsg.tool_calls = toolUseBlocks.map(\n (b: any): ToolCall => ({\n id: b.id,\n type: \"function\",\n function: {\n name: b.name,\n arguments: JSON.stringify(b.input),\n },\n }),\n );\n }\n result.push(openaiMsg);\n }\n continue;\n }\n }\n\n return result;\n}\n\n/**\n * Convert compressed OpenAI messages back to Anthropic format.\n */\nfunction openAIToAnthropic(messages: OpenAIMessage[]): any[] {\n const result: any[] = [];\n\n for (const msg of messages) {\n if (msg.role === \"user\") {\n if (typeof msg.content === \"string\") {\n result.push({ role: \"user\", content: msg.content });\n } else if (Array.isArray(msg.content)) {\n result.push({\n role: \"user\",\n content: msg.content.map((p) => {\n if (p.type === \"text\") return { type: \"text\", text: p.text };\n return { type: \"text\", text: \"\" };\n }),\n });\n }\n continue;\n }\n\n if (msg.role === \"assistant\") {\n const blocks: any[] = [];\n if (msg.content) blocks.push({ type: \"text\", text: msg.content });\n if (msg.tool_calls) {\n for (const tc of msg.tool_calls) {\n blocks.push({\n type: \"tool_use\",\n id: tc.id,\n name: tc.function.name,\n input: JSON.parse(tc.function.arguments),\n });\n }\n }\n result.push({\n role: \"assistant\",\n content:\n blocks.length === 1 && blocks[0].type === \"text\"\n ? blocks[0].text\n : blocks,\n });\n continue;\n }\n\n if (msg.role === \"tool\") {\n result.push({\n role: \"user\",\n content: [\n {\n type: \"tool_result\",\n tool_use_id: msg.tool_call_id,\n content: msg.content,\n },\n ],\n });\n continue;\n }\n\n if (msg.role === \"system\") {\n // System messages are handled separately in Anthropic (as `system` param)\n // Pass through as user message if it appears in the messages array\n result.push({ role: \"user\", content: msg.content });\n }\n }\n\n return result;\n}\n\n/**\n * Wrap an Anthropic client to auto-compress messages before each request.\n *\n * Intercepts `client.messages.create()` only. All other methods pass through.\n *\n * @example\n * ```typescript\n * import { withHeadroom } from 'headroom-ai/anthropic';\n * import Anthropic from '@anthropic-ai/sdk';\n *\n * const client = withHeadroom(new Anthropic());\n * const response = await client.messages.create({\n * model: 'claude-sonnet-4-5-20250929',\n * messages: longConversation,\n * max_tokens: 1024,\n * });\n * ```\n */\nexport function withHeadroom<T extends AnthropicLike>(\n client: T,\n options: CompressOptions = {},\n): T {\n const originalCreate = client.messages.create.bind(client.messages);\n\n const compressedCreate = async (params: any) => {\n const messages = params.messages;\n const model =\n options.model ?? params.model ?? \"claude-sonnet-4-5-20250929\";\n\n const openaiMessages = anthropicToOpenAI(messages);\n const result = await compress(openaiMessages, { ...options, model });\n\n const anthropicMessages = result.compressed\n ? openAIToAnthropic(result.messages)\n : messages;\n\n return originalCreate({\n ...params,\n messages: anthropicMessages,\n });\n };\n\n const messagesProxy = new Proxy(client.messages, {\n get(target, prop) {\n if (prop === \"create\") return compressedCreate;\n return (target as any)[prop];\n },\n });\n\n return new Proxy(client, {\n get(target, prop) {\n if (prop === \"messages\") return messagesProxy;\n return (target as any)[prop];\n },\n }) as T;\n}\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { C as CompressOptions } from '../types-DQtcLXq3.cjs';
|
|
2
|
+
|
|
3
|
+
interface AnthropicLike {
|
|
4
|
+
messages: {
|
|
5
|
+
create: (params: any) => any;
|
|
6
|
+
};
|
|
7
|
+
[key: string]: any;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Wrap an Anthropic client to auto-compress messages before each request.
|
|
11
|
+
*
|
|
12
|
+
* Intercepts `client.messages.create()` only. All other methods pass through.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { withHeadroom } from 'headroom-ai/anthropic';
|
|
17
|
+
* import Anthropic from '@anthropic-ai/sdk';
|
|
18
|
+
*
|
|
19
|
+
* const client = withHeadroom(new Anthropic());
|
|
20
|
+
* const response = await client.messages.create({
|
|
21
|
+
* model: 'claude-sonnet-4-5-20250929',
|
|
22
|
+
* messages: longConversation,
|
|
23
|
+
* max_tokens: 1024,
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
declare function withHeadroom<T extends AnthropicLike>(client: T, options?: CompressOptions): T;
|
|
28
|
+
|
|
29
|
+
export { withHeadroom };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { C as CompressOptions } from '../types-DQtcLXq3.js';
|
|
2
|
+
|
|
3
|
+
interface AnthropicLike {
|
|
4
|
+
messages: {
|
|
5
|
+
create: (params: any) => any;
|
|
6
|
+
};
|
|
7
|
+
[key: string]: any;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Wrap an Anthropic client to auto-compress messages before each request.
|
|
11
|
+
*
|
|
12
|
+
* Intercepts `client.messages.create()` only. All other methods pass through.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { withHeadroom } from 'headroom-ai/anthropic';
|
|
17
|
+
* import Anthropic from '@anthropic-ai/sdk';
|
|
18
|
+
*
|
|
19
|
+
* const client = withHeadroom(new Anthropic());
|
|
20
|
+
* const response = await client.messages.create({
|
|
21
|
+
* model: 'claude-sonnet-4-5-20250929',
|
|
22
|
+
* messages: longConversation,
|
|
23
|
+
* max_tokens: 1024,
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
declare function withHeadroom<T extends AnthropicLike>(client: T, options?: CompressOptions): T;
|
|
28
|
+
|
|
29
|
+
export { withHeadroom };
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { compress } from '../chunk-YTTW7S2Q.js';
|
|
2
|
+
|
|
3
|
+
// src/adapters/anthropic.ts
|
|
4
|
+
function anthropicToOpenAI(messages) {
|
|
5
|
+
const result = [];
|
|
6
|
+
for (const msg of messages) {
|
|
7
|
+
if (msg.role === "user") {
|
|
8
|
+
if (typeof msg.content === "string") {
|
|
9
|
+
result.push({ role: "user", content: msg.content });
|
|
10
|
+
} else if (Array.isArray(msg.content)) {
|
|
11
|
+
const toolResults = msg.content.filter(
|
|
12
|
+
(b) => b.type === "tool_result"
|
|
13
|
+
);
|
|
14
|
+
const textBlocks = msg.content.filter((b) => b.type === "text");
|
|
15
|
+
if (textBlocks.length > 0) {
|
|
16
|
+
result.push({
|
|
17
|
+
role: "user",
|
|
18
|
+
content: textBlocks.map((b) => b.text).join("\n")
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
for (const tr of toolResults) {
|
|
22
|
+
result.push({
|
|
23
|
+
role: "tool",
|
|
24
|
+
content: typeof tr.content === "string" ? tr.content : JSON.stringify(tr.content),
|
|
25
|
+
tool_call_id: tr.tool_use_id
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (msg.role === "assistant") {
|
|
32
|
+
if (typeof msg.content === "string") {
|
|
33
|
+
result.push({ role: "assistant", content: msg.content });
|
|
34
|
+
} else if (Array.isArray(msg.content)) {
|
|
35
|
+
const textBlocks = msg.content.filter((b) => b.type === "text");
|
|
36
|
+
const toolUseBlocks = msg.content.filter(
|
|
37
|
+
(b) => b.type === "tool_use"
|
|
38
|
+
);
|
|
39
|
+
const content = textBlocks.length > 0 ? textBlocks.map((b) => b.text).join("\n") : null;
|
|
40
|
+
const openaiMsg = { role: "assistant", content };
|
|
41
|
+
if (toolUseBlocks.length > 0) {
|
|
42
|
+
openaiMsg.tool_calls = toolUseBlocks.map(
|
|
43
|
+
(b) => ({
|
|
44
|
+
id: b.id,
|
|
45
|
+
type: "function",
|
|
46
|
+
function: {
|
|
47
|
+
name: b.name,
|
|
48
|
+
arguments: JSON.stringify(b.input)
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
result.push(openaiMsg);
|
|
54
|
+
}
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
function openAIToAnthropic(messages) {
|
|
61
|
+
const result = [];
|
|
62
|
+
for (const msg of messages) {
|
|
63
|
+
if (msg.role === "user") {
|
|
64
|
+
if (typeof msg.content === "string") {
|
|
65
|
+
result.push({ role: "user", content: msg.content });
|
|
66
|
+
} else if (Array.isArray(msg.content)) {
|
|
67
|
+
result.push({
|
|
68
|
+
role: "user",
|
|
69
|
+
content: msg.content.map((p) => {
|
|
70
|
+
if (p.type === "text") return { type: "text", text: p.text };
|
|
71
|
+
return { type: "text", text: "" };
|
|
72
|
+
})
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (msg.role === "assistant") {
|
|
78
|
+
const blocks = [];
|
|
79
|
+
if (msg.content) blocks.push({ type: "text", text: msg.content });
|
|
80
|
+
if (msg.tool_calls) {
|
|
81
|
+
for (const tc of msg.tool_calls) {
|
|
82
|
+
blocks.push({
|
|
83
|
+
type: "tool_use",
|
|
84
|
+
id: tc.id,
|
|
85
|
+
name: tc.function.name,
|
|
86
|
+
input: JSON.parse(tc.function.arguments)
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
result.push({
|
|
91
|
+
role: "assistant",
|
|
92
|
+
content: blocks.length === 1 && blocks[0].type === "text" ? blocks[0].text : blocks
|
|
93
|
+
});
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (msg.role === "tool") {
|
|
97
|
+
result.push({
|
|
98
|
+
role: "user",
|
|
99
|
+
content: [
|
|
100
|
+
{
|
|
101
|
+
type: "tool_result",
|
|
102
|
+
tool_use_id: msg.tool_call_id,
|
|
103
|
+
content: msg.content
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
});
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (msg.role === "system") {
|
|
110
|
+
result.push({ role: "user", content: msg.content });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
function withHeadroom(client, options = {}) {
|
|
116
|
+
const originalCreate = client.messages.create.bind(client.messages);
|
|
117
|
+
const compressedCreate = async (params) => {
|
|
118
|
+
const messages = params.messages;
|
|
119
|
+
const model = options.model ?? params.model ?? "claude-sonnet-4-5-20250929";
|
|
120
|
+
const openaiMessages = anthropicToOpenAI(messages);
|
|
121
|
+
const result = await compress(openaiMessages, { ...options, model });
|
|
122
|
+
const anthropicMessages = result.compressed ? openAIToAnthropic(result.messages) : messages;
|
|
123
|
+
return originalCreate({
|
|
124
|
+
...params,
|
|
125
|
+
messages: anthropicMessages
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
const messagesProxy = new Proxy(client.messages, {
|
|
129
|
+
get(target, prop) {
|
|
130
|
+
if (prop === "create") return compressedCreate;
|
|
131
|
+
return target[prop];
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
return new Proxy(client, {
|
|
135
|
+
get(target, prop) {
|
|
136
|
+
if (prop === "messages") return messagesProxy;
|
|
137
|
+
return target[prop];
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export { withHeadroom };
|
|
143
|
+
//# sourceMappingURL=anthropic.js.map
|
|
144
|
+
//# sourceMappingURL=anthropic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/anthropic.ts"],"names":[],"mappings":"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","file":"anthropic.js","sourcesContent":["import { compress } from \"../compress.js\";\nimport type { CompressOptions, OpenAIMessage } from \"../types.js\";\nimport type { AssistantMessage, ToolCall } from \"../types.js\";\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\ninterface AnthropicLike {\n messages: {\n create: (params: any) => any;\n };\n [key: string]: any;\n}\n\n/**\n * Convert Anthropic messages to OpenAI format for compression.\n *\n * Anthropic format:\n * { role: 'user' | 'assistant', content: string | ContentBlock[] }\n * ContentBlock = { type: 'text', text } | { type: 'tool_use', ... } | { type: 'tool_result', ... }\n */\nfunction anthropicToOpenAI(messages: any[]): OpenAIMessage[] {\n const result: OpenAIMessage[] = [];\n\n for (const msg of messages) {\n if (msg.role === \"user\") {\n if (typeof msg.content === \"string\") {\n result.push({ role: \"user\", content: msg.content });\n } else if (Array.isArray(msg.content)) {\n const toolResults = msg.content.filter(\n (b: any) => b.type === \"tool_result\",\n );\n const textBlocks = msg.content.filter((b: any) => b.type === \"text\");\n\n if (textBlocks.length > 0) {\n result.push({\n role: \"user\",\n content: textBlocks.map((b: any) => b.text).join(\"\\n\"),\n });\n }\n for (const tr of toolResults) {\n result.push({\n role: \"tool\",\n content:\n typeof tr.content === \"string\"\n ? tr.content\n : JSON.stringify(tr.content),\n tool_call_id: tr.tool_use_id,\n });\n }\n }\n continue;\n }\n\n if (msg.role === \"assistant\") {\n if (typeof msg.content === \"string\") {\n result.push({ role: \"assistant\", content: msg.content });\n } else if (Array.isArray(msg.content)) {\n const textBlocks = msg.content.filter((b: any) => b.type === \"text\");\n const toolUseBlocks = msg.content.filter(\n (b: any) => b.type === \"tool_use\",\n );\n\n const content =\n textBlocks.length > 0\n ? textBlocks.map((b: any) => b.text).join(\"\\n\")\n : null;\n\n const openaiMsg: AssistantMessage = { role: \"assistant\", content };\n if (toolUseBlocks.length > 0) {\n openaiMsg.tool_calls = toolUseBlocks.map(\n (b: any): ToolCall => ({\n id: b.id,\n type: \"function\",\n function: {\n name: b.name,\n arguments: JSON.stringify(b.input),\n },\n }),\n );\n }\n result.push(openaiMsg);\n }\n continue;\n }\n }\n\n return result;\n}\n\n/**\n * Convert compressed OpenAI messages back to Anthropic format.\n */\nfunction openAIToAnthropic(messages: OpenAIMessage[]): any[] {\n const result: any[] = [];\n\n for (const msg of messages) {\n if (msg.role === \"user\") {\n if (typeof msg.content === \"string\") {\n result.push({ role: \"user\", content: msg.content });\n } else if (Array.isArray(msg.content)) {\n result.push({\n role: \"user\",\n content: msg.content.map((p) => {\n if (p.type === \"text\") return { type: \"text\", text: p.text };\n return { type: \"text\", text: \"\" };\n }),\n });\n }\n continue;\n }\n\n if (msg.role === \"assistant\") {\n const blocks: any[] = [];\n if (msg.content) blocks.push({ type: \"text\", text: msg.content });\n if (msg.tool_calls) {\n for (const tc of msg.tool_calls) {\n blocks.push({\n type: \"tool_use\",\n id: tc.id,\n name: tc.function.name,\n input: JSON.parse(tc.function.arguments),\n });\n }\n }\n result.push({\n role: \"assistant\",\n content:\n blocks.length === 1 && blocks[0].type === \"text\"\n ? blocks[0].text\n : blocks,\n });\n continue;\n }\n\n if (msg.role === \"tool\") {\n result.push({\n role: \"user\",\n content: [\n {\n type: \"tool_result\",\n tool_use_id: msg.tool_call_id,\n content: msg.content,\n },\n ],\n });\n continue;\n }\n\n if (msg.role === \"system\") {\n // System messages are handled separately in Anthropic (as `system` param)\n // Pass through as user message if it appears in the messages array\n result.push({ role: \"user\", content: msg.content });\n }\n }\n\n return result;\n}\n\n/**\n * Wrap an Anthropic client to auto-compress messages before each request.\n *\n * Intercepts `client.messages.create()` only. All other methods pass through.\n *\n * @example\n * ```typescript\n * import { withHeadroom } from 'headroom-ai/anthropic';\n * import Anthropic from '@anthropic-ai/sdk';\n *\n * const client = withHeadroom(new Anthropic());\n * const response = await client.messages.create({\n * model: 'claude-sonnet-4-5-20250929',\n * messages: longConversation,\n * max_tokens: 1024,\n * });\n * ```\n */\nexport function withHeadroom<T extends AnthropicLike>(\n client: T,\n options: CompressOptions = {},\n): T {\n const originalCreate = client.messages.create.bind(client.messages);\n\n const compressedCreate = async (params: any) => {\n const messages = params.messages;\n const model =\n options.model ?? params.model ?? \"claude-sonnet-4-5-20250929\";\n\n const openaiMessages = anthropicToOpenAI(messages);\n const result = await compress(openaiMessages, { ...options, model });\n\n const anthropicMessages = result.compressed\n ? openAIToAnthropic(result.messages)\n : messages;\n\n return originalCreate({\n ...params,\n messages: anthropicMessages,\n });\n };\n\n const messagesProxy = new Proxy(client.messages, {\n get(target, prop) {\n if (prop === \"create\") return compressedCreate;\n return (target as any)[prop];\n },\n });\n\n return new Proxy(client, {\n get(target, prop) {\n if (prop === \"messages\") return messagesProxy;\n return (target as any)[prop];\n },\n }) as T;\n}\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunk3N25JEWK_cjs = require('../chunk-3N25JEWK.cjs');
|
|
4
|
+
|
|
5
|
+
// src/adapters/openai.ts
|
|
6
|
+
function withHeadroom(client, options = {}) {
|
|
7
|
+
const originalCreate = client.chat.completions.create.bind(
|
|
8
|
+
client.chat.completions
|
|
9
|
+
);
|
|
10
|
+
const compressedCreate = async (params) => {
|
|
11
|
+
const messages = params.messages;
|
|
12
|
+
const model = options.model ?? params.model ?? "gpt-4o";
|
|
13
|
+
const result = await chunk3N25JEWK_cjs.compress(messages, { ...options, model });
|
|
14
|
+
return originalCreate({
|
|
15
|
+
...params,
|
|
16
|
+
messages: result.messages
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
const completionsProxy = new Proxy(client.chat.completions, {
|
|
20
|
+
get(target, prop) {
|
|
21
|
+
if (prop === "create") return compressedCreate;
|
|
22
|
+
return target[prop];
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
const chatProxy = new Proxy(client.chat, {
|
|
26
|
+
get(target, prop) {
|
|
27
|
+
if (prop === "completions") return completionsProxy;
|
|
28
|
+
return target[prop];
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return new Proxy(client, {
|
|
32
|
+
get(target, prop) {
|
|
33
|
+
if (prop === "chat") return chatProxy;
|
|
34
|
+
return target[prop];
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
exports.withHeadroom = withHeadroom;
|
|
40
|
+
//# sourceMappingURL=openai.cjs.map
|
|
41
|
+
//# sourceMappingURL=openai.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/openai.ts"],"names":["compress"],"mappings":";;;;;AAgCO,SAAS,YAAA,CACd,MAAA,EACA,OAAA,GAA2B,EAAC,EACzB;AACH,EAAA,MAAM,cAAA,GAAiB,MAAA,CAAO,IAAA,CAAK,WAAA,CAAY,MAAA,CAAO,IAAA;AAAA,IACpD,OAAO,IAAA,CAAK;AAAA,GACd;AAEA,EAAA,MAAM,gBAAA,GAAmB,OAAO,MAAA,KAAgB;AAC9C,IAAA,MAAM,WAA4B,MAAA,CAAO,QAAA;AACzC,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,IAAS,MAAA,CAAO,KAAA,IAAS,QAAA;AAE/C,IAAA,MAAM,MAAA,GAAS,MAAMA,0BAAA,CAAS,QAAA,EAAU,EAAE,GAAG,OAAA,EAAS,OAAO,CAAA;AAE7D,IAAA,OAAO,cAAA,CAAe;AAAA,MACpB,GAAG,MAAA;AAAA,MACH,UAAU,MAAA,CAAO;AAAA,KAClB,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,MAAM,gBAAA,GAAmB,IAAI,KAAA,CAAM,MAAA,CAAO,KAAK,WAAA,EAAa;AAAA,IAC1D,GAAA,CAAI,QAAQ,IAAA,EAAM;AAChB,MAAA,IAAI,IAAA,KAAS,UAAU,OAAO,gBAAA;AAC9B,MAAA,OAAQ,OAAe,IAAI,CAAA;AAAA,IAC7B;AAAA,GACD,CAAA;AAED,EAAA,MAAM,SAAA,GAAY,IAAI,KAAA,CAAM,MAAA,CAAO,IAAA,EAAM;AAAA,IACvC,GAAA,CAAI,QAAQ,IAAA,EAAM;AAChB,MAAA,IAAI,IAAA,KAAS,eAAe,OAAO,gBAAA;AACnC,MAAA,OAAQ,OAAe,IAAI,CAAA;AAAA,IAC7B;AAAA,GACD,CAAA;AAED,EAAA,OAAO,IAAI,MAAM,MAAA,EAAQ;AAAA,IACvB,GAAA,CAAI,QAAQ,IAAA,EAAM;AAChB,MAAA,IAAI,IAAA,KAAS,QAAQ,OAAO,SAAA;AAC5B,MAAA,OAAQ,OAAe,IAAI,CAAA;AAAA,IAC7B;AAAA,GACD,CAAA;AACH","file":"openai.cjs","sourcesContent":["import { compress } from \"../compress.js\";\nimport type { CompressOptions, OpenAIMessage } from \"../types.js\";\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\ninterface OpenAILike {\n chat: {\n completions: {\n create: (params: any) => any;\n };\n };\n [key: string]: any;\n}\n\n/**\n * Wrap an OpenAI client to auto-compress messages before each request.\n *\n * Intercepts `client.chat.completions.create()` only. All other methods\n * (embeddings, images, audio, etc.) pass through unchanged.\n *\n * @example\n * ```typescript\n * import { withHeadroom } from 'headroom-ai/openai';\n * import OpenAI from 'openai';\n *\n * const client = withHeadroom(new OpenAI());\n * const response = await client.chat.completions.create({\n * model: 'gpt-4o',\n * messages: longConversation,\n * });\n * ```\n */\nexport function withHeadroom<T extends OpenAILike>(\n client: T,\n options: CompressOptions = {},\n): T {\n const originalCreate = client.chat.completions.create.bind(\n client.chat.completions,\n );\n\n const compressedCreate = async (params: any) => {\n const messages: OpenAIMessage[] = params.messages;\n const model = options.model ?? params.model ?? \"gpt-4o\";\n\n const result = await compress(messages, { ...options, model });\n\n return originalCreate({\n ...params,\n messages: result.messages,\n });\n };\n\n const completionsProxy = new Proxy(client.chat.completions, {\n get(target, prop) {\n if (prop === \"create\") return compressedCreate;\n return (target as any)[prop];\n },\n });\n\n const chatProxy = new Proxy(client.chat, {\n get(target, prop) {\n if (prop === \"completions\") return completionsProxy;\n return (target as any)[prop];\n },\n });\n\n return new Proxy(client, {\n get(target, prop) {\n if (prop === \"chat\") return chatProxy;\n return (target as any)[prop];\n },\n }) as T;\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { C as CompressOptions } from '../types-DQtcLXq3.cjs';
|
|
2
|
+
|
|
3
|
+
interface OpenAILike {
|
|
4
|
+
chat: {
|
|
5
|
+
completions: {
|
|
6
|
+
create: (params: any) => any;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Wrap an OpenAI client to auto-compress messages before each request.
|
|
13
|
+
*
|
|
14
|
+
* Intercepts `client.chat.completions.create()` only. All other methods
|
|
15
|
+
* (embeddings, images, audio, etc.) pass through unchanged.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import { withHeadroom } from 'headroom-ai/openai';
|
|
20
|
+
* import OpenAI from 'openai';
|
|
21
|
+
*
|
|
22
|
+
* const client = withHeadroom(new OpenAI());
|
|
23
|
+
* const response = await client.chat.completions.create({
|
|
24
|
+
* model: 'gpt-4o',
|
|
25
|
+
* messages: longConversation,
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
declare function withHeadroom<T extends OpenAILike>(client: T, options?: CompressOptions): T;
|
|
30
|
+
|
|
31
|
+
export { withHeadroom };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { C as CompressOptions } from '../types-DQtcLXq3.js';
|
|
2
|
+
|
|
3
|
+
interface OpenAILike {
|
|
4
|
+
chat: {
|
|
5
|
+
completions: {
|
|
6
|
+
create: (params: any) => any;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Wrap an OpenAI client to auto-compress messages before each request.
|
|
13
|
+
*
|
|
14
|
+
* Intercepts `client.chat.completions.create()` only. All other methods
|
|
15
|
+
* (embeddings, images, audio, etc.) pass through unchanged.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import { withHeadroom } from 'headroom-ai/openai';
|
|
20
|
+
* import OpenAI from 'openai';
|
|
21
|
+
*
|
|
22
|
+
* const client = withHeadroom(new OpenAI());
|
|
23
|
+
* const response = await client.chat.completions.create({
|
|
24
|
+
* model: 'gpt-4o',
|
|
25
|
+
* messages: longConversation,
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
declare function withHeadroom<T extends OpenAILike>(client: T, options?: CompressOptions): T;
|
|
30
|
+
|
|
31
|
+
export { withHeadroom };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { compress } from '../chunk-YTTW7S2Q.js';
|
|
2
|
+
|
|
3
|
+
// src/adapters/openai.ts
|
|
4
|
+
function withHeadroom(client, options = {}) {
|
|
5
|
+
const originalCreate = client.chat.completions.create.bind(
|
|
6
|
+
client.chat.completions
|
|
7
|
+
);
|
|
8
|
+
const compressedCreate = async (params) => {
|
|
9
|
+
const messages = params.messages;
|
|
10
|
+
const model = options.model ?? params.model ?? "gpt-4o";
|
|
11
|
+
const result = await compress(messages, { ...options, model });
|
|
12
|
+
return originalCreate({
|
|
13
|
+
...params,
|
|
14
|
+
messages: result.messages
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
const completionsProxy = new Proxy(client.chat.completions, {
|
|
18
|
+
get(target, prop) {
|
|
19
|
+
if (prop === "create") return compressedCreate;
|
|
20
|
+
return target[prop];
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
const chatProxy = new Proxy(client.chat, {
|
|
24
|
+
get(target, prop) {
|
|
25
|
+
if (prop === "completions") return completionsProxy;
|
|
26
|
+
return target[prop];
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
return new Proxy(client, {
|
|
30
|
+
get(target, prop) {
|
|
31
|
+
if (prop === "chat") return chatProxy;
|
|
32
|
+
return target[prop];
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { withHeadroom };
|
|
38
|
+
//# sourceMappingURL=openai.js.map
|
|
39
|
+
//# sourceMappingURL=openai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/openai.ts"],"names":[],"mappings":";;;AAgCO,SAAS,YAAA,CACd,MAAA,EACA,OAAA,GAA2B,EAAC,EACzB;AACH,EAAA,MAAM,cAAA,GAAiB,MAAA,CAAO,IAAA,CAAK,WAAA,CAAY,MAAA,CAAO,IAAA;AAAA,IACpD,OAAO,IAAA,CAAK;AAAA,GACd;AAEA,EAAA,MAAM,gBAAA,GAAmB,OAAO,MAAA,KAAgB;AAC9C,IAAA,MAAM,WAA4B,MAAA,CAAO,QAAA;AACzC,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,IAAS,MAAA,CAAO,KAAA,IAAS,QAAA;AAE/C,IAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,QAAA,EAAU,EAAE,GAAG,OAAA,EAAS,OAAO,CAAA;AAE7D,IAAA,OAAO,cAAA,CAAe;AAAA,MACpB,GAAG,MAAA;AAAA,MACH,UAAU,MAAA,CAAO;AAAA,KAClB,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,MAAM,gBAAA,GAAmB,IAAI,KAAA,CAAM,MAAA,CAAO,KAAK,WAAA,EAAa;AAAA,IAC1D,GAAA,CAAI,QAAQ,IAAA,EAAM;AAChB,MAAA,IAAI,IAAA,KAAS,UAAU,OAAO,gBAAA;AAC9B,MAAA,OAAQ,OAAe,IAAI,CAAA;AAAA,IAC7B;AAAA,GACD,CAAA;AAED,EAAA,MAAM,SAAA,GAAY,IAAI,KAAA,CAAM,MAAA,CAAO,IAAA,EAAM;AAAA,IACvC,GAAA,CAAI,QAAQ,IAAA,EAAM;AAChB,MAAA,IAAI,IAAA,KAAS,eAAe,OAAO,gBAAA;AACnC,MAAA,OAAQ,OAAe,IAAI,CAAA;AAAA,IAC7B;AAAA,GACD,CAAA;AAED,EAAA,OAAO,IAAI,MAAM,MAAA,EAAQ;AAAA,IACvB,GAAA,CAAI,QAAQ,IAAA,EAAM;AAChB,MAAA,IAAI,IAAA,KAAS,QAAQ,OAAO,SAAA;AAC5B,MAAA,OAAQ,OAAe,IAAI,CAAA;AAAA,IAC7B;AAAA,GACD,CAAA;AACH","file":"openai.js","sourcesContent":["import { compress } from \"../compress.js\";\nimport type { CompressOptions, OpenAIMessage } from \"../types.js\";\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\ninterface OpenAILike {\n chat: {\n completions: {\n create: (params: any) => any;\n };\n };\n [key: string]: any;\n}\n\n/**\n * Wrap an OpenAI client to auto-compress messages before each request.\n *\n * Intercepts `client.chat.completions.create()` only. All other methods\n * (embeddings, images, audio, etc.) pass through unchanged.\n *\n * @example\n * ```typescript\n * import { withHeadroom } from 'headroom-ai/openai';\n * import OpenAI from 'openai';\n *\n * const client = withHeadroom(new OpenAI());\n * const response = await client.chat.completions.create({\n * model: 'gpt-4o',\n * messages: longConversation,\n * });\n * ```\n */\nexport function withHeadroom<T extends OpenAILike>(\n client: T,\n options: CompressOptions = {},\n): T {\n const originalCreate = client.chat.completions.create.bind(\n client.chat.completions,\n );\n\n const compressedCreate = async (params: any) => {\n const messages: OpenAIMessage[] = params.messages;\n const model = options.model ?? params.model ?? \"gpt-4o\";\n\n const result = await compress(messages, { ...options, model });\n\n return originalCreate({\n ...params,\n messages: result.messages,\n });\n };\n\n const completionsProxy = new Proxy(client.chat.completions, {\n get(target, prop) {\n if (prop === \"create\") return compressedCreate;\n return (target as any)[prop];\n },\n });\n\n const chatProxy = new Proxy(client.chat, {\n get(target, prop) {\n if (prop === \"completions\") return completionsProxy;\n return (target as any)[prop];\n },\n });\n\n return new Proxy(client, {\n get(target, prop) {\n if (prop === \"chat\") return chatProxy;\n return (target as any)[prop];\n },\n }) as T;\n}\n"]}
|