@opencompress/opencompress 1.9.2 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +84 -0
- package/dist/index.js +375 -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,423 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
// src/config.ts
|
|
2
|
+
var VERSION = "2.0.1";
|
|
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);
|
|
110
153
|
try {
|
|
111
|
-
|
|
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 {
|
|
192
|
+
try {
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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 (Save Tokens + Improve Quality)",
|
|
281
|
+
aliases: ["oc", "compress"],
|
|
282
|
+
envVars: ["OPENCOMPRESS_API_KEY"],
|
|
283
|
+
// Dynamic model catalog — mirrors user's existing providers
|
|
284
|
+
// Detect primary provider's API type for compatibility
|
|
285
|
+
models: (() => {
|
|
286
|
+
const providers = getProviders(api);
|
|
287
|
+
const firstProvider = Object.values(providers).find((p) => p.api);
|
|
288
|
+
const primaryApi = firstProvider?.api || "openai-completions";
|
|
289
|
+
return {
|
|
290
|
+
baseUrl: `http://${PROXY_HOST}:${PROXY_PORT}/v1`,
|
|
291
|
+
api: primaryApi,
|
|
292
|
+
models: generateModelCatalog(providers)
|
|
293
|
+
};
|
|
294
|
+
})(),
|
|
295
|
+
auth: [
|
|
296
|
+
{
|
|
297
|
+
id: "api-key",
|
|
298
|
+
label: "OpenCompress",
|
|
299
|
+
hint: "Save tokens and improve quality on any LLM. Your API keys stay local.",
|
|
300
|
+
kind: "custom",
|
|
301
|
+
run: async (ctx) => {
|
|
302
|
+
ctx.prompter.note(
|
|
303
|
+
"\u{1F99E} OpenCompress \u2014 save tokens and improve quality on every LLM call\n\nUse your existing LLM providers. Your API keys stay on your machine.\nWe compress prompts to reduce costs and sharpen output quality."
|
|
304
|
+
);
|
|
305
|
+
const spinner = ctx.prompter.progress("Creating your account...");
|
|
306
|
+
try {
|
|
307
|
+
const res = await fetch(`${OCC_API}/v1/provision`, {
|
|
308
|
+
method: "POST",
|
|
309
|
+
headers: { "Content-Type": "application/json" },
|
|
310
|
+
body: JSON.stringify({})
|
|
311
|
+
});
|
|
312
|
+
if (!res.ok) {
|
|
313
|
+
spinner.stop("Failed");
|
|
314
|
+
throw new Error(`Provisioning failed: ${res.statusText}`);
|
|
271
315
|
}
|
|
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
|
-
{
|
|
316
|
+
const data = await res.json();
|
|
317
|
+
spinner.stop("Account created!");
|
|
318
|
+
return {
|
|
319
|
+
profiles: [{
|
|
297
320
|
profileId: "default",
|
|
298
321
|
credential: { apiKey: data.apiKey }
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
322
|
+
}],
|
|
323
|
+
notes: [
|
|
324
|
+
"\u{1F99E} OpenCompress ready!",
|
|
325
|
+
`\u{1F4B0} ${data.freeCredit} free credit.`,
|
|
326
|
+
"",
|
|
327
|
+
"Select any opencompress/* model to use compression.",
|
|
328
|
+
"Your existing provider keys are used automatically.",
|
|
329
|
+
"",
|
|
330
|
+
"Dashboard: https://www.opencompress.ai/dashboard"
|
|
331
|
+
]
|
|
332
|
+
};
|
|
333
|
+
} catch (err) {
|
|
334
|
+
spinner.stop("Failed");
|
|
335
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
336
|
+
}
|
|
307
337
|
}
|
|
308
338
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
339
|
+
]
|
|
340
|
+
};
|
|
341
|
+
}
|
|
312
342
|
var plugin = {
|
|
313
343
|
id: "opencompress",
|
|
314
344
|
name: "OpenCompress",
|
|
315
|
-
description: "
|
|
345
|
+
description: "Save tokens and improve quality on any LLM \u2014 use your existing providers",
|
|
316
346
|
version: VERSION,
|
|
317
|
-
register(
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
347
|
+
register(api) {
|
|
348
|
+
api.registerProvider(createProvider(api));
|
|
349
|
+
api.logger.info(`OpenCompress v${VERSION} registered as provider`);
|
|
350
|
+
api.registerService({
|
|
351
|
+
id: "opencompress-proxy",
|
|
352
|
+
start: () => {
|
|
353
|
+
startProxy(
|
|
354
|
+
() => getProviders(api),
|
|
355
|
+
() => getOccKey(api)
|
|
356
|
+
);
|
|
357
|
+
api.logger.info(`Compression proxy started on ${PROXY_HOST}:${PROXY_PORT}`);
|
|
358
|
+
},
|
|
359
|
+
stop: () => {
|
|
360
|
+
stopProxy();
|
|
361
|
+
api.logger.info("Compression proxy stopped");
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
api.registerCommand({
|
|
326
365
|
name: "compress-stats",
|
|
327
|
-
description: "Show
|
|
328
|
-
acceptsArgs: true,
|
|
329
|
-
requireAuth: false,
|
|
366
|
+
description: "Show compression savings and balance",
|
|
330
367
|
handler: async () => {
|
|
331
|
-
const
|
|
332
|
-
if (!
|
|
333
|
-
return {
|
|
334
|
-
text: "No API key found. Run `openclaw onboard opencompress` to set up."
|
|
335
|
-
};
|
|
368
|
+
const occKey = getOccKey(api);
|
|
369
|
+
if (!occKey) {
|
|
370
|
+
return { text: "No API key. Run `openclaw onboard opencompress` first." };
|
|
336
371
|
}
|
|
337
372
|
try {
|
|
338
|
-
const res = await fetch(`${
|
|
339
|
-
headers: { Authorization: `Bearer ${
|
|
373
|
+
const res = await fetch(`${OCC_API}/user/stats`, {
|
|
374
|
+
headers: { Authorization: `Bearer ${occKey}` }
|
|
340
375
|
});
|
|
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
|
-
}
|
|
376
|
+
if (!res.ok) return { text: `Failed: HTTP ${res.status}` };
|
|
377
|
+
const s = await res.json();
|
|
378
|
+
const balance = Number(s.balanceUsd || s.balance || 0);
|
|
379
|
+
const calls = s.monthlyApiCalls ?? s.totalCalls ?? 0;
|
|
380
|
+
const rate = s.avgCompressionRate ? `${(Number(s.avgCompressionRate) * 100).toFixed(1)}%` : "N/A";
|
|
417
381
|
return {
|
|
418
382
|
text: [
|
|
419
|
-
"
|
|
420
|
-
"",
|
|
421
|
-
|
|
383
|
+
"```",
|
|
384
|
+
"\u{1F99E} OpenCompress Stats",
|
|
385
|
+
"=====================",
|
|
386
|
+
`Balance: $${balance.toFixed(2)}`,
|
|
387
|
+
`API calls: ${calls}`,
|
|
388
|
+
`Avg compression: ${rate}`,
|
|
389
|
+
`Tokens saved: ${(Number(s.totalOriginalTokens || 0) - Number(s.totalCompressedTokens || 0)).toLocaleString()}`,
|
|
390
|
+
"```",
|
|
422
391
|
"",
|
|
423
|
-
|
|
392
|
+
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
393
|
].join("\n")
|
|
425
394
|
};
|
|
395
|
+
} catch (err) {
|
|
396
|
+
return { text: `Error: ${err instanceof Error ? err.message : String(err)}` };
|
|
426
397
|
}
|
|
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
398
|
}
|
|
447
399
|
});
|
|
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
|
-
}
|
|
400
|
+
api.registerCommand({
|
|
401
|
+
name: "compress",
|
|
402
|
+
description: "Show OpenCompress status and available compressed models",
|
|
403
|
+
handler: async () => {
|
|
404
|
+
const occKey = getOccKey(api);
|
|
405
|
+
const providers = getProviders(api);
|
|
406
|
+
const models = generateModelCatalog(providers);
|
|
407
|
+
const lines = [
|
|
408
|
+
"**OpenCompress Status**",
|
|
409
|
+
"",
|
|
410
|
+
`API key: ${occKey ? `${occKey.slice(0, 12)}...` : "not set (run `openclaw onboard opencompress`)"}`,
|
|
411
|
+
`Proxy: http://${PROXY_HOST}:${PROXY_PORT}`,
|
|
412
|
+
"",
|
|
413
|
+
"**Available compressed models:**",
|
|
414
|
+
...models.map((m) => ` ${m.id}`),
|
|
415
|
+
"",
|
|
416
|
+
"Select any of these models to use compression."
|
|
417
|
+
];
|
|
418
|
+
return { text: lines.join("\n") };
|
|
507
419
|
}
|
|
508
420
|
});
|
|
509
|
-
api2.logger.info(`LLM input logging enabled \u2192 ${logDir}`);
|
|
510
421
|
}
|
|
511
422
|
};
|
|
512
423
|
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": "Save tokens and improve quality on any LLM. Use your existing providers — 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
|
}
|