@opencompress/opencompress 1.1.0 → 1.3.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 CHANGED
@@ -34,12 +34,18 @@ var OPENCOMPRESS_MODELS = [
34
34
  // Mistral
35
35
  { id: "mistralai/mistral-large-2411", name: "Mistral Large (Compressed)", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 131072, maxTokens: 8192 }
36
36
  ];
37
- function buildProviderModels(baseUrl) {
38
- return {
37
+ function buildProviderModels(baseUrl, upstreamKey, upstreamBaseUrl) {
38
+ const config = {
39
39
  baseUrl: `${baseUrl}/v1`,
40
40
  api: "openai-completions",
41
41
  models: OPENCOMPRESS_MODELS
42
42
  };
43
+ if (upstreamKey || upstreamBaseUrl) {
44
+ config.headers = {};
45
+ if (upstreamKey) config.headers["X-Upstream-Key"] = upstreamKey;
46
+ if (upstreamBaseUrl) config.headers["X-Upstream-Base-Url"] = upstreamBaseUrl;
47
+ }
48
+ return config;
43
49
  }
44
50
  var opencompressProvider = {
45
51
  id: "opencompress",
@@ -53,18 +59,41 @@ var opencompressProvider = {
53
59
  {
54
60
  id: "api-key",
55
61
  label: "OpenCompress",
56
- hint: "One-click setup \u2014 no API key needed",
62
+ hint: "Connect your LLM key \u2014 compress every call, save 40-70%",
57
63
  kind: "custom",
58
64
  run: async (ctx) => {
59
65
  ctx.prompter.note(
60
- "OpenCompress compresses all LLM prompts automatically.\n53% fewer tokens, 62% faster, 96% quality preserved.\nWe'll create your account now \u2014 $1 free credit included."
66
+ "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"
61
67
  );
68
+ const llmKey = await ctx.prompter.text({
69
+ message: "Enter your LLM API key (OpenAI/Anthropic/OpenRouter):",
70
+ validate: (val) => {
71
+ if (!val || val.length < 10) return "Please enter a valid API key";
72
+ if (val.startsWith("sk-occ-")) return "Enter your LLM provider key, not an OpenCompress key";
73
+ return void 0;
74
+ }
75
+ });
76
+ if (typeof llmKey === "symbol") {
77
+ throw new Error("Setup cancelled");
78
+ }
79
+ let provider = "openrouter";
80
+ let upstreamBaseUrl = "https://openrouter.ai/api/v1";
81
+ if (llmKey.startsWith("sk-proj-") || llmKey.startsWith("sk-") && !llmKey.startsWith("sk-ant-") && !llmKey.startsWith("sk-or-")) {
82
+ provider = "openai";
83
+ upstreamBaseUrl = "https://api.openai.com/v1";
84
+ } else if (llmKey.startsWith("sk-ant-")) {
85
+ provider = "anthropic";
86
+ upstreamBaseUrl = "https://api.anthropic.com/v1";
87
+ } else if (llmKey.startsWith("AIza")) {
88
+ provider = "google";
89
+ upstreamBaseUrl = "https://generativelanguage.googleapis.com/v1beta/openai";
90
+ }
62
91
  const spinner = ctx.prompter.progress("Creating account...");
63
92
  try {
64
93
  const res = await fetch(`${DEFAULT_BASE_URL}/v1/provision`, {
65
94
  method: "POST",
66
95
  headers: { "Content-Type": "application/json" },
67
- body: "{}"
96
+ body: JSON.stringify({ upstreamApiKey: llmKey })
68
97
  });
69
98
  if (!res.ok) {
70
99
  const err = await res.json().catch(() => ({ error: { message: "Unknown error" } }));
@@ -75,6 +104,17 @@ var opencompressProvider = {
75
104
  }
76
105
  const data = await res.json();
77
106
  spinner.stop("Account created");
107
+ try {
108
+ await fetch(`${DEFAULT_BASE_URL}/v1/byok`, {
109
+ method: "POST",
110
+ headers: {
111
+ Authorization: `Bearer ${data.apiKey}`,
112
+ "Content-Type": "application/json"
113
+ },
114
+ body: JSON.stringify({ provider, passthrough: true })
115
+ });
116
+ } catch {
117
+ }
78
118
  return {
79
119
  profiles: [
80
120
  {
@@ -82,10 +122,18 @@ var opencompressProvider = {
82
122
  credential: { apiKey: data.apiKey }
83
123
  }
84
124
  ],
125
+ configPatch: {
126
+ models: {
127
+ providers: {
128
+ opencompress: buildProviderModels(DEFAULT_BASE_URL, llmKey, upstreamBaseUrl)
129
+ }
130
+ }
131
+ },
85
132
  defaultModel: "gpt-4o-mini",
86
133
  notes: [
87
- "OpenCompress is ready! All LLM calls are now compressed automatically.",
88
- `Free credit: ${data.freeCredit}. Add more: POST /api/v1/topup or visit opencompress.ai/dashboard`
134
+ `OpenCompress is ready! Connected to ${provider}.`,
135
+ "Your LLM key is stored locally only \u2014 never on our server.",
136
+ `Free credit: ${data.freeCredit}. Dashboard: opencompress.ai/dashboard`
89
137
  ]
90
138
  };
91
139
  } catch (err) {
@@ -103,9 +151,11 @@ var plugin = {
103
151
  version: VERSION,
104
152
  async register(api) {
105
153
  const baseUrl = api.pluginConfig?.baseUrl || DEFAULT_BASE_URL;
106
- if (baseUrl !== DEFAULT_BASE_URL) {
107
- opencompressProvider.models = buildProviderModels(baseUrl);
108
- }
154
+ const existingHeaders = api.config.models?.providers?.opencompress?.headers;
155
+ const existingUpstreamKey = existingHeaders?.["X-Upstream-Key"];
156
+ const existingUpstreamBaseUrl = existingHeaders?.["X-Upstream-Base-Url"];
157
+ const providerModels = buildProviderModels(baseUrl, existingUpstreamKey, existingUpstreamBaseUrl);
158
+ opencompressProvider.models = providerModels;
109
159
  api.registerProvider(opencompressProvider);
110
160
  if (!api.config.models) {
111
161
  api.config.models = { providers: {} };
@@ -113,7 +163,7 @@ var plugin = {
113
163
  if (!api.config.models.providers) {
114
164
  api.config.models.providers = {};
115
165
  }
116
- api.config.models.providers.opencompress = buildProviderModels(baseUrl);
166
+ api.config.models.providers.opencompress = providerModels;
117
167
  api.logger.info("OpenCompress provider registered (20 models, 5-layer compression)");
118
168
  api.registerCommand({
119
169
  name: "compress-stats",
@@ -175,10 +225,10 @@ var plugin = {
175
225
  }
176
226
  const upstreamKey = ctx.args?.trim();
177
227
  if (!upstreamKey) {
178
- const res2 = await fetch(`${baseUrl}/v1/topup`, {
228
+ const res = await fetch(`${baseUrl}/v1/topup`, {
179
229
  headers: { Authorization: `Bearer ${apiKey}` }
180
230
  });
181
- const data2 = res2.ok ? await res2.json() : null;
231
+ const data = res.ok ? await res.json() : null;
182
232
  return {
183
233
  text: [
184
234
  "**BYOK (Bring Your Own Key)**",
@@ -191,19 +241,23 @@ var plugin = {
191
241
  " `/compress-byok sk-or-xxx` \u2014 Connect OpenRouter key",
192
242
  " `/compress-byok off` \u2014 Switch back to router mode",
193
243
  "",
194
- data2 ? `**Balance:** $${data2.balance.toFixed(2)}` : ""
244
+ data ? `**Balance:** $${data.balance.toFixed(2)}` : ""
195
245
  ].join("\n")
196
246
  };
197
247
  }
198
248
  if (upstreamKey === "off" || upstreamKey === "disable" || upstreamKey === "router") {
199
- const res2 = await fetch(`${baseUrl}/v1/byok`, {
200
- method: "DELETE",
201
- headers: { Authorization: `Bearer ${apiKey}` }
202
- });
203
- if (!res2.ok) {
204
- return { text: `Failed to switch: HTTP ${res2.status}` };
249
+ const cleanModels = buildProviderModels(baseUrl);
250
+ if (api.config.models?.providers) {
251
+ api.config.models.providers.opencompress = cleanModels;
252
+ }
253
+ try {
254
+ await fetch(`${baseUrl}/v1/byok`, {
255
+ method: "DELETE",
256
+ headers: { Authorization: `Bearer ${apiKey}` }
257
+ });
258
+ } catch {
205
259
  }
206
- return { text: "Switched back to **router mode**. We handle LLM routing via OpenRouter." };
260
+ return { text: "Switched back to **router mode**. Your upstream key has been removed from local config." };
207
261
  }
208
262
  if (upstreamKey.startsWith("sk-occ-")) {
209
263
  return { text: "That's an OpenCompress key. Provide your LLM provider key (OpenAI, Anthropic, etc.)." };
@@ -211,24 +265,42 @@ var plugin = {
211
265
  if (upstreamKey.length < 10) {
212
266
  return { text: "Key looks too short. Provide your full LLM API key." };
213
267
  }
214
- const res = await fetch(`${baseUrl}/v1/byok`, {
215
- method: "POST",
216
- headers: {
217
- Authorization: `Bearer ${apiKey}`,
218
- "Content-Type": "application/json"
219
- },
220
- body: JSON.stringify({ upstreamApiKey: upstreamKey })
221
- });
222
- if (!res.ok) {
223
- const err = await res.json().catch(() => ({ error: { message: "Unknown error" } }));
224
- return { text: `Failed: ${err.error?.message || res.statusText}` };
268
+ let provider = "unknown";
269
+ let upstreamBaseUrl = "";
270
+ if (upstreamKey.startsWith("sk-proj-") || upstreamKey.startsWith("sk-") && !upstreamKey.startsWith("sk-ant-") && !upstreamKey.startsWith("sk-or-")) {
271
+ provider = "openai";
272
+ upstreamBaseUrl = "https://api.openai.com/v1";
273
+ } else if (upstreamKey.startsWith("sk-ant-")) {
274
+ provider = "anthropic";
275
+ upstreamBaseUrl = "https://api.anthropic.com/v1";
276
+ } else if (upstreamKey.startsWith("sk-or-")) {
277
+ provider = "openrouter";
278
+ upstreamBaseUrl = "https://openrouter.ai/api/v1";
279
+ } else if (upstreamKey.startsWith("AIza")) {
280
+ provider = "google";
281
+ upstreamBaseUrl = "https://generativelanguage.googleapis.com/v1beta/openai";
282
+ }
283
+ const updatedModels = buildProviderModels(baseUrl, upstreamKey, upstreamBaseUrl);
284
+ if (api.config.models?.providers) {
285
+ api.config.models.providers.opencompress = updatedModels;
286
+ }
287
+ try {
288
+ await fetch(`${baseUrl}/v1/byok`, {
289
+ method: "POST",
290
+ headers: {
291
+ Authorization: `Bearer ${apiKey}`,
292
+ "Content-Type": "application/json"
293
+ },
294
+ body: JSON.stringify({ provider, passthrough: true })
295
+ });
296
+ } catch {
225
297
  }
226
- const data = await res.json();
227
298
  return {
228
299
  text: [
229
- `Switched to **BYOK mode** (${data.provider}).`,
300
+ `Switched to **BYOK mode** (${provider}).`,
230
301
  "",
231
- data.billing,
302
+ "Your key is stored **locally only** \u2014 never sent to our server for storage.",
303
+ "It's passed through on each request via header and discarded immediately.",
232
304
  "",
233
305
  "To switch back: `/compress-byok off`"
234
306
  ].join("\n")
@@ -0,0 +1,72 @@
1
+ {
2
+ "id": "opencompress",
3
+ "version": "1.0.0",
4
+ "securityProfile": {
5
+ "dataHandling": {
6
+ "promptContent": "pass-through",
7
+ "promptStorage": "none",
8
+ "responseContent": "pass-through",
9
+ "responseStorage": "none",
10
+ "upstreamApiKeys": "local-only",
11
+ "upstreamApiKeyStorage": "never-server-side"
12
+ },
13
+ "networkAccess": {
14
+ "outbound": [
15
+ {
16
+ "host": "www.opencompress.ai",
17
+ "port": 443,
18
+ "protocol": "https",
19
+ "purpose": "API proxy — compress prompts and forward to upstream LLM provider"
20
+ }
21
+ ],
22
+ "inbound": []
23
+ },
24
+ "localStorage": {
25
+ "authProfiles": {
26
+ "path": "~/.openclaw/agents/*/agent/auth-profiles.json",
27
+ "content": "OpenCompress API key (sk-occ-*)",
28
+ "sensitive": true
29
+ },
30
+ "providerConfig": {
31
+ "path": "~/.openclaw/openclaw.json",
32
+ "content": "Provider config, optional upstream key in headers (BYOK pass-through)",
33
+ "sensitive": true
34
+ }
35
+ },
36
+ "secrets": {
37
+ "required": [
38
+ {
39
+ "name": "OpenCompress API Key",
40
+ "format": "sk-occ-*",
41
+ "purpose": "Authentication and billing for compression service",
42
+ "serverSideStorage": "hashed (SHA-256), never plaintext"
43
+ }
44
+ ],
45
+ "optional": [
46
+ {
47
+ "name": "Upstream LLM API Key",
48
+ "format": "sk-proj-* / sk-ant-* / sk-or-* / AIza*",
49
+ "purpose": "BYOK mode — forwarded to user's LLM provider",
50
+ "serverSideStorage": "NEVER stored. Passed via X-Upstream-Key header per-request, discarded after forwarding."
51
+ }
52
+ ]
53
+ },
54
+ "permissions": {
55
+ "fileSystem": "read/write ~/.openclaw/ config files only",
56
+ "environment": ["OPENCOMPRESS_API_KEY", "OPENCOMPRESS_LLM_KEY"],
57
+ "shell": "none",
58
+ "network": "outbound HTTPS to www.opencompress.ai only"
59
+ }
60
+ },
61
+ "privacyPolicy": {
62
+ "promptsAndResponses": "OpenCompress compresses prompts in-memory and forwards to the upstream LLM provider. Prompt and response content is NEVER stored, logged, or used for training. Only token counts are recorded for billing.",
63
+ "apiKeys": "Your upstream LLM API key (OpenAI, Anthropic, etc.) is stored ONLY on your local machine. It is sent to our server as a per-request header (X-Upstream-Key) and discarded immediately after forwarding. We never persist your upstream keys.",
64
+ "billing": "We record: timestamp, model, original token count, compressed token count, cost. We do NOT record prompt content.",
65
+ "thirdParties": "We do not share any data with third parties. Your requests are forwarded only to the LLM provider you specify."
66
+ },
67
+ "auditability": {
68
+ "pluginSource": "Open source — https://github.com/claw-compactor/openclaw-plugin",
69
+ "serverSource": "Forwarding logic documented in security docs. Core proxy is open for audit.",
70
+ "verification": "Users can inspect all network requests via OpenClaw's diagnostics plugin or standard proxy tools."
71
+ }
72
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencompress/opencompress",
3
- "version": "1.1.0",
3
+ "version": "1.3.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",
@@ -18,7 +18,8 @@
18
18
  },
19
19
  "files": [
20
20
  "dist",
21
- "openclaw.plugin.json"
21
+ "openclaw.plugin.json",
22
+ "openclaw.security.json"
22
23
  ],
23
24
  "keywords": [
24
25
  "openclaw",