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
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunk3N25JEWK_cjs = require('../chunk-3N25JEWK.cjs');
|
|
4
|
+
|
|
5
|
+
// src/adapters/vercel-ai.ts
|
|
6
|
+
function headroomMiddleware(options = {}) {
|
|
7
|
+
return {
|
|
8
|
+
transformParams: async ({
|
|
9
|
+
params
|
|
10
|
+
}) => {
|
|
11
|
+
const prompt = params.prompt;
|
|
12
|
+
if (!prompt || prompt.length === 0) return params;
|
|
13
|
+
const model = options.model ?? params.modelId ?? "gpt-4o";
|
|
14
|
+
const openaiMessages = chunk3N25JEWK_cjs.vercelToOpenAI(prompt);
|
|
15
|
+
const result = await chunk3N25JEWK_cjs.compress(openaiMessages, { ...options, model });
|
|
16
|
+
if (!result.compressed) return params;
|
|
17
|
+
const compressedPrompt = chunk3N25JEWK_cjs.openAIToVercel(result.messages);
|
|
18
|
+
return { ...params, prompt: compressedPrompt };
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
async function compressVercelMessages(messages, options = {}) {
|
|
23
|
+
const openaiMessages = chunk3N25JEWK_cjs.vercelToOpenAI(messages);
|
|
24
|
+
const result = await chunk3N25JEWK_cjs.compress(openaiMessages, options);
|
|
25
|
+
const vercelMessages = chunk3N25JEWK_cjs.openAIToVercel(result.messages);
|
|
26
|
+
return {
|
|
27
|
+
...result,
|
|
28
|
+
messages: vercelMessages
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
exports.compressVercelMessages = compressVercelMessages;
|
|
33
|
+
exports.headroomMiddleware = headroomMiddleware;
|
|
34
|
+
//# sourceMappingURL=vercel-ai.cjs.map
|
|
35
|
+
//# sourceMappingURL=vercel-ai.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/vercel-ai.ts"],"names":["vercelToOpenAI","compress","openAIToVercel"],"mappings":";;;;;AAuBO,SAAS,kBAAA,CAAmB,OAAA,GAA2B,EAAC,EAAG;AAChE,EAAA,OAAO;AAAA,IACL,iBAAiB,OAAO;AAAA,MACtB;AAAA,KACF,KAIM;AACJ,MAAA,MAAM,SAA0B,MAAA,CAAO,MAAA;AACvC,MAAA,IAAI,CAAC,MAAA,IAAU,MAAA,CAAO,MAAA,KAAW,GAAG,OAAO,MAAA;AAE3C,MAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,IAAS,MAAA,CAAO,OAAA,IAAW,QAAA;AAGjD,MAAA,MAAM,cAAA,GAAiBA,iCAAe,MAAM,CAAA;AAG5C,MAAA,MAAM,MAAA,GAAS,MAAMC,0BAAA,CAAS,cAAA,EAAgB,EAAE,GAAG,OAAA,EAAS,OAAO,CAAA;AAEnE,MAAA,IAAI,CAAC,MAAA,CAAO,UAAA,EAAY,OAAO,MAAA;AAG/B,MAAA,MAAM,gBAAA,GAAmBC,gCAAA,CAAe,MAAA,CAAO,QAAQ,CAAA;AAEvD,MAAA,OAAO,EAAE,GAAG,MAAA,EAAQ,MAAA,EAAQ,gBAAA,EAAiB;AAAA,IAC/C;AAAA,GACF;AACF;AAMA,eAAsB,sBAAA,CACpB,QAAA,EACA,OAAA,GAA2B,EAAC,EAC6B;AACzD,EAAA,MAAM,cAAA,GAAiBF,iCAAe,QAAQ,CAAA;AAC9C,EAAA,MAAM,MAAA,GAAS,MAAMC,0BAAA,CAAS,cAAA,EAAgB,OAAO,CAAA;AACrD,EAAA,MAAM,cAAA,GAAiBC,gCAAA,CAAe,MAAA,CAAO,QAAQ,CAAA;AAErD,EAAA,OAAO;AAAA,IACL,GAAG,MAAA;AAAA,IACH,QAAA,EAAU;AAAA,GACZ;AACF","file":"vercel-ai.cjs","sourcesContent":["import { compress } from \"../compress.js\";\nimport type { CompressOptions, CompressResult } from \"../types.js\";\nimport { vercelToOpenAI, openAIToVercel } from \"../utils/format.js\";\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\ntype VercelMessage = any;\n\n/**\n * Vercel AI SDK LanguageModelV3Middleware that compresses messages\n * before they reach the LLM.\n *\n * @example\n * ```typescript\n * import { headroomMiddleware } from 'headroom-ai/vercel-ai';\n * import { wrapLanguageModel } from 'ai';\n * import { openai } from '@ai-sdk/openai';\n *\n * const model = wrapLanguageModel({\n * model: openai('gpt-4o'),\n * middleware: headroomMiddleware(),\n * });\n * ```\n */\nexport function headroomMiddleware(options: CompressOptions = {}) {\n return {\n transformParams: async ({\n params,\n }: {\n params: any;\n model: any;\n type: string;\n }) => {\n const prompt: VercelMessage[] = params.prompt;\n if (!prompt || prompt.length === 0) return params;\n\n const model = options.model ?? params.modelId ?? \"gpt-4o\";\n\n // Convert Vercel format → OpenAI format\n const openaiMessages = vercelToOpenAI(prompt);\n\n // Compress via Headroom\n const result = await compress(openaiMessages, { ...options, model });\n\n if (!result.compressed) return params;\n\n // Convert back to Vercel format\n const compressedPrompt = openAIToVercel(result.messages);\n\n return { ...params, prompt: compressedPrompt };\n },\n };\n}\n\n/**\n * Standalone: compress Vercel AI SDK ModelMessage[] directly.\n * Returns compressed messages in Vercel format + compression stats.\n */\nexport async function compressVercelMessages(\n messages: VercelMessage[],\n options: CompressOptions = {},\n): Promise<CompressResult & { messages: VercelMessage[] }> {\n const openaiMessages = vercelToOpenAI(messages);\n const result = await compress(openaiMessages, options);\n const vercelMessages = openAIToVercel(result.messages);\n\n return {\n ...result,\n messages: vercelMessages,\n };\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { C as CompressOptions, a as CompressResult } from '../types-DQtcLXq3.cjs';
|
|
2
|
+
|
|
3
|
+
type VercelMessage = any;
|
|
4
|
+
/**
|
|
5
|
+
* Vercel AI SDK LanguageModelV3Middleware that compresses messages
|
|
6
|
+
* before they reach the LLM.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { headroomMiddleware } from 'headroom-ai/vercel-ai';
|
|
11
|
+
* import { wrapLanguageModel } from 'ai';
|
|
12
|
+
* import { openai } from '@ai-sdk/openai';
|
|
13
|
+
*
|
|
14
|
+
* const model = wrapLanguageModel({
|
|
15
|
+
* model: openai('gpt-4o'),
|
|
16
|
+
* middleware: headroomMiddleware(),
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
declare function headroomMiddleware(options?: CompressOptions): {
|
|
21
|
+
transformParams: ({ params, }: {
|
|
22
|
+
params: any;
|
|
23
|
+
model: any;
|
|
24
|
+
type: string;
|
|
25
|
+
}) => Promise<any>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Standalone: compress Vercel AI SDK ModelMessage[] directly.
|
|
29
|
+
* Returns compressed messages in Vercel format + compression stats.
|
|
30
|
+
*/
|
|
31
|
+
declare function compressVercelMessages(messages: VercelMessage[], options?: CompressOptions): Promise<CompressResult & {
|
|
32
|
+
messages: VercelMessage[];
|
|
33
|
+
}>;
|
|
34
|
+
|
|
35
|
+
export { compressVercelMessages, headroomMiddleware };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { C as CompressOptions, a as CompressResult } from '../types-DQtcLXq3.js';
|
|
2
|
+
|
|
3
|
+
type VercelMessage = any;
|
|
4
|
+
/**
|
|
5
|
+
* Vercel AI SDK LanguageModelV3Middleware that compresses messages
|
|
6
|
+
* before they reach the LLM.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { headroomMiddleware } from 'headroom-ai/vercel-ai';
|
|
11
|
+
* import { wrapLanguageModel } from 'ai';
|
|
12
|
+
* import { openai } from '@ai-sdk/openai';
|
|
13
|
+
*
|
|
14
|
+
* const model = wrapLanguageModel({
|
|
15
|
+
* model: openai('gpt-4o'),
|
|
16
|
+
* middleware: headroomMiddleware(),
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
declare function headroomMiddleware(options?: CompressOptions): {
|
|
21
|
+
transformParams: ({ params, }: {
|
|
22
|
+
params: any;
|
|
23
|
+
model: any;
|
|
24
|
+
type: string;
|
|
25
|
+
}) => Promise<any>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Standalone: compress Vercel AI SDK ModelMessage[] directly.
|
|
29
|
+
* Returns compressed messages in Vercel format + compression stats.
|
|
30
|
+
*/
|
|
31
|
+
declare function compressVercelMessages(messages: VercelMessage[], options?: CompressOptions): Promise<CompressResult & {
|
|
32
|
+
messages: VercelMessage[];
|
|
33
|
+
}>;
|
|
34
|
+
|
|
35
|
+
export { compressVercelMessages, headroomMiddleware };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { vercelToOpenAI, compress, openAIToVercel } from '../chunk-YTTW7S2Q.js';
|
|
2
|
+
|
|
3
|
+
// src/adapters/vercel-ai.ts
|
|
4
|
+
function headroomMiddleware(options = {}) {
|
|
5
|
+
return {
|
|
6
|
+
transformParams: async ({
|
|
7
|
+
params
|
|
8
|
+
}) => {
|
|
9
|
+
const prompt = params.prompt;
|
|
10
|
+
if (!prompt || prompt.length === 0) return params;
|
|
11
|
+
const model = options.model ?? params.modelId ?? "gpt-4o";
|
|
12
|
+
const openaiMessages = vercelToOpenAI(prompt);
|
|
13
|
+
const result = await compress(openaiMessages, { ...options, model });
|
|
14
|
+
if (!result.compressed) return params;
|
|
15
|
+
const compressedPrompt = openAIToVercel(result.messages);
|
|
16
|
+
return { ...params, prompt: compressedPrompt };
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async function compressVercelMessages(messages, options = {}) {
|
|
21
|
+
const openaiMessages = vercelToOpenAI(messages);
|
|
22
|
+
const result = await compress(openaiMessages, options);
|
|
23
|
+
const vercelMessages = openAIToVercel(result.messages);
|
|
24
|
+
return {
|
|
25
|
+
...result,
|
|
26
|
+
messages: vercelMessages
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { compressVercelMessages, headroomMiddleware };
|
|
31
|
+
//# sourceMappingURL=vercel-ai.js.map
|
|
32
|
+
//# sourceMappingURL=vercel-ai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/vercel-ai.ts"],"names":[],"mappings":";;;AAuBO,SAAS,kBAAA,CAAmB,OAAA,GAA2B,EAAC,EAAG;AAChE,EAAA,OAAO;AAAA,IACL,iBAAiB,OAAO;AAAA,MACtB;AAAA,KACF,KAIM;AACJ,MAAA,MAAM,SAA0B,MAAA,CAAO,MAAA;AACvC,MAAA,IAAI,CAAC,MAAA,IAAU,MAAA,CAAO,MAAA,KAAW,GAAG,OAAO,MAAA;AAE3C,MAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,IAAS,MAAA,CAAO,OAAA,IAAW,QAAA;AAGjD,MAAA,MAAM,cAAA,GAAiB,eAAe,MAAM,CAAA;AAG5C,MAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,cAAA,EAAgB,EAAE,GAAG,OAAA,EAAS,OAAO,CAAA;AAEnE,MAAA,IAAI,CAAC,MAAA,CAAO,UAAA,EAAY,OAAO,MAAA;AAG/B,MAAA,MAAM,gBAAA,GAAmB,cAAA,CAAe,MAAA,CAAO,QAAQ,CAAA;AAEvD,MAAA,OAAO,EAAE,GAAG,MAAA,EAAQ,MAAA,EAAQ,gBAAA,EAAiB;AAAA,IAC/C;AAAA,GACF;AACF;AAMA,eAAsB,sBAAA,CACpB,QAAA,EACA,OAAA,GAA2B,EAAC,EAC6B;AACzD,EAAA,MAAM,cAAA,GAAiB,eAAe,QAAQ,CAAA;AAC9C,EAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,cAAA,EAAgB,OAAO,CAAA;AACrD,EAAA,MAAM,cAAA,GAAiB,cAAA,CAAe,MAAA,CAAO,QAAQ,CAAA;AAErD,EAAA,OAAO;AAAA,IACL,GAAG,MAAA;AAAA,IACH,QAAA,EAAU;AAAA,GACZ;AACF","file":"vercel-ai.js","sourcesContent":["import { compress } from \"../compress.js\";\nimport type { CompressOptions, CompressResult } from \"../types.js\";\nimport { vercelToOpenAI, openAIToVercel } from \"../utils/format.js\";\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\ntype VercelMessage = any;\n\n/**\n * Vercel AI SDK LanguageModelV3Middleware that compresses messages\n * before they reach the LLM.\n *\n * @example\n * ```typescript\n * import { headroomMiddleware } from 'headroom-ai/vercel-ai';\n * import { wrapLanguageModel } from 'ai';\n * import { openai } from '@ai-sdk/openai';\n *\n * const model = wrapLanguageModel({\n * model: openai('gpt-4o'),\n * middleware: headroomMiddleware(),\n * });\n * ```\n */\nexport function headroomMiddleware(options: CompressOptions = {}) {\n return {\n transformParams: async ({\n params,\n }: {\n params: any;\n model: any;\n type: string;\n }) => {\n const prompt: VercelMessage[] = params.prompt;\n if (!prompt || prompt.length === 0) return params;\n\n const model = options.model ?? params.modelId ?? \"gpt-4o\";\n\n // Convert Vercel format → OpenAI format\n const openaiMessages = vercelToOpenAI(prompt);\n\n // Compress via Headroom\n const result = await compress(openaiMessages, { ...options, model });\n\n if (!result.compressed) return params;\n\n // Convert back to Vercel format\n const compressedPrompt = openAIToVercel(result.messages);\n\n return { ...params, prompt: compressedPrompt };\n },\n };\n}\n\n/**\n * Standalone: compress Vercel AI SDK ModelMessage[] directly.\n * Returns compressed messages in Vercel format + compression stats.\n */\nexport async function compressVercelMessages(\n messages: VercelMessage[],\n options: CompressOptions = {},\n): Promise<CompressResult & { messages: VercelMessage[] }> {\n const openaiMessages = vercelToOpenAI(messages);\n const result = await compress(openaiMessages, options);\n const vercelMessages = openAIToVercel(result.messages);\n\n return {\n ...result,\n messages: vercelMessages,\n };\n}\n"]}
|
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/types.ts
|
|
4
|
+
var HeadroomError = class extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "HeadroomError";
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
var HeadroomConnectionError = class extends HeadroomError {
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "HeadroomConnectionError";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var HeadroomAuthError = class extends HeadroomError {
|
|
17
|
+
constructor(message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "HeadroomAuthError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var HeadroomCompressError = class extends HeadroomError {
|
|
23
|
+
statusCode;
|
|
24
|
+
errorType;
|
|
25
|
+
constructor(statusCode, errorType, message) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = "HeadroomCompressError";
|
|
28
|
+
this.statusCode = statusCode;
|
|
29
|
+
this.errorType = errorType;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// src/client.ts
|
|
34
|
+
var DEFAULT_BASE_URL = "http://localhost:8787";
|
|
35
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
36
|
+
var DEFAULT_RETRIES = 1;
|
|
37
|
+
function getEnv(key) {
|
|
38
|
+
if (typeof process !== "undefined" && process.env) {
|
|
39
|
+
return process.env[key];
|
|
40
|
+
}
|
|
41
|
+
return void 0;
|
|
42
|
+
}
|
|
43
|
+
function makeFallbackResult(messages) {
|
|
44
|
+
return {
|
|
45
|
+
messages,
|
|
46
|
+
tokensBefore: 0,
|
|
47
|
+
tokensAfter: 0,
|
|
48
|
+
tokensSaved: 0,
|
|
49
|
+
compressionRatio: 1,
|
|
50
|
+
transformsApplied: [],
|
|
51
|
+
ccrHashes: [],
|
|
52
|
+
compressed: false
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
var HeadroomClient = class {
|
|
56
|
+
baseUrl;
|
|
57
|
+
apiKey;
|
|
58
|
+
timeout;
|
|
59
|
+
fallback;
|
|
60
|
+
retries;
|
|
61
|
+
constructor(options = {}) {
|
|
62
|
+
this.baseUrl = (options.baseUrl ?? getEnv("HEADROOM_BASE_URL") ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
63
|
+
this.apiKey = options.apiKey ?? getEnv("HEADROOM_API_KEY");
|
|
64
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
65
|
+
this.fallback = options.fallback ?? true;
|
|
66
|
+
this.retries = options.retries ?? DEFAULT_RETRIES;
|
|
67
|
+
}
|
|
68
|
+
async compress(messages, options = {}) {
|
|
69
|
+
const model = options.model ?? "gpt-4o";
|
|
70
|
+
let lastError;
|
|
71
|
+
const maxAttempts = 1 + this.retries;
|
|
72
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
73
|
+
try {
|
|
74
|
+
return await this._doCompress(messages, model);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
lastError = error;
|
|
77
|
+
if (error instanceof HeadroomAuthError) throw error;
|
|
78
|
+
if (error instanceof HeadroomCompressError && error.statusCode < 500) {
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (this.fallback) {
|
|
84
|
+
return makeFallbackResult(messages);
|
|
85
|
+
}
|
|
86
|
+
if (lastError instanceof HeadroomConnectionError) throw lastError;
|
|
87
|
+
if (lastError instanceof HeadroomCompressError) throw lastError;
|
|
88
|
+
throw new HeadroomConnectionError(
|
|
89
|
+
`Failed after ${maxAttempts} attempts: ${lastError}`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
async _doCompress(messages, model) {
|
|
93
|
+
const url = `${this.baseUrl}/v1/compress`;
|
|
94
|
+
const headers = {
|
|
95
|
+
"Content-Type": "application/json"
|
|
96
|
+
};
|
|
97
|
+
if (this.apiKey) {
|
|
98
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
99
|
+
}
|
|
100
|
+
let response;
|
|
101
|
+
try {
|
|
102
|
+
response = await fetch(url, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers,
|
|
105
|
+
body: JSON.stringify({ messages, model }),
|
|
106
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
107
|
+
});
|
|
108
|
+
} catch (error) {
|
|
109
|
+
throw new HeadroomConnectionError(
|
|
110
|
+
`Failed to connect to Headroom at ${this.baseUrl}: ${error}`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
if (!response.ok) {
|
|
114
|
+
let errorBody;
|
|
115
|
+
try {
|
|
116
|
+
errorBody = await response.json();
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
const errorType = errorBody?.error?.type ?? "unknown";
|
|
120
|
+
const errorMessage = errorBody?.error?.message ?? `HTTP ${response.status}`;
|
|
121
|
+
if (response.status === 401) {
|
|
122
|
+
throw new HeadroomAuthError(errorMessage);
|
|
123
|
+
}
|
|
124
|
+
throw new HeadroomCompressError(response.status, errorType, errorMessage);
|
|
125
|
+
}
|
|
126
|
+
const data = await response.json();
|
|
127
|
+
return {
|
|
128
|
+
messages: data.messages,
|
|
129
|
+
tokensBefore: data.tokens_before,
|
|
130
|
+
tokensAfter: data.tokens_after,
|
|
131
|
+
tokensSaved: data.tokens_saved,
|
|
132
|
+
compressionRatio: data.compression_ratio,
|
|
133
|
+
transformsApplied: data.transforms_applied,
|
|
134
|
+
ccrHashes: data.ccr_hashes,
|
|
135
|
+
compressed: true
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// src/utils/format.ts
|
|
141
|
+
function detectFormat(messages) {
|
|
142
|
+
for (const msg of messages) {
|
|
143
|
+
if ("parts" in msg && !("content" in msg)) return "gemini";
|
|
144
|
+
if (msg.role === "model") return "gemini";
|
|
145
|
+
if (msg.tool_calls && msg.role === "assistant") return "openai";
|
|
146
|
+
if (msg.role === "tool" && "tool_call_id" in msg && typeof msg.content === "string") return "openai";
|
|
147
|
+
if (Array.isArray(msg.content)) {
|
|
148
|
+
for (const part of msg.content) {
|
|
149
|
+
if (part.type === "tool-call" || part.type === "tool-result") return "vercel";
|
|
150
|
+
if (part.type === "tool_use" || part.type === "tool_result") return "anthropic";
|
|
151
|
+
if (part.type === "image" && part.source?.type) return "anthropic";
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return "openai";
|
|
156
|
+
}
|
|
157
|
+
function anthropicToOpenAI(messages) {
|
|
158
|
+
const result = [];
|
|
159
|
+
for (const msg of messages) {
|
|
160
|
+
if (msg.role === "user") {
|
|
161
|
+
if (typeof msg.content === "string") {
|
|
162
|
+
result.push({ role: "user", content: msg.content });
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (Array.isArray(msg.content)) {
|
|
166
|
+
const textBlocks = msg.content.filter((b) => b.type === "text");
|
|
167
|
+
const toolResults = msg.content.filter((b) => b.type === "tool_result");
|
|
168
|
+
if (textBlocks.length > 0) {
|
|
169
|
+
result.push({
|
|
170
|
+
role: "user",
|
|
171
|
+
content: textBlocks.map((b) => b.text).join("\n")
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
for (const tr of toolResults) {
|
|
175
|
+
const content = typeof tr.content === "string" ? tr.content : Array.isArray(tr.content) ? tr.content.map((b) => b.text ?? JSON.stringify(b)).join("\n") : JSON.stringify(tr.content);
|
|
176
|
+
result.push({
|
|
177
|
+
role: "tool",
|
|
178
|
+
content,
|
|
179
|
+
tool_call_id: tr.tool_use_id
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (msg.role === "assistant") {
|
|
186
|
+
if (typeof msg.content === "string") {
|
|
187
|
+
result.push({ role: "assistant", content: msg.content });
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (Array.isArray(msg.content)) {
|
|
191
|
+
const textBlocks = msg.content.filter((b) => b.type === "text");
|
|
192
|
+
const toolUseBlocks = msg.content.filter((b) => b.type === "tool_use");
|
|
193
|
+
const content = textBlocks.length > 0 ? textBlocks.map((b) => b.text).join("\n") : null;
|
|
194
|
+
const openaiMsg = { role: "assistant", content };
|
|
195
|
+
if (toolUseBlocks.length > 0) {
|
|
196
|
+
openaiMsg.tool_calls = toolUseBlocks.map((b) => ({
|
|
197
|
+
id: b.id,
|
|
198
|
+
type: "function",
|
|
199
|
+
function: {
|
|
200
|
+
name: b.name,
|
|
201
|
+
arguments: typeof b.input === "string" ? b.input : JSON.stringify(b.input)
|
|
202
|
+
}
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
205
|
+
result.push(openaiMsg);
|
|
206
|
+
}
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
function openAIToAnthropic(messages) {
|
|
213
|
+
const result = [];
|
|
214
|
+
for (const msg of messages) {
|
|
215
|
+
if (msg.role === "system") {
|
|
216
|
+
result.push({ role: "user", content: msg.content });
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (msg.role === "user") {
|
|
220
|
+
if (typeof msg.content === "string") {
|
|
221
|
+
result.push({ role: "user", content: msg.content });
|
|
222
|
+
} else if (Array.isArray(msg.content)) {
|
|
223
|
+
result.push({
|
|
224
|
+
role: "user",
|
|
225
|
+
content: msg.content.map(
|
|
226
|
+
(p) => p.type === "text" ? { type: "text", text: p.text } : { type: "text", text: "" }
|
|
227
|
+
)
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (msg.role === "assistant") {
|
|
233
|
+
const blocks = [];
|
|
234
|
+
if (msg.content) blocks.push({ type: "text", text: msg.content });
|
|
235
|
+
if (msg.tool_calls) {
|
|
236
|
+
for (const tc of msg.tool_calls) {
|
|
237
|
+
blocks.push({
|
|
238
|
+
type: "tool_use",
|
|
239
|
+
id: tc.id,
|
|
240
|
+
name: tc.function.name,
|
|
241
|
+
input: JSON.parse(tc.function.arguments)
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
result.push({
|
|
246
|
+
role: "assistant",
|
|
247
|
+
content: blocks.length === 1 && blocks[0].type === "text" ? blocks[0].text : blocks
|
|
248
|
+
});
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (msg.role === "tool") {
|
|
252
|
+
result.push({
|
|
253
|
+
role: "user",
|
|
254
|
+
content: [
|
|
255
|
+
{ type: "tool_result", tool_use_id: msg.tool_call_id, content: msg.content }
|
|
256
|
+
]
|
|
257
|
+
});
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
function vercelToOpenAI(messages) {
|
|
264
|
+
const result = [];
|
|
265
|
+
for (const msg of messages) {
|
|
266
|
+
if (msg.role === "system") {
|
|
267
|
+
result.push({ role: "system", content: typeof msg.content === "string" ? msg.content : String(msg.content) });
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (msg.role === "user") {
|
|
271
|
+
if (typeof msg.content === "string") {
|
|
272
|
+
result.push({ role: "user", content: msg.content });
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const parts = Array.isArray(msg.content) ? msg.content : [];
|
|
276
|
+
const textParts = parts.filter((p) => p.type === "text");
|
|
277
|
+
const imageParts = parts.filter((p) => p.type === "image");
|
|
278
|
+
if (imageParts.length === 0 && textParts.length > 0) {
|
|
279
|
+
result.push({ role: "user", content: textParts.map((p) => p.text).join("") });
|
|
280
|
+
} else {
|
|
281
|
+
const openaiParts = parts.filter((p) => p.type === "text" || p.type === "image").map((p) => {
|
|
282
|
+
if (p.type === "text") return { type: "text", text: p.text };
|
|
283
|
+
if (p.type === "image") {
|
|
284
|
+
const url = p.image instanceof URL ? p.image.toString() : String(p.image);
|
|
285
|
+
return { type: "image_url", image_url: { url } };
|
|
286
|
+
}
|
|
287
|
+
return { type: "text", text: "" };
|
|
288
|
+
});
|
|
289
|
+
result.push({ role: "user", content: openaiParts });
|
|
290
|
+
}
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (msg.role === "assistant") {
|
|
294
|
+
if (typeof msg.content === "string") {
|
|
295
|
+
result.push({ role: "assistant", content: msg.content });
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const parts = Array.isArray(msg.content) ? msg.content : [];
|
|
299
|
+
const textParts = parts.filter((p) => p.type === "text");
|
|
300
|
+
const toolCallParts = parts.filter((p) => p.type === "tool-call");
|
|
301
|
+
const content = textParts.length > 0 ? textParts.map((p) => p.text).join("") : null;
|
|
302
|
+
const openaiMsg = { role: "assistant", content };
|
|
303
|
+
if (toolCallParts.length > 0) {
|
|
304
|
+
openaiMsg.tool_calls = toolCallParts.map((p) => ({
|
|
305
|
+
id: p.toolCallId,
|
|
306
|
+
type: "function",
|
|
307
|
+
// AI SDK v6 uses `input`, earlier versions used `args`
|
|
308
|
+
function: { name: p.toolName, arguments: JSON.stringify(p.input ?? p.args) }
|
|
309
|
+
}));
|
|
310
|
+
}
|
|
311
|
+
result.push(openaiMsg);
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (msg.role === "tool") {
|
|
315
|
+
const parts = Array.isArray(msg.content) ? msg.content : [];
|
|
316
|
+
for (const part of parts) {
|
|
317
|
+
if (part.type === "tool-result") {
|
|
318
|
+
let contentStr;
|
|
319
|
+
if (part.output !== void 0) {
|
|
320
|
+
const val = part.output?.value ?? part.output;
|
|
321
|
+
contentStr = typeof val === "string" ? val : JSON.stringify(val);
|
|
322
|
+
} else if (part.result !== void 0) {
|
|
323
|
+
contentStr = typeof part.result === "string" ? part.result : JSON.stringify(part.result);
|
|
324
|
+
} else {
|
|
325
|
+
contentStr = "";
|
|
326
|
+
}
|
|
327
|
+
result.push({
|
|
328
|
+
role: "tool",
|
|
329
|
+
content: contentStr,
|
|
330
|
+
tool_call_id: part.toolCallId
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return result;
|
|
338
|
+
}
|
|
339
|
+
function openAIToVercel(messages) {
|
|
340
|
+
const result = [];
|
|
341
|
+
for (const msg of messages) {
|
|
342
|
+
if (msg.role === "system") {
|
|
343
|
+
result.push({ role: "system", content: msg.content });
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (msg.role === "user") {
|
|
347
|
+
if (typeof msg.content === "string") {
|
|
348
|
+
result.push({ role: "user", content: [{ type: "text", text: msg.content }] });
|
|
349
|
+
} else if (Array.isArray(msg.content)) {
|
|
350
|
+
const parts = msg.content.map((p) => {
|
|
351
|
+
if (p.type === "text") return { type: "text", text: p.text };
|
|
352
|
+
if (p.type === "image_url") return { type: "image", image: new URL(p.image_url.url) };
|
|
353
|
+
return { type: "text", text: "" };
|
|
354
|
+
});
|
|
355
|
+
result.push({ role: "user", content: parts });
|
|
356
|
+
}
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
if (msg.role === "assistant") {
|
|
360
|
+
const parts = [];
|
|
361
|
+
if (msg.content) parts.push({ type: "text", text: msg.content });
|
|
362
|
+
if (msg.tool_calls) {
|
|
363
|
+
for (const tc of msg.tool_calls) {
|
|
364
|
+
let input;
|
|
365
|
+
try {
|
|
366
|
+
input = JSON.parse(tc.function.arguments);
|
|
367
|
+
} catch {
|
|
368
|
+
input = tc.function.arguments ?? {};
|
|
369
|
+
}
|
|
370
|
+
parts.push({
|
|
371
|
+
type: "tool-call",
|
|
372
|
+
toolCallId: tc.id,
|
|
373
|
+
toolName: tc.function.name,
|
|
374
|
+
input
|
|
375
|
+
// AI SDK v6 uses `input`, not `args`
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
result.push({ role: "assistant", content: parts });
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
if (msg.role === "tool") {
|
|
383
|
+
let parsed;
|
|
384
|
+
try {
|
|
385
|
+
parsed = JSON.parse(msg.content);
|
|
386
|
+
} catch {
|
|
387
|
+
parsed = msg.content;
|
|
388
|
+
}
|
|
389
|
+
const output = typeof parsed === "string" ? { type: "text", value: parsed } : { type: "json", value: parsed };
|
|
390
|
+
result.push({
|
|
391
|
+
role: "tool",
|
|
392
|
+
content: [{
|
|
393
|
+
type: "tool-result",
|
|
394
|
+
toolCallId: msg.tool_call_id,
|
|
395
|
+
toolName: "unknown",
|
|
396
|
+
output
|
|
397
|
+
}]
|
|
398
|
+
});
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return result;
|
|
403
|
+
}
|
|
404
|
+
function geminiToOpenAI(messages) {
|
|
405
|
+
const result = [];
|
|
406
|
+
for (const msg of messages) {
|
|
407
|
+
const role = msg.role === "model" ? "assistant" : "user";
|
|
408
|
+
const parts = msg.parts ?? [];
|
|
409
|
+
if (role === "user") {
|
|
410
|
+
const funcResponses = parts.filter((p) => p.functionResponse);
|
|
411
|
+
const textParts = parts.filter((p) => p.text !== void 0);
|
|
412
|
+
if (textParts.length > 0) {
|
|
413
|
+
result.push({ role: "user", content: textParts.map((p) => p.text).join("\n") });
|
|
414
|
+
}
|
|
415
|
+
for (const fr of funcResponses) {
|
|
416
|
+
result.push({
|
|
417
|
+
role: "tool",
|
|
418
|
+
content: JSON.stringify(fr.functionResponse.response),
|
|
419
|
+
tool_call_id: `gemini_${fr.functionResponse.name}`
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
if (role === "assistant") {
|
|
425
|
+
const textParts = parts.filter((p) => p.text !== void 0);
|
|
426
|
+
const funcCalls = parts.filter((p) => p.functionCall);
|
|
427
|
+
const content = textParts.length > 0 ? textParts.map((p) => p.text).join("\n") : null;
|
|
428
|
+
const openaiMsg = { role: "assistant", content };
|
|
429
|
+
if (funcCalls.length > 0) {
|
|
430
|
+
openaiMsg.tool_calls = funcCalls.map((p) => ({
|
|
431
|
+
id: `gemini_${p.functionCall.name}`,
|
|
432
|
+
type: "function",
|
|
433
|
+
function: {
|
|
434
|
+
name: p.functionCall.name,
|
|
435
|
+
arguments: JSON.stringify(p.functionCall.args)
|
|
436
|
+
}
|
|
437
|
+
}));
|
|
438
|
+
}
|
|
439
|
+
result.push(openaiMsg);
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return result;
|
|
444
|
+
}
|
|
445
|
+
function openAIToGemini(messages) {
|
|
446
|
+
const result = [];
|
|
447
|
+
for (const msg of messages) {
|
|
448
|
+
if (msg.role === "system") {
|
|
449
|
+
result.push({ role: "user", parts: [{ text: msg.content }] });
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (msg.role === "user") {
|
|
453
|
+
const text = typeof msg.content === "string" ? msg.content : (msg.content ?? []).filter((p) => p.type === "text").map((p) => p.text).join("\n");
|
|
454
|
+
result.push({ role: "user", parts: [{ text }] });
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
if (msg.role === "assistant") {
|
|
458
|
+
const parts = [];
|
|
459
|
+
if (msg.content) parts.push({ text: msg.content });
|
|
460
|
+
if (msg.tool_calls) {
|
|
461
|
+
for (const tc of msg.tool_calls) {
|
|
462
|
+
parts.push({
|
|
463
|
+
functionCall: { name: tc.function.name, args: JSON.parse(tc.function.arguments) }
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
result.push({ role: "model", parts });
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
if (msg.role === "tool") {
|
|
471
|
+
let response;
|
|
472
|
+
try {
|
|
473
|
+
response = JSON.parse(msg.content);
|
|
474
|
+
} catch {
|
|
475
|
+
response = { result: msg.content };
|
|
476
|
+
}
|
|
477
|
+
result.push({
|
|
478
|
+
role: "user",
|
|
479
|
+
parts: [{ functionResponse: { name: msg.tool_call_id?.replace("gemini_", "") ?? "unknown", response } }]
|
|
480
|
+
});
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return result;
|
|
485
|
+
}
|
|
486
|
+
function toOpenAI(messages) {
|
|
487
|
+
const format = detectFormat(messages);
|
|
488
|
+
switch (format) {
|
|
489
|
+
case "openai":
|
|
490
|
+
return messages;
|
|
491
|
+
case "anthropic":
|
|
492
|
+
return anthropicToOpenAI(messages);
|
|
493
|
+
case "vercel":
|
|
494
|
+
return vercelToOpenAI(messages);
|
|
495
|
+
case "gemini":
|
|
496
|
+
return geminiToOpenAI(messages);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
function fromOpenAI(messages, targetFormat) {
|
|
500
|
+
switch (targetFormat) {
|
|
501
|
+
case "openai":
|
|
502
|
+
return messages;
|
|
503
|
+
case "anthropic":
|
|
504
|
+
return openAIToAnthropic(messages);
|
|
505
|
+
case "vercel":
|
|
506
|
+
return openAIToVercel(messages);
|
|
507
|
+
case "gemini":
|
|
508
|
+
return openAIToGemini(messages);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// src/compress.ts
|
|
513
|
+
async function compress(messages, options = {}) {
|
|
514
|
+
const { client: providedClient, model, ...clientOptions } = options;
|
|
515
|
+
const inputFormat = detectFormat(messages);
|
|
516
|
+
const openaiMessages = toOpenAI(messages);
|
|
517
|
+
const client = providedClient ?? new HeadroomClient(clientOptions);
|
|
518
|
+
const result = await client.compress(openaiMessages, { model });
|
|
519
|
+
const outputMessages = fromOpenAI(result.messages, inputFormat);
|
|
520
|
+
return {
|
|
521
|
+
...result,
|
|
522
|
+
messages: outputMessages
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
exports.HeadroomAuthError = HeadroomAuthError;
|
|
527
|
+
exports.HeadroomClient = HeadroomClient;
|
|
528
|
+
exports.HeadroomCompressError = HeadroomCompressError;
|
|
529
|
+
exports.HeadroomConnectionError = HeadroomConnectionError;
|
|
530
|
+
exports.HeadroomError = HeadroomError;
|
|
531
|
+
exports.compress = compress;
|
|
532
|
+
exports.detectFormat = detectFormat;
|
|
533
|
+
exports.fromOpenAI = fromOpenAI;
|
|
534
|
+
exports.openAIToVercel = openAIToVercel;
|
|
535
|
+
exports.toOpenAI = toOpenAI;
|
|
536
|
+
exports.vercelToOpenAI = vercelToOpenAI;
|
|
537
|
+
//# sourceMappingURL=chunk-3N25JEWK.cjs.map
|
|
538
|
+
//# sourceMappingURL=chunk-3N25JEWK.cjs.map
|