@rethinkingstudio/clawpilot 2.0.0-beta.0 → 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/commands/pair.js +3 -5
- package/dist/commands/pair.js.map +1 -1
- package/dist/platform/service-manager.js +9 -13
- package/dist/platform/service-manager.js.map +1 -1
- package/package.json +7 -1
- package/rethinkingstudio-clawpilot-1.1.17.tgz +0 -0
- package/src/commands/assistant-send.ts +0 -91
- package/src/commands/install.ts +0 -131
- package/src/commands/local-handlers.ts +0 -533
- package/src/commands/openclaw-cli.ts +0 -354
- package/src/commands/pair.ts +0 -128
- package/src/commands/provider-config.ts +0 -275
- package/src/commands/provider-handlers.ts +0 -184
- package/src/commands/provider-registry.ts +0 -138
- package/src/commands/run.ts +0 -34
- package/src/commands/send.ts +0 -42
- package/src/commands/session-key.ts +0 -77
- package/src/commands/set-token.ts +0 -45
- package/src/commands/status.ts +0 -157
- package/src/commands/upload.ts +0 -141
- package/src/config/config.ts +0 -137
- package/src/generated/build-config.ts +0 -1
- package/src/i18n/index.ts +0 -185
- package/src/index.ts +0 -166
- package/src/media/assistant-attachments.ts +0 -205
- package/src/media/oss-uploader.ts +0 -306
- package/src/platform/service-manager.ts +0 -919
- package/src/relay/gateway-client.ts +0 -359
- package/src/relay/reconnect.ts +0 -37
- package/src/relay/relay-manager.ts +0 -328
- package/test-chat.mjs +0 -64
- package/test-direct.mjs +0 -171
- package/tsconfig.json +0 -16
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
import { readFile, writeFile, mkdir, readdir } from "fs/promises";
|
|
2
|
-
import { join } from "path";
|
|
3
|
-
import { PROVIDER_REGISTRY } from "./provider-registry.js";
|
|
4
|
-
import { resolveOpenclawConfigPath, resolveOpenclawDir } from "./openclaw-cli.js";
|
|
5
|
-
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
// Types
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
|
|
10
|
-
export interface ProviderEntry {
|
|
11
|
-
id: string; // e.g. "anthropic", "custom-<uuid>"
|
|
12
|
-
type: string; // provider type key
|
|
13
|
-
name: string; // display name from registry
|
|
14
|
-
baseUrl: string;
|
|
15
|
-
modelId: string | null; // model used for this provider
|
|
16
|
-
keyMasked: string | null; // e.g. "sk-a***xyz", null if no key
|
|
17
|
-
hasKey: boolean;
|
|
18
|
-
isDefault: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
// Internal helpers
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
|
|
25
|
-
function maskKey(key: string): string {
|
|
26
|
-
if (key.length <= 8) return key.slice(0, 2) + "***" + key.slice(-2);
|
|
27
|
-
return key.slice(0, 4) + "***" + key.slice(-4);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function resolveAgentId(): Promise<string> {
|
|
31
|
-
const agentsDir = join(resolveOpenclawDir(), "agents");
|
|
32
|
-
try {
|
|
33
|
-
const entries = await readdir(agentsDir, { withFileTypes: true });
|
|
34
|
-
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
35
|
-
if (dirs.length > 0) return dirs[0];
|
|
36
|
-
} catch {
|
|
37
|
-
// agents dir missing or unreadable
|
|
38
|
-
}
|
|
39
|
-
return "main";
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function readJson(filePath: string): Promise<Record<string, unknown>> {
|
|
43
|
-
try {
|
|
44
|
-
const raw = await readFile(filePath, "utf-8");
|
|
45
|
-
return JSON.parse(raw) as Record<string, unknown>;
|
|
46
|
-
} catch {
|
|
47
|
-
return {};
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function writeJson(filePath: string, data: unknown): Promise<void> {
|
|
52
|
-
await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function getNestedObj(
|
|
56
|
-
root: Record<string, unknown>,
|
|
57
|
-
keys: string[],
|
|
58
|
-
): Record<string, unknown> {
|
|
59
|
-
let cur: Record<string, unknown> = root;
|
|
60
|
-
for (const key of keys) {
|
|
61
|
-
if (cur[key] == null || typeof cur[key] !== "object" || Array.isArray(cur[key])) {
|
|
62
|
-
cur[key] = {};
|
|
63
|
-
}
|
|
64
|
-
cur = cur[key] as Record<string, unknown>;
|
|
65
|
-
}
|
|
66
|
-
return cur;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function authProfilesPath(agentId: string): string {
|
|
70
|
-
return join(resolveOpenclawDir(), "agents", agentId, "agent", "auth-profiles.json");
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** Extract the first model ID from a provider's models array in openclaw.json */
|
|
74
|
-
function extractModelId(providerRaw: Record<string, unknown>): string | null {
|
|
75
|
-
const models = providerRaw["models"];
|
|
76
|
-
if (Array.isArray(models) && models.length > 0) {
|
|
77
|
-
const first = models[0] as Record<string, unknown>;
|
|
78
|
-
return typeof first["id"] === "string" ? first["id"] : null;
|
|
79
|
-
}
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Infer provider type from its id in models.providers */
|
|
84
|
-
function inferType(id: string): string {
|
|
85
|
-
// custom providers have ids like "custom-<uuid>"
|
|
86
|
-
if (id.startsWith("custom-")) return "custom";
|
|
87
|
-
return id;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// ---------------------------------------------------------------------------
|
|
91
|
-
// Public API
|
|
92
|
-
// ---------------------------------------------------------------------------
|
|
93
|
-
|
|
94
|
-
export async function listProviderEntries(): Promise<ProviderEntry[]> {
|
|
95
|
-
const config = await readJson(resolveOpenclawConfigPath());
|
|
96
|
-
|
|
97
|
-
const agentId = await resolveAgentId();
|
|
98
|
-
const authProfiles = await readJson(authProfilesPath(agentId));
|
|
99
|
-
const profiles = (authProfiles["profiles"] as Record<string, unknown> | undefined) ?? {};
|
|
100
|
-
|
|
101
|
-
// Determine current default
|
|
102
|
-
const agents = (config["agents"] as Record<string, unknown> | undefined) ?? {};
|
|
103
|
-
const defaults = (agents["defaults"] as Record<string, unknown> | undefined) ?? {};
|
|
104
|
-
const model = (defaults["model"] as Record<string, unknown> | undefined) ?? {};
|
|
105
|
-
const primaryModel = typeof model["primary"] === "string" ? model["primary"] : "";
|
|
106
|
-
|
|
107
|
-
const entries: ProviderEntry[] = [];
|
|
108
|
-
|
|
109
|
-
// --- Providers from models.providers (non-builtIn) ---
|
|
110
|
-
const models = (config["models"] as Record<string, unknown> | undefined) ?? {};
|
|
111
|
-
const providers = (models["providers"] as Record<string, unknown> | undefined) ?? {};
|
|
112
|
-
|
|
113
|
-
for (const [id, providerRaw] of Object.entries(providers)) {
|
|
114
|
-
const p = (providerRaw as Record<string, unknown>) ?? {};
|
|
115
|
-
const type = inferType(id);
|
|
116
|
-
const info = PROVIDER_REGISTRY[type];
|
|
117
|
-
const baseUrl = typeof p["baseUrl"] === "string" ? p["baseUrl"] : "";
|
|
118
|
-
const modelId = extractModelId(p);
|
|
119
|
-
|
|
120
|
-
// auth-profiles key is "${id}:default"
|
|
121
|
-
const profileKey = `${id}:default`;
|
|
122
|
-
const profileRaw = profiles[profileKey];
|
|
123
|
-
const profile = profileRaw != null && typeof profileRaw === "object" && !Array.isArray(profileRaw)
|
|
124
|
-
? (profileRaw as Record<string, unknown>)
|
|
125
|
-
: null;
|
|
126
|
-
const apiKey = profile != null && typeof profile["key"] === "string" ? profile["key"] : null;
|
|
127
|
-
|
|
128
|
-
const hasKey = apiKey != null && apiKey.length > 0;
|
|
129
|
-
const keyMasked = hasKey ? maskKey(apiKey!) : null;
|
|
130
|
-
const isDefault = primaryModel === id || primaryModel.startsWith(`${id}/`);
|
|
131
|
-
|
|
132
|
-
entries.push({
|
|
133
|
-
id,
|
|
134
|
-
type,
|
|
135
|
-
name: info?.displayName ?? type,
|
|
136
|
-
baseUrl,
|
|
137
|
-
modelId,
|
|
138
|
-
keyMasked,
|
|
139
|
-
hasKey,
|
|
140
|
-
isDefault,
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// --- Built-in providers (anthropic, google) — detected via auth-profiles ---
|
|
145
|
-
for (const [type, info] of Object.entries(PROVIDER_REGISTRY)) {
|
|
146
|
-
if (!info.builtIn) continue;
|
|
147
|
-
const profileKey = `${type}:default`;
|
|
148
|
-
const profileRaw = profiles[profileKey];
|
|
149
|
-
if (profileRaw == null) continue; // not configured
|
|
150
|
-
|
|
151
|
-
const profile = typeof profileRaw === "object" && !Array.isArray(profileRaw)
|
|
152
|
-
? (profileRaw as Record<string, unknown>)
|
|
153
|
-
: null;
|
|
154
|
-
const apiKey = profile != null && typeof profile["key"] === "string" ? profile["key"] : null;
|
|
155
|
-
const hasKey = apiKey != null && apiKey.length > 0;
|
|
156
|
-
const keyMasked = hasKey ? maskKey(apiKey!) : null;
|
|
157
|
-
const isDefault = primaryModel === type || primaryModel.startsWith(`${type}/`);
|
|
158
|
-
|
|
159
|
-
entries.push({
|
|
160
|
-
id: type,
|
|
161
|
-
type,
|
|
162
|
-
name: info.displayName,
|
|
163
|
-
baseUrl: info.defaultBaseUrl,
|
|
164
|
-
modelId: info.defaultModelId ?? null,
|
|
165
|
-
keyMasked,
|
|
166
|
-
hasKey,
|
|
167
|
-
isDefault,
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return entries;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export async function addProvider(params: {
|
|
175
|
-
id: string;
|
|
176
|
-
type: string;
|
|
177
|
-
apiKey: string | null;
|
|
178
|
-
baseUrl: string;
|
|
179
|
-
api: string;
|
|
180
|
-
apiKeyEnvName: string; // env var name, written as "apiKey" in openclaw.json
|
|
181
|
-
modelId?: string;
|
|
182
|
-
}): Promise<void> {
|
|
183
|
-
const { id, type, apiKey, baseUrl, api, apiKeyEnvName, modelId } = params;
|
|
184
|
-
const info = PROVIDER_REGISTRY[type];
|
|
185
|
-
|
|
186
|
-
// --- Update openclaw.json (skip for builtIn providers) ---
|
|
187
|
-
if (!info?.builtIn) {
|
|
188
|
-
const config = await readJson(resolveOpenclawConfigPath());
|
|
189
|
-
const models = getNestedObj(config, ["models"]);
|
|
190
|
-
const providers = getNestedObj(models, ["providers"]);
|
|
191
|
-
|
|
192
|
-
// "apiKey" is the env var name reference (not the actual key value)
|
|
193
|
-
const providerEntry: Record<string, unknown> = { type, baseUrl, api, apiKey: apiKeyEnvName };
|
|
194
|
-
if (modelId !== undefined && modelId.length > 0) {
|
|
195
|
-
providerEntry["models"] = [{ id: modelId, name: modelId }];
|
|
196
|
-
}
|
|
197
|
-
providers[id] = providerEntry;
|
|
198
|
-
|
|
199
|
-
await writeJson(resolveOpenclawConfigPath(), config);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// --- Update auth-profiles.json (only if apiKey provided) ---
|
|
203
|
-
if (apiKey != null && apiKey.length > 0) {
|
|
204
|
-
const agentId = await resolveAgentId();
|
|
205
|
-
const profilesPath = authProfilesPath(agentId);
|
|
206
|
-
const profilesDir = join(resolveOpenclawDir(), "agents", agentId, "agent");
|
|
207
|
-
|
|
208
|
-
await mkdir(profilesDir, { recursive: true });
|
|
209
|
-
|
|
210
|
-
const authProfiles = await readJson(profilesPath);
|
|
211
|
-
const profiles = getNestedObj(authProfiles, ["profiles"]);
|
|
212
|
-
|
|
213
|
-
// Profile key is "${id}:default" — for builtIn, id === type
|
|
214
|
-
const profileKey = `${id}:default`;
|
|
215
|
-
profiles[profileKey] = { type: "api_key", provider: type, key: apiKey };
|
|
216
|
-
|
|
217
|
-
await writeJson(profilesPath, authProfiles);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
export async function deleteProvider(id: string): Promise<void> {
|
|
222
|
-
const type = inferType(id);
|
|
223
|
-
const info = PROVIDER_REGISTRY[type];
|
|
224
|
-
|
|
225
|
-
// --- Update openclaw.json (skip for builtIn) ---
|
|
226
|
-
if (!info?.builtIn) {
|
|
227
|
-
const config = await readJson(resolveOpenclawConfigPath());
|
|
228
|
-
const models = getNestedObj(config, ["models"]);
|
|
229
|
-
const providers = getNestedObj(models, ["providers"]);
|
|
230
|
-
delete providers[id];
|
|
231
|
-
await writeJson(resolveOpenclawConfigPath(), config);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// --- Update auth-profiles.json ---
|
|
235
|
-
const agentId = await resolveAgentId();
|
|
236
|
-
const profilesPath = authProfilesPath(agentId);
|
|
237
|
-
const authProfiles = await readJson(profilesPath);
|
|
238
|
-
const profiles = getNestedObj(authProfiles, ["profiles"]);
|
|
239
|
-
|
|
240
|
-
const profileKey = `${id}:default`;
|
|
241
|
-
if (profileKey in profiles) {
|
|
242
|
-
delete profiles[profileKey];
|
|
243
|
-
await writeJson(profilesPath, authProfiles);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
export async function setDefaultProvider(id: string): Promise<void> {
|
|
248
|
-
const type = inferType(id);
|
|
249
|
-
const info = PROVIDER_REGISTRY[type];
|
|
250
|
-
|
|
251
|
-
// Determine the model ID for this provider
|
|
252
|
-
let modelId: string | null = null;
|
|
253
|
-
|
|
254
|
-
if (info?.builtIn) {
|
|
255
|
-
// Built-in providers: use registry default, no models.providers entry
|
|
256
|
-
modelId = info.defaultModelId ?? null;
|
|
257
|
-
} else {
|
|
258
|
-
// Read from models.providers entry
|
|
259
|
-
const config = await readJson(resolveOpenclawConfigPath());
|
|
260
|
-
const models = (config["models"] as Record<string, unknown>) ?? {};
|
|
261
|
-
const providers = (models["providers"] as Record<string, unknown>) ?? {};
|
|
262
|
-
const providerRaw = (providers[id] as Record<string, unknown>) ?? {};
|
|
263
|
-
modelId = extractModelId(providerRaw) ?? info?.defaultModelId ?? null;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const primary = modelId ? `${id}/${modelId}` : id;
|
|
267
|
-
|
|
268
|
-
const config = await readJson(resolveOpenclawConfigPath());
|
|
269
|
-
const agents = getNestedObj(config, ["agents"]);
|
|
270
|
-
const defaults = getNestedObj(agents, ["defaults"]);
|
|
271
|
-
const model = getNestedObj(defaults, ["model"]);
|
|
272
|
-
model["primary"] = primary;
|
|
273
|
-
|
|
274
|
-
await writeJson(resolveOpenclawConfigPath(), config);
|
|
275
|
-
}
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import { execSync } from "child_process";
|
|
2
|
-
import { randomUUID } from "crypto";
|
|
3
|
-
import type { LocalResult } from "./local-handlers.js";
|
|
4
|
-
import { execOpenclaw, SUBPROCESS_ENV } from "./openclaw-cli.js";
|
|
5
|
-
import { PROVIDER_REGISTRY } from "./provider-registry.js";
|
|
6
|
-
import {
|
|
7
|
-
listProviderEntries,
|
|
8
|
-
addProvider,
|
|
9
|
-
deleteProvider,
|
|
10
|
-
setDefaultProvider,
|
|
11
|
-
} from "./provider-config.js";
|
|
12
|
-
|
|
13
|
-
function restartGateway(): void {
|
|
14
|
-
try {
|
|
15
|
-
execOpenclaw("gateway restart");
|
|
16
|
-
console.log("[provider] gateway restarted");
|
|
17
|
-
} catch (err) {
|
|
18
|
-
console.warn("[provider] gateway restart failed:", String(err));
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
// HTTP key validation
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
|
|
26
|
-
async function validateApiKey(
|
|
27
|
-
type: string,
|
|
28
|
-
apiKey: string,
|
|
29
|
-
baseUrl: string
|
|
30
|
-
): Promise<{ ok: boolean; error?: string }> {
|
|
31
|
-
const info = PROVIDER_REGISTRY[type];
|
|
32
|
-
if (!info) return { ok: false, error: `Unknown provider type: ${type}` };
|
|
33
|
-
if (!info.requiresApiKey || !apiKey) return { ok: true };
|
|
34
|
-
|
|
35
|
-
return new Promise((resolve) => {
|
|
36
|
-
try {
|
|
37
|
-
const url = new URL(info.validationPath, baseUrl);
|
|
38
|
-
if (info.validationAuth === "google-query-param") {
|
|
39
|
-
url.searchParams.set("key", apiKey);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const headers: Record<string, string> = {};
|
|
43
|
-
if (info.validationAuth === "bearer") {
|
|
44
|
-
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
45
|
-
} else if (info.validationAuth === "x-api-key") {
|
|
46
|
-
headers["x-api-key"] = apiKey;
|
|
47
|
-
headers["anthropic-version"] = "2023-06-01";
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Use http or https based on protocol
|
|
51
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
52
|
-
const mod = url.protocol === "https:" ? require("https") : require("http");
|
|
53
|
-
const req = mod.get(
|
|
54
|
-
{
|
|
55
|
-
hostname: url.hostname,
|
|
56
|
-
port: url.port || undefined,
|
|
57
|
-
path: url.pathname + url.search,
|
|
58
|
-
headers,
|
|
59
|
-
},
|
|
60
|
-
(res: { statusCode?: number; resume: () => void }) => {
|
|
61
|
-
res.resume(); // drain body
|
|
62
|
-
const status = res.statusCode ?? 0;
|
|
63
|
-
if (status >= 200 && status < 300) {
|
|
64
|
-
resolve({ ok: true });
|
|
65
|
-
} else if (status === 401 || status === 403) {
|
|
66
|
-
resolve({ ok: false, error: "API Key 无效" });
|
|
67
|
-
} else {
|
|
68
|
-
resolve({ ok: false, error: `验证失败 (HTTP ${status})` });
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
);
|
|
72
|
-
req.setTimeout(10_000, () => {
|
|
73
|
-
req.destroy();
|
|
74
|
-
resolve({ ok: false, error: "验证超时" });
|
|
75
|
-
});
|
|
76
|
-
req.on("error", (e: Error) => resolve({ ok: false, error: e.message }));
|
|
77
|
-
} catch (e) {
|
|
78
|
-
resolve({ ok: false, error: String(e) });
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ---------------------------------------------------------------------------
|
|
84
|
-
// Command handlers
|
|
85
|
-
// ---------------------------------------------------------------------------
|
|
86
|
-
|
|
87
|
-
async function cmdList(): Promise<LocalResult> {
|
|
88
|
-
try {
|
|
89
|
-
const providers = await listProviderEntries();
|
|
90
|
-
return { ok: true, payload: { providers } };
|
|
91
|
-
} catch (err) {
|
|
92
|
-
return { ok: false, error: String(err) };
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async function cmdValidateKey(params: Record<string, unknown>): Promise<LocalResult> {
|
|
97
|
-
try {
|
|
98
|
-
const type = params.type as string;
|
|
99
|
-
const apiKey = params.apiKey as string;
|
|
100
|
-
const baseUrl = (params.baseUrl as string | undefined) ?? PROVIDER_REGISTRY[type]?.defaultBaseUrl ?? "";
|
|
101
|
-
const result = await validateApiKey(type, apiKey, baseUrl);
|
|
102
|
-
if (result.ok) return { ok: true };
|
|
103
|
-
return { ok: false, error: result.error ?? "验证失败" };
|
|
104
|
-
} catch (err) {
|
|
105
|
-
return { ok: false, error: String(err) };
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async function cmdAdd(params: Record<string, unknown>): Promise<LocalResult> {
|
|
110
|
-
try {
|
|
111
|
-
const type = params.type as string;
|
|
112
|
-
const apiKey = (params.apiKey as string | null | undefined) ?? null;
|
|
113
|
-
const info = PROVIDER_REGISTRY[type];
|
|
114
|
-
if (!info) return { ok: false, error: `Unknown provider type: ${type}` };
|
|
115
|
-
|
|
116
|
-
const baseUrl = (params.baseUrl as string | undefined) || info.defaultBaseUrl;
|
|
117
|
-
const modelId = (params.modelId as string | undefined) ?? info.defaultModelId;
|
|
118
|
-
|
|
119
|
-
// Validate key before writing config
|
|
120
|
-
if (info.requiresApiKey && apiKey) {
|
|
121
|
-
const v = await validateApiKey(type, apiKey, baseUrl);
|
|
122
|
-
if (!v.ok) return { ok: false, error: v.error ?? "API Key 无效" };
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// custom providers get a UUID suffix; id and apiKeyEnvName share the same UUID so they're traceable
|
|
126
|
-
const uuid = info.allowMultiple ? randomUUID() : null;
|
|
127
|
-
const id = uuid ? `custom-${uuid}` : type;
|
|
128
|
-
const apiKeyEnvName = uuid
|
|
129
|
-
? `CUSTOM_${uuid.replace(/-/g, "_").toUpperCase()}_API_KEY`
|
|
130
|
-
: info.apiKeyEnvName;
|
|
131
|
-
|
|
132
|
-
await addProvider({ id, type, apiKey, baseUrl, api: info.api, apiKeyEnvName, modelId });
|
|
133
|
-
restartGateway();
|
|
134
|
-
return { ok: true, payload: { id } };
|
|
135
|
-
} catch (err) {
|
|
136
|
-
return { ok: false, error: String(err) };
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
async function cmdDelete(params: Record<string, unknown>): Promise<LocalResult> {
|
|
141
|
-
try {
|
|
142
|
-
const id = params.id as string;
|
|
143
|
-
if (!id) return { ok: false, error: "id required" };
|
|
144
|
-
await deleteProvider(id);
|
|
145
|
-
restartGateway();
|
|
146
|
-
return { ok: true };
|
|
147
|
-
} catch (err) {
|
|
148
|
-
return { ok: false, error: String(err) };
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async function cmdSetDefault(params: Record<string, unknown>): Promise<LocalResult> {
|
|
153
|
-
try {
|
|
154
|
-
const id = params.id as string;
|
|
155
|
-
if (!id) return { ok: false, error: "id required" };
|
|
156
|
-
await setDefaultProvider(id);
|
|
157
|
-
restartGateway();
|
|
158
|
-
return { ok: true };
|
|
159
|
-
} catch (err) {
|
|
160
|
-
return { ok: false, error: String(err) };
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// ---------------------------------------------------------------------------
|
|
165
|
-
// Main dispatch
|
|
166
|
-
// ---------------------------------------------------------------------------
|
|
167
|
-
|
|
168
|
-
export function handleProviderCommand(
|
|
169
|
-
method: string,
|
|
170
|
-
params: unknown
|
|
171
|
-
): Promise<LocalResult> | null {
|
|
172
|
-
if (!method.startsWith("clawpilot.provider.")) return null;
|
|
173
|
-
const p = (params ?? {}) as Record<string, unknown>;
|
|
174
|
-
|
|
175
|
-
switch (method) {
|
|
176
|
-
case "clawpilot.provider.list": return cmdList();
|
|
177
|
-
case "clawpilot.provider.validateKey": return cmdValidateKey(p);
|
|
178
|
-
case "clawpilot.provider.add": return cmdAdd(p);
|
|
179
|
-
case "clawpilot.provider.delete": return cmdDelete(p);
|
|
180
|
-
case "clawpilot.provider.setDefault": return cmdSetDefault(p);
|
|
181
|
-
default:
|
|
182
|
-
return Promise.resolve({ ok: false, error: `Unknown provider command: ${method}` });
|
|
183
|
-
}
|
|
184
|
-
}
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
export interface ProviderTypeInfo {
|
|
2
|
-
displayName: string;
|
|
3
|
-
defaultBaseUrl: string;
|
|
4
|
-
api: "anthropic-messages" | "openai-completions" | "openai-responses";
|
|
5
|
-
apiKeyEnvName: string;
|
|
6
|
-
validationPath: string;
|
|
7
|
-
validationAuth: "bearer" | "x-api-key" | "google-query-param" | "none";
|
|
8
|
-
requiresApiKey: boolean;
|
|
9
|
-
allowMultiple: boolean;
|
|
10
|
-
showBaseUrl: boolean;
|
|
11
|
-
showModelId: boolean;
|
|
12
|
-
defaultModelId?: string;
|
|
13
|
-
/**
|
|
14
|
-
* Built-in OpenClaw providers (anthropic, google) — do NOT write a
|
|
15
|
-
* models.providers entry; only write to auth-profiles.json.
|
|
16
|
-
*/
|
|
17
|
-
builtIn?: boolean;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export const PROVIDER_REGISTRY: Record<string, ProviderTypeInfo> = {
|
|
21
|
-
anthropic: {
|
|
22
|
-
displayName: "Anthropic",
|
|
23
|
-
defaultBaseUrl: "https://api.anthropic.com",
|
|
24
|
-
api: "anthropic-messages",
|
|
25
|
-
apiKeyEnvName: "ANTHROPIC_API_KEY",
|
|
26
|
-
validationPath: "/v1/models",
|
|
27
|
-
validationAuth: "x-api-key",
|
|
28
|
-
requiresApiKey: true,
|
|
29
|
-
allowMultiple: false,
|
|
30
|
-
showBaseUrl: false,
|
|
31
|
-
showModelId: false,
|
|
32
|
-
defaultModelId: "claude-opus-4-6",
|
|
33
|
-
builtIn: true,
|
|
34
|
-
},
|
|
35
|
-
openai: {
|
|
36
|
-
displayName: "OpenAI",
|
|
37
|
-
defaultBaseUrl: "https://api.openai.com",
|
|
38
|
-
api: "openai-responses",
|
|
39
|
-
apiKeyEnvName: "OPENAI_API_KEY",
|
|
40
|
-
validationPath: "/v1/models",
|
|
41
|
-
validationAuth: "bearer",
|
|
42
|
-
requiresApiKey: true,
|
|
43
|
-
allowMultiple: false,
|
|
44
|
-
showBaseUrl: false,
|
|
45
|
-
showModelId: false,
|
|
46
|
-
defaultModelId: "gpt-4o",
|
|
47
|
-
},
|
|
48
|
-
google: {
|
|
49
|
-
displayName: "Google",
|
|
50
|
-
defaultBaseUrl: "https://generativelanguage.googleapis.com",
|
|
51
|
-
api: "openai-completions",
|
|
52
|
-
apiKeyEnvName: "GOOGLE_API_KEY",
|
|
53
|
-
validationPath: "/v1beta/models",
|
|
54
|
-
validationAuth: "google-query-param",
|
|
55
|
-
requiresApiKey: true,
|
|
56
|
-
allowMultiple: false,
|
|
57
|
-
showBaseUrl: false,
|
|
58
|
-
showModelId: false,
|
|
59
|
-
defaultModelId: "gemini-2.0-flash",
|
|
60
|
-
builtIn: true,
|
|
61
|
-
},
|
|
62
|
-
openrouter: {
|
|
63
|
-
displayName: "OpenRouter",
|
|
64
|
-
defaultBaseUrl: "https://openrouter.ai",
|
|
65
|
-
api: "openai-completions",
|
|
66
|
-
apiKeyEnvName: "OPENROUTER_API_KEY",
|
|
67
|
-
validationPath: "/api/v1/models",
|
|
68
|
-
validationAuth: "bearer",
|
|
69
|
-
requiresApiKey: true,
|
|
70
|
-
allowMultiple: false,
|
|
71
|
-
showBaseUrl: false,
|
|
72
|
-
showModelId: false,
|
|
73
|
-
defaultModelId: "anthropic/claude-opus-4-6",
|
|
74
|
-
},
|
|
75
|
-
ark: {
|
|
76
|
-
displayName: "ByteDance (Ark)",
|
|
77
|
-
defaultBaseUrl: "https://ark.cn-beijing.volces.com",
|
|
78
|
-
api: "openai-completions",
|
|
79
|
-
apiKeyEnvName: "ARK_API_KEY",
|
|
80
|
-
validationPath: "/api/v3/models",
|
|
81
|
-
validationAuth: "bearer",
|
|
82
|
-
requiresApiKey: true,
|
|
83
|
-
allowMultiple: false,
|
|
84
|
-
showBaseUrl: false,
|
|
85
|
-
showModelId: true, // Ark is a platform; user must specify the model endpoint ID
|
|
86
|
-
},
|
|
87
|
-
moonshot: {
|
|
88
|
-
displayName: "Moonshot (Kimi)",
|
|
89
|
-
defaultBaseUrl: "https://api.moonshot.cn",
|
|
90
|
-
api: "openai-completions",
|
|
91
|
-
apiKeyEnvName: "MOONSHOT_API_KEY",
|
|
92
|
-
validationPath: "/v1/models",
|
|
93
|
-
validationAuth: "bearer",
|
|
94
|
-
requiresApiKey: true,
|
|
95
|
-
allowMultiple: false,
|
|
96
|
-
showBaseUrl: false,
|
|
97
|
-
showModelId: false,
|
|
98
|
-
defaultModelId: "kimi-k2.5",
|
|
99
|
-
},
|
|
100
|
-
siliconflow: {
|
|
101
|
-
displayName: "SiliconFlow",
|
|
102
|
-
defaultBaseUrl: "https://api.siliconflow.cn",
|
|
103
|
-
api: "openai-completions",
|
|
104
|
-
apiKeyEnvName: "SILICONFLOW_API_KEY",
|
|
105
|
-
validationPath: "/v1/models",
|
|
106
|
-
validationAuth: "bearer",
|
|
107
|
-
requiresApiKey: true,
|
|
108
|
-
allowMultiple: false,
|
|
109
|
-
showBaseUrl: false,
|
|
110
|
-
showModelId: false,
|
|
111
|
-
defaultModelId: "Qwen/QwQ-32B",
|
|
112
|
-
},
|
|
113
|
-
ollama: {
|
|
114
|
-
displayName: "Ollama",
|
|
115
|
-
defaultBaseUrl: "http://localhost:11434",
|
|
116
|
-
api: "openai-completions",
|
|
117
|
-
apiKeyEnvName: "OLLAMA_API_KEY",
|
|
118
|
-
validationPath: "/api/tags",
|
|
119
|
-
validationAuth: "none",
|
|
120
|
-
requiresApiKey: false,
|
|
121
|
-
allowMultiple: false,
|
|
122
|
-
showBaseUrl: true,
|
|
123
|
-
showModelId: true, // User must specify which model they have pulled
|
|
124
|
-
},
|
|
125
|
-
custom: {
|
|
126
|
-
displayName: "Custom",
|
|
127
|
-
defaultBaseUrl: "",
|
|
128
|
-
api: "openai-completions",
|
|
129
|
-
apiKeyEnvName: "CUSTOM_API_KEY",
|
|
130
|
-
validationPath: "/v1/models",
|
|
131
|
-
validationAuth: "bearer",
|
|
132
|
-
requiresApiKey: false,
|
|
133
|
-
allowMultiple: true,
|
|
134
|
-
showBaseUrl: true,
|
|
135
|
-
showModelId: true,
|
|
136
|
-
defaultModelId: "custom-model",
|
|
137
|
-
},
|
|
138
|
-
};
|
package/src/commands/run.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { readConfig, readGatewayUrl, readGatewayAuth } from "../config/config.js";
|
|
2
|
-
import { runRelayManager } from "../relay/relay-manager.js";
|
|
3
|
-
import { withReconnect } from "../relay/reconnect.js";
|
|
4
|
-
import { t } from "../i18n/index.js";
|
|
5
|
-
|
|
6
|
-
export async function runCommand(): Promise<void> {
|
|
7
|
-
const config = readConfig();
|
|
8
|
-
const gatewayUrl = readGatewayUrl();
|
|
9
|
-
const gatewayAuth = readGatewayAuth(config);
|
|
10
|
-
|
|
11
|
-
console.log(t("run.starting"));
|
|
12
|
-
console.log(t("run.gatewayId", config.gatewayId));
|
|
13
|
-
console.log(t("run.relayServer", config.relayServerUrl));
|
|
14
|
-
console.log(t("run.gatewayUrl", gatewayUrl));
|
|
15
|
-
|
|
16
|
-
await withReconnect(
|
|
17
|
-
() =>
|
|
18
|
-
runRelayManager({
|
|
19
|
-
relayServerUrl: config.relayServerUrl,
|
|
20
|
-
gatewayId: config.gatewayId,
|
|
21
|
-
relaySecret: config.relaySecret,
|
|
22
|
-
gatewayUrl,
|
|
23
|
-
gatewayToken: gatewayAuth.token,
|
|
24
|
-
gatewayPassword: gatewayAuth.password,
|
|
25
|
-
onConnected: () => console.log(t("run.connected")),
|
|
26
|
-
onDisconnected: () => console.log(t("run.disconnected")),
|
|
27
|
-
}),
|
|
28
|
-
{
|
|
29
|
-
onRetry: (attempt, delayMs) => {
|
|
30
|
-
console.log(t("run.retry", String(attempt), String(delayMs)));
|
|
31
|
-
},
|
|
32
|
-
}
|
|
33
|
-
);
|
|
34
|
-
}
|
package/src/commands/send.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { basename } from "path";
|
|
2
|
-
import { t } from "../i18n/index.js";
|
|
3
|
-
import { sendSyntheticAssistantMessage } from "./assistant-send.js";
|
|
4
|
-
import { uploadFile } from "./upload.js";
|
|
5
|
-
|
|
6
|
-
export interface SendCommandOptions {
|
|
7
|
-
json?: boolean;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export async function sendCommand(filePath: string, options: SendCommandOptions = {}): Promise<void> {
|
|
11
|
-
if (!options.json) {
|
|
12
|
-
console.log(t("send.preparing", filePath));
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const uploaded = await uploadFile(filePath);
|
|
16
|
-
const result = await sendSyntheticAssistantMessage({
|
|
17
|
-
message: "uploaded file",
|
|
18
|
-
url: uploaded.url,
|
|
19
|
-
mimeType: uploaded.mimeType,
|
|
20
|
-
fileName: uploaded.fileName || basename(uploaded.path),
|
|
21
|
-
thumbnailUrl: uploaded.thumbnailUrl ?? undefined,
|
|
22
|
-
size: String(uploaded.size),
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
if (options.json) {
|
|
26
|
-
console.log(JSON.stringify({
|
|
27
|
-
path: uploaded.path,
|
|
28
|
-
fileName: uploaded.fileName,
|
|
29
|
-
mimeType: uploaded.mimeType,
|
|
30
|
-
url: uploaded.url,
|
|
31
|
-
thumbnailUrl: uploaded.thumbnailUrl,
|
|
32
|
-
size: uploaded.size,
|
|
33
|
-
runId: result.runId,
|
|
34
|
-
sessionKey: result.sessionKey,
|
|
35
|
-
messageId: result.messageId,
|
|
36
|
-
}));
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
console.log(t("send.completed", uploaded.fileName, result.sessionKey));
|
|
41
|
-
console.log(uploaded.url);
|
|
42
|
-
}
|