@opencompress/opencompress 1.9.1 → 2.0.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.d.ts CHANGED
@@ -1,144 +1,84 @@
1
1
  /**
2
- * OpenClaw Plugin Types (locally defined)
2
+ * OpenCompress Plugin for OpenClaw v2
3
3
  *
4
- * OpenClaw's plugin SDK uses duck typing these match the shapes
5
- * expected by registerProvider() and the plugin system.
6
- * Defined locally to avoid depending on internal OpenClaw paths.
4
+ * Registers as a Providerusers select opencompress/* models.
5
+ * Runs a local HTTP proxy that compresses requests via opencompress.ai
6
+ * then forwards to the user's upstream provider. Keys stay local.
7
7
  */
8
8
  type ModelApi = "openai-completions" | "openai-responses" | "anthropic-messages" | "google-generative-ai";
9
9
  type ModelDefinitionConfig = {
10
10
  id: string;
11
11
  name: string;
12
12
  api?: ModelApi;
13
- reasoning: boolean;
14
- input: Array<"text" | "image">;
15
- cost: {
13
+ reasoning?: boolean;
14
+ input?: string[];
15
+ cost?: {
16
16
  input: number;
17
17
  output: number;
18
18
  cacheRead: number;
19
19
  cacheWrite: number;
20
20
  };
21
- contextWindow: number;
22
- maxTokens: number;
23
- headers?: Record<string, string>;
21
+ contextWindow?: number;
22
+ maxTokens?: number;
23
+ [key: string]: unknown;
24
24
  };
25
25
  type ModelProviderConfig = {
26
26
  baseUrl: string;
27
27
  apiKey?: string;
28
28
  api?: ModelApi;
29
- headers?: Record<string, string>;
30
- authHeader?: boolean;
31
29
  models: ModelDefinitionConfig[];
32
30
  [key: string]: unknown;
33
31
  };
34
- type AuthProfileCredential = {
35
- apiKey?: string;
36
- type?: string;
37
- [key: string]: unknown;
38
- };
39
- type ProviderAuthResult = {
40
- profiles: Array<{
41
- profileId: string;
42
- credential: AuthProfileCredential;
43
- }>;
44
- configPatch?: Record<string, unknown>;
45
- defaultModel?: string;
46
- notes?: string[];
47
- };
48
- type WizardPrompter = {
49
- text: (opts: {
50
- message: string;
51
- validate?: (value: string) => string | undefined;
52
- }) => Promise<string | symbol>;
53
- note: (message: string) => void;
54
- progress: (message: string) => {
55
- stop: (message?: string) => void;
56
- };
57
- };
58
- type ProviderAuthContext = {
59
- config: Record<string, unknown>;
60
- agentDir?: string;
61
- workspaceDir?: string;
62
- prompter: WizardPrompter;
63
- runtime: {
64
- log: (message: string) => void;
65
- };
66
- isRemote: boolean;
67
- openUrl: (url: string) => Promise<void>;
68
- };
69
- type ProviderAuthMethod = {
70
- id: string;
71
- label: string;
72
- hint?: string;
73
- kind: "oauth" | "api_key" | "token" | "device_code" | "custom";
74
- run: (ctx: ProviderAuthContext) => Promise<ProviderAuthResult>;
75
- };
76
32
  type ProviderPlugin = {
77
33
  id: string;
78
34
  label: string;
79
- docsPath?: string;
80
35
  aliases?: string[];
81
36
  envVars?: string[];
82
37
  models?: ModelProviderConfig;
83
- auth: ProviderAuthMethod[];
84
- formatApiKey?: (cred: AuthProfileCredential) => string;
85
- };
86
- type PluginLogger = {
87
- debug?: (message: string) => void;
88
- info: (message: string) => void;
89
- warn: (message: string) => void;
90
- error: (message: string) => void;
91
- };
92
- type OpenClawPluginService = {
93
- id: string;
94
- start: () => void | Promise<void>;
95
- stop?: () => void | Promise<void>;
96
- };
97
- type CommandHandler = {
98
- name: string;
99
- description: string;
100
- acceptsArgs?: boolean;
101
- requireAuth?: boolean;
102
- handler: (ctx: {
103
- args?: string;
104
- }) => Promise<{
105
- text: string;
38
+ auth: Array<{
39
+ id: string;
40
+ label: string;
41
+ hint?: string;
42
+ kind: string;
43
+ run: (ctx: any) => Promise<any>;
106
44
  }>;
107
45
  };
108
46
  type OpenClawPluginApi = {
109
47
  id: string;
110
48
  name: string;
111
49
  version?: string;
112
- description?: string;
113
- source: string;
114
- config: Record<string, unknown> & {
115
- models?: {
116
- providers?: Record<string, ModelProviderConfig>;
117
- };
118
- agents?: Record<string, unknown>;
50
+ config: Record<string, any>;
51
+ pluginConfig?: Record<string, any>;
52
+ logger: {
53
+ info: (msg: string) => void;
54
+ warn: (msg: string) => void;
55
+ error: (msg: string) => void;
119
56
  };
120
- pluginConfig?: Record<string, unknown>;
121
- logger: PluginLogger;
122
57
  registerProvider: (provider: ProviderPlugin) => void;
123
- registerTool: (tool: unknown, opts?: unknown) => void;
124
- registerHook: (events: string | string[], handler: unknown, opts?: unknown) => void;
125
- registerHttpRoute: (params: {
126
- path: string;
127
- handler: unknown;
58
+ registerService: (service: {
59
+ id: string;
60
+ start: () => void | Promise<void>;
61
+ stop?: () => void | Promise<void>;
62
+ }) => void;
63
+ registerCommand: (command: {
64
+ name: string;
65
+ description: string;
66
+ acceptsArgs?: boolean;
67
+ handler: (ctx: {
68
+ args?: string;
69
+ }) => Promise<{
70
+ text: string;
71
+ }>;
128
72
  }) => void;
129
- registerService: (service: OpenClawPluginService) => void;
130
- registerCommand: (command: CommandHandler) => void;
131
73
  resolvePath: (input: string) => string;
132
- on: (hookName: string, handler: unknown, opts?: unknown) => void;
74
+ on: (hookName: string, handler: unknown) => void;
133
75
  };
134
- type OpenClawPluginDefinition = {
135
- id?: string;
136
- name?: string;
137
- description?: string;
138
- version?: string;
139
- register?: (api: OpenClawPluginApi) => void | Promise<void>;
140
- activate?: (api: OpenClawPluginApi) => void | Promise<void>;
76
+ declare const plugin: {
77
+ id: string;
78
+ name: string;
79
+ description: string;
80
+ version: string;
81
+ register(api: OpenClawPluginApi): void;
141
82
  };
142
- declare const plugin: OpenClawPluginDefinition;
143
83
 
144
84
  export { plugin as default };
package/dist/index.js CHANGED
@@ -1,571 +1,417 @@
1
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
- }) : x)(function(x) {
4
- if (typeof require !== "undefined") return require.apply(this, arguments);
5
- throw Error('Dynamic require of "' + x + '" is not supported');
6
- });
1
+ // src/config.ts
2
+ var VERSION = "2.0.0";
3
+ var PROXY_PORT = 8401;
4
+ var PROXY_HOST = "127.0.0.1";
5
+ var OCC_API = "https://www.opencompress.ai/api";
6
+ var PROVIDER_ID = "opencompress";
7
7
 
8
- // src/index.ts
9
- var VERSION = "1.9.1";
10
- var DEFAULT_BASE_URL = "https://www.opencompress.ai/api";
11
- function getApiKey(api) {
12
- const auth = api.config.auth;
13
- const fromConfig = auth?.profiles?.opencompress?.credentials?.["api-key"]?.apiKey;
14
- if (fromConfig) return fromConfig;
15
- if (process.env.OPENCOMPRESS_API_KEY) return process.env.OPENCOMPRESS_API_KEY;
16
- try {
17
- const os = __require("os");
18
- const fs = __require("fs");
19
- const path = __require("path");
20
- const agentsDir = path.join(os.homedir(), ".openclaw", "agents");
21
- if (!fs.existsSync(agentsDir)) return void 0;
22
- const agentDirs = fs.readdirSync(agentsDir);
23
- for (const agent of agentDirs) {
24
- const authPath = path.join(agentsDir, agent, "agent", "auth-profiles.json");
25
- if (!fs.existsSync(authPath)) continue;
26
- try {
27
- const profiles = JSON.parse(fs.readFileSync(authPath, "utf-8"));
28
- const ocProfile = profiles?.profiles?.["opencompress:default"];
29
- if (ocProfile?.key) return ocProfile.key;
30
- } catch {
31
- continue;
32
- }
8
+ // src/models.ts
9
+ function resolveUpstream(modelId, providers) {
10
+ const stripped = modelId.replace(/^opencompress\//, "");
11
+ if (stripped === "auto") {
12
+ for (const [id, config2] of Object.entries(providers)) {
13
+ if (id === "opencompress") continue;
14
+ const firstModel = config2.models?.[0]?.id;
15
+ if (!firstModel) continue;
16
+ return {
17
+ upstreamProvider: id,
18
+ upstreamModel: firstModel,
19
+ upstreamKey: config2.apiKey,
20
+ upstreamBaseUrl: config2.baseUrl,
21
+ upstreamApi: config2.api || "openai-completions"
22
+ };
33
23
  }
34
- } catch {
24
+ return null;
35
25
  }
36
- return void 0;
26
+ const slashIdx = stripped.indexOf("/");
27
+ if (slashIdx === -1) {
28
+ const config2 = providers[stripped];
29
+ if (!config2) return null;
30
+ return {
31
+ upstreamProvider: stripped,
32
+ upstreamModel: config2.models?.[0]?.id || stripped,
33
+ upstreamKey: config2.apiKey,
34
+ upstreamBaseUrl: config2.baseUrl,
35
+ upstreamApi: config2.api || "openai-completions"
36
+ };
37
+ }
38
+ const upstreamProvider = stripped.slice(0, slashIdx);
39
+ const upstreamModel = stripped.slice(slashIdx + 1);
40
+ const config = providers[upstreamProvider];
41
+ if (!config) return null;
42
+ return {
43
+ upstreamProvider,
44
+ upstreamModel,
45
+ upstreamKey: config.apiKey,
46
+ upstreamBaseUrl: config.baseUrl,
47
+ upstreamApi: config.api || "openai-completions"
48
+ };
37
49
  }
38
- function persistAuthProfile(apiKey) {
39
- try {
40
- const os = __require("os");
41
- const fs = __require("fs");
42
- const path = __require("path");
43
- const agentsDir = path.join(os.homedir(), ".openclaw", "agents");
44
- if (!fs.existsSync(agentsDir)) return;
45
- const agentDirs = fs.readdirSync(agentsDir);
46
- for (const agent of agentDirs) {
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 });
50
+ function generateModelCatalog(providers) {
51
+ const models = [];
52
+ for (const [providerId, config] of Object.entries(providers)) {
53
+ if (providerId === "opencompress") continue;
54
+ for (const model of config.models || []) {
55
+ models.push({
56
+ ...model,
57
+ id: `opencompress/${providerId}/${model.id}`,
58
+ name: `${model.name || model.id} (compressed)`,
59
+ api: config.api || "openai-completions"
60
+ });
61
+ }
62
+ }
63
+ models.unshift({
64
+ id: "opencompress/auto",
65
+ name: "OpenCompress Auto (compressed, uses default provider)",
66
+ api: "openai-completions",
67
+ reasoning: false,
68
+ input: ["text"],
69
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
70
+ contextWindow: 2e5,
71
+ maxTokens: 8192
72
+ });
73
+ return models;
74
+ }
75
+
76
+ // src/proxy.ts
77
+ import http from "http";
78
+ var server = null;
79
+ function startProxy(getProviders2, getOccKey2) {
80
+ if (server) return server;
81
+ server = http.createServer(async (req, res) => {
82
+ if (req.url === "/health" && req.method === "GET") {
83
+ res.writeHead(200, { "Content-Type": "application/json" });
84
+ res.end(JSON.stringify({ status: "ok", version: "2.0.0" }));
85
+ return;
86
+ }
87
+ if (req.method !== "POST") {
88
+ res.writeHead(405);
89
+ res.end("Method not allowed");
90
+ return;
91
+ }
92
+ const isMessages = req.url === "/v1/messages";
93
+ const isCompletions = req.url === "/v1/chat/completions";
94
+ if (!isMessages && !isCompletions) {
95
+ res.writeHead(404);
96
+ res.end("Not found");
97
+ return;
98
+ }
99
+ try {
100
+ const body = await readBody(req);
101
+ const parsed = JSON.parse(body);
102
+ const modelId = parsed.model || "opencompress/auto";
103
+ const upstream = resolveUpstream(modelId, getProviders2());
104
+ if (!upstream) {
105
+ res.writeHead(400, { "Content-Type": "application/json" });
106
+ res.end(JSON.stringify({
107
+ error: { message: `Cannot resolve upstream for model: ${modelId}. Check your provider config.` }
108
+ }));
109
+ return;
110
+ }
111
+ const occKey = getOccKey2();
112
+ if (!occKey) {
113
+ res.writeHead(401, { "Content-Type": "application/json" });
114
+ res.end(JSON.stringify({
115
+ error: { message: "No OpenCompress API key. Run: openclaw onboard opencompress" }
116
+ }));
117
+ return;
51
118
  }
52
- let profiles = {
53
- version: 1,
54
- profiles: {}
119
+ const occEndpoint = upstream.upstreamApi === "anthropic-messages" ? `${OCC_API}/v1/messages` : `${OCC_API}/v1/chat/completions`;
120
+ const headers = {
121
+ "Content-Type": "application/json",
122
+ "x-api-key": occKey
55
123
  };
56
- if (fs.existsSync(authPath)) {
57
- try {
58
- profiles = JSON.parse(fs.readFileSync(authPath, "utf-8"));
59
- } catch {
124
+ if (upstream.upstreamKey) {
125
+ headers["x-upstream-key"] = upstream.upstreamKey;
126
+ }
127
+ if (upstream.upstreamBaseUrl) {
128
+ headers["x-upstream-base-url"] = upstream.upstreamBaseUrl;
129
+ }
130
+ if (upstream.upstreamApi === "anthropic-messages") {
131
+ headers["anthropic-version"] = req.headers["anthropic-version"] || "2023-06-01";
132
+ }
133
+ for (const [key, val] of Object.entries(req.headers)) {
134
+ if (key.startsWith("anthropic-") && typeof val === "string") {
135
+ headers[key] = val;
60
136
  }
61
137
  }
62
- profiles.profiles["opencompress:default"] = {
63
- type: "api_key",
64
- provider: "opencompress",
65
- key: apiKey
66
- };
67
- fs.writeFileSync(authPath, JSON.stringify(profiles, null, 2) + "\n");
68
- }
69
- } catch {
70
- }
71
- }
72
- function persistAgentAuthJson(apiKey) {
73
- try {
74
- const os = __require("os");
75
- const fs = __require("fs");
76
- const path = __require("path");
77
- const agentsDir = path.join(os.homedir(), ".openclaw", "agents");
78
- if (!fs.existsSync(agentsDir)) return;
79
- const agentDirs = fs.readdirSync(agentsDir);
80
- for (const agent of agentDirs) {
81
- const authPath = path.join(agentsDir, agent, "agent", "auth.json");
82
- const authDir = path.dirname(authPath);
83
- if (!fs.existsSync(authDir)) continue;
84
- let data = {};
85
- if (fs.existsSync(authPath)) {
138
+ parsed.model = upstream.upstreamModel;
139
+ const isStream = parsed.stream !== false;
140
+ if (isStream) {
141
+ res.writeHead(200, {
142
+ "Content-Type": "text/event-stream",
143
+ "Cache-Control": "no-cache",
144
+ Connection: "keep-alive"
145
+ });
146
+ const heartbeat = setInterval(() => {
147
+ try {
148
+ res.write(": heartbeat\n\n");
149
+ } catch {
150
+ clearInterval(heartbeat);
151
+ }
152
+ }, 2e3);
86
153
  try {
87
- data = JSON.parse(fs.readFileSync(authPath, "utf-8"));
88
- } catch {
89
- data = {};
154
+ const occRes = await fetch(occEndpoint, {
155
+ method: "POST",
156
+ headers,
157
+ body: JSON.stringify(parsed)
158
+ });
159
+ clearInterval(heartbeat);
160
+ if (!occRes.ok) {
161
+ const fallbackRes = await directUpstream(upstream, parsed, req.headers);
162
+ if (fallbackRes) {
163
+ for await (const chunk of fallbackRes.body) {
164
+ res.write(chunk);
165
+ }
166
+ } else {
167
+ res.write(`data: ${JSON.stringify({ error: { message: `OpenCompress error: ${occRes.status}` } })}
168
+
169
+ `);
170
+ }
171
+ res.end();
172
+ return;
173
+ }
174
+ for await (const chunk of occRes.body) {
175
+ res.write(chunk);
176
+ }
177
+ res.end();
178
+ } catch (err) {
179
+ clearInterval(heartbeat);
180
+ try {
181
+ const fallbackRes = await directUpstream(upstream, parsed, req.headers);
182
+ if (fallbackRes) {
183
+ for await (const chunk of fallbackRes.body) {
184
+ res.write(chunk);
185
+ }
186
+ }
187
+ } catch {
188
+ }
189
+ res.end();
90
190
  }
91
- }
92
- data.opencompress = {
93
- type: "api_key",
94
- key: apiKey
95
- };
96
- fs.writeFileSync(authPath, JSON.stringify(data, null, 2) + "\n");
97
- }
98
- } catch {
99
- }
100
- }
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() {
107
- try {
108
- const fs = __require("fs");
109
- const p = proxyStatePath();
110
- if (!fs.existsSync(p)) return { enabled: false, originals: {} };
111
- return JSON.parse(fs.readFileSync(p, "utf-8"));
112
- } catch {
113
- return { enabled: false, originals: {} };
114
- }
115
- }
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() {
124
- try {
125
- const os = __require("os");
126
- const fs = __require("fs");
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 {
160
- const agentsDir = path.join(os.homedir(), ".openclaw", "agents");
161
- if (!fs.existsSync(agentsDir)) return;
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)) {
191
+ } else {
167
192
  try {
168
- data = JSON.parse(fs.readFileSync(modelsPath, "utf-8"));
193
+ const occRes = await fetch(occEndpoint, {
194
+ method: "POST",
195
+ headers,
196
+ body: JSON.stringify(parsed)
197
+ });
198
+ if (!occRes.ok) {
199
+ const fallbackRes = await directUpstream(upstream, parsed, req.headers);
200
+ const fallbackBody = fallbackRes ? await fallbackRes.text() : JSON.stringify({ error: { message: "Compression + direct both failed" } });
201
+ res.writeHead(fallbackRes?.status || 502, { "Content-Type": "application/json" });
202
+ res.end(fallbackBody);
203
+ return;
204
+ }
205
+ const data = await occRes.text();
206
+ res.writeHead(200, { "Content-Type": "application/json" });
207
+ res.end(data);
169
208
  } catch {
209
+ const fallbackRes = await directUpstream(upstream, parsed, req.headers);
210
+ const fallbackBody = fallbackRes ? await fallbackRes.text() : JSON.stringify({ error: { message: "Both paths failed" } });
211
+ res.writeHead(fallbackRes?.status || 502, { "Content-Type": "application/json" });
212
+ res.end(fallbackBody);
170
213
  }
171
- if (!data.providers) data.providers = {};
172
214
  }
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 } : {}
188
- };
189
- fs.writeFileSync(modelsPath, JSON.stringify(data, null, 2) + "\n");
215
+ } catch (err) {
216
+ res.writeHead(500, { "Content-Type": "application/json" });
217
+ res.end(JSON.stringify({ error: { message: String(err) } }));
190
218
  }
191
- } catch {
192
- }
219
+ });
220
+ server.listen(PROXY_PORT, PROXY_HOST, () => {
221
+ });
222
+ server.on("error", (err) => {
223
+ if (err.code === "EADDRINUSE") {
224
+ server = null;
225
+ }
226
+ });
227
+ return server;
193
228
  }
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)";
229
+ function stopProxy() {
230
+ if (server) {
231
+ server.close();
232
+ server = null;
202
233
  }
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
234
+ }
235
+ async function directUpstream(upstream, body, originalHeaders) {
236
+ try {
237
+ const url = upstream.upstreamApi === "anthropic-messages" ? `${upstream.upstreamBaseUrl}/v1/messages` : `${upstream.upstreamBaseUrl}/v1/chat/completions`;
238
+ const headers = {
239
+ "Content-Type": "application/json"
221
240
  };
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;
241
+ if (upstream.upstreamApi === "anthropic-messages") {
242
+ headers["x-api-key"] = upstream.upstreamKey || "";
243
+ headers["anthropic-version"] = originalHeaders["anthropic-version"] || "2023-06-01";
229
244
  } 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;
245
+ headers["Authorization"] = `Bearer ${upstream.upstreamKey || ""}`;
237
246
  }
238
- persistProviderToDisk(id, provider);
239
- proxied.push(id);
247
+ return await fetch(url, {
248
+ method: "POST",
249
+ headers,
250
+ body: JSON.stringify(body)
251
+ });
252
+ } catch {
253
+ return null;
240
254
  }
241
- state.enabled = true;
242
- writeProxyState(state);
243
- return { proxied, skipped, source };
244
255
  }
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;
256
+ function readBody(req) {
257
+ return new Promise((resolve, reject) => {
258
+ let data = "";
259
+ req.on("data", (chunk) => data += chunk);
260
+ req.on("end", () => resolve(data));
261
+ req.on("error", reject);
262
+ });
279
263
  }
280
- var opencompressProvider = {
281
- id: "opencompress",
282
- label: "OpenCompress",
283
- docsPath: "https://docs.opencompress.ai",
284
- aliases: ["oc", "compress"],
285
- envVars: ["OPENCOMPRESS_API_KEY"],
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
- },
293
- formatApiKey: (cred) => cred.apiKey || "",
294
- auth: [
295
- {
296
- id: "api-key",
297
- label: "OpenCompress",
298
- hint: "Compress all LLM calls automatically \u2014 save 40-60% on any provider",
299
- kind: "custom",
300
- run: async (ctx) => {
301
- ctx.prompter.note(
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!"
303
- );
304
- const spinner = ctx.prompter.progress("Setting up your account...");
305
- try {
306
- const res = await fetch(`${DEFAULT_BASE_URL}/v1/provision`, {
307
- method: "POST",
308
- headers: { "Content-Type": "application/json" },
309
- body: JSON.stringify({})
310
- // BYOK — no upstream key needed here, user's existing providers handle that
311
- });
312
- if (!res.ok) {
313
- const err = await res.json().catch(() => ({ error: { message: "Unknown error" } }));
314
- spinner.stop("Setup failed");
315
- throw new Error(
316
- `Provisioning failed: ${err.error?.message || res.statusText}`
317
- );
318
- }
319
- const data = await res.json();
320
- spinner.stop("Account created!");
321
- persistAuthProfile(data.apiKey);
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;
264
+
265
+ // src/index.ts
266
+ function getOccKey(api) {
267
+ const auth = api.config.auth;
268
+ const fromConfig = auth?.profiles?.opencompress?.credentials?.["api-key"]?.apiKey;
269
+ if (fromConfig) return fromConfig;
270
+ if (process.env.OPENCOMPRESS_API_KEY) return process.env.OPENCOMPRESS_API_KEY;
271
+ if (api.pluginConfig?.apiKey) return api.pluginConfig.apiKey;
272
+ return void 0;
273
+ }
274
+ function getProviders(api) {
275
+ return api.config.models?.providers || {};
276
+ }
277
+ function createProvider(api) {
278
+ return {
279
+ id: PROVIDER_ID,
280
+ label: "OpenCompress (BYOK Compression Proxy)",
281
+ aliases: ["oc", "compress"],
282
+ envVars: ["OPENCOMPRESS_API_KEY"],
283
+ // Dynamic model catalog — mirrors user's existing providers
284
+ models: {
285
+ baseUrl: `http://${PROXY_HOST}:${PROXY_PORT}/v1`,
286
+ api: "openai-completions",
287
+ models: generateModelCatalog(getProviders(api))
288
+ },
289
+ auth: [
290
+ {
291
+ id: "api-key",
292
+ label: "OpenCompress",
293
+ hint: "Save 20-40% on any LLM. Your API keys stay local.",
294
+ kind: "custom",
295
+ run: async (ctx) => {
296
+ ctx.prompter.note(
297
+ "\u{1F99E} OpenCompress \u2014 compress every LLM call, save 20-40%\n\nYour existing API keys (Claude, GPT, etc.) stay on your machine.\nWe just compress the prompts before they're sent."
298
+ );
299
+ const spinner = ctx.prompter.progress("Creating your account...");
300
+ try {
301
+ const res = await fetch(`${OCC_API}/v1/provision`, {
302
+ method: "POST",
303
+ headers: { "Content-Type": "application/json" },
304
+ body: JSON.stringify({})
305
+ });
306
+ if (!res.ok) {
307
+ spinner.stop("Failed");
308
+ throw new Error(`Provisioning failed: ${res.statusText}`);
329
309
  }
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");
352
- return {
353
- profiles: [
354
- {
310
+ const data = await res.json();
311
+ spinner.stop("Account created!");
312
+ return {
313
+ profiles: [{
355
314
  profileId: "default",
356
315
  credential: { apiKey: data.apiKey }
357
- }
358
- ],
359
- configPatch: shouldEnable ? { _autoEnableProxy: true } : void 0,
360
- notes
361
- };
362
- } catch (err) {
363
- spinner.stop("Setup failed");
364
- throw err instanceof Error ? err : new Error(String(err));
316
+ }],
317
+ notes: [
318
+ "\u{1F99E} OpenCompress ready!",
319
+ `\u{1F4B0} ${data.freeCredit} free credit.`,
320
+ "",
321
+ "Select any opencompress/* model to use compression.",
322
+ "Your existing provider keys are used automatically.",
323
+ "",
324
+ "Dashboard: https://www.opencompress.ai/dashboard"
325
+ ]
326
+ };
327
+ } catch (err) {
328
+ spinner.stop("Failed");
329
+ throw err instanceof Error ? err : new Error(String(err));
330
+ }
365
331
  }
366
332
  }
367
- }
368
- ]
369
- };
333
+ ]
334
+ };
335
+ }
370
336
  var plugin = {
371
337
  id: "opencompress",
372
338
  name: "OpenCompress",
373
- description: "Transparent prompt compression \u2014 save 40-70% on Claude, GPT, and any LLM provider",
339
+ description: "BYOK prompt compression \u2014 save 20-40% on any LLM provider",
374
340
  version: VERSION,
375
341
  register(api) {
376
- const baseUrl = api.pluginConfig?.baseUrl || DEFAULT_BASE_URL;
377
- api.registerProvider(opencompressProvider);
378
- const apiKey = getApiKey(api);
379
- if (apiKey) {
380
- persistAuthProfile(apiKey);
381
- persistAgentAuthJson(apiKey);
382
- }
383
- api.logger.info("OpenCompress registered (transparent proxy mode)");
342
+ api.registerProvider(createProvider(api));
343
+ api.logger.info(`OpenCompress v${VERSION} registered as provider`);
344
+ api.registerService({
345
+ id: "opencompress-proxy",
346
+ start: () => {
347
+ startProxy(
348
+ () => getProviders(api),
349
+ () => getOccKey(api)
350
+ );
351
+ api.logger.info(`Compression proxy started on ${PROXY_HOST}:${PROXY_PORT}`);
352
+ },
353
+ stop: () => {
354
+ stopProxy();
355
+ api.logger.info("Compression proxy stopped");
356
+ }
357
+ });
384
358
  api.registerCommand({
385
359
  name: "compress-stats",
386
- description: "Show OpenCompress usage statistics and savings",
387
- acceptsArgs: true,
388
- requireAuth: false,
360
+ description: "Show compression savings and balance",
389
361
  handler: async () => {
390
- const apiKey2 = getApiKey(api);
391
- if (!apiKey2) {
392
- return {
393
- text: "No API key found. Run `openclaw onboard opencompress` to set up."
394
- };
362
+ const occKey = getOccKey(api);
363
+ if (!occKey) {
364
+ return { text: "No API key. Run `openclaw onboard opencompress` first." };
395
365
  }
396
366
  try {
397
- const res = await fetch(`${baseUrl}/user/stats`, {
398
- headers: { Authorization: `Bearer ${apiKey2}` }
367
+ const res = await fetch(`${OCC_API}/user/stats`, {
368
+ headers: { Authorization: `Bearer ${occKey}` }
399
369
  });
400
- if (!res.ok) {
401
- return { text: `Failed to fetch stats: HTTP ${res.status}` };
402
- }
403
- const stats = await res.json();
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);
407
- const rate = stats.avgCompressionRate ? `${(Number(stats.avgCompressionRate) * 100).toFixed(1)}%` : "N/A";
408
- const origTokens = Number(stats.totalOriginalTokens || 0).toLocaleString();
409
- const compTokens = Number(stats.totalCompressedTokens || 0).toLocaleString();
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(
425
- "",
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") };
433
- } catch (err) {
370
+ if (!res.ok) return { text: `Failed: HTTP ${res.status}` };
371
+ const s = await res.json();
372
+ const balance = Number(s.balanceUsd || s.balance || 0);
373
+ const calls = s.monthlyApiCalls ?? s.totalCalls ?? 0;
374
+ const rate = s.avgCompressionRate ? `${(Number(s.avgCompressionRate) * 100).toFixed(1)}%` : "N/A";
434
375
  return {
435
- text: `Error fetching stats: ${err instanceof Error ? err.message : String(err)}`
376
+ text: [
377
+ "```",
378
+ "\u{1F99E} OpenCompress Stats",
379
+ "=====================",
380
+ `Balance: $${balance.toFixed(2)}`,
381
+ `API calls: ${calls}`,
382
+ `Avg compression: ${rate}`,
383
+ `Tokens saved: ${(Number(s.totalOriginalTokens || 0) - Number(s.totalCompressedTokens || 0)).toLocaleString()}`,
384
+ "```",
385
+ "",
386
+ balance < 0.5 ? `\u26A0\uFE0F Low balance! Link account for $10 bonus: https://www.opencompress.ai/dashboard?link=${encodeURIComponent(occKey)}` : "Dashboard: https://www.opencompress.ai/dashboard"
387
+ ].join("\n")
436
388
  };
389
+ } catch (err) {
390
+ return { text: `Error: ${err instanceof Error ? err.message : String(err)}` };
437
391
  }
438
392
  }
439
393
  });
440
- api.logger.info("Registered /compress-stats command");
441
394
  api.registerCommand({
442
395
  name: "compress",
443
- description: "Toggle transparent compression for all LLM providers",
444
- acceptsArgs: true,
445
- requireAuth: false,
446
- handler: async (ctx) => {
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") };
470
- }
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
- }
476
- return {
477
- text: [
478
- "**Compression disabled.** Restored original provider configs.",
479
- "",
480
- `Restored: ${restored.join(", ")}`,
481
- "",
482
- "To re-enable: `/compress on`"
483
- ].join("\n")
484
- };
485
- }
486
- const proxyState = readProxyState();
487
- const occKey = getApiKey(api);
488
- const statusLines = [
489
- "**OpenCompress Transparent Proxy**",
396
+ description: "Show OpenCompress status and available compressed models",
397
+ handler: async () => {
398
+ const occKey = getOccKey(api);
399
+ const providers = getProviders(api);
400
+ const models = generateModelCatalog(providers);
401
+ const lines = [
402
+ "**OpenCompress Status**",
490
403
  "",
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
- }
497
- statusLines.push(
404
+ `API key: ${occKey ? `${occKey.slice(0, 12)}...` : "not set (run `openclaw onboard opencompress`)"}`,
405
+ `Proxy: http://${PROXY_HOST}:${PROXY_PORT}`,
498
406
  "",
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") };
505
- }
506
- });
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);
518
- const os = __require("os");
519
- const fs = __require("fs");
520
- const path = __require("path");
521
- const logDir = path.join(os.homedir(), ".openclaw", "opencompress-logs");
522
- try {
523
- fs.mkdirSync(logDir, { recursive: true });
524
- } catch {
525
- }
526
- api.on("llm_input", (...args) => {
527
- try {
528
- const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
529
- const event = args[0] || {};
530
- const systemPromptLen = typeof event.systemPrompt === "string" ? event.systemPrompt.length : 0;
531
- const historyMessages = Array.isArray(event.historyMessages) ? event.historyMessages : [];
532
- const promptLen = typeof event.prompt === "string" ? event.prompt.length : 0;
533
- const systemTokens = Math.ceil(systemPromptLen / 4);
534
- const historyText = JSON.stringify(historyMessages);
535
- const historyTokens = Math.ceil(historyText.length / 4);
536
- const promptTokens = Math.ceil(promptLen / 4);
537
- const totalTokens = systemTokens + historyTokens + promptTokens;
538
- const entry = {
539
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
540
- _argCount: args.length,
541
- _eventKeys: Object.keys(event),
542
- runId: event.runId,
543
- sessionId: event.sessionId,
544
- provider: event.provider,
545
- model: event.model,
546
- imagesCount: event.imagesCount || 0,
547
- tokenEstimate: { systemPrompt: systemTokens, history: historyTokens, currentPrompt: promptTokens, total: totalTokens },
548
- charLengths: { systemPrompt: systemPromptLen, history: historyText.length, currentPrompt: promptLen },
549
- historyMessageCount: historyMessages.length,
550
- systemPrompt: event.systemPrompt,
551
- historyMessages: event.historyMessages,
552
- prompt: event.prompt
553
- };
554
- const model = typeof event.model === "string" ? event.model.replace(/\//g, "_") : "unknown";
555
- const filename = `${ts}_${model}.json`;
556
- fs.writeFileSync(path.join(logDir, filename), JSON.stringify(entry, null, 2) + "\n");
557
- } catch (err) {
558
- try {
559
- const errFile = path.join(logDir, `error_${Date.now()}.txt`);
560
- fs.writeFileSync(errFile, `${err}
561
- ${err.stack || ""}
562
- args: ${JSON.stringify(args?.map((a) => typeof a))}
563
- `);
564
- } catch {
565
- }
407
+ "**Available compressed models:**",
408
+ ...models.map((m) => ` ${m.id}`),
409
+ "",
410
+ "Select any of these models to use compression."
411
+ ];
412
+ return { text: lines.join("\n") };
566
413
  }
567
414
  });
568
- api.logger.info(`LLM input logging enabled \u2192 ${logDir}`);
569
415
  }
570
416
  };
571
417
  var index_default = plugin;
@@ -1,20 +1,19 @@
1
1
  {
2
2
  "id": "opencompress",
3
3
  "name": "OpenCompress",
4
- "description": "5-layer prompt compression — 53% input reduction, 62% latency cut, 96% quality. Save 40-70% on any LLM.",
4
+ "description": "BYOK prompt compression — save 20-40% on any LLM. Your keys stay local.",
5
+ "providers": ["opencompress"],
5
6
  "configSchema": {
6
7
  "type": "object",
7
8
  "properties": {
8
9
  "apiKey": {
9
10
  "type": "string",
10
11
  "description": "OpenCompress API key (sk-occ-...)"
11
- },
12
- "baseUrl": {
13
- "type": "string",
14
- "default": "https://www.opencompress.ai/api",
15
- "description": "OpenCompress API base URL"
16
12
  }
17
13
  },
18
14
  "required": []
15
+ },
16
+ "providerAuthEnvVars": {
17
+ "opencompress": ["OPENCOMPRESS_API_KEY"]
19
18
  }
20
19
  }
@@ -1,14 +1,10 @@
1
1
  {
2
2
  "id": "opencompress",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "securityProfile": {
5
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"
6
+ "promptContent": "pass-through (compressed in-transit, never stored)",
7
+ "upstreamApiKeys": "local-only (read from api.config, sent per-request as header, never persisted)"
12
8
  },
13
9
  "networkAccess": {
14
10
  "outbound": [
@@ -16,57 +12,26 @@
16
12
  "host": "www.opencompress.ai",
17
13
  "port": 443,
18
14
  "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": [
15
+ "purpose": "Compress prompts and forward to user's upstream LLM provider"
16
+ },
38
17
  {
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."
18
+ "host": "127.0.0.1",
19
+ "port": 8401,
20
+ "protocol": "http",
21
+ "purpose": "Local proxy — receives requests from OpenClaw, routes to opencompress.ai"
51
22
  }
52
23
  ]
53
24
  },
54
25
  "permissions": {
55
- "fileSystem": "read/write ~/.openclaw/ config files only",
56
- "environment": ["OPENCOMPRESS_API_KEY", "OPENCOMPRESS_LLM_KEY"],
26
+ "fileSystem": "none (all config via api.config runtime API)",
27
+ "environment": ["OPENCOMPRESS_API_KEY"],
57
28
  "shell": "none",
58
- "network": "outbound HTTPS to www.opencompress.ai only"
29
+ "network": "outbound HTTPS to www.opencompress.ai + local HTTP proxy on 127.0.0.1:8401"
59
30
  }
60
31
  },
61
32
  "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."
33
+ "prompts": "Compressed in-memory and forwarded. NEVER stored or logged.",
34
+ "upstreamKeys": "Read from OpenClaw runtime config (api.config). Sent per-request via x-upstream-key header. Never persisted server-side.",
35
+ "billing": "Only token counts recorded (original, compressed, model). No prompt content."
71
36
  }
72
37
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencompress/opencompress",
3
- "version": "1.9.1",
3
+ "version": "2.0.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",