@opencompress/opencompress 1.8.2 → 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.
Files changed (2) hide show
  1. package/dist/index.js +311 -344
  2. 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.8.2";
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
- var FALLBACK_MODELS = [
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 modelsPath = path.join(agentsDir, agent, "agent", "models.json");
128
- const modelsDir = path.dirname(modelsPath);
129
- if (!fs.existsSync(modelsDir)) continue;
130
- let data = { providers: {} };
131
- if (fs.existsSync(modelsPath)) {
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
- data = JSON.parse(fs.readFileSync(modelsPath, "utf-8"));
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
- data.providers.opencompress = {
142
- baseUrl: providerModels.baseUrl,
143
- api: providerModels.api || "openai-completions",
144
- apiKey: providerModels.apiKey || void 0,
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(modelsPath, JSON.stringify(data, null, 2) + "\n");
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 injectModelsAllowlist(models) {
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 path = __require("path");
195
- const configPath = path.join(os.homedir(), ".openclaw", "openclaw.json");
196
- if (!fs.existsSync(configPath)) return;
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 persistAuthProfile(apiKey) {
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 agentDirs = fs.readdirSync(agentsDir);
236
- for (const agent of agentDirs) {
237
- const authPath = path.join(agentsDir, agent, "agent", "auth-profiles.json");
238
- const authDir = path.dirname(authPath);
239
- if (!fs.existsSync(authDir)) {
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
- profiles = JSON.parse(fs.readFileSync(authPath, "utf-8"));
168
+ data = JSON.parse(fs.readFileSync(modelsPath, "utf-8"));
249
169
  } catch {
250
170
  }
171
+ if (!data.providers) data.providers = {};
251
172
  }
252
- profiles.profiles["opencompress:default"] = {
253
- type: "api_key",
254
- provider: "opencompress",
255
- key: apiKey
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(authPath, JSON.stringify(profiles, null, 2) + "\n");
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: buildProviderModels(DEFAULT_BASE_URL),
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: "Connect your LLM key \u2014 compress every call, save 40-70%",
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 compresses all LLM prompts automatically.\n53% fewer tokens, 62% faster, 96% quality preserved.\n\nConnect your existing LLM API key to get started.\nSupported: OpenAI, Anthropic, OpenRouter, Google"
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 llmKey = await ctx.prompter.text({
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({ upstreamApiKey: llmKey })
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
- models: {
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,52 +369,19 @@ var opencompressProvider = {
354
369
  var plugin = {
355
370
  id: "opencompress",
356
371
  name: "OpenCompress",
357
- description: "5-layer prompt compression \u2014 53% input reduction, 62% latency cut, 96% quality",
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
- const modelCount = existingModels ? existingModels.length : FALLBACK_MODELS.length;
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
- name: "compress_stats",
384
+ name: "compress-stats",
403
385
  description: "Show OpenCompress usage statistics and savings",
404
386
  acceptsArgs: true,
405
387
  requireAuth: false,
@@ -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 calls = stats.totalCalls ?? 0;
422
- const savings = Number(stats.totalSavings || 0).toFixed(4);
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
- return {
427
- text: [
428
- "```",
429
- "OpenCompress Stats",
430
- "==================",
431
- `API calls: ${calls}`,
432
- `Avg compression: ${rate}`,
433
- `Original tokens: ${origTokens}`,
434
- `Compressed tokens: ${compTokens}`,
435
- `Total savings: $${savings}`,
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
- "Dashboard: https://www.opencompress.ai/dashboard"
439
- ].join("\n")
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)}`
@@ -445,108 +436,84 @@ var plugin = {
445
436
  }
446
437
  }
447
438
  });
448
- api.logger.info("Registered /compress_stats command");
439
+ api.logger.info("Registered /compress-stats command");
449
440
  api.registerCommand({
450
- name: "compress_byok",
451
- description: "Connect your own LLM key (OpenAI/Anthropic/OpenRouter) to save more",
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 apiKey2 = getApiKey(api);
456
- if (!apiKey2) {
457
- return { text: "Not set up. Run `openclaw onboard opencompress` first." };
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
- const upstreamKey = ctx.args?.trim();
460
- if (!upstreamKey) {
461
- const res = await fetch(`${baseUrl}/v1/topup`, {
462
- headers: { Authorization: `Bearer ${apiKey2}` }
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
- "**BYOK (Bring Your Own Key)**",
477
+ "**Compression disabled.** Restored original provider configs.",
468
478
  "",
469
- "Connect your LLM provider key to pay them directly \u2014 we only charge the compression fee (20% of savings).",
479
+ `Restored: ${restored.join(", ")}`,
470
480
  "",
471
- "**Usage:**",
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
- if (upstreamKey === "off" || upstreamKey === "disable" || upstreamKey === "router") {
482
- const cleanModels = buildProviderModels(baseUrl);
483
- if (api.config.models?.providers) {
484
- api.config.models.providers.opencompress = cleanModels;
485
- }
486
- persistModelsConfig(cleanModels);
487
- persistAgentModelsJson(cleanModels);
488
- try {
489
- await fetch(`${baseUrl}/v1/byok`, {
490
- method: "DELETE",
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
- if (upstreamKey.startsWith("sk-occ-")) {
498
- return { text: "That's an OpenCompress key. Provide your LLM provider key (OpenAI, Anthropic, etc.)." };
499
- }
500
- if (upstreamKey.length < 10) {
501
- return { text: "Key looks too short. Provide your full LLM API key." };
502
- }
503
- let provider = "unknown";
504
- let upstreamBaseUrl = "";
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_byok command");
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");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencompress/opencompress",
3
- "version": "1.8.2",
3
+ "version": "1.9.0",
4
4
  "description": "OpenCompress plugin for OpenClaw — automatic 5-layer prompt compression for any LLM",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",