@opencompress/opencompress 1.8.2 → 1.9.1

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 +312 -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.1";
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,216 @@ 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-upstream-key": provider.apiKey || ""
226
+ };
227
+ provider.baseUrl = `${baseUrl}/v1`;
228
+ provider.apiKey = occKey;
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
+ delete h["x-upstream-key"];
269
+ if (Object.keys(h).length === 0) delete provider.headers;
270
+ }
271
+ }
272
+ persistProviderToDisk(id, provider);
273
+ restored.push(id);
274
+ }
275
+ state.enabled = false;
276
+ state.originals = {};
277
+ writeProxyState(state);
278
+ return restored;
279
+ }
262
280
  var opencompressProvider = {
263
281
  id: "opencompress",
264
282
  label: "OpenCompress",
265
283
  docsPath: "https://docs.opencompress.ai",
266
284
  aliases: ["oc", "compress"],
267
285
  envVars: ["OPENCOMPRESS_API_KEY"],
268
- models: buildProviderModels(DEFAULT_BASE_URL),
286
+ // No models — we're a transparent proxy, not a router.
287
+ // Users keep their existing providers; we just compress their traffic.
288
+ models: {
289
+ baseUrl: `${DEFAULT_BASE_URL}/v1`,
290
+ api: "openai-completions",
291
+ models: []
292
+ },
269
293
  formatApiKey: (cred) => cred.apiKey || "",
270
294
  auth: [
271
295
  {
272
296
  id: "api-key",
273
297
  label: "OpenCompress",
274
- hint: "Connect your LLM key \u2014 compress every call, save 40-70%",
298
+ hint: "Compress all LLM calls automatically \u2014 save 40-60% on any provider",
275
299
  kind: "custom",
276
300
  run: async (ctx) => {
277
301
  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"
302
+ "\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
303
  );
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...");
304
+ const spinner = ctx.prompter.progress("Setting up your account...");
304
305
  try {
305
306
  const res = await fetch(`${DEFAULT_BASE_URL}/v1/provision`, {
306
307
  method: "POST",
307
308
  headers: { "Content-Type": "application/json" },
308
- body: JSON.stringify({ upstreamApiKey: llmKey })
309
+ body: JSON.stringify({})
310
+ // BYOK — no upstream key needed here, user's existing providers handle that
309
311
  });
310
312
  if (!res.ok) {
311
313
  const err = await res.json().catch(() => ({ error: { message: "Unknown error" } }));
@@ -315,13 +317,38 @@ var opencompressProvider = {
315
317
  );
316
318
  }
317
319
  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);
320
+ spinner.stop("Account created!");
323
321
  persistAuthProfile(data.apiKey);
324
322
  persistAgentAuthJson(data.apiKey);
323
+ const enableNow = await ctx.prompter.text({
324
+ message: "Ready to compress? This will route your existing LLM providers through OpenCompress. Enable now? (yes/no)",
325
+ validate: (v) => {
326
+ const lower = v.toLowerCase().trim();
327
+ if (!["yes", "no", "y", "n"].includes(lower)) return "Please answer yes or no";
328
+ return void 0;
329
+ }
330
+ });
331
+ const shouldEnable = typeof enableNow === "string" && ["yes", "y"].includes(enableNow.toLowerCase().trim());
332
+ const notes = [
333
+ "\u{1F99E} OpenCompress is ready!",
334
+ `\u{1F4B0} $1 free credit \u2014 no credit card needed.`,
335
+ "",
336
+ "How it works: your existing API keys (Claude, GPT, etc.) stay the same.",
337
+ "We just compress the prompts in between \u2014 you save 40-70% on every call."
338
+ ];
339
+ if (shouldEnable) {
340
+ notes.push(
341
+ "",
342
+ "\u2705 Compression is now active! All your LLM calls are being compressed.",
343
+ "Run `/compress-stats` anytime to see how much you're saving."
344
+ );
345
+ } else {
346
+ notes.push(
347
+ "",
348
+ "Run `/compress on` whenever you're ready to start saving."
349
+ );
350
+ }
351
+ notes.push("", "Dashboard: https://www.opencompress.ai/dashboard");
325
352
  return {
326
353
  profiles: [
327
354
  {
@@ -329,19 +356,8 @@ var opencompressProvider = {
329
356
  credential: { apiKey: data.apiKey }
330
357
  }
331
358
  ],
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
- ]
359
+ configPatch: shouldEnable ? { _autoEnableProxy: true } : void 0,
360
+ notes
345
361
  };
346
362
  } catch (err) {
347
363
  spinner.stop("Setup failed");
@@ -354,52 +370,19 @@ var opencompressProvider = {
354
370
  var plugin = {
355
371
  id: "opencompress",
356
372
  name: "OpenCompress",
357
- description: "5-layer prompt compression \u2014 53% input reduction, 62% latency cut, 96% quality",
373
+ description: "Transparent prompt compression \u2014 save 40-70% on Claude, GPT, and any LLM provider",
358
374
  version: VERSION,
359
375
  register(api) {
360
376
  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
377
  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
378
  const apiKey = getApiKey(api);
394
379
  if (apiKey) {
395
380
  persistAuthProfile(apiKey);
396
381
  persistAgentAuthJson(apiKey);
397
382
  }
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)`);
383
+ api.logger.info("OpenCompress registered (transparent proxy mode)");
401
384
  api.registerCommand({
402
- name: "compress_stats",
385
+ name: "compress-stats",
403
386
  description: "Show OpenCompress usage statistics and savings",
404
387
  acceptsArgs: true,
405
388
  requireAuth: false,
@@ -418,26 +401,35 @@ var plugin = {
418
401
  return { text: `Failed to fetch stats: HTTP ${res.status}` };
419
402
  }
420
403
  const stats = await res.json();
421
- const calls = stats.totalCalls ?? 0;
422
- const savings = Number(stats.totalSavings || 0).toFixed(4);
404
+ const balance = Number(stats.balanceUsd || 0);
405
+ const calls = stats.monthlyApiCalls ?? stats.totalCalls ?? 0;
406
+ const savings = Number(stats.monthlySavings || stats.totalSavings || 0).toFixed(4);
423
407
  const rate = stats.avgCompressionRate ? `${(Number(stats.avgCompressionRate) * 100).toFixed(1)}%` : "N/A";
424
408
  const origTokens = Number(stats.totalOriginalTokens || 0).toLocaleString();
425
409
  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
- "```",
410
+ const lines = [
411
+ "```",
412
+ "\u{1F99E} OpenCompress Stats",
413
+ "=====================",
414
+ `Balance: $${balance.toFixed(2)}`,
415
+ `API calls: ${calls}`,
416
+ `Avg compression: ${rate}`,
417
+ `Original tokens: ${origTokens}`,
418
+ `Compressed tokens: ${compTokens}`,
419
+ `Total savings: $${savings}`,
420
+ "```"
421
+ ];
422
+ if (balance < 0.5) {
423
+ const linkUrl = `https://www.opencompress.ai/dashboard?link=${encodeURIComponent(apiKey2)}`;
424
+ lines.push(
437
425
  "",
438
- "Dashboard: https://www.opencompress.ai/dashboard"
439
- ].join("\n")
440
- };
426
+ "\u26A0\uFE0F **Balance is low!** Link your account to get **$10 bonus credit**:",
427
+ linkUrl
428
+ );
429
+ } else {
430
+ lines.push("", "Dashboard: https://www.opencompress.ai/dashboard");
431
+ }
432
+ return { text: lines.join("\n") };
441
433
  } catch (err) {
442
434
  return {
443
435
  text: `Error fetching stats: ${err instanceof Error ? err.message : String(err)}`
@@ -445,108 +437,84 @@ var plugin = {
445
437
  }
446
438
  }
447
439
  });
448
- api.logger.info("Registered /compress_stats command");
440
+ api.logger.info("Registered /compress-stats command");
449
441
  api.registerCommand({
450
- name: "compress_byok",
451
- description: "Connect your own LLM key (OpenAI/Anthropic/OpenRouter) to save more",
442
+ name: "compress",
443
+ description: "Toggle transparent compression for all LLM providers",
452
444
  acceptsArgs: true,
453
445
  requireAuth: false,
454
446
  handler: async (ctx) => {
455
- const apiKey2 = getApiKey(api);
456
- if (!apiKey2) {
457
- return { text: "Not set up. Run `openclaw onboard opencompress` first." };
447
+ const arg = ctx.args?.trim().toLowerCase();
448
+ if (arg === "on" || arg === "enable") {
449
+ const occKey2 = getApiKey(api);
450
+ if (!occKey2) {
451
+ return { text: "No API key found. Run `openclaw onboard opencompress` first." };
452
+ }
453
+ const result = enableProxy(api, baseUrl);
454
+ if (result.proxied.length === 0 && result.skipped.length === 0) {
455
+ return { text: "No providers found to proxy. Add LLM providers first." };
456
+ }
457
+ const lines = [
458
+ `**Compression enabled** for all compatible providers (source: ${result.source}).`,
459
+ ""
460
+ ];
461
+ if (result.proxied.length > 0) {
462
+ lines.push(`Proxied (${result.proxied.length}): ${result.proxied.join(", ")}`);
463
+ }
464
+ if (result.skipped.length > 0) {
465
+ lines.push(`Skipped (incompatible format): ${result.skipped.join(", ")}`);
466
+ }
467
+ lines.push("", "All requests now route through OpenCompress for automatic compression.");
468
+ lines.push("To disable: `/compress off`");
469
+ return { text: lines.join("\n") };
458
470
  }
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;
471
+ if (arg === "off" || arg === "disable") {
472
+ const restored = disableProxy(api);
473
+ if (restored.length === 0) {
474
+ return { text: "Compression proxy was not active." };
475
+ }
465
476
  return {
466
477
  text: [
467
- "**BYOK (Bring Your Own Key)**",
478
+ "**Compression disabled.** Restored original provider configs.",
468
479
  "",
469
- "Connect your LLM provider key to pay them directly \u2014 we only charge the compression fee (20% of savings).",
480
+ `Restored: ${restored.join(", ")}`,
470
481
  "",
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)}` : ""
482
+ "To re-enable: `/compress on`"
478
483
  ].join("\n")
479
484
  };
480
485
  }
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." };
486
+ const proxyState = readProxyState();
487
+ const occKey = getApiKey(api);
488
+ const statusLines = [
489
+ "**OpenCompress Transparent Proxy**",
490
+ "",
491
+ `Status: ${proxyState.enabled ? "**ON**" : "**OFF**"}`,
492
+ `API key: ${occKey ? `${occKey.slice(0, 12)}...` : "not set"}`
493
+ ];
494
+ if (proxyState.enabled && Object.keys(proxyState.originals).length > 0) {
495
+ statusLines.push(`Proxied providers: ${Object.keys(proxyState.originals).join(", ")}`);
496
496
  }
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
- };
497
+ statusLines.push(
498
+ "",
499
+ "**Usage:**",
500
+ " `/compress on` \u2014 Route all providers through OpenCompress",
501
+ " `/compress off` \u2014 Restore original provider configs",
502
+ " `/compress-stats` \u2014 View compression savings"
503
+ );
504
+ return { text: statusLines.join("\n") };
547
505
  }
548
506
  });
549
- api.logger.info("Registered /compress_byok command");
507
+ api.logger.info("Registered /compress command");
508
+ setTimeout(() => {
509
+ const proxyState = readProxyState();
510
+ const autoEnable = api.pluginConfig?._autoEnableProxy;
511
+ if (proxyState.enabled || autoEnable) {
512
+ const result = enableProxy(api, baseUrl);
513
+ if (result.proxied.length > 0) {
514
+ api.logger.info(`Compression active: ${result.proxied.length} providers (${result.proxied.join(", ")})`);
515
+ }
516
+ }
517
+ }, 2e3);
550
518
  const os = __require("os");
551
519
  const fs = __require("fs");
552
520
  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.1",
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",