@opencompress/opencompress 1.8.2 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +312 -344
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
6
6
|
});
|
|
7
7
|
|
|
8
8
|
// src/index.ts
|
|
9
|
-
var VERSION = "1.
|
|
9
|
+
var VERSION = "1.9.1";
|
|
10
10
|
var DEFAULT_BASE_URL = "https://www.opencompress.ai/api";
|
|
11
11
|
function getApiKey(api) {
|
|
12
12
|
const auth = api.config.auth;
|
|
@@ -35,87 +35,7 @@ function getApiKey(api) {
|
|
|
35
35
|
}
|
|
36
36
|
return void 0;
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
{ id: "gpt-4o", name: "GPT-4o", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128e3, maxTokens: 16384 },
|
|
40
|
-
{ id: "gpt-4o-mini", name: "GPT-4o Mini", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128e3, maxTokens: 16384 },
|
|
41
|
-
{ id: "gpt-4.1", name: "GPT-4.1", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 1047576, maxTokens: 32768 },
|
|
42
|
-
{ id: "gpt-4.1-mini", name: "GPT-4.1 Mini", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 1047576, maxTokens: 32768 },
|
|
43
|
-
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 2e5, maxTokens: 128e3 },
|
|
44
|
-
{ id: "claude-haiku-4-5-20251001", name: "Claude Haiku 4.5", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 2e5, maxTokens: 128e3 },
|
|
45
|
-
{ id: "gemini-2.5-pro", name: "Gemini 2.5 Pro", reasoning: true, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 1048576, maxTokens: 65536 },
|
|
46
|
-
{ id: "gemini-2.5-flash", name: "Gemini 2.5 Flash", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 1048576, maxTokens: 65536 },
|
|
47
|
-
{ id: "deepseek/deepseek-chat-v3-0324", name: "DeepSeek V3", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 131072, maxTokens: 8192 },
|
|
48
|
-
{ id: "deepseek/deepseek-reasoner", name: "DeepSeek Reasoner", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 131072, maxTokens: 8192 }
|
|
49
|
-
];
|
|
50
|
-
function readExistingModels(api) {
|
|
51
|
-
const providers = api.config.models?.providers;
|
|
52
|
-
if (!providers) return null;
|
|
53
|
-
const seen = /* @__PURE__ */ new Set();
|
|
54
|
-
const models = [];
|
|
55
|
-
for (const [providerId, providerConfig] of Object.entries(providers)) {
|
|
56
|
-
if (providerId === "opencompress") continue;
|
|
57
|
-
const providerModels = providerConfig.models || [];
|
|
58
|
-
for (const m of providerModels) {
|
|
59
|
-
if (m.name?.includes("\u2192")) continue;
|
|
60
|
-
const rawId = m.id.includes("/") ? m.id.split("/").slice(1).join("/") : m.id;
|
|
61
|
-
if (seen.has(rawId)) continue;
|
|
62
|
-
seen.add(rawId);
|
|
63
|
-
const upstreamId = m.id.startsWith(`${providerId}/`) ? rawId : m.id;
|
|
64
|
-
models.push({
|
|
65
|
-
...m,
|
|
66
|
-
id: upstreamId,
|
|
67
|
-
name: m.name || upstreamId,
|
|
68
|
-
// Zero out cost — billing handled by OpenCompress proxy
|
|
69
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return models.length > 0 ? models : null;
|
|
74
|
-
}
|
|
75
|
-
function buildProviderModels(baseUrl, models, apiKey) {
|
|
76
|
-
return {
|
|
77
|
-
baseUrl: `${baseUrl}/v1`,
|
|
78
|
-
api: "openai-completions",
|
|
79
|
-
apiKey: apiKey || void 0,
|
|
80
|
-
models: models || FALLBACK_MODELS
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
function persistModelsConfig(providerModels) {
|
|
84
|
-
try {
|
|
85
|
-
const os = __require("os");
|
|
86
|
-
const fs = __require("fs");
|
|
87
|
-
const path = __require("path");
|
|
88
|
-
const configPath = path.join(os.homedir(), ".openclaw", "openclaw.json");
|
|
89
|
-
if (!fs.existsSync(configPath)) return;
|
|
90
|
-
const raw = fs.readFileSync(configPath, "utf-8");
|
|
91
|
-
let config;
|
|
92
|
-
try {
|
|
93
|
-
config = JSON.parse(raw);
|
|
94
|
-
} catch {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
if (!config.models) config.models = {};
|
|
98
|
-
const models = config.models;
|
|
99
|
-
if (!models.providers) models.providers = {};
|
|
100
|
-
const providers = models.providers;
|
|
101
|
-
const configSafeModels = providerModels.models.map((m) => ({
|
|
102
|
-
id: m.id,
|
|
103
|
-
name: m.name
|
|
104
|
-
}));
|
|
105
|
-
const configEntry = {
|
|
106
|
-
baseUrl: providerModels.baseUrl,
|
|
107
|
-
api: providerModels.api || "openai-completions",
|
|
108
|
-
models: configSafeModels
|
|
109
|
-
};
|
|
110
|
-
if (providerModels.apiKey) {
|
|
111
|
-
configEntry.apiKey = providerModels.apiKey;
|
|
112
|
-
}
|
|
113
|
-
providers.opencompress = configEntry;
|
|
114
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
115
|
-
} catch {
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
function persistAgentModelsJson(providerModels) {
|
|
38
|
+
function persistAuthProfile(apiKey) {
|
|
119
39
|
try {
|
|
120
40
|
const os = __require("os");
|
|
121
41
|
const fs = __require("fs");
|
|
@@ -124,36 +44,27 @@ function persistAgentModelsJson(providerModels) {
|
|
|
124
44
|
if (!fs.existsSync(agentsDir)) return;
|
|
125
45
|
const agentDirs = fs.readdirSync(agentsDir);
|
|
126
46
|
for (const agent of agentDirs) {
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
if (!fs.existsSync(
|
|
130
|
-
|
|
131
|
-
|
|
47
|
+
const authPath = path.join(agentsDir, agent, "agent", "auth-profiles.json");
|
|
48
|
+
const authDir = path.dirname(authPath);
|
|
49
|
+
if (!fs.existsSync(authDir)) {
|
|
50
|
+
fs.mkdirSync(authDir, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
let profiles = {
|
|
53
|
+
version: 1,
|
|
54
|
+
profiles: {}
|
|
55
|
+
};
|
|
56
|
+
if (fs.existsSync(authPath)) {
|
|
132
57
|
try {
|
|
133
|
-
|
|
134
|
-
if (!data.providers || typeof data.providers !== "object") {
|
|
135
|
-
data.providers = {};
|
|
136
|
-
}
|
|
58
|
+
profiles = JSON.parse(fs.readFileSync(authPath, "utf-8"));
|
|
137
59
|
} catch {
|
|
138
|
-
data = { providers: {} };
|
|
139
60
|
}
|
|
140
61
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
models: providerModels.models.map((m) => ({
|
|
146
|
-
id: m.id,
|
|
147
|
-
name: m.name,
|
|
148
|
-
api: m.api || "openai-completions",
|
|
149
|
-
reasoning: m.reasoning ?? false,
|
|
150
|
-
input: m.input || ["text"],
|
|
151
|
-
cost: m.cost || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
152
|
-
contextWindow: m.contextWindow || 2e5,
|
|
153
|
-
maxTokens: m.maxTokens || 8192
|
|
154
|
-
}))
|
|
62
|
+
profiles.profiles["opencompress:default"] = {
|
|
63
|
+
type: "api_key",
|
|
64
|
+
provider: "opencompress",
|
|
65
|
+
key: apiKey
|
|
155
66
|
};
|
|
156
|
-
fs.writeFileSync(
|
|
67
|
+
fs.writeFileSync(authPath, JSON.stringify(profiles, null, 2) + "\n");
|
|
157
68
|
}
|
|
158
69
|
} catch {
|
|
159
70
|
}
|
|
@@ -187,125 +98,216 @@ function persistAgentAuthJson(apiKey) {
|
|
|
187
98
|
} catch {
|
|
188
99
|
}
|
|
189
100
|
}
|
|
190
|
-
function
|
|
101
|
+
function proxyStatePath() {
|
|
102
|
+
const os = __require("os");
|
|
103
|
+
const path = __require("path");
|
|
104
|
+
return path.join(os.homedir(), ".openclaw", "opencompress-proxy.json");
|
|
105
|
+
}
|
|
106
|
+
function readProxyState() {
|
|
191
107
|
try {
|
|
192
|
-
const os = __require("os");
|
|
193
108
|
const fs = __require("fs");
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const raw = fs.readFileSync(configPath, "utf-8");
|
|
198
|
-
let config;
|
|
199
|
-
try {
|
|
200
|
-
config = JSON.parse(raw);
|
|
201
|
-
} catch {
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
if (!config.agents) config.agents = {};
|
|
205
|
-
const agents = config.agents;
|
|
206
|
-
if (!agents.defaults) agents.defaults = {};
|
|
207
|
-
const defaults = agents.defaults;
|
|
208
|
-
if (!defaults.models) defaults.models = {};
|
|
209
|
-
const allowlist = defaults.models;
|
|
210
|
-
const existingKeys = Object.keys(allowlist);
|
|
211
|
-
if (existingKeys.length === 0) return;
|
|
212
|
-
let changed = false;
|
|
213
|
-
for (const m of models) {
|
|
214
|
-
const fullId = `opencompress/${m.id}`;
|
|
215
|
-
if (!allowlist[fullId]) {
|
|
216
|
-
allowlist[fullId] = {};
|
|
217
|
-
changed = true;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
if (changed) {
|
|
221
|
-
const tmpPath = `${configPath}.tmp.${process.pid}`;
|
|
222
|
-
fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2) + "\n");
|
|
223
|
-
fs.renameSync(tmpPath, configPath);
|
|
224
|
-
}
|
|
109
|
+
const p = proxyStatePath();
|
|
110
|
+
if (!fs.existsSync(p)) return { enabled: false, originals: {} };
|
|
111
|
+
return JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
225
112
|
} catch {
|
|
113
|
+
return { enabled: false, originals: {} };
|
|
226
114
|
}
|
|
227
115
|
}
|
|
228
|
-
function
|
|
116
|
+
function writeProxyState(state) {
|
|
117
|
+
try {
|
|
118
|
+
const fs = __require("fs");
|
|
119
|
+
fs.writeFileSync(proxyStatePath(), JSON.stringify(state, null, 2) + "\n");
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function readProvidersFromDisk() {
|
|
229
124
|
try {
|
|
230
125
|
const os = __require("os");
|
|
231
126
|
const fs = __require("fs");
|
|
232
127
|
const path = __require("path");
|
|
128
|
+
const configPath = path.join(os.homedir(), ".openclaw", "openclaw.json");
|
|
129
|
+
if (!fs.existsSync(configPath)) return void 0;
|
|
130
|
+
const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
131
|
+
const providers = raw?.models?.providers;
|
|
132
|
+
if (!providers || Object.keys(providers).length === 0) return void 0;
|
|
133
|
+
return providers;
|
|
134
|
+
} catch {
|
|
135
|
+
return void 0;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function persistProviderToDisk(providerId, config) {
|
|
139
|
+
const os = __require("os");
|
|
140
|
+
const fs = __require("fs");
|
|
141
|
+
const path = __require("path");
|
|
142
|
+
try {
|
|
143
|
+
const configPath = path.join(os.homedir(), ".openclaw", "openclaw.json");
|
|
144
|
+
if (fs.existsSync(configPath)) {
|
|
145
|
+
const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
146
|
+
if (!raw.models) raw.models = {};
|
|
147
|
+
if (!raw.models.providers) raw.models.providers = {};
|
|
148
|
+
raw.models.providers[providerId] = {
|
|
149
|
+
baseUrl: config.baseUrl,
|
|
150
|
+
api: config.api || "openai-completions",
|
|
151
|
+
apiKey: config.apiKey || void 0,
|
|
152
|
+
models: config.models?.map((m) => ({ id: m.id, name: m.name })) || [],
|
|
153
|
+
...config.headers ? { headers: config.headers } : {}
|
|
154
|
+
};
|
|
155
|
+
fs.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
233
160
|
const agentsDir = path.join(os.homedir(), ".openclaw", "agents");
|
|
234
161
|
if (!fs.existsSync(agentsDir)) return;
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (
|
|
240
|
-
fs.mkdirSync(authDir, { recursive: true });
|
|
241
|
-
}
|
|
242
|
-
let profiles = {
|
|
243
|
-
version: 1,
|
|
244
|
-
profiles: {}
|
|
245
|
-
};
|
|
246
|
-
if (fs.existsSync(authPath)) {
|
|
162
|
+
for (const agent of fs.readdirSync(agentsDir)) {
|
|
163
|
+
const modelsPath = path.join(agentsDir, agent, "agent", "models.json");
|
|
164
|
+
if (!fs.existsSync(path.dirname(modelsPath))) continue;
|
|
165
|
+
let data = { providers: {} };
|
|
166
|
+
if (fs.existsSync(modelsPath)) {
|
|
247
167
|
try {
|
|
248
|
-
|
|
168
|
+
data = JSON.parse(fs.readFileSync(modelsPath, "utf-8"));
|
|
249
169
|
} catch {
|
|
250
170
|
}
|
|
171
|
+
if (!data.providers) data.providers = {};
|
|
251
172
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
173
|
+
data.providers[providerId] = {
|
|
174
|
+
baseUrl: config.baseUrl,
|
|
175
|
+
api: config.api || "openai-completions",
|
|
176
|
+
apiKey: config.apiKey || void 0,
|
|
177
|
+
models: config.models?.map((m) => ({
|
|
178
|
+
id: m.id,
|
|
179
|
+
name: m.name,
|
|
180
|
+
api: m.api || config.api || "openai-completions",
|
|
181
|
+
reasoning: m.reasoning ?? false,
|
|
182
|
+
input: m.input || ["text"],
|
|
183
|
+
cost: m.cost || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
184
|
+
contextWindow: m.contextWindow || 2e5,
|
|
185
|
+
maxTokens: m.maxTokens || 8192
|
|
186
|
+
})) || [],
|
|
187
|
+
...config.headers ? { headers: config.headers } : {}
|
|
256
188
|
};
|
|
257
|
-
fs.writeFileSync(
|
|
189
|
+
fs.writeFileSync(modelsPath, JSON.stringify(data, null, 2) + "\n");
|
|
258
190
|
}
|
|
259
191
|
} catch {
|
|
260
192
|
}
|
|
261
193
|
}
|
|
194
|
+
function enableProxy(api, baseUrl) {
|
|
195
|
+
const occKey = getApiKey(api);
|
|
196
|
+
if (!occKey) return { proxied: [], skipped: [] };
|
|
197
|
+
let providers = api.config.models?.providers;
|
|
198
|
+
let source = "api.config";
|
|
199
|
+
if (!providers || Object.keys(providers).length === 0) {
|
|
200
|
+
providers = readProvidersFromDisk();
|
|
201
|
+
source = "disk (openclaw.json)";
|
|
202
|
+
}
|
|
203
|
+
if (!providers) return { proxied: [], skipped: [] };
|
|
204
|
+
const state = readProxyState();
|
|
205
|
+
const proxied = [];
|
|
206
|
+
const skipped = [];
|
|
207
|
+
for (const [id, raw] of Object.entries(providers)) {
|
|
208
|
+
if (id === "opencompress") continue;
|
|
209
|
+
const provider = raw;
|
|
210
|
+
if (provider.baseUrl?.includes("opencompress.ai")) continue;
|
|
211
|
+
const pApi = provider.api || "openai-completions";
|
|
212
|
+
if (pApi === "google-generative-ai") {
|
|
213
|
+
skipped.push(`${id} (${pApi})`);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
state.originals[id] = {
|
|
217
|
+
baseUrl: provider.baseUrl,
|
|
218
|
+
apiKey: provider.apiKey,
|
|
219
|
+
api: provider.api,
|
|
220
|
+
headers: provider.headers ? { ...provider.headers } : void 0
|
|
221
|
+
};
|
|
222
|
+
if (pApi === "anthropic-messages") {
|
|
223
|
+
provider.headers = {
|
|
224
|
+
...provider.headers || {},
|
|
225
|
+
"x-upstream-key": provider.apiKey || ""
|
|
226
|
+
};
|
|
227
|
+
provider.baseUrl = `${baseUrl}/v1`;
|
|
228
|
+
provider.apiKey = occKey;
|
|
229
|
+
} else {
|
|
230
|
+
provider.headers = {
|
|
231
|
+
...provider.headers || {},
|
|
232
|
+
"X-Upstream-Key": provider.apiKey || "",
|
|
233
|
+
"X-Upstream-Base-Url": provider.baseUrl
|
|
234
|
+
};
|
|
235
|
+
provider.baseUrl = `${baseUrl}/v1`;
|
|
236
|
+
provider.apiKey = occKey;
|
|
237
|
+
}
|
|
238
|
+
persistProviderToDisk(id, provider);
|
|
239
|
+
proxied.push(id);
|
|
240
|
+
}
|
|
241
|
+
state.enabled = true;
|
|
242
|
+
writeProxyState(state);
|
|
243
|
+
return { proxied, skipped, source };
|
|
244
|
+
}
|
|
245
|
+
function disableProxy(api) {
|
|
246
|
+
const state = readProxyState();
|
|
247
|
+
if (!state.enabled) return [];
|
|
248
|
+
let providers = api.config.models?.providers;
|
|
249
|
+
if (!providers || Object.keys(providers).length === 0) {
|
|
250
|
+
providers = readProvidersFromDisk();
|
|
251
|
+
}
|
|
252
|
+
if (!providers) return [];
|
|
253
|
+
const restored = [];
|
|
254
|
+
for (const [id, orig] of Object.entries(state.originals)) {
|
|
255
|
+
const provider = providers[id];
|
|
256
|
+
if (!provider) continue;
|
|
257
|
+
provider.baseUrl = orig.baseUrl;
|
|
258
|
+
provider.apiKey = orig.apiKey;
|
|
259
|
+
provider.api = orig.api;
|
|
260
|
+
if (orig.headers) {
|
|
261
|
+
provider.headers = orig.headers;
|
|
262
|
+
} else {
|
|
263
|
+
const h = provider.headers;
|
|
264
|
+
if (h) {
|
|
265
|
+
delete h["X-Upstream-Key"];
|
|
266
|
+
delete h["X-Upstream-Base-Url"];
|
|
267
|
+
delete h["x-api-key"];
|
|
268
|
+
delete h["x-upstream-key"];
|
|
269
|
+
if (Object.keys(h).length === 0) delete provider.headers;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
persistProviderToDisk(id, provider);
|
|
273
|
+
restored.push(id);
|
|
274
|
+
}
|
|
275
|
+
state.enabled = false;
|
|
276
|
+
state.originals = {};
|
|
277
|
+
writeProxyState(state);
|
|
278
|
+
return restored;
|
|
279
|
+
}
|
|
262
280
|
var opencompressProvider = {
|
|
263
281
|
id: "opencompress",
|
|
264
282
|
label: "OpenCompress",
|
|
265
283
|
docsPath: "https://docs.opencompress.ai",
|
|
266
284
|
aliases: ["oc", "compress"],
|
|
267
285
|
envVars: ["OPENCOMPRESS_API_KEY"],
|
|
268
|
-
models
|
|
286
|
+
// No models — we're a transparent proxy, not a router.
|
|
287
|
+
// Users keep their existing providers; we just compress their traffic.
|
|
288
|
+
models: {
|
|
289
|
+
baseUrl: `${DEFAULT_BASE_URL}/v1`,
|
|
290
|
+
api: "openai-completions",
|
|
291
|
+
models: []
|
|
292
|
+
},
|
|
269
293
|
formatApiKey: (cred) => cred.apiKey || "",
|
|
270
294
|
auth: [
|
|
271
295
|
{
|
|
272
296
|
id: "api-key",
|
|
273
297
|
label: "OpenCompress",
|
|
274
|
-
hint: "
|
|
298
|
+
hint: "Compress all LLM calls automatically \u2014 save 40-60% on any provider",
|
|
275
299
|
kind: "custom",
|
|
276
300
|
run: async (ctx) => {
|
|
277
301
|
ctx.prompter.note(
|
|
278
|
-
"OpenCompress
|
|
302
|
+
"\u{1F99E} Welcome to OpenCompress!\n\nWe compress your LLM prompts automatically \u2014 saving 40-70% on token costs.\nWorks with Claude, GPT, and any provider you already have.\nYour API keys stay yours. We just make the traffic smaller.\n\nWe're giving you $1 free credit to try it out!"
|
|
279
303
|
);
|
|
280
|
-
const
|
|
281
|
-
message: "Enter your LLM API key (OpenAI/Anthropic/OpenRouter):",
|
|
282
|
-
validate: (val) => {
|
|
283
|
-
if (!val || val.length < 10) return "Please enter a valid API key";
|
|
284
|
-
if (val.startsWith("sk-occ-")) return "Enter your LLM provider key, not an OpenCompress key";
|
|
285
|
-
return void 0;
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
if (typeof llmKey === "symbol") {
|
|
289
|
-
throw new Error("Setup cancelled");
|
|
290
|
-
}
|
|
291
|
-
let provider = "openrouter";
|
|
292
|
-
let upstreamBaseUrl = "https://openrouter.ai/api/v1";
|
|
293
|
-
if (llmKey.startsWith("sk-proj-") || llmKey.startsWith("sk-") && !llmKey.startsWith("sk-ant-") && !llmKey.startsWith("sk-or-")) {
|
|
294
|
-
provider = "openai";
|
|
295
|
-
upstreamBaseUrl = "https://api.openai.com/v1";
|
|
296
|
-
} else if (llmKey.startsWith("sk-ant-")) {
|
|
297
|
-
provider = "anthropic";
|
|
298
|
-
upstreamBaseUrl = "https://api.anthropic.com/v1";
|
|
299
|
-
} else if (llmKey.startsWith("AIza")) {
|
|
300
|
-
provider = "google";
|
|
301
|
-
upstreamBaseUrl = "https://generativelanguage.googleapis.com/v1beta/openai";
|
|
302
|
-
}
|
|
303
|
-
const spinner = ctx.prompter.progress("Creating account...");
|
|
304
|
+
const spinner = ctx.prompter.progress("Setting up your account...");
|
|
304
305
|
try {
|
|
305
306
|
const res = await fetch(`${DEFAULT_BASE_URL}/v1/provision`, {
|
|
306
307
|
method: "POST",
|
|
307
308
|
headers: { "Content-Type": "application/json" },
|
|
308
|
-
body: JSON.stringify({
|
|
309
|
+
body: JSON.stringify({})
|
|
310
|
+
// BYOK — no upstream key needed here, user's existing providers handle that
|
|
309
311
|
});
|
|
310
312
|
if (!res.ok) {
|
|
311
313
|
const err = await res.json().catch(() => ({ error: { message: "Unknown error" } }));
|
|
@@ -315,13 +317,38 @@ var opencompressProvider = {
|
|
|
315
317
|
);
|
|
316
318
|
}
|
|
317
319
|
const data = await res.json();
|
|
318
|
-
spinner.stop("Account created");
|
|
319
|
-
const onboardModels = buildProviderModels(DEFAULT_BASE_URL, void 0, data.apiKey);
|
|
320
|
-
const modelCount = FALLBACK_MODELS.length;
|
|
321
|
-
persistModelsConfig(onboardModels);
|
|
322
|
-
persistAgentModelsJson(onboardModels);
|
|
320
|
+
spinner.stop("Account created!");
|
|
323
321
|
persistAuthProfile(data.apiKey);
|
|
324
322
|
persistAgentAuthJson(data.apiKey);
|
|
323
|
+
const enableNow = await ctx.prompter.text({
|
|
324
|
+
message: "Ready to compress? This will route your existing LLM providers through OpenCompress. Enable now? (yes/no)",
|
|
325
|
+
validate: (v) => {
|
|
326
|
+
const lower = v.toLowerCase().trim();
|
|
327
|
+
if (!["yes", "no", "y", "n"].includes(lower)) return "Please answer yes or no";
|
|
328
|
+
return void 0;
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
const shouldEnable = typeof enableNow === "string" && ["yes", "y"].includes(enableNow.toLowerCase().trim());
|
|
332
|
+
const notes = [
|
|
333
|
+
"\u{1F99E} OpenCompress is ready!",
|
|
334
|
+
`\u{1F4B0} $1 free credit \u2014 no credit card needed.`,
|
|
335
|
+
"",
|
|
336
|
+
"How it works: your existing API keys (Claude, GPT, etc.) stay the same.",
|
|
337
|
+
"We just compress the prompts in between \u2014 you save 40-70% on every call."
|
|
338
|
+
];
|
|
339
|
+
if (shouldEnable) {
|
|
340
|
+
notes.push(
|
|
341
|
+
"",
|
|
342
|
+
"\u2705 Compression is now active! All your LLM calls are being compressed.",
|
|
343
|
+
"Run `/compress-stats` anytime to see how much you're saving."
|
|
344
|
+
);
|
|
345
|
+
} else {
|
|
346
|
+
notes.push(
|
|
347
|
+
"",
|
|
348
|
+
"Run `/compress on` whenever you're ready to start saving."
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
notes.push("", "Dashboard: https://www.opencompress.ai/dashboard");
|
|
325
352
|
return {
|
|
326
353
|
profiles: [
|
|
327
354
|
{
|
|
@@ -329,19 +356,8 @@ var opencompressProvider = {
|
|
|
329
356
|
credential: { apiKey: data.apiKey }
|
|
330
357
|
}
|
|
331
358
|
],
|
|
332
|
-
configPatch: {
|
|
333
|
-
|
|
334
|
-
providers: {
|
|
335
|
-
opencompress: onboardModels
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
},
|
|
339
|
-
defaultModel: "gpt-4o-mini",
|
|
340
|
-
notes: [
|
|
341
|
-
`OpenCompress is ready! Connected to ${provider} (${modelCount} models).`,
|
|
342
|
-
"Your LLM key is stored locally only \u2014 never on our server.",
|
|
343
|
-
`Free credit: ${data.freeCredit}. Dashboard: opencompress.ai/dashboard`
|
|
344
|
-
]
|
|
359
|
+
configPatch: shouldEnable ? { _autoEnableProxy: true } : void 0,
|
|
360
|
+
notes
|
|
345
361
|
};
|
|
346
362
|
} catch (err) {
|
|
347
363
|
spinner.stop("Setup failed");
|
|
@@ -354,52 +370,19 @@ var opencompressProvider = {
|
|
|
354
370
|
var plugin = {
|
|
355
371
|
id: "opencompress",
|
|
356
372
|
name: "OpenCompress",
|
|
357
|
-
description: "
|
|
373
|
+
description: "Transparent prompt compression \u2014 save 40-70% on Claude, GPT, and any LLM provider",
|
|
358
374
|
version: VERSION,
|
|
359
375
|
register(api) {
|
|
360
376
|
const baseUrl = api.pluginConfig?.baseUrl || DEFAULT_BASE_URL;
|
|
361
|
-
const existingApiKey = api.config.models?.providers?.opencompress?.apiKey || getApiKey(api);
|
|
362
|
-
const existingModels = readExistingModels(api);
|
|
363
|
-
const providerModels = buildProviderModels(baseUrl, existingModels || void 0, existingApiKey);
|
|
364
|
-
opencompressProvider.models = providerModels;
|
|
365
377
|
api.registerProvider(opencompressProvider);
|
|
366
|
-
if (!api.config.models) {
|
|
367
|
-
api.config.models = { providers: {} };
|
|
368
|
-
}
|
|
369
|
-
if (!api.config.models.providers) {
|
|
370
|
-
api.config.models.providers = {};
|
|
371
|
-
}
|
|
372
|
-
api.config.models.providers.opencompress = providerModels;
|
|
373
|
-
persistModelsConfig(providerModels);
|
|
374
|
-
persistAgentModelsJson(providerModels);
|
|
375
|
-
{
|
|
376
|
-
if (!api.config.agents) api.config.agents = {};
|
|
377
|
-
const agents = api.config.agents;
|
|
378
|
-
if (!agents.defaults) agents.defaults = {};
|
|
379
|
-
const defaults = agents.defaults;
|
|
380
|
-
if (!defaults.models) defaults.models = {};
|
|
381
|
-
const allowlist = defaults.models;
|
|
382
|
-
if (Object.keys(allowlist).length > 0) {
|
|
383
|
-
for (const m of providerModels.models) {
|
|
384
|
-
const fullId = `opencompress/${m.id}`;
|
|
385
|
-
if (!allowlist[fullId]) {
|
|
386
|
-
allowlist[fullId] = {};
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
const modelsForAllowlist = providerModels.models;
|
|
392
|
-
setTimeout(() => injectModelsAllowlist(modelsForAllowlist), 3e3);
|
|
393
378
|
const apiKey = getApiKey(api);
|
|
394
379
|
if (apiKey) {
|
|
395
380
|
persistAuthProfile(apiKey);
|
|
396
381
|
persistAgentAuthJson(apiKey);
|
|
397
382
|
}
|
|
398
|
-
|
|
399
|
-
const source = existingModels ? "from existing providers" : "fallback";
|
|
400
|
-
api.logger.info(`OpenCompress provider registered (${modelCount} models ${source}, 5-layer compression)`);
|
|
383
|
+
api.logger.info("OpenCompress registered (transparent proxy mode)");
|
|
401
384
|
api.registerCommand({
|
|
402
|
-
name: "
|
|
385
|
+
name: "compress-stats",
|
|
403
386
|
description: "Show OpenCompress usage statistics and savings",
|
|
404
387
|
acceptsArgs: true,
|
|
405
388
|
requireAuth: false,
|
|
@@ -418,26 +401,35 @@ var plugin = {
|
|
|
418
401
|
return { text: `Failed to fetch stats: HTTP ${res.status}` };
|
|
419
402
|
}
|
|
420
403
|
const stats = await res.json();
|
|
421
|
-
const
|
|
422
|
-
const
|
|
404
|
+
const balance = Number(stats.balanceUsd || 0);
|
|
405
|
+
const calls = stats.monthlyApiCalls ?? stats.totalCalls ?? 0;
|
|
406
|
+
const savings = Number(stats.monthlySavings || stats.totalSavings || 0).toFixed(4);
|
|
423
407
|
const rate = stats.avgCompressionRate ? `${(Number(stats.avgCompressionRate) * 100).toFixed(1)}%` : "N/A";
|
|
424
408
|
const origTokens = Number(stats.totalOriginalTokens || 0).toLocaleString();
|
|
425
409
|
const compTokens = Number(stats.totalCompressedTokens || 0).toLocaleString();
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
410
|
+
const lines = [
|
|
411
|
+
"```",
|
|
412
|
+
"\u{1F99E} OpenCompress Stats",
|
|
413
|
+
"=====================",
|
|
414
|
+
`Balance: $${balance.toFixed(2)}`,
|
|
415
|
+
`API calls: ${calls}`,
|
|
416
|
+
`Avg compression: ${rate}`,
|
|
417
|
+
`Original tokens: ${origTokens}`,
|
|
418
|
+
`Compressed tokens: ${compTokens}`,
|
|
419
|
+
`Total savings: $${savings}`,
|
|
420
|
+
"```"
|
|
421
|
+
];
|
|
422
|
+
if (balance < 0.5) {
|
|
423
|
+
const linkUrl = `https://www.opencompress.ai/dashboard?link=${encodeURIComponent(apiKey2)}`;
|
|
424
|
+
lines.push(
|
|
437
425
|
"",
|
|
438
|
-
"
|
|
439
|
-
|
|
440
|
-
|
|
426
|
+
"\u26A0\uFE0F **Balance is low!** Link your account to get **$10 bonus credit**:",
|
|
427
|
+
linkUrl
|
|
428
|
+
);
|
|
429
|
+
} else {
|
|
430
|
+
lines.push("", "Dashboard: https://www.opencompress.ai/dashboard");
|
|
431
|
+
}
|
|
432
|
+
return { text: lines.join("\n") };
|
|
441
433
|
} catch (err) {
|
|
442
434
|
return {
|
|
443
435
|
text: `Error fetching stats: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -445,108 +437,84 @@ var plugin = {
|
|
|
445
437
|
}
|
|
446
438
|
}
|
|
447
439
|
});
|
|
448
|
-
api.logger.info("Registered /
|
|
440
|
+
api.logger.info("Registered /compress-stats command");
|
|
449
441
|
api.registerCommand({
|
|
450
|
-
name: "
|
|
451
|
-
description: "
|
|
442
|
+
name: "compress",
|
|
443
|
+
description: "Toggle transparent compression for all LLM providers",
|
|
452
444
|
acceptsArgs: true,
|
|
453
445
|
requireAuth: false,
|
|
454
446
|
handler: async (ctx) => {
|
|
455
|
-
const
|
|
456
|
-
if (
|
|
457
|
-
|
|
447
|
+
const arg = ctx.args?.trim().toLowerCase();
|
|
448
|
+
if (arg === "on" || arg === "enable") {
|
|
449
|
+
const occKey2 = getApiKey(api);
|
|
450
|
+
if (!occKey2) {
|
|
451
|
+
return { text: "No API key found. Run `openclaw onboard opencompress` first." };
|
|
452
|
+
}
|
|
453
|
+
const result = enableProxy(api, baseUrl);
|
|
454
|
+
if (result.proxied.length === 0 && result.skipped.length === 0) {
|
|
455
|
+
return { text: "No providers found to proxy. Add LLM providers first." };
|
|
456
|
+
}
|
|
457
|
+
const lines = [
|
|
458
|
+
`**Compression enabled** for all compatible providers (source: ${result.source}).`,
|
|
459
|
+
""
|
|
460
|
+
];
|
|
461
|
+
if (result.proxied.length > 0) {
|
|
462
|
+
lines.push(`Proxied (${result.proxied.length}): ${result.proxied.join(", ")}`);
|
|
463
|
+
}
|
|
464
|
+
if (result.skipped.length > 0) {
|
|
465
|
+
lines.push(`Skipped (incompatible format): ${result.skipped.join(", ")}`);
|
|
466
|
+
}
|
|
467
|
+
lines.push("", "All requests now route through OpenCompress for automatic compression.");
|
|
468
|
+
lines.push("To disable: `/compress off`");
|
|
469
|
+
return { text: lines.join("\n") };
|
|
458
470
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
}
|
|
464
|
-
const data = res.ok ? await res.json() : null;
|
|
471
|
+
if (arg === "off" || arg === "disable") {
|
|
472
|
+
const restored = disableProxy(api);
|
|
473
|
+
if (restored.length === 0) {
|
|
474
|
+
return { text: "Compression proxy was not active." };
|
|
475
|
+
}
|
|
465
476
|
return {
|
|
466
477
|
text: [
|
|
467
|
-
"**
|
|
478
|
+
"**Compression disabled.** Restored original provider configs.",
|
|
468
479
|
"",
|
|
469
|
-
|
|
480
|
+
`Restored: ${restored.join(", ")}`,
|
|
470
481
|
"",
|
|
471
|
-
"
|
|
472
|
-
" `/compress_byok sk-proj-xxx` \u2014 Connect OpenAI key",
|
|
473
|
-
" `/compress_byok sk-ant-xxx` \u2014 Connect Anthropic key",
|
|
474
|
-
" `/compress_byok sk-or-xxx` \u2014 Connect OpenRouter key",
|
|
475
|
-
" `/compress_byok off` \u2014 Switch back to router mode",
|
|
476
|
-
"",
|
|
477
|
-
data ? `**Balance:** $${Number(data.balance || 0).toFixed(2)}` : ""
|
|
482
|
+
"To re-enable: `/compress on`"
|
|
478
483
|
].join("\n")
|
|
479
484
|
};
|
|
480
485
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
headers: { Authorization: `Bearer ${apiKey2}` }
|
|
492
|
-
});
|
|
493
|
-
} catch {
|
|
494
|
-
}
|
|
495
|
-
return { text: "Switched back to **router mode**. Your upstream key has been removed from local config." };
|
|
486
|
+
const proxyState = readProxyState();
|
|
487
|
+
const occKey = getApiKey(api);
|
|
488
|
+
const statusLines = [
|
|
489
|
+
"**OpenCompress Transparent Proxy**",
|
|
490
|
+
"",
|
|
491
|
+
`Status: ${proxyState.enabled ? "**ON**" : "**OFF**"}`,
|
|
492
|
+
`API key: ${occKey ? `${occKey.slice(0, 12)}...` : "not set"}`
|
|
493
|
+
];
|
|
494
|
+
if (proxyState.enabled && Object.keys(proxyState.originals).length > 0) {
|
|
495
|
+
statusLines.push(`Proxied providers: ${Object.keys(proxyState.originals).join(", ")}`);
|
|
496
496
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
if (upstreamKey.startsWith("sk-proj-") || upstreamKey.startsWith("sk-") && !upstreamKey.startsWith("sk-ant-") && !upstreamKey.startsWith("sk-or-")) {
|
|
506
|
-
provider = "openai";
|
|
507
|
-
upstreamBaseUrl = "https://api.openai.com/v1";
|
|
508
|
-
} else if (upstreamKey.startsWith("sk-ant-")) {
|
|
509
|
-
provider = "anthropic";
|
|
510
|
-
upstreamBaseUrl = "https://api.anthropic.com/v1";
|
|
511
|
-
} else if (upstreamKey.startsWith("sk-or-")) {
|
|
512
|
-
provider = "openrouter";
|
|
513
|
-
upstreamBaseUrl = "https://openrouter.ai/api/v1";
|
|
514
|
-
} else if (upstreamKey.startsWith("AIza")) {
|
|
515
|
-
provider = "google";
|
|
516
|
-
upstreamBaseUrl = "https://generativelanguage.googleapis.com/v1beta/openai";
|
|
517
|
-
}
|
|
518
|
-
const existingModels2 = readExistingModels(api);
|
|
519
|
-
const updatedModels = buildProviderModels(baseUrl, existingModels2 || void 0);
|
|
520
|
-
if (api.config.models?.providers) {
|
|
521
|
-
api.config.models.providers.opencompress = updatedModels;
|
|
522
|
-
}
|
|
523
|
-
persistModelsConfig(updatedModels);
|
|
524
|
-
persistAgentModelsJson(updatedModels);
|
|
525
|
-
try {
|
|
526
|
-
await fetch(`${baseUrl}/v1/byok`, {
|
|
527
|
-
method: "POST",
|
|
528
|
-
headers: {
|
|
529
|
-
Authorization: `Bearer ${apiKey2}`,
|
|
530
|
-
"Content-Type": "application/json"
|
|
531
|
-
},
|
|
532
|
-
body: JSON.stringify({ upstreamApiKey: upstreamKey, upstreamBaseUrl })
|
|
533
|
-
});
|
|
534
|
-
} catch {
|
|
535
|
-
}
|
|
536
|
-
const modelCount2 = existingModels2 ? existingModels2.length : FALLBACK_MODELS.length;
|
|
537
|
-
return {
|
|
538
|
-
text: [
|
|
539
|
-
`Switched to **BYOK mode** (${provider}).`,
|
|
540
|
-
`Loaded **${modelCount2} models** from your ${provider} account.`,
|
|
541
|
-
"",
|
|
542
|
-
"Your upstream key is stored securely on the server, associated with your API key.",
|
|
543
|
-
"",
|
|
544
|
-
"To switch back: `/compress_byok off`"
|
|
545
|
-
].join("\n")
|
|
546
|
-
};
|
|
497
|
+
statusLines.push(
|
|
498
|
+
"",
|
|
499
|
+
"**Usage:**",
|
|
500
|
+
" `/compress on` \u2014 Route all providers through OpenCompress",
|
|
501
|
+
" `/compress off` \u2014 Restore original provider configs",
|
|
502
|
+
" `/compress-stats` \u2014 View compression savings"
|
|
503
|
+
);
|
|
504
|
+
return { text: statusLines.join("\n") };
|
|
547
505
|
}
|
|
548
506
|
});
|
|
549
|
-
api.logger.info("Registered /
|
|
507
|
+
api.logger.info("Registered /compress command");
|
|
508
|
+
setTimeout(() => {
|
|
509
|
+
const proxyState = readProxyState();
|
|
510
|
+
const autoEnable = api.pluginConfig?._autoEnableProxy;
|
|
511
|
+
if (proxyState.enabled || autoEnable) {
|
|
512
|
+
const result = enableProxy(api, baseUrl);
|
|
513
|
+
if (result.proxied.length > 0) {
|
|
514
|
+
api.logger.info(`Compression active: ${result.proxied.length} providers (${result.proxied.join(", ")})`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}, 2e3);
|
|
550
518
|
const os = __require("os");
|
|
551
519
|
const fs = __require("fs");
|
|
552
520
|
const path = __require("path");
|