@opencompress/opencompress 1.9.2 → 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 +84 -0
- package/dist/index.js +369 -464
- package/openclaw.plugin.json +5 -6
- package/openclaw.security.json +15 -50
- package/package.json +1 -1
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCompress Plugin for OpenClaw v2
|
|
3
|
+
*
|
|
4
|
+
* Registers as a Provider — users 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
|
+
*/
|
|
8
|
+
type ModelApi = "openai-completions" | "openai-responses" | "anthropic-messages" | "google-generative-ai";
|
|
9
|
+
type ModelDefinitionConfig = {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
api?: ModelApi;
|
|
13
|
+
reasoning?: boolean;
|
|
14
|
+
input?: string[];
|
|
15
|
+
cost?: {
|
|
16
|
+
input: number;
|
|
17
|
+
output: number;
|
|
18
|
+
cacheRead: number;
|
|
19
|
+
cacheWrite: number;
|
|
20
|
+
};
|
|
21
|
+
contextWindow?: number;
|
|
22
|
+
maxTokens?: number;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
};
|
|
25
|
+
type ModelProviderConfig = {
|
|
26
|
+
baseUrl: string;
|
|
27
|
+
apiKey?: string;
|
|
28
|
+
api?: ModelApi;
|
|
29
|
+
models: ModelDefinitionConfig[];
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
};
|
|
32
|
+
type ProviderPlugin = {
|
|
33
|
+
id: string;
|
|
34
|
+
label: string;
|
|
35
|
+
aliases?: string[];
|
|
36
|
+
envVars?: string[];
|
|
37
|
+
models?: ModelProviderConfig;
|
|
38
|
+
auth: Array<{
|
|
39
|
+
id: string;
|
|
40
|
+
label: string;
|
|
41
|
+
hint?: string;
|
|
42
|
+
kind: string;
|
|
43
|
+
run: (ctx: any) => Promise<any>;
|
|
44
|
+
}>;
|
|
45
|
+
};
|
|
46
|
+
type OpenClawPluginApi = {
|
|
47
|
+
id: string;
|
|
48
|
+
name: string;
|
|
49
|
+
version?: string;
|
|
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;
|
|
56
|
+
};
|
|
57
|
+
registerProvider: (provider: ProviderPlugin) => void;
|
|
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
|
+
}>;
|
|
72
|
+
}) => void;
|
|
73
|
+
resolvePath: (input: string) => string;
|
|
74
|
+
on: (hookName: string, handler: unknown) => void;
|
|
75
|
+
};
|
|
76
|
+
declare const plugin: {
|
|
77
|
+
id: string;
|
|
78
|
+
name: string;
|
|
79
|
+
description: string;
|
|
80
|
+
version: string;
|
|
81
|
+
register(api: OpenClawPluginApi): void;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export { plugin as default };
|
package/dist/index.js
CHANGED
|
@@ -1,512 +1,417 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const agentDir = api2.resolvePath(".");
|
|
24
|
-
const authPath = path.join(agentDir, "auth-profiles.json");
|
|
25
|
-
let profiles = {
|
|
26
|
-
version: 1,
|
|
27
|
-
profiles: {}
|
|
28
|
-
};
|
|
29
|
-
if (fs.existsSync(authPath)) {
|
|
30
|
-
try {
|
|
31
|
-
profiles = JSON.parse(fs.readFileSync(authPath, "utf-8"));
|
|
32
|
-
} catch {
|
|
33
|
-
}
|
|
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
|
+
};
|
|
34
23
|
}
|
|
35
|
-
|
|
36
|
-
type: "api_key",
|
|
37
|
-
provider: "opencompress",
|
|
38
|
-
key: apiKey
|
|
39
|
-
};
|
|
40
|
-
fs.writeFileSync(authPath, JSON.stringify(profiles, null, 2) + "\n");
|
|
41
|
-
} catch {
|
|
24
|
+
return null;
|
|
42
25
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
55
|
-
} catch {
|
|
56
|
-
return { enabled: false, originals: {} };
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
function writeProxyState(state) {
|
|
60
|
-
try {
|
|
61
|
-
const fs = __require("fs");
|
|
62
|
-
fs.writeFileSync(proxyStatePath(), JSON.stringify(state, null, 2) + "\n");
|
|
63
|
-
} catch {
|
|
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
|
+
};
|
|
64
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
|
+
};
|
|
65
49
|
}
|
|
66
|
-
function
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return void 0;
|
|
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
|
+
}
|
|
79
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;
|
|
80
74
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
baseUrl: config.baseUrl,
|
|
93
|
-
api: config.api || "openai-completions",
|
|
94
|
-
apiKey: config.apiKey || void 0,
|
|
95
|
-
models: config.models?.map((m) => ({ id: m.id, name: m.name })) || [],
|
|
96
|
-
...config.headers ? { headers: config.headers } : {}
|
|
97
|
-
};
|
|
98
|
-
fs.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
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;
|
|
99
86
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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;
|
|
118
|
+
}
|
|
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
|
|
123
|
+
};
|
|
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;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
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);
|
|
153
|
+
try {
|
|
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();
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
110
192
|
try {
|
|
111
|
-
|
|
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);
|
|
112
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);
|
|
113
213
|
}
|
|
114
|
-
if (!data.providers) data.providers = {};
|
|
115
214
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
apiKey: config.apiKey || void 0,
|
|
120
|
-
models: config.models?.map((m) => ({
|
|
121
|
-
id: m.id,
|
|
122
|
-
name: m.name,
|
|
123
|
-
api: m.api || config.api || "openai-completions",
|
|
124
|
-
reasoning: m.reasoning ?? false,
|
|
125
|
-
input: m.input || ["text"],
|
|
126
|
-
cost: m.cost || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
127
|
-
contextWindow: m.contextWindow || 2e5,
|
|
128
|
-
maxTokens: m.maxTokens || 8192
|
|
129
|
-
})) || [],
|
|
130
|
-
...config.headers ? { headers: config.headers } : {}
|
|
131
|
-
};
|
|
132
|
-
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) } }));
|
|
133
218
|
}
|
|
134
|
-
}
|
|
135
|
-
|
|
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;
|
|
136
228
|
}
|
|
137
|
-
function
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
let source = "api.config";
|
|
142
|
-
if (!providers || Object.keys(providers).length === 0) {
|
|
143
|
-
providers = readProvidersFromDisk();
|
|
144
|
-
source = "disk (openclaw.json)";
|
|
229
|
+
function stopProxy() {
|
|
230
|
+
if (server) {
|
|
231
|
+
server.close();
|
|
232
|
+
server = null;
|
|
145
233
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const provider = raw;
|
|
153
|
-
if (provider.baseUrl?.includes("opencompress.ai")) continue;
|
|
154
|
-
const pApi = provider.api || "openai-completions";
|
|
155
|
-
if (pApi === "google-generative-ai") {
|
|
156
|
-
skipped.push(`${id} (${pApi})`);
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
state.originals[id] = {
|
|
160
|
-
baseUrl: provider.baseUrl,
|
|
161
|
-
apiKey: provider.apiKey,
|
|
162
|
-
api: provider.api,
|
|
163
|
-
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"
|
|
164
240
|
};
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
"x-upstream-key": provider.apiKey || ""
|
|
169
|
-
};
|
|
170
|
-
provider.baseUrl = `${baseUrl}/v1`;
|
|
171
|
-
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";
|
|
172
244
|
} else {
|
|
173
|
-
|
|
174
|
-
...provider.headers || {},
|
|
175
|
-
"X-Upstream-Key": provider.apiKey || "",
|
|
176
|
-
"X-Upstream-Base-Url": provider.baseUrl
|
|
177
|
-
};
|
|
178
|
-
provider.baseUrl = `${baseUrl}/v1`;
|
|
179
|
-
provider.apiKey = occKey;
|
|
245
|
+
headers["Authorization"] = `Bearer ${upstream.upstreamKey || ""}`;
|
|
180
246
|
}
|
|
181
|
-
|
|
182
|
-
|
|
247
|
+
return await fetch(url, {
|
|
248
|
+
method: "POST",
|
|
249
|
+
headers,
|
|
250
|
+
body: JSON.stringify(body)
|
|
251
|
+
});
|
|
252
|
+
} catch {
|
|
253
|
+
return null;
|
|
183
254
|
}
|
|
184
|
-
state.enabled = true;
|
|
185
|
-
writeProxyState(state);
|
|
186
|
-
return { proxied, skipped, source };
|
|
187
255
|
}
|
|
188
|
-
function
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
if (!providers) return [];
|
|
196
|
-
const restored = [];
|
|
197
|
-
for (const [id, orig] of Object.entries(state.originals)) {
|
|
198
|
-
const provider = providers[id];
|
|
199
|
-
if (!provider) continue;
|
|
200
|
-
provider.baseUrl = orig.baseUrl;
|
|
201
|
-
provider.apiKey = orig.apiKey;
|
|
202
|
-
provider.api = orig.api;
|
|
203
|
-
if (orig.headers) {
|
|
204
|
-
provider.headers = orig.headers;
|
|
205
|
-
} else {
|
|
206
|
-
const h = provider.headers;
|
|
207
|
-
if (h) {
|
|
208
|
-
delete h["X-Upstream-Key"];
|
|
209
|
-
delete h["X-Upstream-Base-Url"];
|
|
210
|
-
delete h["x-api-key"];
|
|
211
|
-
delete h["x-upstream-key"];
|
|
212
|
-
if (Object.keys(h).length === 0) delete provider.headers;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
persistProviderToDisk(id, provider);
|
|
216
|
-
restored.push(id);
|
|
217
|
-
}
|
|
218
|
-
state.enabled = false;
|
|
219
|
-
state.originals = {};
|
|
220
|
-
writeProxyState(state);
|
|
221
|
-
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
|
+
});
|
|
222
263
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const lower = v.toLowerCase().trim();
|
|
269
|
-
if (!["yes", "no", "y", "n"].includes(lower)) return "Please answer yes or no";
|
|
270
|
-
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}`);
|
|
271
309
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
`\u{1F4B0} $1 free credit \u2014 no credit card needed.`,
|
|
277
|
-
"",
|
|
278
|
-
"How it works: your existing API keys (Claude, GPT, etc.) stay the same.",
|
|
279
|
-
"We just compress the prompts in between \u2014 you save 40-70% on every call."
|
|
280
|
-
];
|
|
281
|
-
if (shouldEnable) {
|
|
282
|
-
notes.push(
|
|
283
|
-
"",
|
|
284
|
-
"\u2705 Compression is now active! All your LLM calls are being compressed.",
|
|
285
|
-
"Run `/compress-stats` anytime to see how much you're saving."
|
|
286
|
-
);
|
|
287
|
-
} else {
|
|
288
|
-
notes.push(
|
|
289
|
-
"",
|
|
290
|
-
"Run `/compress on` whenever you're ready to start saving."
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
notes.push("", "Dashboard: https://www.opencompress.ai/dashboard");
|
|
294
|
-
return {
|
|
295
|
-
profiles: [
|
|
296
|
-
{
|
|
310
|
+
const data = await res.json();
|
|
311
|
+
spinner.stop("Account created!");
|
|
312
|
+
return {
|
|
313
|
+
profiles: [{
|
|
297
314
|
profileId: "default",
|
|
298
315
|
credential: { apiKey: data.apiKey }
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
+
}
|
|
307
331
|
}
|
|
308
332
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
333
|
+
]
|
|
334
|
+
};
|
|
335
|
+
}
|
|
312
336
|
var plugin = {
|
|
313
337
|
id: "opencompress",
|
|
314
338
|
name: "OpenCompress",
|
|
315
|
-
description: "
|
|
339
|
+
description: "BYOK prompt compression \u2014 save 20-40% on any LLM provider",
|
|
316
340
|
version: VERSION,
|
|
317
|
-
register(
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
341
|
+
register(api) {
|
|
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
|
+
});
|
|
358
|
+
api.registerCommand({
|
|
326
359
|
name: "compress-stats",
|
|
327
|
-
description: "Show
|
|
328
|
-
acceptsArgs: true,
|
|
329
|
-
requireAuth: false,
|
|
360
|
+
description: "Show compression savings and balance",
|
|
330
361
|
handler: async () => {
|
|
331
|
-
const
|
|
332
|
-
if (!
|
|
333
|
-
return {
|
|
334
|
-
text: "No API key found. Run `openclaw onboard opencompress` to set up."
|
|
335
|
-
};
|
|
362
|
+
const occKey = getOccKey(api);
|
|
363
|
+
if (!occKey) {
|
|
364
|
+
return { text: "No API key. Run `openclaw onboard opencompress` first." };
|
|
336
365
|
}
|
|
337
366
|
try {
|
|
338
|
-
const res = await fetch(`${
|
|
339
|
-
headers: { Authorization: `Bearer ${
|
|
367
|
+
const res = await fetch(`${OCC_API}/user/stats`, {
|
|
368
|
+
headers: { Authorization: `Bearer ${occKey}` }
|
|
340
369
|
});
|
|
341
|
-
if (!res.ok) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const
|
|
345
|
-
const
|
|
346
|
-
const calls = stats.monthlyApiCalls ?? stats.totalCalls ?? 0;
|
|
347
|
-
const savings = Number(stats.monthlySavings || stats.totalSavings || 0).toFixed(4);
|
|
348
|
-
const rate = stats.avgCompressionRate ? `${(Number(stats.avgCompressionRate) * 100).toFixed(1)}%` : "N/A";
|
|
349
|
-
const origTokens = Number(stats.totalOriginalTokens || 0).toLocaleString();
|
|
350
|
-
const compTokens = Number(stats.totalCompressedTokens || 0).toLocaleString();
|
|
351
|
-
const lines = [
|
|
352
|
-
"```",
|
|
353
|
-
"\u{1F99E} OpenCompress Stats",
|
|
354
|
-
"=====================",
|
|
355
|
-
`Balance: $${balance.toFixed(2)}`,
|
|
356
|
-
`API calls: ${calls}`,
|
|
357
|
-
`Avg compression: ${rate}`,
|
|
358
|
-
`Original tokens: ${origTokens}`,
|
|
359
|
-
`Compressed tokens: ${compTokens}`,
|
|
360
|
-
`Total savings: $${savings}`,
|
|
361
|
-
"```"
|
|
362
|
-
];
|
|
363
|
-
if (balance < 0.5) {
|
|
364
|
-
const linkUrl = `https://www.opencompress.ai/dashboard?link=${encodeURIComponent(apiKey2)}`;
|
|
365
|
-
lines.push(
|
|
366
|
-
"",
|
|
367
|
-
"\u26A0\uFE0F **Balance is low!** Link your account to get **$10 bonus credit**:",
|
|
368
|
-
linkUrl
|
|
369
|
-
);
|
|
370
|
-
} else {
|
|
371
|
-
lines.push("", "Dashboard: https://www.opencompress.ai/dashboard");
|
|
372
|
-
}
|
|
373
|
-
return { text: lines.join("\n") };
|
|
374
|
-
} catch (err) {
|
|
375
|
-
return {
|
|
376
|
-
text: `Error fetching stats: ${err instanceof Error ? err.message : String(err)}`
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
});
|
|
381
|
-
api2.logger.info("Registered /compress-stats command");
|
|
382
|
-
api2.registerCommand({
|
|
383
|
-
name: "compress",
|
|
384
|
-
description: "Toggle transparent compression for all LLM providers",
|
|
385
|
-
acceptsArgs: true,
|
|
386
|
-
requireAuth: false,
|
|
387
|
-
handler: async (ctx) => {
|
|
388
|
-
const arg = ctx.args?.trim().toLowerCase();
|
|
389
|
-
if (arg === "on" || arg === "enable") {
|
|
390
|
-
const occKey2 = getApiKey(api2);
|
|
391
|
-
if (!occKey2) {
|
|
392
|
-
return { text: "No API key found. Run `openclaw onboard opencompress` first." };
|
|
393
|
-
}
|
|
394
|
-
const result = enableProxy(api2, baseUrl);
|
|
395
|
-
if (result.proxied.length === 0 && result.skipped.length === 0) {
|
|
396
|
-
return { text: "No providers found to proxy. Add LLM providers first." };
|
|
397
|
-
}
|
|
398
|
-
const lines = [
|
|
399
|
-
`**Compression enabled** for all compatible providers (source: ${result.source}).`,
|
|
400
|
-
""
|
|
401
|
-
];
|
|
402
|
-
if (result.proxied.length > 0) {
|
|
403
|
-
lines.push(`Proxied (${result.proxied.length}): ${result.proxied.join(", ")}`);
|
|
404
|
-
}
|
|
405
|
-
if (result.skipped.length > 0) {
|
|
406
|
-
lines.push(`Skipped (incompatible format): ${result.skipped.join(", ")}`);
|
|
407
|
-
}
|
|
408
|
-
lines.push("", "All requests now route through OpenCompress for automatic compression.");
|
|
409
|
-
lines.push("To disable: `/compress off`");
|
|
410
|
-
return { text: lines.join("\n") };
|
|
411
|
-
}
|
|
412
|
-
if (arg === "off" || arg === "disable") {
|
|
413
|
-
const restored = disableProxy(api2);
|
|
414
|
-
if (restored.length === 0) {
|
|
415
|
-
return { text: "Compression proxy was not active." };
|
|
416
|
-
}
|
|
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";
|
|
417
375
|
return {
|
|
418
376
|
text: [
|
|
419
|
-
"
|
|
420
|
-
"",
|
|
421
|
-
|
|
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
|
+
"```",
|
|
422
385
|
"",
|
|
423
|
-
|
|
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"
|
|
424
387
|
].join("\n")
|
|
425
388
|
};
|
|
389
|
+
} catch (err) {
|
|
390
|
+
return { text: `Error: ${err instanceof Error ? err.message : String(err)}` };
|
|
426
391
|
}
|
|
427
|
-
const proxyState = readProxyState();
|
|
428
|
-
const occKey = getApiKey(api2);
|
|
429
|
-
const statusLines = [
|
|
430
|
-
"**OpenCompress Transparent Proxy**",
|
|
431
|
-
"",
|
|
432
|
-
`Status: ${proxyState.enabled ? "**ON**" : "**OFF**"}`,
|
|
433
|
-
`API key: ${occKey ? `${occKey.slice(0, 12)}...` : "not set"}`
|
|
434
|
-
];
|
|
435
|
-
if (proxyState.enabled && Object.keys(proxyState.originals).length > 0) {
|
|
436
|
-
statusLines.push(`Proxied providers: ${Object.keys(proxyState.originals).join(", ")}`);
|
|
437
|
-
}
|
|
438
|
-
statusLines.push(
|
|
439
|
-
"",
|
|
440
|
-
"**Usage:**",
|
|
441
|
-
" `/compress on` \u2014 Route all providers through OpenCompress",
|
|
442
|
-
" `/compress off` \u2014 Restore original provider configs",
|
|
443
|
-
" `/compress-stats` \u2014 View compression savings"
|
|
444
|
-
);
|
|
445
|
-
return { text: statusLines.join("\n") };
|
|
446
392
|
}
|
|
447
393
|
});
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
api2.on("llm_input", (...args) => {
|
|
468
|
-
try {
|
|
469
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
470
|
-
const event = args[0] || {};
|
|
471
|
-
const systemPromptLen = typeof event.systemPrompt === "string" ? event.systemPrompt.length : 0;
|
|
472
|
-
const historyMessages = Array.isArray(event.historyMessages) ? event.historyMessages : [];
|
|
473
|
-
const promptLen = typeof event.prompt === "string" ? event.prompt.length : 0;
|
|
474
|
-
const systemTokens = Math.ceil(systemPromptLen / 4);
|
|
475
|
-
const historyText = JSON.stringify(historyMessages);
|
|
476
|
-
const historyTokens = Math.ceil(historyText.length / 4);
|
|
477
|
-
const promptTokens = Math.ceil(promptLen / 4);
|
|
478
|
-
const totalTokens = systemTokens + historyTokens + promptTokens;
|
|
479
|
-
const entry = {
|
|
480
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
481
|
-
_argCount: args.length,
|
|
482
|
-
_eventKeys: Object.keys(event),
|
|
483
|
-
runId: event.runId,
|
|
484
|
-
sessionId: event.sessionId,
|
|
485
|
-
provider: event.provider,
|
|
486
|
-
model: event.model,
|
|
487
|
-
imagesCount: event.imagesCount || 0,
|
|
488
|
-
tokenEstimate: { systemPrompt: systemTokens, history: historyTokens, currentPrompt: promptTokens, total: totalTokens },
|
|
489
|
-
charLengths: { systemPrompt: systemPromptLen, history: historyText.length, currentPrompt: promptLen },
|
|
490
|
-
historyMessageCount: historyMessages.length,
|
|
491
|
-
systemPrompt: event.systemPrompt,
|
|
492
|
-
historyMessages: event.historyMessages,
|
|
493
|
-
prompt: event.prompt
|
|
494
|
-
};
|
|
495
|
-
const model = typeof event.model === "string" ? event.model.replace(/\//g, "_") : "unknown";
|
|
496
|
-
const filename = `${ts}_${model}.json`;
|
|
497
|
-
fs.writeFileSync(path.join(logDir, filename), JSON.stringify(entry, null, 2) + "\n");
|
|
498
|
-
} catch (err) {
|
|
499
|
-
try {
|
|
500
|
-
const errFile = path.join(logDir, `error_${Date.now()}.txt`);
|
|
501
|
-
fs.writeFileSync(errFile, `${err}
|
|
502
|
-
${err.stack || ""}
|
|
503
|
-
args: ${JSON.stringify(args?.map((a) => typeof a))}
|
|
504
|
-
`);
|
|
505
|
-
} catch {
|
|
506
|
-
}
|
|
394
|
+
api.registerCommand({
|
|
395
|
+
name: "compress",
|
|
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**",
|
|
403
|
+
"",
|
|
404
|
+
`API key: ${occKey ? `${occKey.slice(0, 12)}...` : "not set (run `openclaw onboard opencompress`)"}`,
|
|
405
|
+
`Proxy: http://${PROXY_HOST}:${PROXY_PORT}`,
|
|
406
|
+
"",
|
|
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") };
|
|
507
413
|
}
|
|
508
414
|
});
|
|
509
|
-
api2.logger.info(`LLM input logging enabled \u2192 ${logDir}`);
|
|
510
415
|
}
|
|
511
416
|
};
|
|
512
417
|
var index_default = plugin;
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "opencompress",
|
|
3
3
|
"name": "OpenCompress",
|
|
4
|
-
"description": "
|
|
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
|
}
|
package/openclaw.security.json
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "opencompress",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"securityProfile": {
|
|
5
5
|
"dataHandling": {
|
|
6
|
-
"promptContent": "pass-through",
|
|
7
|
-
"
|
|
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": "
|
|
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
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
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": "
|
|
56
|
-
"environment": ["OPENCOMPRESS_API_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
|
|
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
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"billing": "
|
|
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
|
}
|