@opencompress/opencompress 1.8.1 → 1.9.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/index.js +309 -342
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
6
6
|
});
|
|
7
7
|
|
|
8
8
|
// src/index.ts
|
|
9
|
-
var VERSION = "1.
|
|
9
|
+
var VERSION = "1.9.0";
|
|
10
10
|
var DEFAULT_BASE_URL = "https://www.opencompress.ai/api";
|
|
11
11
|
function getApiKey(api) {
|
|
12
12
|
const auth = api.config.auth;
|
|
@@ -35,87 +35,7 @@ function getApiKey(api) {
|
|
|
35
35
|
}
|
|
36
36
|
return void 0;
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
{ id: "gpt-4o", name: "GPT-4o", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128e3, maxTokens: 16384 },
|
|
40
|
-
{ id: "gpt-4o-mini", name: "GPT-4o Mini", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128e3, maxTokens: 16384 },
|
|
41
|
-
{ id: "gpt-4.1", name: "GPT-4.1", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 1047576, maxTokens: 32768 },
|
|
42
|
-
{ id: "gpt-4.1-mini", name: "GPT-4.1 Mini", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 1047576, maxTokens: 32768 },
|
|
43
|
-
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 2e5, maxTokens: 128e3 },
|
|
44
|
-
{ id: "claude-haiku-4-5-20251001", name: "Claude Haiku 4.5", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 2e5, maxTokens: 128e3 },
|
|
45
|
-
{ id: "gemini-2.5-pro", name: "Gemini 2.5 Pro", reasoning: true, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 1048576, maxTokens: 65536 },
|
|
46
|
-
{ id: "gemini-2.5-flash", name: "Gemini 2.5 Flash", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 1048576, maxTokens: 65536 },
|
|
47
|
-
{ id: "deepseek/deepseek-chat-v3-0324", name: "DeepSeek V3", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 131072, maxTokens: 8192 },
|
|
48
|
-
{ id: "deepseek/deepseek-reasoner", name: "DeepSeek Reasoner", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 131072, maxTokens: 8192 }
|
|
49
|
-
];
|
|
50
|
-
function readExistingModels(api) {
|
|
51
|
-
const providers = api.config.models?.providers;
|
|
52
|
-
if (!providers) return null;
|
|
53
|
-
const seen = /* @__PURE__ */ new Set();
|
|
54
|
-
const models = [];
|
|
55
|
-
for (const [providerId, providerConfig] of Object.entries(providers)) {
|
|
56
|
-
if (providerId === "opencompress") continue;
|
|
57
|
-
const providerModels = providerConfig.models || [];
|
|
58
|
-
for (const m of providerModels) {
|
|
59
|
-
if (m.name?.includes("\u2192")) continue;
|
|
60
|
-
const rawId = m.id.includes("/") ? m.id.split("/").slice(1).join("/") : m.id;
|
|
61
|
-
if (seen.has(rawId)) continue;
|
|
62
|
-
seen.add(rawId);
|
|
63
|
-
const upstreamId = m.id.startsWith(`${providerId}/`) ? rawId : m.id;
|
|
64
|
-
models.push({
|
|
65
|
-
...m,
|
|
66
|
-
id: upstreamId,
|
|
67
|
-
name: m.name || upstreamId,
|
|
68
|
-
// Zero out cost — billing handled by OpenCompress proxy
|
|
69
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return models.length > 0 ? models : null;
|
|
74
|
-
}
|
|
75
|
-
function buildProviderModels(baseUrl, models, apiKey) {
|
|
76
|
-
return {
|
|
77
|
-
baseUrl: `${baseUrl}/v1`,
|
|
78
|
-
api: "openai-completions",
|
|
79
|
-
apiKey: apiKey || void 0,
|
|
80
|
-
models: models || FALLBACK_MODELS
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
function persistModelsConfig(providerModels) {
|
|
84
|
-
try {
|
|
85
|
-
const os = __require("os");
|
|
86
|
-
const fs = __require("fs");
|
|
87
|
-
const path = __require("path");
|
|
88
|
-
const configPath = path.join(os.homedir(), ".openclaw", "openclaw.json");
|
|
89
|
-
if (!fs.existsSync(configPath)) return;
|
|
90
|
-
const raw = fs.readFileSync(configPath, "utf-8");
|
|
91
|
-
let config;
|
|
92
|
-
try {
|
|
93
|
-
config = JSON.parse(raw);
|
|
94
|
-
} catch {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
if (!config.models) config.models = {};
|
|
98
|
-
const models = config.models;
|
|
99
|
-
if (!models.providers) models.providers = {};
|
|
100
|
-
const providers = models.providers;
|
|
101
|
-
const configSafeModels = providerModels.models.map((m) => ({
|
|
102
|
-
id: m.id,
|
|
103
|
-
name: m.name
|
|
104
|
-
}));
|
|
105
|
-
const configEntry = {
|
|
106
|
-
baseUrl: providerModels.baseUrl,
|
|
107
|
-
api: providerModels.api || "openai-completions",
|
|
108
|
-
models: configSafeModels
|
|
109
|
-
};
|
|
110
|
-
if (providerModels.apiKey) {
|
|
111
|
-
configEntry.apiKey = providerModels.apiKey;
|
|
112
|
-
}
|
|
113
|
-
providers.opencompress = configEntry;
|
|
114
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
115
|
-
} catch {
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
function persistAgentModelsJson(providerModels) {
|
|
38
|
+
function persistAuthProfile(apiKey) {
|
|
119
39
|
try {
|
|
120
40
|
const os = __require("os");
|
|
121
41
|
const fs = __require("fs");
|
|
@@ -124,36 +44,27 @@ function persistAgentModelsJson(providerModels) {
|
|
|
124
44
|
if (!fs.existsSync(agentsDir)) return;
|
|
125
45
|
const agentDirs = fs.readdirSync(agentsDir);
|
|
126
46
|
for (const agent of agentDirs) {
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
if (!fs.existsSync(
|
|
130
|
-
|
|
131
|
-
|
|
47
|
+
const authPath = path.join(agentsDir, agent, "agent", "auth-profiles.json");
|
|
48
|
+
const authDir = path.dirname(authPath);
|
|
49
|
+
if (!fs.existsSync(authDir)) {
|
|
50
|
+
fs.mkdirSync(authDir, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
let profiles = {
|
|
53
|
+
version: 1,
|
|
54
|
+
profiles: {}
|
|
55
|
+
};
|
|
56
|
+
if (fs.existsSync(authPath)) {
|
|
132
57
|
try {
|
|
133
|
-
|
|
134
|
-
if (!data.providers || typeof data.providers !== "object") {
|
|
135
|
-
data.providers = {};
|
|
136
|
-
}
|
|
58
|
+
profiles = JSON.parse(fs.readFileSync(authPath, "utf-8"));
|
|
137
59
|
} catch {
|
|
138
|
-
data = { providers: {} };
|
|
139
60
|
}
|
|
140
61
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
models: providerModels.models.map((m) => ({
|
|
146
|
-
id: m.id,
|
|
147
|
-
name: m.name,
|
|
148
|
-
api: m.api || "openai-completions",
|
|
149
|
-
reasoning: m.reasoning ?? false,
|
|
150
|
-
input: m.input || ["text"],
|
|
151
|
-
cost: m.cost || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
152
|
-
contextWindow: m.contextWindow || 2e5,
|
|
153
|
-
maxTokens: m.maxTokens || 8192
|
|
154
|
-
}))
|
|
62
|
+
profiles.profiles["opencompress:default"] = {
|
|
63
|
+
type: "api_key",
|
|
64
|
+
provider: "opencompress",
|
|
65
|
+
key: apiKey
|
|
155
66
|
};
|
|
156
|
-
fs.writeFileSync(
|
|
67
|
+
fs.writeFileSync(authPath, JSON.stringify(profiles, null, 2) + "\n");
|
|
157
68
|
}
|
|
158
69
|
} catch {
|
|
159
70
|
}
|
|
@@ -187,125 +98,215 @@ function persistAgentAuthJson(apiKey) {
|
|
|
187
98
|
} catch {
|
|
188
99
|
}
|
|
189
100
|
}
|
|
190
|
-
function
|
|
101
|
+
function proxyStatePath() {
|
|
102
|
+
const os = __require("os");
|
|
103
|
+
const path = __require("path");
|
|
104
|
+
return path.join(os.homedir(), ".openclaw", "opencompress-proxy.json");
|
|
105
|
+
}
|
|
106
|
+
function readProxyState() {
|
|
191
107
|
try {
|
|
192
|
-
const os = __require("os");
|
|
193
108
|
const fs = __require("fs");
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const raw = fs.readFileSync(configPath, "utf-8");
|
|
198
|
-
let config;
|
|
199
|
-
try {
|
|
200
|
-
config = JSON.parse(raw);
|
|
201
|
-
} catch {
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
if (!config.agents) config.agents = {};
|
|
205
|
-
const agents = config.agents;
|
|
206
|
-
if (!agents.defaults) agents.defaults = {};
|
|
207
|
-
const defaults = agents.defaults;
|
|
208
|
-
if (!defaults.models) defaults.models = {};
|
|
209
|
-
const allowlist = defaults.models;
|
|
210
|
-
const existingKeys = Object.keys(allowlist);
|
|
211
|
-
if (existingKeys.length === 0) return;
|
|
212
|
-
let changed = false;
|
|
213
|
-
for (const m of models) {
|
|
214
|
-
const fullId = `opencompress/${m.id}`;
|
|
215
|
-
if (!allowlist[fullId]) {
|
|
216
|
-
allowlist[fullId] = {};
|
|
217
|
-
changed = true;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
if (changed) {
|
|
221
|
-
const tmpPath = `${configPath}.tmp.${process.pid}`;
|
|
222
|
-
fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2) + "\n");
|
|
223
|
-
fs.renameSync(tmpPath, configPath);
|
|
224
|
-
}
|
|
109
|
+
const p = proxyStatePath();
|
|
110
|
+
if (!fs.existsSync(p)) return { enabled: false, originals: {} };
|
|
111
|
+
return JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
225
112
|
} catch {
|
|
113
|
+
return { enabled: false, originals: {} };
|
|
226
114
|
}
|
|
227
115
|
}
|
|
228
|
-
function
|
|
116
|
+
function writeProxyState(state) {
|
|
117
|
+
try {
|
|
118
|
+
const fs = __require("fs");
|
|
119
|
+
fs.writeFileSync(proxyStatePath(), JSON.stringify(state, null, 2) + "\n");
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function readProvidersFromDisk() {
|
|
229
124
|
try {
|
|
230
125
|
const os = __require("os");
|
|
231
126
|
const fs = __require("fs");
|
|
232
127
|
const path = __require("path");
|
|
128
|
+
const configPath = path.join(os.homedir(), ".openclaw", "openclaw.json");
|
|
129
|
+
if (!fs.existsSync(configPath)) return void 0;
|
|
130
|
+
const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
131
|
+
const providers = raw?.models?.providers;
|
|
132
|
+
if (!providers || Object.keys(providers).length === 0) return void 0;
|
|
133
|
+
return providers;
|
|
134
|
+
} catch {
|
|
135
|
+
return void 0;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function persistProviderToDisk(providerId, config) {
|
|
139
|
+
const os = __require("os");
|
|
140
|
+
const fs = __require("fs");
|
|
141
|
+
const path = __require("path");
|
|
142
|
+
try {
|
|
143
|
+
const configPath = path.join(os.homedir(), ".openclaw", "openclaw.json");
|
|
144
|
+
if (fs.existsSync(configPath)) {
|
|
145
|
+
const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
146
|
+
if (!raw.models) raw.models = {};
|
|
147
|
+
if (!raw.models.providers) raw.models.providers = {};
|
|
148
|
+
raw.models.providers[providerId] = {
|
|
149
|
+
baseUrl: config.baseUrl,
|
|
150
|
+
api: config.api || "openai-completions",
|
|
151
|
+
apiKey: config.apiKey || void 0,
|
|
152
|
+
models: config.models?.map((m) => ({ id: m.id, name: m.name })) || [],
|
|
153
|
+
...config.headers ? { headers: config.headers } : {}
|
|
154
|
+
};
|
|
155
|
+
fs.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
233
160
|
const agentsDir = path.join(os.homedir(), ".openclaw", "agents");
|
|
234
161
|
if (!fs.existsSync(agentsDir)) return;
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (
|
|
240
|
-
fs.mkdirSync(authDir, { recursive: true });
|
|
241
|
-
}
|
|
242
|
-
let profiles = {
|
|
243
|
-
version: 1,
|
|
244
|
-
profiles: {}
|
|
245
|
-
};
|
|
246
|
-
if (fs.existsSync(authPath)) {
|
|
162
|
+
for (const agent of fs.readdirSync(agentsDir)) {
|
|
163
|
+
const modelsPath = path.join(agentsDir, agent, "agent", "models.json");
|
|
164
|
+
if (!fs.existsSync(path.dirname(modelsPath))) continue;
|
|
165
|
+
let data = { providers: {} };
|
|
166
|
+
if (fs.existsSync(modelsPath)) {
|
|
247
167
|
try {
|
|
248
|
-
|
|
168
|
+
data = JSON.parse(fs.readFileSync(modelsPath, "utf-8"));
|
|
249
169
|
} catch {
|
|
250
170
|
}
|
|
171
|
+
if (!data.providers) data.providers = {};
|
|
251
172
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
173
|
+
data.providers[providerId] = {
|
|
174
|
+
baseUrl: config.baseUrl,
|
|
175
|
+
api: config.api || "openai-completions",
|
|
176
|
+
apiKey: config.apiKey || void 0,
|
|
177
|
+
models: config.models?.map((m) => ({
|
|
178
|
+
id: m.id,
|
|
179
|
+
name: m.name,
|
|
180
|
+
api: m.api || config.api || "openai-completions",
|
|
181
|
+
reasoning: m.reasoning ?? false,
|
|
182
|
+
input: m.input || ["text"],
|
|
183
|
+
cost: m.cost || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
184
|
+
contextWindow: m.contextWindow || 2e5,
|
|
185
|
+
maxTokens: m.maxTokens || 8192
|
|
186
|
+
})) || [],
|
|
187
|
+
...config.headers ? { headers: config.headers } : {}
|
|
256
188
|
};
|
|
257
|
-
fs.writeFileSync(
|
|
189
|
+
fs.writeFileSync(modelsPath, JSON.stringify(data, null, 2) + "\n");
|
|
258
190
|
}
|
|
259
191
|
} catch {
|
|
260
192
|
}
|
|
261
193
|
}
|
|
194
|
+
function enableProxy(api, baseUrl) {
|
|
195
|
+
const occKey = getApiKey(api);
|
|
196
|
+
if (!occKey) return { proxied: [], skipped: [] };
|
|
197
|
+
let providers = api.config.models?.providers;
|
|
198
|
+
let source = "api.config";
|
|
199
|
+
if (!providers || Object.keys(providers).length === 0) {
|
|
200
|
+
providers = readProvidersFromDisk();
|
|
201
|
+
source = "disk (openclaw.json)";
|
|
202
|
+
}
|
|
203
|
+
if (!providers) return { proxied: [], skipped: [] };
|
|
204
|
+
const state = readProxyState();
|
|
205
|
+
const proxied = [];
|
|
206
|
+
const skipped = [];
|
|
207
|
+
for (const [id, raw] of Object.entries(providers)) {
|
|
208
|
+
if (id === "opencompress") continue;
|
|
209
|
+
const provider = raw;
|
|
210
|
+
if (provider.baseUrl?.includes("opencompress.ai")) continue;
|
|
211
|
+
const pApi = provider.api || "openai-completions";
|
|
212
|
+
if (pApi === "google-generative-ai") {
|
|
213
|
+
skipped.push(`${id} (${pApi})`);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
state.originals[id] = {
|
|
217
|
+
baseUrl: provider.baseUrl,
|
|
218
|
+
apiKey: provider.apiKey,
|
|
219
|
+
api: provider.api,
|
|
220
|
+
headers: provider.headers ? { ...provider.headers } : void 0
|
|
221
|
+
};
|
|
222
|
+
if (pApi === "anthropic-messages") {
|
|
223
|
+
provider.headers = {
|
|
224
|
+
...provider.headers || {},
|
|
225
|
+
"x-api-key": provider.apiKey || ""
|
|
226
|
+
};
|
|
227
|
+
provider.baseUrl = `${baseUrl}/v1`;
|
|
228
|
+
provider.apiKey = void 0;
|
|
229
|
+
} else {
|
|
230
|
+
provider.headers = {
|
|
231
|
+
...provider.headers || {},
|
|
232
|
+
"X-Upstream-Key": provider.apiKey || "",
|
|
233
|
+
"X-Upstream-Base-Url": provider.baseUrl
|
|
234
|
+
};
|
|
235
|
+
provider.baseUrl = `${baseUrl}/v1`;
|
|
236
|
+
provider.apiKey = occKey;
|
|
237
|
+
}
|
|
238
|
+
persistProviderToDisk(id, provider);
|
|
239
|
+
proxied.push(id);
|
|
240
|
+
}
|
|
241
|
+
state.enabled = true;
|
|
242
|
+
writeProxyState(state);
|
|
243
|
+
return { proxied, skipped, source };
|
|
244
|
+
}
|
|
245
|
+
function disableProxy(api) {
|
|
246
|
+
const state = readProxyState();
|
|
247
|
+
if (!state.enabled) return [];
|
|
248
|
+
let providers = api.config.models?.providers;
|
|
249
|
+
if (!providers || Object.keys(providers).length === 0) {
|
|
250
|
+
providers = readProvidersFromDisk();
|
|
251
|
+
}
|
|
252
|
+
if (!providers) return [];
|
|
253
|
+
const restored = [];
|
|
254
|
+
for (const [id, orig] of Object.entries(state.originals)) {
|
|
255
|
+
const provider = providers[id];
|
|
256
|
+
if (!provider) continue;
|
|
257
|
+
provider.baseUrl = orig.baseUrl;
|
|
258
|
+
provider.apiKey = orig.apiKey;
|
|
259
|
+
provider.api = orig.api;
|
|
260
|
+
if (orig.headers) {
|
|
261
|
+
provider.headers = orig.headers;
|
|
262
|
+
} else {
|
|
263
|
+
const h = provider.headers;
|
|
264
|
+
if (h) {
|
|
265
|
+
delete h["X-Upstream-Key"];
|
|
266
|
+
delete h["X-Upstream-Base-Url"];
|
|
267
|
+
delete h["x-api-key"];
|
|
268
|
+
if (Object.keys(h).length === 0) delete provider.headers;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
persistProviderToDisk(id, provider);
|
|
272
|
+
restored.push(id);
|
|
273
|
+
}
|
|
274
|
+
state.enabled = false;
|
|
275
|
+
state.originals = {};
|
|
276
|
+
writeProxyState(state);
|
|
277
|
+
return restored;
|
|
278
|
+
}
|
|
262
279
|
var opencompressProvider = {
|
|
263
280
|
id: "opencompress",
|
|
264
281
|
label: "OpenCompress",
|
|
265
282
|
docsPath: "https://docs.opencompress.ai",
|
|
266
283
|
aliases: ["oc", "compress"],
|
|
267
284
|
envVars: ["OPENCOMPRESS_API_KEY"],
|
|
268
|
-
models
|
|
285
|
+
// No models — we're a transparent proxy, not a router.
|
|
286
|
+
// Users keep their existing providers; we just compress their traffic.
|
|
287
|
+
models: {
|
|
288
|
+
baseUrl: `${DEFAULT_BASE_URL}/v1`,
|
|
289
|
+
api: "openai-completions",
|
|
290
|
+
models: []
|
|
291
|
+
},
|
|
269
292
|
formatApiKey: (cred) => cred.apiKey || "",
|
|
270
293
|
auth: [
|
|
271
294
|
{
|
|
272
295
|
id: "api-key",
|
|
273
296
|
label: "OpenCompress",
|
|
274
|
-
hint: "
|
|
297
|
+
hint: "Compress all LLM calls automatically \u2014 save 40-60% on any provider",
|
|
275
298
|
kind: "custom",
|
|
276
299
|
run: async (ctx) => {
|
|
277
300
|
ctx.prompter.note(
|
|
278
|
-
"OpenCompress
|
|
301
|
+
"\u{1F99E} Welcome to OpenCompress!\n\nWe compress your LLM prompts automatically \u2014 saving 40-70% on token costs.\nWorks with Claude, GPT, and any provider you already have.\nYour API keys stay yours. We just make the traffic smaller.\n\nWe're giving you $1 free credit to try it out!"
|
|
279
302
|
);
|
|
280
|
-
const
|
|
281
|
-
message: "Enter your LLM API key (OpenAI/Anthropic/OpenRouter):",
|
|
282
|
-
validate: (val) => {
|
|
283
|
-
if (!val || val.length < 10) return "Please enter a valid API key";
|
|
284
|
-
if (val.startsWith("sk-occ-")) return "Enter your LLM provider key, not an OpenCompress key";
|
|
285
|
-
return void 0;
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
if (typeof llmKey === "symbol") {
|
|
289
|
-
throw new Error("Setup cancelled");
|
|
290
|
-
}
|
|
291
|
-
let provider = "openrouter";
|
|
292
|
-
let upstreamBaseUrl = "https://openrouter.ai/api/v1";
|
|
293
|
-
if (llmKey.startsWith("sk-proj-") || llmKey.startsWith("sk-") && !llmKey.startsWith("sk-ant-") && !llmKey.startsWith("sk-or-")) {
|
|
294
|
-
provider = "openai";
|
|
295
|
-
upstreamBaseUrl = "https://api.openai.com/v1";
|
|
296
|
-
} else if (llmKey.startsWith("sk-ant-")) {
|
|
297
|
-
provider = "anthropic";
|
|
298
|
-
upstreamBaseUrl = "https://api.anthropic.com/v1";
|
|
299
|
-
} else if (llmKey.startsWith("AIza")) {
|
|
300
|
-
provider = "google";
|
|
301
|
-
upstreamBaseUrl = "https://generativelanguage.googleapis.com/v1beta/openai";
|
|
302
|
-
}
|
|
303
|
-
const spinner = ctx.prompter.progress("Creating account...");
|
|
303
|
+
const spinner = ctx.prompter.progress("Setting up your account...");
|
|
304
304
|
try {
|
|
305
305
|
const res = await fetch(`${DEFAULT_BASE_URL}/v1/provision`, {
|
|
306
306
|
method: "POST",
|
|
307
307
|
headers: { "Content-Type": "application/json" },
|
|
308
|
-
body: JSON.stringify({
|
|
308
|
+
body: JSON.stringify({})
|
|
309
|
+
// BYOK — no upstream key needed here, user's existing providers handle that
|
|
309
310
|
});
|
|
310
311
|
if (!res.ok) {
|
|
311
312
|
const err = await res.json().catch(() => ({ error: { message: "Unknown error" } }));
|
|
@@ -315,13 +316,38 @@ var opencompressProvider = {
|
|
|
315
316
|
);
|
|
316
317
|
}
|
|
317
318
|
const data = await res.json();
|
|
318
|
-
spinner.stop("Account created");
|
|
319
|
-
const onboardModels = buildProviderModels(DEFAULT_BASE_URL, void 0, data.apiKey);
|
|
320
|
-
const modelCount = FALLBACK_MODELS.length;
|
|
321
|
-
persistModelsConfig(onboardModels);
|
|
322
|
-
persistAgentModelsJson(onboardModels);
|
|
319
|
+
spinner.stop("Account created!");
|
|
323
320
|
persistAuthProfile(data.apiKey);
|
|
324
321
|
persistAgentAuthJson(data.apiKey);
|
|
322
|
+
const enableNow = await ctx.prompter.text({
|
|
323
|
+
message: "Ready to compress? This will route your existing LLM providers through OpenCompress. Enable now? (yes/no)",
|
|
324
|
+
validate: (v) => {
|
|
325
|
+
const lower = v.toLowerCase().trim();
|
|
326
|
+
if (!["yes", "no", "y", "n"].includes(lower)) return "Please answer yes or no";
|
|
327
|
+
return void 0;
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
const shouldEnable = typeof enableNow === "string" && ["yes", "y"].includes(enableNow.toLowerCase().trim());
|
|
331
|
+
const notes = [
|
|
332
|
+
"\u{1F99E} OpenCompress is ready!",
|
|
333
|
+
`\u{1F4B0} $1 free credit \u2014 no credit card needed.`,
|
|
334
|
+
"",
|
|
335
|
+
"How it works: your existing API keys (Claude, GPT, etc.) stay the same.",
|
|
336
|
+
"We just compress the prompts in between \u2014 you save 40-70% on every call."
|
|
337
|
+
];
|
|
338
|
+
if (shouldEnable) {
|
|
339
|
+
notes.push(
|
|
340
|
+
"",
|
|
341
|
+
"\u2705 Compression is now active! All your LLM calls are being compressed.",
|
|
342
|
+
"Run `/compress-stats` anytime to see how much you're saving."
|
|
343
|
+
);
|
|
344
|
+
} else {
|
|
345
|
+
notes.push(
|
|
346
|
+
"",
|
|
347
|
+
"Run `/compress on` whenever you're ready to start saving."
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
notes.push("", "Dashboard: https://www.opencompress.ai/dashboard");
|
|
325
351
|
return {
|
|
326
352
|
profiles: [
|
|
327
353
|
{
|
|
@@ -329,19 +355,8 @@ var opencompressProvider = {
|
|
|
329
355
|
credential: { apiKey: data.apiKey }
|
|
330
356
|
}
|
|
331
357
|
],
|
|
332
|
-
configPatch: {
|
|
333
|
-
|
|
334
|
-
providers: {
|
|
335
|
-
opencompress: onboardModels
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
},
|
|
339
|
-
defaultModel: "gpt-4o-mini",
|
|
340
|
-
notes: [
|
|
341
|
-
`OpenCompress is ready! Connected to ${provider} (${modelCount} models).`,
|
|
342
|
-
"Your LLM key is stored locally only \u2014 never on our server.",
|
|
343
|
-
`Free credit: ${data.freeCredit}. Dashboard: opencompress.ai/dashboard`
|
|
344
|
-
]
|
|
358
|
+
configPatch: shouldEnable ? { _autoEnableProxy: true } : void 0,
|
|
359
|
+
notes
|
|
345
360
|
};
|
|
346
361
|
} catch (err) {
|
|
347
362
|
spinner.stop("Setup failed");
|
|
@@ -354,50 +369,17 @@ var opencompressProvider = {
|
|
|
354
369
|
var plugin = {
|
|
355
370
|
id: "opencompress",
|
|
356
371
|
name: "OpenCompress",
|
|
357
|
-
description: "
|
|
372
|
+
description: "Transparent prompt compression \u2014 save 40-70% on Claude, GPT, and any LLM provider",
|
|
358
373
|
version: VERSION,
|
|
359
374
|
register(api) {
|
|
360
375
|
const baseUrl = api.pluginConfig?.baseUrl || DEFAULT_BASE_URL;
|
|
361
|
-
const existingApiKey = api.config.models?.providers?.opencompress?.apiKey || getApiKey(api);
|
|
362
|
-
const existingModels = readExistingModels(api);
|
|
363
|
-
const providerModels = buildProviderModels(baseUrl, existingModels || void 0, existingApiKey);
|
|
364
|
-
opencompressProvider.models = providerModels;
|
|
365
376
|
api.registerProvider(opencompressProvider);
|
|
366
|
-
if (!api.config.models) {
|
|
367
|
-
api.config.models = { providers: {} };
|
|
368
|
-
}
|
|
369
|
-
if (!api.config.models.providers) {
|
|
370
|
-
api.config.models.providers = {};
|
|
371
|
-
}
|
|
372
|
-
api.config.models.providers.opencompress = providerModels;
|
|
373
|
-
persistModelsConfig(providerModels);
|
|
374
|
-
persistAgentModelsJson(providerModels);
|
|
375
|
-
{
|
|
376
|
-
if (!api.config.agents) api.config.agents = {};
|
|
377
|
-
const agents = api.config.agents;
|
|
378
|
-
if (!agents.defaults) agents.defaults = {};
|
|
379
|
-
const defaults = agents.defaults;
|
|
380
|
-
if (!defaults.models) defaults.models = {};
|
|
381
|
-
const allowlist = defaults.models;
|
|
382
|
-
if (Object.keys(allowlist).length > 0) {
|
|
383
|
-
for (const m of providerModels.models) {
|
|
384
|
-
const fullId = `opencompress/${m.id}`;
|
|
385
|
-
if (!allowlist[fullId]) {
|
|
386
|
-
allowlist[fullId] = {};
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
const modelsForAllowlist = providerModels.models;
|
|
392
|
-
setTimeout(() => injectModelsAllowlist(modelsForAllowlist), 3e3);
|
|
393
377
|
const apiKey = getApiKey(api);
|
|
394
378
|
if (apiKey) {
|
|
395
379
|
persistAuthProfile(apiKey);
|
|
396
380
|
persistAgentAuthJson(apiKey);
|
|
397
381
|
}
|
|
398
|
-
|
|
399
|
-
const source = existingModels ? "from existing providers" : "fallback";
|
|
400
|
-
api.logger.info(`OpenCompress provider registered (${modelCount} models ${source}, 5-layer compression)`);
|
|
382
|
+
api.logger.info("OpenCompress registered (transparent proxy mode)");
|
|
401
383
|
api.registerCommand({
|
|
402
384
|
name: "compress-stats",
|
|
403
385
|
description: "Show OpenCompress usage statistics and savings",
|
|
@@ -418,26 +400,35 @@ var plugin = {
|
|
|
418
400
|
return { text: `Failed to fetch stats: HTTP ${res.status}` };
|
|
419
401
|
}
|
|
420
402
|
const stats = await res.json();
|
|
421
|
-
const
|
|
422
|
-
const
|
|
403
|
+
const balance = Number(stats.balanceUsd || 0);
|
|
404
|
+
const calls = stats.monthlyApiCalls ?? stats.totalCalls ?? 0;
|
|
405
|
+
const savings = Number(stats.monthlySavings || stats.totalSavings || 0).toFixed(4);
|
|
423
406
|
const rate = stats.avgCompressionRate ? `${(Number(stats.avgCompressionRate) * 100).toFixed(1)}%` : "N/A";
|
|
424
407
|
const origTokens = Number(stats.totalOriginalTokens || 0).toLocaleString();
|
|
425
408
|
const compTokens = Number(stats.totalCompressedTokens || 0).toLocaleString();
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
409
|
+
const lines = [
|
|
410
|
+
"```",
|
|
411
|
+
"\u{1F99E} OpenCompress Stats",
|
|
412
|
+
"=====================",
|
|
413
|
+
`Balance: $${balance.toFixed(2)}`,
|
|
414
|
+
`API calls: ${calls}`,
|
|
415
|
+
`Avg compression: ${rate}`,
|
|
416
|
+
`Original tokens: ${origTokens}`,
|
|
417
|
+
`Compressed tokens: ${compTokens}`,
|
|
418
|
+
`Total savings: $${savings}`,
|
|
419
|
+
"```"
|
|
420
|
+
];
|
|
421
|
+
if (balance < 0.5) {
|
|
422
|
+
const linkUrl = `https://www.opencompress.ai/dashboard?link=${encodeURIComponent(apiKey2)}`;
|
|
423
|
+
lines.push(
|
|
437
424
|
"",
|
|
438
|
-
"
|
|
439
|
-
|
|
440
|
-
|
|
425
|
+
"\u26A0\uFE0F **Balance is low!** Link your account to get **$10 bonus credit**:",
|
|
426
|
+
linkUrl
|
|
427
|
+
);
|
|
428
|
+
} else {
|
|
429
|
+
lines.push("", "Dashboard: https://www.opencompress.ai/dashboard");
|
|
430
|
+
}
|
|
431
|
+
return { text: lines.join("\n") };
|
|
441
432
|
} catch (err) {
|
|
442
433
|
return {
|
|
443
434
|
text: `Error fetching stats: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -447,106 +438,82 @@ var plugin = {
|
|
|
447
438
|
});
|
|
448
439
|
api.logger.info("Registered /compress-stats command");
|
|
449
440
|
api.registerCommand({
|
|
450
|
-
name: "compress
|
|
451
|
-
description: "
|
|
441
|
+
name: "compress",
|
|
442
|
+
description: "Toggle transparent compression for all LLM providers",
|
|
452
443
|
acceptsArgs: true,
|
|
453
444
|
requireAuth: false,
|
|
454
445
|
handler: async (ctx) => {
|
|
455
|
-
const
|
|
456
|
-
if (
|
|
457
|
-
|
|
446
|
+
const arg = ctx.args?.trim().toLowerCase();
|
|
447
|
+
if (arg === "on" || arg === "enable") {
|
|
448
|
+
const occKey2 = getApiKey(api);
|
|
449
|
+
if (!occKey2) {
|
|
450
|
+
return { text: "No API key found. Run `openclaw onboard opencompress` first." };
|
|
451
|
+
}
|
|
452
|
+
const result = enableProxy(api, baseUrl);
|
|
453
|
+
if (result.proxied.length === 0 && result.skipped.length === 0) {
|
|
454
|
+
return { text: "No providers found to proxy. Add LLM providers first." };
|
|
455
|
+
}
|
|
456
|
+
const lines = [
|
|
457
|
+
`**Compression enabled** for all compatible providers (source: ${result.source}).`,
|
|
458
|
+
""
|
|
459
|
+
];
|
|
460
|
+
if (result.proxied.length > 0) {
|
|
461
|
+
lines.push(`Proxied (${result.proxied.length}): ${result.proxied.join(", ")}`);
|
|
462
|
+
}
|
|
463
|
+
if (result.skipped.length > 0) {
|
|
464
|
+
lines.push(`Skipped (incompatible format): ${result.skipped.join(", ")}`);
|
|
465
|
+
}
|
|
466
|
+
lines.push("", "All requests now route through OpenCompress for automatic compression.");
|
|
467
|
+
lines.push("To disable: `/compress off`");
|
|
468
|
+
return { text: lines.join("\n") };
|
|
458
469
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
}
|
|
464
|
-
const data = res.ok ? await res.json() : null;
|
|
470
|
+
if (arg === "off" || arg === "disable") {
|
|
471
|
+
const restored = disableProxy(api);
|
|
472
|
+
if (restored.length === 0) {
|
|
473
|
+
return { text: "Compression proxy was not active." };
|
|
474
|
+
}
|
|
465
475
|
return {
|
|
466
476
|
text: [
|
|
467
|
-
"**
|
|
477
|
+
"**Compression disabled.** Restored original provider configs.",
|
|
468
478
|
"",
|
|
469
|
-
|
|
479
|
+
`Restored: ${restored.join(", ")}`,
|
|
470
480
|
"",
|
|
471
|
-
"
|
|
472
|
-
" `/compress-byok sk-proj-xxx` \u2014 Connect OpenAI key",
|
|
473
|
-
" `/compress-byok sk-ant-xxx` \u2014 Connect Anthropic key",
|
|
474
|
-
" `/compress-byok sk-or-xxx` \u2014 Connect OpenRouter key",
|
|
475
|
-
" `/compress-byok off` \u2014 Switch back to router mode",
|
|
476
|
-
"",
|
|
477
|
-
data ? `**Balance:** $${Number(data.balance || 0).toFixed(2)}` : ""
|
|
481
|
+
"To re-enable: `/compress on`"
|
|
478
482
|
].join("\n")
|
|
479
483
|
};
|
|
480
484
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
headers: { Authorization: `Bearer ${apiKey2}` }
|
|
492
|
-
});
|
|
493
|
-
} catch {
|
|
494
|
-
}
|
|
495
|
-
return { text: "Switched back to **router mode**. Your upstream key has been removed from local config." };
|
|
485
|
+
const proxyState = readProxyState();
|
|
486
|
+
const occKey = getApiKey(api);
|
|
487
|
+
const statusLines = [
|
|
488
|
+
"**OpenCompress Transparent Proxy**",
|
|
489
|
+
"",
|
|
490
|
+
`Status: ${proxyState.enabled ? "**ON**" : "**OFF**"}`,
|
|
491
|
+
`API key: ${occKey ? `${occKey.slice(0, 12)}...` : "not set"}`
|
|
492
|
+
];
|
|
493
|
+
if (proxyState.enabled && Object.keys(proxyState.originals).length > 0) {
|
|
494
|
+
statusLines.push(`Proxied providers: ${Object.keys(proxyState.originals).join(", ")}`);
|
|
496
495
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
if (upstreamKey.startsWith("sk-proj-") || upstreamKey.startsWith("sk-") && !upstreamKey.startsWith("sk-ant-") && !upstreamKey.startsWith("sk-or-")) {
|
|
506
|
-
provider = "openai";
|
|
507
|
-
upstreamBaseUrl = "https://api.openai.com/v1";
|
|
508
|
-
} else if (upstreamKey.startsWith("sk-ant-")) {
|
|
509
|
-
provider = "anthropic";
|
|
510
|
-
upstreamBaseUrl = "https://api.anthropic.com/v1";
|
|
511
|
-
} else if (upstreamKey.startsWith("sk-or-")) {
|
|
512
|
-
provider = "openrouter";
|
|
513
|
-
upstreamBaseUrl = "https://openrouter.ai/api/v1";
|
|
514
|
-
} else if (upstreamKey.startsWith("AIza")) {
|
|
515
|
-
provider = "google";
|
|
516
|
-
upstreamBaseUrl = "https://generativelanguage.googleapis.com/v1beta/openai";
|
|
517
|
-
}
|
|
518
|
-
const existingModels2 = readExistingModels(api);
|
|
519
|
-
const updatedModels = buildProviderModels(baseUrl, existingModels2 || void 0);
|
|
520
|
-
if (api.config.models?.providers) {
|
|
521
|
-
api.config.models.providers.opencompress = updatedModels;
|
|
522
|
-
}
|
|
523
|
-
persistModelsConfig(updatedModels);
|
|
524
|
-
persistAgentModelsJson(updatedModels);
|
|
525
|
-
try {
|
|
526
|
-
await fetch(`${baseUrl}/v1/byok`, {
|
|
527
|
-
method: "POST",
|
|
528
|
-
headers: {
|
|
529
|
-
Authorization: `Bearer ${apiKey2}`,
|
|
530
|
-
"Content-Type": "application/json"
|
|
531
|
-
},
|
|
532
|
-
body: JSON.stringify({ upstreamApiKey: upstreamKey, upstreamBaseUrl })
|
|
533
|
-
});
|
|
534
|
-
} catch {
|
|
535
|
-
}
|
|
536
|
-
const modelCount2 = existingModels2 ? existingModels2.length : FALLBACK_MODELS.length;
|
|
537
|
-
return {
|
|
538
|
-
text: [
|
|
539
|
-
`Switched to **BYOK mode** (${provider}).`,
|
|
540
|
-
`Loaded **${modelCount2} models** from your ${provider} account.`,
|
|
541
|
-
"",
|
|
542
|
-
"Your upstream key is stored securely on the server, associated with your API key.",
|
|
543
|
-
"",
|
|
544
|
-
"To switch back: `/compress-byok off`"
|
|
545
|
-
].join("\n")
|
|
546
|
-
};
|
|
496
|
+
statusLines.push(
|
|
497
|
+
"",
|
|
498
|
+
"**Usage:**",
|
|
499
|
+
" `/compress on` \u2014 Route all providers through OpenCompress",
|
|
500
|
+
" `/compress off` \u2014 Restore original provider configs",
|
|
501
|
+
" `/compress-stats` \u2014 View compression savings"
|
|
502
|
+
);
|
|
503
|
+
return { text: statusLines.join("\n") };
|
|
547
504
|
}
|
|
548
505
|
});
|
|
549
|
-
api.logger.info("Registered /compress
|
|
506
|
+
api.logger.info("Registered /compress command");
|
|
507
|
+
setTimeout(() => {
|
|
508
|
+
const proxyState = readProxyState();
|
|
509
|
+
const autoEnable = api.pluginConfig?._autoEnableProxy;
|
|
510
|
+
if (proxyState.enabled || autoEnable) {
|
|
511
|
+
const result = enableProxy(api, baseUrl);
|
|
512
|
+
if (result.proxied.length > 0) {
|
|
513
|
+
api.logger.info(`Compression active: ${result.proxied.length} providers (${result.proxied.join(", ")})`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}, 2e3);
|
|
550
517
|
const os = __require("os");
|
|
551
518
|
const fs = __require("fs");
|
|
552
519
|
const path = __require("path");
|