@nextclaw/server 0.13.3 → 0.13.5
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 +156 -64
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +534 -580
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import { compress } from "hono/compress";
|
|
3
3
|
import { serve } from "@hono/node-server";
|
|
4
|
+
import { AccessManager, PanelAppError, isPanelAppError, isServiceAppError } from "@nextclaw/kernel";
|
|
4
5
|
import { WebSocket, WebSocketServer } from "ws";
|
|
5
6
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
7
|
import { open, readFile, readdir, realpath, stat } from "node:fs/promises";
|
|
7
8
|
import { dirname, extname, isAbsolute, join, parse, resolve } from "node:path";
|
|
8
|
-
import { createHash, randomBytes, randomUUID
|
|
9
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
9
10
|
import * as NextclawCore from "@nextclaw/core";
|
|
10
11
|
import { ConfigSchema, DEFAULT_WORKSPACE_PATH, buildConfigSchema, createAgentProfile, expandHome, findEffectiveAgentProfile, getDataDir, getPackageVersion, getProviderName, hasSecretRef, isSensitiveConfigPath, loadConfig, mergeExtensionConfigView, normalizeProviderModelConfig, parseThinkingLevel, probeFeishu, readAgentAvatarContent, removeAgentProfile, resolveEffectiveAgentProfiles, saveConfig, toExtensionConfigView, updateAgentProfile } from "@nextclaw/core";
|
|
11
12
|
import { homedir } from "node:os";
|
|
12
13
|
import { findBuiltinProviderByName, listBuiltinProviders } from "@nextclaw/runtime";
|
|
13
14
|
import { McpInstalledViewService } from "@nextclaw/mcp";
|
|
14
|
-
import { PanelAppError, isPanelAppError, isServiceAppError } from "@nextclaw/kernel";
|
|
15
15
|
import { serveStatic } from "hono/serve-static";
|
|
16
16
|
//#region src/features/auth/utils/auth-bridge.utils.ts
|
|
17
17
|
const REMOTE_BRIDGE_DIR = join(getDataDir(), "remote");
|
|
@@ -107,7 +107,7 @@ var AuthRoutesController = class {
|
|
|
107
107
|
if (!body.ok) return c.json(err("INVALID_BODY", "invalid json body"), 400);
|
|
108
108
|
if (typeof body.data.username !== "string" || typeof body.data.password !== "string") return c.json(err("INVALID_BODY", "username and password are required"), 400);
|
|
109
109
|
try {
|
|
110
|
-
const result = this.authService.setup(c.req.raw, body.data);
|
|
110
|
+
const result = await this.authService.setup(c.req.raw, body.data);
|
|
111
111
|
setCookieHeader(c, result.cookie);
|
|
112
112
|
return c.json(ok(result.status), 201);
|
|
113
113
|
} catch (error) {
|
|
@@ -140,7 +140,7 @@ var AuthRoutesController = class {
|
|
|
140
140
|
if (!body.ok) return c.json(err("INVALID_BODY", "invalid json body"), 400);
|
|
141
141
|
if (typeof body.data.password !== "string") return c.json(err("INVALID_BODY", "password is required"), 400);
|
|
142
142
|
try {
|
|
143
|
-
const result = this.authService.updatePassword(c.req.raw, body.data);
|
|
143
|
+
const result = await this.authService.updatePassword(c.req.raw, body.data);
|
|
144
144
|
setCookieHeader(c, result.cookie);
|
|
145
145
|
return c.json(ok(result.status));
|
|
146
146
|
} catch (error) {
|
|
@@ -155,7 +155,7 @@ var AuthRoutesController = class {
|
|
|
155
155
|
if (!body.ok) return c.json(err("INVALID_BODY", "invalid json body"), 400);
|
|
156
156
|
if (typeof body.data.enabled !== "boolean") return c.json(err("INVALID_BODY", "enabled is required"), 400);
|
|
157
157
|
try {
|
|
158
|
-
const result = this.authService.updateEnabled(c.req.raw, body.data);
|
|
158
|
+
const result = await this.authService.updateEnabled(c.req.raw, body.data);
|
|
159
159
|
setCookieHeader(c, result.cookie);
|
|
160
160
|
return c.json(ok(result.status));
|
|
161
161
|
} catch (error) {
|
|
@@ -173,36 +173,49 @@ var AuthRoutesController = class {
|
|
|
173
173
|
};
|
|
174
174
|
};
|
|
175
175
|
//#endregion
|
|
176
|
-
//#region src/features/auth/
|
|
176
|
+
//#region src/features/auth/utils/access-cookie.utils.ts
|
|
177
177
|
const SESSION_COOKIE_NAME = "nextclaw_ui_session";
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return value.trim();
|
|
181
|
-
}
|
|
182
|
-
function parseCookieHeader(rawHeader) {
|
|
183
|
-
if (!rawHeader) return {};
|
|
184
|
-
const cookies = {};
|
|
178
|
+
function readAccessSessionTokenFromCookieHeader(rawHeader) {
|
|
179
|
+
if (!rawHeader) return null;
|
|
185
180
|
for (const chunk of rawHeader.split(";")) {
|
|
186
181
|
const [rawKey, ...rawValue] = chunk.split("=");
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
182
|
+
if (rawKey?.trim() === SESSION_COOKIE_NAME) {
|
|
183
|
+
const value = decodeURIComponent(rawValue.join("=").trim());
|
|
184
|
+
return value.trim() ? value.trim() : null;
|
|
185
|
+
}
|
|
190
186
|
}
|
|
191
|
-
return
|
|
187
|
+
return null;
|
|
192
188
|
}
|
|
193
|
-
function
|
|
194
|
-
const {
|
|
189
|
+
function buildAccessLoginCookie(params) {
|
|
190
|
+
const { expiresAt, secure, token } = params;
|
|
195
191
|
const parts = [
|
|
196
|
-
`${SESSION_COOKIE_NAME}=${encodeURIComponent(
|
|
192
|
+
`${SESSION_COOKIE_NAME}=${encodeURIComponent(token)}`,
|
|
197
193
|
"Path=/",
|
|
198
194
|
"HttpOnly",
|
|
199
195
|
"SameSite=Lax"
|
|
200
196
|
];
|
|
201
197
|
if (secure) parts.push("Secure");
|
|
202
|
-
if (
|
|
203
|
-
|
|
198
|
+
if (expiresAt) {
|
|
199
|
+
const expiresAtMs = Date.parse(expiresAt);
|
|
200
|
+
if (Number.isFinite(expiresAtMs)) {
|
|
201
|
+
const maxAgeSeconds = Math.max(0, Math.trunc((expiresAtMs - Date.now()) / 1e3));
|
|
202
|
+
parts.push(`Max-Age=${maxAgeSeconds}`);
|
|
203
|
+
parts.push(`Expires=${new Date(expiresAtMs).toUTCString()}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
204
206
|
return parts.join("; ");
|
|
205
207
|
}
|
|
208
|
+
function buildAccessLogoutCookie(secure) {
|
|
209
|
+
return [
|
|
210
|
+
`${SESSION_COOKIE_NAME}=`,
|
|
211
|
+
"Path=/",
|
|
212
|
+
"HttpOnly",
|
|
213
|
+
"SameSite=Lax",
|
|
214
|
+
secure ? "Secure" : null,
|
|
215
|
+
"Max-Age=0",
|
|
216
|
+
`Expires=${(/* @__PURE__ */ new Date(0)).toUTCString()}`
|
|
217
|
+
].filter((part) => part !== null).join("; ");
|
|
218
|
+
}
|
|
206
219
|
function resolveSecureRequest(url, protocolHint) {
|
|
207
220
|
if (protocolHint?.trim().toLowerCase() === "https") return true;
|
|
208
221
|
try {
|
|
@@ -211,232 +224,78 @@ function resolveSecureRequest(url, protocolHint) {
|
|
|
211
224
|
return false;
|
|
212
225
|
}
|
|
213
226
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
function verifyPassword(password, expectedHash, salt) {
|
|
218
|
-
const actualHashBuffer = Buffer.from(hashPassword(password, salt), "hex");
|
|
219
|
-
const expectedHashBuffer = Buffer.from(expectedHash, "hex");
|
|
220
|
-
if (actualHashBuffer.length !== expectedHashBuffer.length) return false;
|
|
221
|
-
return timingSafeEqual(actualHashBuffer, expectedHashBuffer);
|
|
222
|
-
}
|
|
223
|
-
function createPasswordRecord(password) {
|
|
224
|
-
const passwordSalt = randomBytes(16).toString("hex");
|
|
225
|
-
return {
|
|
226
|
-
passwordHash: hashPassword(password, passwordSalt),
|
|
227
|
-
passwordSalt
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
function validateUsernameAndPassword(username, password) {
|
|
231
|
-
if (!username) throw new Error("Username is required.");
|
|
232
|
-
if (password.trim().length < PASSWORD_MIN_LENGTH) throw new Error(`Password must be at least ${PASSWORD_MIN_LENGTH} characters.`);
|
|
233
|
-
}
|
|
227
|
+
//#endregion
|
|
228
|
+
//#region src/features/auth/services/ui-auth.service.ts
|
|
234
229
|
var UiAuthService = class {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
this.configPath = configPath;
|
|
230
|
+
constructor(accessManager) {
|
|
231
|
+
this.accessManager = accessManager;
|
|
238
232
|
}
|
|
239
|
-
|
|
240
|
-
return loadConfig(this.configPath);
|
|
241
|
-
};
|
|
242
|
-
saveCurrentConfig = (config) => {
|
|
243
|
-
saveConfig(ConfigSchema.parse(config), this.configPath);
|
|
244
|
-
};
|
|
245
|
-
readAuthConfig = () => {
|
|
246
|
-
return this.loadCurrentConfig().ui.auth;
|
|
247
|
-
};
|
|
248
|
-
isConfigured = (auth) => {
|
|
249
|
-
return Boolean(normalizeUsername(auth.username).length > 0 && auth.passwordHash.trim().length > 0 && auth.passwordSalt.trim().length > 0);
|
|
250
|
-
};
|
|
251
|
-
isProtectionEnabled = () => {
|
|
252
|
-
const auth = this.readAuthConfig();
|
|
253
|
-
return Boolean(auth.enabled && this.isConfigured(auth));
|
|
254
|
-
};
|
|
255
|
-
getSessionIdFromCookieHeader = (rawCookieHeader) => {
|
|
256
|
-
const sessionId = parseCookieHeader(rawCookieHeader)[SESSION_COOKIE_NAME];
|
|
257
|
-
return sessionId?.trim() ? sessionId.trim() : null;
|
|
258
|
-
};
|
|
259
|
-
getValidSession = (sessionId, username) => {
|
|
260
|
-
if (!sessionId) return null;
|
|
261
|
-
const session = this.sessions.get(sessionId);
|
|
262
|
-
if (!session || session.username !== username) return null;
|
|
263
|
-
return session;
|
|
264
|
-
};
|
|
233
|
+
isProtectionEnabled = () => this.accessManager.isPasswordProtectionEnabled();
|
|
265
234
|
isRequestAuthenticated = (request) => {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const username = normalizeUsername(auth.username);
|
|
269
|
-
const sessionId = this.getSessionIdFromCookieHeader(request.headers.get("cookie"));
|
|
270
|
-
return Boolean(this.getValidSession(sessionId, username));
|
|
235
|
+
if (!this.isProtectionEnabled()) return true;
|
|
236
|
+
return this.accessManager.authenticateSession(this.readRequestToken(request)) !== null;
|
|
271
237
|
};
|
|
272
238
|
isSocketAuthenticated = (request) => {
|
|
273
|
-
|
|
274
|
-
if (!auth.enabled || !this.isConfigured(auth)) return true;
|
|
275
|
-
const username = normalizeUsername(auth.username);
|
|
239
|
+
if (!this.isProtectionEnabled()) return true;
|
|
276
240
|
const rawCookieHeader = Array.isArray(request.headers.cookie) ? request.headers.cookie.join("; ") : request.headers.cookie;
|
|
277
|
-
|
|
278
|
-
return Boolean(this.getValidSession(sessionId, username));
|
|
241
|
+
return this.accessManager.authenticateSession(readAccessSessionTokenFromCookieHeader(rawCookieHeader)) !== null;
|
|
279
242
|
};
|
|
280
243
|
getStatus = (request) => {
|
|
281
|
-
|
|
282
|
-
const configured = this.isConfigured(auth);
|
|
283
|
-
const enabled = Boolean(auth.enabled && configured);
|
|
284
|
-
const username = configured ? normalizeUsername(auth.username) : void 0;
|
|
285
|
-
return {
|
|
286
|
-
enabled,
|
|
287
|
-
configured,
|
|
288
|
-
authenticated: enabled ? this.isRequestAuthenticated(request) : false,
|
|
289
|
-
...username ? { username } : {}
|
|
290
|
-
};
|
|
244
|
+
return this.accessManager.getPasswordAuthStatus(this.readRequestToken(request));
|
|
291
245
|
};
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
this.sessions.set(sessionId, {
|
|
295
|
-
sessionId,
|
|
296
|
-
username,
|
|
297
|
-
createdAt: Date.now()
|
|
298
|
-
});
|
|
299
|
-
return sessionId;
|
|
246
|
+
setup = async (request, payload) => {
|
|
247
|
+
return this.toCookieResult(request, await this.accessManager.setupPasswordAdmin(payload));
|
|
300
248
|
};
|
|
301
|
-
|
|
302
|
-
this.
|
|
249
|
+
login = (request, payload) => {
|
|
250
|
+
return this.toCookieResult(request, this.accessManager.loginWithPassword(payload));
|
|
303
251
|
};
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
252
|
+
logout = (request) => {
|
|
253
|
+
this.accessManager.logout(this.readRequestToken(request));
|
|
254
|
+
};
|
|
255
|
+
updatePassword = async (request, payload) => {
|
|
256
|
+
return this.toOptionalCookieResult(request, await this.accessManager.updatePassword({
|
|
257
|
+
token: this.readRequestToken(request),
|
|
258
|
+
password: payload.password
|
|
259
|
+
}));
|
|
308
260
|
};
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
261
|
+
updateEnabled = async (request, payload) => {
|
|
262
|
+
const result = await this.accessManager.setPasswordAuthEnabled({
|
|
263
|
+
token: this.readRequestToken(request),
|
|
264
|
+
enabled: payload.enabled
|
|
313
265
|
});
|
|
266
|
+
if (!payload.enabled) return {
|
|
267
|
+
status: result.status,
|
|
268
|
+
cookie: this.buildLogoutCookie(request)
|
|
269
|
+
};
|
|
270
|
+
return this.toOptionalCookieResult(request, result);
|
|
314
271
|
};
|
|
315
272
|
buildTrustedRequestCookieHeader = () => {
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
const username = normalizeUsername(auth.username);
|
|
319
|
-
if (!username) return null;
|
|
320
|
-
const sessionId = this.createSession(username);
|
|
321
|
-
return `${SESSION_COOKIE_NAME}=${encodeURIComponent(sessionId)}`;
|
|
273
|
+
const result = this.accessManager.createTrustedSession();
|
|
274
|
+
return result?.token ? `nextclaw_ui_session=${encodeURIComponent(result.token)}` : null;
|
|
322
275
|
};
|
|
323
276
|
buildLogoutCookie = (request) => {
|
|
324
|
-
return
|
|
325
|
-
value: "",
|
|
326
|
-
secure: resolveSecureRequest(request.url, request.headers.get("x-forwarded-proto")),
|
|
327
|
-
maxAgeSeconds: 0,
|
|
328
|
-
expires: (/* @__PURE__ */ new Date(0)).toUTCString()
|
|
329
|
-
});
|
|
277
|
+
return buildAccessLogoutCookie(this.isSecureRequest(request));
|
|
330
278
|
};
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
const currentAuth = config.ui.auth;
|
|
334
|
-
if (this.isConfigured(currentAuth)) throw new Error("UI authentication is already configured.");
|
|
335
|
-
const username = normalizeUsername(payload.username);
|
|
336
|
-
const password = payload.password;
|
|
337
|
-
validateUsernameAndPassword(username, password);
|
|
338
|
-
const nextPassword = createPasswordRecord(password);
|
|
339
|
-
config.ui.auth = {
|
|
340
|
-
enabled: true,
|
|
341
|
-
username,
|
|
342
|
-
...nextPassword
|
|
343
|
-
};
|
|
344
|
-
this.saveCurrentConfig(config);
|
|
345
|
-
this.clearAllSessions();
|
|
346
|
-
const sessionId = this.createSession(username);
|
|
347
|
-
return {
|
|
348
|
-
status: {
|
|
349
|
-
enabled: true,
|
|
350
|
-
configured: true,
|
|
351
|
-
authenticated: true,
|
|
352
|
-
username
|
|
353
|
-
},
|
|
354
|
-
cookie: this.buildLoginCookie(request, sessionId)
|
|
355
|
-
};
|
|
279
|
+
readRequestToken = (request) => {
|
|
280
|
+
return readAccessSessionTokenFromCookieHeader(request.headers.get("cookie"));
|
|
356
281
|
};
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
if (!auth.enabled || !this.isConfigured(auth)) throw new Error("UI authentication is not enabled.");
|
|
360
|
-
const username = normalizeUsername(payload.username);
|
|
361
|
-
if (username !== normalizeUsername(auth.username) || !verifyPassword(payload.password, auth.passwordHash, auth.passwordSalt)) throw new Error("Invalid username or password.");
|
|
362
|
-
const sessionId = this.createSession(username);
|
|
282
|
+
toCookieResult = (request, result) => {
|
|
283
|
+
if (!result.token) throw new Error("Access session token was not created.");
|
|
363
284
|
return {
|
|
364
|
-
status:
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
cookie: this.buildLoginCookie(request, sessionId)
|
|
285
|
+
status: result.status,
|
|
286
|
+
cookie: buildAccessLoginCookie({
|
|
287
|
+
token: result.token,
|
|
288
|
+
secure: this.isSecureRequest(request),
|
|
289
|
+
expiresAt: result.expiresAt
|
|
290
|
+
})
|
|
371
291
|
};
|
|
372
292
|
};
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
updatePassword = (request, payload) => {
|
|
377
|
-
const config = this.loadCurrentConfig();
|
|
378
|
-
const auth = config.ui.auth;
|
|
379
|
-
if (!this.isConfigured(auth)) throw new Error("UI authentication is not configured.");
|
|
380
|
-
if (auth.enabled && !this.isRequestAuthenticated(request)) throw new Error("Authentication required.");
|
|
381
|
-
validateUsernameAndPassword(normalizeUsername(auth.username), payload.password);
|
|
382
|
-
const nextPassword = createPasswordRecord(payload.password);
|
|
383
|
-
config.ui.auth = {
|
|
384
|
-
...auth,
|
|
385
|
-
...nextPassword
|
|
386
|
-
};
|
|
387
|
-
this.saveCurrentConfig(config);
|
|
388
|
-
this.clearAllSessions();
|
|
389
|
-
if (!auth.enabled) return { status: {
|
|
390
|
-
enabled: false,
|
|
391
|
-
configured: true,
|
|
392
|
-
authenticated: false,
|
|
393
|
-
username: normalizeUsername(auth.username)
|
|
394
|
-
} };
|
|
395
|
-
const sessionId = this.createSession(normalizeUsername(auth.username));
|
|
396
|
-
return {
|
|
397
|
-
status: {
|
|
398
|
-
enabled: true,
|
|
399
|
-
configured: true,
|
|
400
|
-
authenticated: true,
|
|
401
|
-
username: normalizeUsername(auth.username)
|
|
402
|
-
},
|
|
403
|
-
cookie: this.buildLoginCookie(request, sessionId)
|
|
404
|
-
};
|
|
293
|
+
toOptionalCookieResult = (request, result) => {
|
|
294
|
+
if (!result.token) return { status: result.status };
|
|
295
|
+
return this.toCookieResult(request, result);
|
|
405
296
|
};
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
const auth = config.ui.auth;
|
|
409
|
-
const configured = this.isConfigured(auth);
|
|
410
|
-
if (Boolean(auth.enabled && configured) && !this.isRequestAuthenticated(request)) throw new Error("Authentication required.");
|
|
411
|
-
if (payload.enabled && !configured) throw new Error("UI authentication must be configured before it can be enabled.");
|
|
412
|
-
config.ui.auth = {
|
|
413
|
-
...auth,
|
|
414
|
-
enabled: Boolean(payload.enabled)
|
|
415
|
-
};
|
|
416
|
-
this.saveCurrentConfig(config);
|
|
417
|
-
if (!payload.enabled) {
|
|
418
|
-
this.clearAllSessions();
|
|
419
|
-
return {
|
|
420
|
-
status: {
|
|
421
|
-
enabled: false,
|
|
422
|
-
configured,
|
|
423
|
-
authenticated: false,
|
|
424
|
-
...configured ? { username: normalizeUsername(auth.username) } : {}
|
|
425
|
-
},
|
|
426
|
-
cookie: this.buildLogoutCookie(request)
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
const username = normalizeUsername(auth.username);
|
|
430
|
-
const sessionId = this.createSession(username);
|
|
431
|
-
return {
|
|
432
|
-
status: {
|
|
433
|
-
enabled: true,
|
|
434
|
-
configured: true,
|
|
435
|
-
authenticated: true,
|
|
436
|
-
username
|
|
437
|
-
},
|
|
438
|
-
cookie: this.buildLoginCookie(request, sessionId)
|
|
439
|
-
};
|
|
297
|
+
isSecureRequest = (request) => {
|
|
298
|
+
return resolveSecureRequest(request.url, request.headers.get("x-forwarded-proto"));
|
|
440
299
|
};
|
|
441
300
|
};
|
|
442
301
|
//#endregion
|
|
@@ -973,23 +832,6 @@ async function connectChannelAuth(params) {
|
|
|
973
832
|
return toPublicChannelAuthPollResult(result);
|
|
974
833
|
}
|
|
975
834
|
//#endregion
|
|
976
|
-
//#region src/features/config/utils/default-provider-config.utils.ts
|
|
977
|
-
function createDefaultProviderConfig(defaultWireApi = "auto", defaultModels = [], modelConfig = {}) {
|
|
978
|
-
return {
|
|
979
|
-
enabled: true,
|
|
980
|
-
displayName: "",
|
|
981
|
-
apiKey: "",
|
|
982
|
-
apiBase: null,
|
|
983
|
-
extraHeaders: null,
|
|
984
|
-
wireApi: defaultWireApi,
|
|
985
|
-
models: [...defaultModels],
|
|
986
|
-
modelConfig
|
|
987
|
-
};
|
|
988
|
-
}
|
|
989
|
-
function createDefaultProviderConfigFromSpec(spec) {
|
|
990
|
-
return createDefaultProviderConfig(spec?.defaultWireApi ?? "auto", spec?.defaultModels ?? [], normalizeProviderModelConfig(spec?.modelConfig ?? {}));
|
|
991
|
-
}
|
|
992
|
-
//#endregion
|
|
993
835
|
//#region src/features/config/providers/server-builtin-provider.provider.ts
|
|
994
836
|
const SERVER_BUILTIN_PROVIDER_OVERRIDES = [{
|
|
995
837
|
name: "minimax-portal",
|
|
@@ -1005,7 +847,12 @@ const SERVER_BUILTIN_PROVIDER_OVERRIDES = [{
|
|
|
1005
847
|
detectByKeyPrefix: "",
|
|
1006
848
|
detectByBaseKeyword: "",
|
|
1007
849
|
defaultApiBase: "https://api.minimax.io/v1",
|
|
1008
|
-
defaultModels: [
|
|
850
|
+
defaultModels: [
|
|
851
|
+
"minimax-portal/MiniMax-M3",
|
|
852
|
+
"minimax-portal/MiniMax-M2.5",
|
|
853
|
+
"minimax-portal/MiniMax-M2.5-highspeed"
|
|
854
|
+
],
|
|
855
|
+
modelConfig: { "minimax-portal/MiniMax-M3": { vision: true } },
|
|
1009
856
|
stripModelPrefix: false,
|
|
1010
857
|
modelOverrides: [],
|
|
1011
858
|
logo: "minimax.svg",
|
|
@@ -1209,15 +1056,33 @@ function readFieldAsString(source, fieldName) {
|
|
|
1209
1056
|
function setProviderApiKey({ configPath, provider, accessToken, defaultApiBase }) {
|
|
1210
1057
|
const config = loadConfig(configPath);
|
|
1211
1058
|
const providers = config.providers;
|
|
1212
|
-
if (!providers[provider])
|
|
1059
|
+
if (!providers[provider]) return;
|
|
1213
1060
|
const target = providers[provider];
|
|
1214
1061
|
target.apiKey = accessToken;
|
|
1215
|
-
if (
|
|
1062
|
+
if (defaultApiBase) target.apiBase = defaultApiBase;
|
|
1216
1063
|
saveConfig(ConfigSchema.parse(config), configPath);
|
|
1217
1064
|
}
|
|
1218
|
-
|
|
1065
|
+
function resolveProviderAuthTarget(configPath, providerId) {
|
|
1066
|
+
const provider = loadConfig(configPath).providers[providerId];
|
|
1067
|
+
if (!provider) return null;
|
|
1068
|
+
const configuredType = typeof provider.providerType === "string" ? provider.providerType.trim() : "";
|
|
1069
|
+
if (configuredType && findServerBuiltinProviderByName(configuredType)) return {
|
|
1070
|
+
providerId,
|
|
1071
|
+
providerType: configuredType,
|
|
1072
|
+
provider
|
|
1073
|
+
};
|
|
1074
|
+
if (findServerBuiltinProviderByName(providerId)) return {
|
|
1075
|
+
providerId,
|
|
1076
|
+
providerType: providerId,
|
|
1077
|
+
provider
|
|
1078
|
+
};
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1081
|
+
async function startProviderAuth(configPath, providerId, options) {
|
|
1219
1082
|
cleanupExpiredAuthSessions();
|
|
1220
|
-
const
|
|
1083
|
+
const target = resolveProviderAuthTarget(configPath, providerId);
|
|
1084
|
+
if (!target) return null;
|
|
1085
|
+
const spec = findServerBuiltinProviderByName(target.providerType);
|
|
1221
1086
|
if (!spec?.auth || spec.auth.kind !== "device_code") return null;
|
|
1222
1087
|
const resolvedMethod = resolveAuthMethod(spec.auth, options?.methodId);
|
|
1223
1088
|
const { deviceCodeEndpoint, tokenEndpoint } = resolveDeviceCodeEndpoints(resolvedMethod.baseUrl, resolvedMethod.deviceCodePath, resolvedMethod.tokenPath);
|
|
@@ -1291,7 +1156,8 @@ async function startProviderAuth(configPath, providerName, options) {
|
|
|
1291
1156
|
const sessionId = randomUUID();
|
|
1292
1157
|
authSessions.set(sessionId, {
|
|
1293
1158
|
sessionId,
|
|
1294
|
-
|
|
1159
|
+
providerId,
|
|
1160
|
+
providerType: target.providerType,
|
|
1295
1161
|
configPath,
|
|
1296
1162
|
authorizationCode,
|
|
1297
1163
|
tokenCodeField,
|
|
@@ -1309,7 +1175,7 @@ async function startProviderAuth(configPath, providerName, options) {
|
|
|
1309
1175
|
const methodLabel = methodConfig ? resolveLocalizedMethodLabel(methodConfig, resolvedMethod.id ?? "") : void 0;
|
|
1310
1176
|
const methodHint = methodConfig ? resolveLocalizedMethodHint(methodConfig) : void 0;
|
|
1311
1177
|
return {
|
|
1312
|
-
provider:
|
|
1178
|
+
provider: providerId,
|
|
1313
1179
|
kind: "device_code",
|
|
1314
1180
|
methodId: resolvedMethod.id,
|
|
1315
1181
|
sessionId,
|
|
@@ -1321,14 +1187,14 @@ async function startProviderAuth(configPath, providerName, options) {
|
|
|
1321
1187
|
};
|
|
1322
1188
|
}
|
|
1323
1189
|
async function pollProviderAuth(params) {
|
|
1324
|
-
const { configPath, providerName, sessionId } = params;
|
|
1190
|
+
const { configPath, providerName: providerId, sessionId } = params;
|
|
1325
1191
|
cleanupExpiredAuthSessions();
|
|
1326
1192
|
const session = authSessions.get(sessionId);
|
|
1327
|
-
if (!session || session.
|
|
1193
|
+
if (!session || session.providerId !== providerId || session.configPath !== configPath) return null;
|
|
1328
1194
|
if (Date.now() >= session.expiresAtMs) {
|
|
1329
1195
|
authSessions.delete(sessionId);
|
|
1330
1196
|
return {
|
|
1331
|
-
provider:
|
|
1197
|
+
provider: providerId,
|
|
1332
1198
|
status: "expired",
|
|
1333
1199
|
message: "authorization session expired"
|
|
1334
1200
|
};
|
|
@@ -1357,7 +1223,7 @@ async function pollProviderAuth(params) {
|
|
|
1357
1223
|
payload = {};
|
|
1358
1224
|
}
|
|
1359
1225
|
if (!response.ok) return {
|
|
1360
|
-
provider:
|
|
1226
|
+
provider: providerId,
|
|
1361
1227
|
status: "error",
|
|
1362
1228
|
message: buildMinimaxErrorMessage(payload, raw || response.statusText || "authorization failed")
|
|
1363
1229
|
};
|
|
@@ -1365,7 +1231,7 @@ async function pollProviderAuth(params) {
|
|
|
1365
1231
|
if (status === "success") {
|
|
1366
1232
|
accessToken = payload.access_token?.trim() ?? "";
|
|
1367
1233
|
if (!accessToken) return {
|
|
1368
|
-
provider:
|
|
1234
|
+
provider: providerId,
|
|
1369
1235
|
status: "error",
|
|
1370
1236
|
message: "provider token response missing access token"
|
|
1371
1237
|
};
|
|
@@ -1374,7 +1240,7 @@ async function pollProviderAuth(params) {
|
|
|
1374
1240
|
const classified = classifyMiniMaxErrorStatus(message);
|
|
1375
1241
|
if (classified === "denied" || classified === "expired") authSessions.delete(sessionId);
|
|
1376
1242
|
return {
|
|
1377
|
-
provider:
|
|
1243
|
+
provider: providerId,
|
|
1378
1244
|
status: classified,
|
|
1379
1245
|
message
|
|
1380
1246
|
};
|
|
@@ -1383,7 +1249,7 @@ async function pollProviderAuth(params) {
|
|
|
1383
1249
|
session.intervalMs = nextPollMs;
|
|
1384
1250
|
authSessions.set(sessionId, session);
|
|
1385
1251
|
return {
|
|
1386
|
-
provider:
|
|
1252
|
+
provider: providerId,
|
|
1387
1253
|
status: "pending",
|
|
1388
1254
|
nextPollMs
|
|
1389
1255
|
};
|
|
@@ -1393,7 +1259,7 @@ async function pollProviderAuth(params) {
|
|
|
1393
1259
|
if (!response.ok) {
|
|
1394
1260
|
const errorCode = payload.error?.trim().toLowerCase();
|
|
1395
1261
|
if (errorCode === "authorization_pending") return {
|
|
1396
|
-
provider:
|
|
1262
|
+
provider: providerId,
|
|
1397
1263
|
status: "pending",
|
|
1398
1264
|
nextPollMs: session.intervalMs
|
|
1399
1265
|
};
|
|
@@ -1402,7 +1268,7 @@ async function pollProviderAuth(params) {
|
|
|
1402
1268
|
session.intervalMs = nextPollMs;
|
|
1403
1269
|
authSessions.set(sessionId, session);
|
|
1404
1270
|
return {
|
|
1405
|
-
provider:
|
|
1271
|
+
provider: providerId,
|
|
1406
1272
|
status: "pending",
|
|
1407
1273
|
nextPollMs
|
|
1408
1274
|
};
|
|
@@ -1410,7 +1276,7 @@ async function pollProviderAuth(params) {
|
|
|
1410
1276
|
if (errorCode === "access_denied") {
|
|
1411
1277
|
authSessions.delete(sessionId);
|
|
1412
1278
|
return {
|
|
1413
|
-
provider:
|
|
1279
|
+
provider: providerId,
|
|
1414
1280
|
status: "denied",
|
|
1415
1281
|
message: payload.error_description || "authorization denied"
|
|
1416
1282
|
};
|
|
@@ -1418,38 +1284,40 @@ async function pollProviderAuth(params) {
|
|
|
1418
1284
|
if (errorCode === "expired_token") {
|
|
1419
1285
|
authSessions.delete(sessionId);
|
|
1420
1286
|
return {
|
|
1421
|
-
provider:
|
|
1287
|
+
provider: providerId,
|
|
1422
1288
|
status: "expired",
|
|
1423
1289
|
message: payload.error_description || "authorization session expired"
|
|
1424
1290
|
};
|
|
1425
1291
|
}
|
|
1426
1292
|
return {
|
|
1427
|
-
provider:
|
|
1293
|
+
provider: providerId,
|
|
1428
1294
|
status: "error",
|
|
1429
1295
|
message: payload.error_description || payload.error || response.statusText || "authorization failed"
|
|
1430
1296
|
};
|
|
1431
1297
|
}
|
|
1432
1298
|
accessToken = payload.access_token?.trim() ?? "";
|
|
1433
1299
|
if (!accessToken) return {
|
|
1434
|
-
provider:
|
|
1300
|
+
provider: providerId,
|
|
1435
1301
|
status: "error",
|
|
1436
1302
|
message: "provider token response missing access token"
|
|
1437
1303
|
};
|
|
1438
1304
|
}
|
|
1439
1305
|
setProviderApiKey({
|
|
1440
1306
|
configPath,
|
|
1441
|
-
provider:
|
|
1307
|
+
provider: providerId,
|
|
1442
1308
|
accessToken,
|
|
1443
1309
|
defaultApiBase: session.defaultApiBase
|
|
1444
1310
|
});
|
|
1445
1311
|
authSessions.delete(sessionId);
|
|
1446
1312
|
return {
|
|
1447
|
-
provider:
|
|
1313
|
+
provider: providerId,
|
|
1448
1314
|
status: "authorized"
|
|
1449
1315
|
};
|
|
1450
1316
|
}
|
|
1451
|
-
async function importProviderAuthFromCli(configPath,
|
|
1452
|
-
const
|
|
1317
|
+
async function importProviderAuthFromCli(configPath, providerId) {
|
|
1318
|
+
const target = resolveProviderAuthTarget(configPath, providerId);
|
|
1319
|
+
if (!target) return null;
|
|
1320
|
+
const spec = findServerBuiltinProviderByName(target.providerType);
|
|
1453
1321
|
if (!spec?.auth || spec.auth.kind !== "device_code" || !spec.auth.cliCredential) return null;
|
|
1454
1322
|
const credentialPath = resolveHomePath(spec.auth.cliCredential.path);
|
|
1455
1323
|
if (!credentialPath) throw new Error("provider cli credential path is empty");
|
|
@@ -1475,12 +1343,12 @@ async function importProviderAuthFromCli(configPath, providerName) {
|
|
|
1475
1343
|
if (typeof expiresAtMs === "number" && expiresAtMs <= Date.now()) throw new Error("CLI credential has expired, please login again");
|
|
1476
1344
|
setProviderApiKey({
|
|
1477
1345
|
configPath,
|
|
1478
|
-
provider:
|
|
1346
|
+
provider: providerId,
|
|
1479
1347
|
accessToken,
|
|
1480
1348
|
defaultApiBase: spec.defaultApiBase
|
|
1481
1349
|
});
|
|
1482
1350
|
return {
|
|
1483
|
-
provider:
|
|
1351
|
+
provider: providerId,
|
|
1484
1352
|
status: "imported",
|
|
1485
1353
|
source: "cli",
|
|
1486
1354
|
expiresAt: expiresAtMs ? new Date(expiresAtMs).toISOString() : void 0
|
|
@@ -1546,6 +1414,13 @@ var ConfigRoutesController = class {
|
|
|
1546
1414
|
const config = loadConfigOrDefault(this.options.configPath);
|
|
1547
1415
|
return c.json(ok(buildConfigMeta(config, this.getExtensionConfigProjectionOptions())));
|
|
1548
1416
|
};
|
|
1417
|
+
listProviders = (c) => {
|
|
1418
|
+
const config = loadConfigOrDefault(this.options.configPath);
|
|
1419
|
+
return c.json(ok(buildProvidersView(config)));
|
|
1420
|
+
};
|
|
1421
|
+
listProviderTemplates = (c) => {
|
|
1422
|
+
return c.json(ok(buildProviderTemplatesView()));
|
|
1423
|
+
};
|
|
1549
1424
|
getConfigSchema = (c) => {
|
|
1550
1425
|
const config = loadConfigOrDefault(this.options.configPath);
|
|
1551
1426
|
return c.json(ok(buildConfigSchemaView(config, this.getExtensionConfigProjectionOptions())));
|
|
@@ -1576,35 +1451,36 @@ var ConfigRoutesController = class {
|
|
|
1576
1451
|
return c.json(ok(result));
|
|
1577
1452
|
};
|
|
1578
1453
|
updateProvider = async (c) => {
|
|
1579
|
-
const
|
|
1454
|
+
const providerId = c.req.param("providerId");
|
|
1580
1455
|
const body = await readJson(c.req.raw);
|
|
1581
1456
|
if (!body.ok) return c.json(err("INVALID_BODY", "invalid json body"), 400);
|
|
1582
|
-
const result = updateProvider(this.options.configPath,
|
|
1583
|
-
if (!result) return c.json(err("NOT_FOUND", `unknown provider: ${
|
|
1584
|
-
await this.publishConfigUpdates([`providers.${
|
|
1457
|
+
const result = updateProvider(this.options.configPath, providerId, body.data);
|
|
1458
|
+
if (!result) return c.json(err("NOT_FOUND", `unknown provider: ${providerId}`), 404);
|
|
1459
|
+
await this.publishConfigUpdates([`providers.${providerId}`]);
|
|
1585
1460
|
return c.json(ok(result));
|
|
1586
1461
|
};
|
|
1587
1462
|
createProvider = async (c) => {
|
|
1588
1463
|
const body = await readJson(c.req.raw);
|
|
1589
1464
|
if (!body.ok) return c.json(err("INVALID_BODY", "invalid json body"), 400);
|
|
1590
|
-
const result =
|
|
1591
|
-
|
|
1465
|
+
const result = createProvider(this.options.configPath, body.data);
|
|
1466
|
+
if (!result) return c.json(err("PROVIDER_EXISTS", "provider already exists"), 409);
|
|
1467
|
+
await this.publishConfigUpdates([`providers.${result.providerId}`]);
|
|
1592
1468
|
return c.json(ok({
|
|
1593
|
-
|
|
1469
|
+
providerId: result.providerId,
|
|
1594
1470
|
provider: result.provider
|
|
1595
1471
|
}));
|
|
1596
1472
|
};
|
|
1597
1473
|
deleteProvider = async (c) => {
|
|
1598
|
-
const
|
|
1599
|
-
if (
|
|
1600
|
-
await this.publishConfigUpdates([`providers.${
|
|
1474
|
+
const providerId = c.req.param("providerId");
|
|
1475
|
+
if (deleteProvider(this.options.configPath, providerId) === null) return c.json(err("NOT_FOUND", `provider not found: ${providerId}`), 404);
|
|
1476
|
+
await this.publishConfigUpdates([`providers.${providerId}`]);
|
|
1601
1477
|
return c.json(ok({
|
|
1602
1478
|
deleted: true,
|
|
1603
|
-
|
|
1479
|
+
providerId
|
|
1604
1480
|
}));
|
|
1605
1481
|
};
|
|
1606
1482
|
testProviderConnection = async (c) => {
|
|
1607
|
-
const provider = c.req.param("
|
|
1483
|
+
const provider = c.req.param("providerId");
|
|
1608
1484
|
const body = await readJson(c.req.raw);
|
|
1609
1485
|
if (!body.ok) return c.json(err("INVALID_BODY", "invalid json body"), 400);
|
|
1610
1486
|
const result = await testProviderConnection(this.options.configPath, provider, body.data, this.options.kernel.llmProviders);
|
|
@@ -1612,7 +1488,7 @@ var ConfigRoutesController = class {
|
|
|
1612
1488
|
return c.json(ok(result));
|
|
1613
1489
|
};
|
|
1614
1490
|
startProviderAuth = async (c) => {
|
|
1615
|
-
const provider = c.req.param("
|
|
1491
|
+
const provider = c.req.param("providerId");
|
|
1616
1492
|
let payload = {};
|
|
1617
1493
|
const rawBody = await c.req.raw.text();
|
|
1618
1494
|
if (rawBody.trim().length > 0) try {
|
|
@@ -1631,7 +1507,7 @@ var ConfigRoutesController = class {
|
|
|
1631
1507
|
}
|
|
1632
1508
|
};
|
|
1633
1509
|
pollProviderAuth = async (c) => {
|
|
1634
|
-
const provider = c.req.param("
|
|
1510
|
+
const provider = c.req.param("providerId");
|
|
1635
1511
|
const body = await readJson(c.req.raw);
|
|
1636
1512
|
if (!body.ok) return c.json(err("INVALID_BODY", "invalid json body"), 400);
|
|
1637
1513
|
const sessionId = typeof body.data.sessionId === "string" ? body.data.sessionId.trim() : "";
|
|
@@ -1646,7 +1522,7 @@ var ConfigRoutesController = class {
|
|
|
1646
1522
|
return c.json(ok(result));
|
|
1647
1523
|
};
|
|
1648
1524
|
importProviderAuthFromCli = async (c) => {
|
|
1649
|
-
const provider = c.req.param("
|
|
1525
|
+
const provider = c.req.param("providerId");
|
|
1650
1526
|
try {
|
|
1651
1527
|
const result = await importProviderAuthFromCli(this.options.configPath, provider);
|
|
1652
1528
|
if (!result) return c.json(err("NOT_SUPPORTED", `provider cli auth import is not supported: ${provider}`), 404);
|
|
@@ -2013,11 +1889,6 @@ const PREFERRED_PROVIDER_ORDER_INDEX = new Map([
|
|
|
2013
1889
|
].map((name, index) => [name, index]));
|
|
2014
1890
|
const BUILTIN_PROVIDERS = listServerBuiltinProviders();
|
|
2015
1891
|
const BUILTIN_PROVIDER_NAMES = new Set(BUILTIN_PROVIDERS.map((spec) => spec.name));
|
|
2016
|
-
const CUSTOM_PROVIDER_WIRE_API_OPTIONS = [
|
|
2017
|
-
"auto",
|
|
2018
|
-
"chat",
|
|
2019
|
-
"responses"
|
|
2020
|
-
];
|
|
2021
1892
|
const CUSTOM_PROVIDER_PREFIX = "custom-";
|
|
2022
1893
|
const PROVIDER_TEST_MAX_TOKENS = 16;
|
|
2023
1894
|
function normalizeOptionalDisplayName(value) {
|
|
@@ -2035,13 +1906,8 @@ function resolveCustomProviderFallbackDisplayName(name) {
|
|
|
2035
1906
|
}
|
|
2036
1907
|
return name;
|
|
2037
1908
|
}
|
|
2038
|
-
function
|
|
2039
|
-
|
|
2040
|
-
if (isCustomProviderName(providerName)) return configDisplayName ?? resolveCustomProviderFallbackDisplayName(providerName);
|
|
2041
|
-
return spec?.displayName ?? configDisplayName ?? spec?.name;
|
|
2042
|
-
}
|
|
2043
|
-
function listCustomProviderNames(config) {
|
|
2044
|
-
return Object.keys(config.providers).filter((name) => isCustomProviderName(name));
|
|
1909
|
+
function resolveProviderInstanceDisplayName(providerId, provider, spec) {
|
|
1910
|
+
return normalizeOptionalDisplayName(provider?.displayName) ?? spec?.displayName ?? (providerId.startsWith(CUSTOM_PROVIDER_PREFIX) ? resolveCustomProviderFallbackDisplayName(providerId) : providerId);
|
|
2045
1911
|
}
|
|
2046
1912
|
function findNextCustomProviderName(config) {
|
|
2047
1913
|
const providers = config.providers;
|
|
@@ -2049,16 +1915,39 @@ function findNextCustomProviderName(config) {
|
|
|
2049
1915
|
while (providers[`${CUSTOM_PROVIDER_PREFIX}${index}`]) index += 1;
|
|
2050
1916
|
return `${CUSTOM_PROVIDER_PREFIX}${index}`;
|
|
2051
1917
|
}
|
|
2052
|
-
function
|
|
1918
|
+
function normalizeProviderId(value) {
|
|
1919
|
+
if (typeof value !== "string") return null;
|
|
1920
|
+
const trimmed = value.trim();
|
|
1921
|
+
if (!trimmed || trimmed.includes("/")) return null;
|
|
1922
|
+
return trimmed;
|
|
1923
|
+
}
|
|
1924
|
+
function resolveProviderType(providerId, provider) {
|
|
1925
|
+
const configuredType = normalizeProviderId(provider?.providerType);
|
|
1926
|
+
if (configuredType && findServerBuiltinProviderByName(configuredType)) return configuredType;
|
|
1927
|
+
if (findServerBuiltinProviderByName(providerId)) return providerId;
|
|
1928
|
+
return null;
|
|
1929
|
+
}
|
|
1930
|
+
function findNextProviderId(config, baseProviderId) {
|
|
2053
1931
|
const providers = config.providers;
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
1932
|
+
let providerId = baseProviderId;
|
|
1933
|
+
let index = 2;
|
|
1934
|
+
while (providers[providerId]) {
|
|
1935
|
+
providerId = `${baseProviderId}-${index}`;
|
|
1936
|
+
index += 1;
|
|
1937
|
+
}
|
|
1938
|
+
return providerId;
|
|
1939
|
+
}
|
|
1940
|
+
function resolveProviderDisplayNameSuffix(providerId, baseProviderId) {
|
|
1941
|
+
if (providerId === baseProviderId) return "";
|
|
1942
|
+
const suffix = providerId.slice(baseProviderId.length + 1).trim();
|
|
1943
|
+
return suffix ? ` ${suffix}` : "";
|
|
1944
|
+
}
|
|
1945
|
+
function buildProviderScopedModels(providerId, models) {
|
|
1946
|
+
return normalizeModelList(models).map((model) => {
|
|
1947
|
+
const slashIndex = model.indexOf("/");
|
|
1948
|
+
const modelSuffix = slashIndex >= 0 ? model.slice(slashIndex + 1).trim() : model;
|
|
1949
|
+
return modelSuffix ? `${providerId}/${modelSuffix}` : "";
|
|
1950
|
+
}).filter(Boolean);
|
|
2062
1951
|
}
|
|
2063
1952
|
function clearSecretRefsByPrefix(refs, pathPrefix) {
|
|
2064
1953
|
return Object.fromEntries(Object.entries(refs).filter(([key]) => key !== pathPrefix && !key.startsWith(`${pathPrefix}.`)));
|
|
@@ -2223,13 +2112,18 @@ function normalizeModelList(input) {
|
|
|
2223
2112
|
}
|
|
2224
2113
|
return [...deduped];
|
|
2225
2114
|
}
|
|
2226
|
-
function toProviderView(config, provider,
|
|
2227
|
-
const
|
|
2115
|
+
function toProviderView(config, provider, providerId, uiHints, spec) {
|
|
2116
|
+
const providerType = resolveProviderType(providerId, provider);
|
|
2117
|
+
const apiKeyRefSet = hasSecretRef(config, `providers.${providerId}.apiKey`);
|
|
2228
2118
|
const masked = maskApiKey(provider.apiKey);
|
|
2229
|
-
const extraHeaders = provider.extraHeaders && Object.keys(provider.extraHeaders).length > 0 ? sanitizePublicConfigValue(provider.extraHeaders, `providers.${
|
|
2119
|
+
const extraHeaders = provider.extraHeaders && Object.keys(provider.extraHeaders).length > 0 ? sanitizePublicConfigValue(provider.extraHeaders, `providers.${providerId}.extraHeaders`, uiHints) : null;
|
|
2230
2120
|
const view = {
|
|
2121
|
+
providerId,
|
|
2122
|
+
providerType,
|
|
2123
|
+
isBuiltInType: providerType !== null,
|
|
2124
|
+
isCustom: providerType === null,
|
|
2231
2125
|
enabled: provider.enabled !== false,
|
|
2232
|
-
displayName:
|
|
2126
|
+
displayName: resolveProviderInstanceDisplayName(providerId, provider, spec),
|
|
2233
2127
|
apiKeySet: masked.apiKeySet || apiKeyRefSet,
|
|
2234
2128
|
apiKeyMasked: masked.apiKeyMasked ?? (apiKeyRefSet ? "****" : void 0),
|
|
2235
2129
|
apiBase: provider.apiBase ?? null,
|
|
@@ -2237,14 +2131,17 @@ function toProviderView(config, provider, providerName, uiHints, spec) {
|
|
|
2237
2131
|
models: normalizeModelList(provider.models ?? []),
|
|
2238
2132
|
modelConfig: normalizeProviderModelConfig(provider.modelConfig ?? {})
|
|
2239
2133
|
};
|
|
2240
|
-
if (Boolean(spec?.supportsWireApi) ||
|
|
2134
|
+
if (Boolean(spec?.supportsWireApi) || providerType === null) view.wireApi = provider.wireApi ?? spec?.defaultWireApi ?? "auto";
|
|
2241
2135
|
return view;
|
|
2242
2136
|
}
|
|
2243
2137
|
function buildConfigView(config, options) {
|
|
2244
2138
|
const uiHints = buildUiHints(config, options);
|
|
2245
2139
|
const projectedChannels = getProjectedChannelMap(config, options);
|
|
2246
2140
|
const providers = {};
|
|
2247
|
-
for (const [
|
|
2141
|
+
for (const [providerId, provider] of Object.entries(config.providers)) {
|
|
2142
|
+
const providerConfig = provider;
|
|
2143
|
+
providers[providerId] = toProviderView(config, providerConfig, providerId, uiHints, findServerBuiltinProviderByName(resolveProviderType(providerId, providerConfig) ?? ""));
|
|
2144
|
+
}
|
|
2248
2145
|
return {
|
|
2249
2146
|
companion: sanitizePublicConfigValue(config.companion, "companion", uiHints),
|
|
2250
2147
|
agents: sanitizePublicConfigValue(config.agents, "agents", uiHints),
|
|
@@ -2323,13 +2220,18 @@ function clearSecretRef(refs, path) {
|
|
|
2323
2220
|
return nextRefs;
|
|
2324
2221
|
}
|
|
2325
2222
|
function buildConfigMeta(config, options) {
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2223
|
+
return {
|
|
2224
|
+
search: SEARCH_PROVIDER_META,
|
|
2225
|
+
channels: buildProjectedChannelMeta(config, options)
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
function buildProviderTemplatesView() {
|
|
2229
|
+
return { providerTemplates: BUILTIN_PROVIDERS.map((spec) => {
|
|
2329
2230
|
return {
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2231
|
+
id: spec.name,
|
|
2232
|
+
providerType: spec.name,
|
|
2233
|
+
displayName: spec.displayName ?? spec.name,
|
|
2234
|
+
apiProtocol: spec.apiProtocol,
|
|
2333
2235
|
modelPrefix: spec.modelPrefix,
|
|
2334
2236
|
keywords: spec.keywords,
|
|
2335
2237
|
envKey: spec.envKey,
|
|
@@ -2357,43 +2259,22 @@ function buildConfigMeta(config, options) {
|
|
|
2357
2259
|
defaultWireApi: spec.defaultWireApi
|
|
2358
2260
|
};
|
|
2359
2261
|
}).sort((left, right) => {
|
|
2360
|
-
const leftRank = PREFERRED_PROVIDER_ORDER_INDEX.get(left.
|
|
2361
|
-
const rightRank = PREFERRED_PROVIDER_ORDER_INDEX.get(right.
|
|
2262
|
+
const leftRank = PREFERRED_PROVIDER_ORDER_INDEX.get(left.id);
|
|
2263
|
+
const rightRank = PREFERRED_PROVIDER_ORDER_INDEX.get(right.id);
|
|
2362
2264
|
if (leftRank !== void 0 && rightRank !== void 0) return leftRank - rightRank;
|
|
2363
2265
|
if (leftRank !== void 0) return -1;
|
|
2364
2266
|
if (rightRank !== void 0) return 1;
|
|
2365
|
-
return left.
|
|
2366
|
-
});
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
displayName,
|
|
2377
|
-
isCustom: true,
|
|
2378
|
-
modelPrefix: name,
|
|
2379
|
-
keywords: normalizeModelList([name, displayName ?? ""]),
|
|
2380
|
-
envKey: "OPENAI_API_KEY",
|
|
2381
|
-
isGateway: false,
|
|
2382
|
-
isLocal: false,
|
|
2383
|
-
defaultApiBase: void 0,
|
|
2384
|
-
logo: void 0,
|
|
2385
|
-
apiBaseHelp: void 0,
|
|
2386
|
-
auth: void 0,
|
|
2387
|
-
defaultModels: [],
|
|
2388
|
-
modelConfig: {},
|
|
2389
|
-
supportsWireApi: true,
|
|
2390
|
-
wireApiOptions: CUSTOM_PROVIDER_WIRE_API_OPTIONS,
|
|
2391
|
-
defaultWireApi: "auto"
|
|
2392
|
-
};
|
|
2393
|
-
}), ...builtinProviders],
|
|
2394
|
-
search: SEARCH_PROVIDER_META,
|
|
2395
|
-
channels: buildProjectedChannelMeta(config, options)
|
|
2396
|
-
};
|
|
2267
|
+
return left.id.localeCompare(right.id);
|
|
2268
|
+
}) };
|
|
2269
|
+
}
|
|
2270
|
+
function buildProvidersView(config) {
|
|
2271
|
+
const uiHints = buildUiHints(config);
|
|
2272
|
+
const providers = {};
|
|
2273
|
+
for (const [providerId, provider] of Object.entries(config.providers)) {
|
|
2274
|
+
const providerConfig = provider;
|
|
2275
|
+
providers[providerId] = toProviderView(config, providerConfig, providerId, uiHints, findServerBuiltinProviderByName(resolveProviderType(providerId, providerConfig) ?? ""));
|
|
2276
|
+
}
|
|
2277
|
+
return { providers };
|
|
2397
2278
|
}
|
|
2398
2279
|
function buildConfigSchemaView(_config, options) {
|
|
2399
2280
|
const base = buildConfigSchema({ version: getPackageVersion() });
|
|
@@ -2456,60 +2337,67 @@ function updateModel(configPath, patch) {
|
|
|
2456
2337
|
saveConfig(next, configPath);
|
|
2457
2338
|
return buildConfigView(next);
|
|
2458
2339
|
}
|
|
2459
|
-
function updateProvider(configPath,
|
|
2340
|
+
function updateProvider(configPath, providerId, patch) {
|
|
2460
2341
|
const config = loadConfigOrDefault(configPath);
|
|
2461
|
-
const provider =
|
|
2342
|
+
const provider = config.providers[providerId];
|
|
2462
2343
|
if (!provider) return null;
|
|
2463
|
-
const
|
|
2464
|
-
const
|
|
2465
|
-
if (Object.prototype.hasOwnProperty.call(patch, "
|
|
2344
|
+
const currentProviderType = resolveProviderType(providerId, provider);
|
|
2345
|
+
const spec = findServerBuiltinProviderByName((Object.prototype.hasOwnProperty.call(patch, "providerType") ? normalizeProviderId(patch.providerType) : currentProviderType) ?? "");
|
|
2346
|
+
if (Object.prototype.hasOwnProperty.call(patch, "providerType")) provider.providerType = spec?.name ?? null;
|
|
2347
|
+
if (Object.prototype.hasOwnProperty.call(patch, "displayName")) provider.displayName = normalizeOptionalDisplayName(patch.displayName) ?? "";
|
|
2466
2348
|
if (Object.prototype.hasOwnProperty.call(patch, "enabled")) provider.enabled = patch.enabled !== false;
|
|
2467
2349
|
if (Object.prototype.hasOwnProperty.call(patch, "apiKey")) {
|
|
2468
2350
|
provider.apiKey = patch.apiKey ?? "";
|
|
2469
|
-
config.secrets.refs = clearSecretRef(config.secrets.refs, `providers.${
|
|
2351
|
+
config.secrets.refs = clearSecretRef(config.secrets.refs, `providers.${providerId}.apiKey`);
|
|
2470
2352
|
}
|
|
2471
2353
|
if (Object.prototype.hasOwnProperty.call(patch, "apiBase")) provider.apiBase = patch.apiBase ?? null;
|
|
2472
2354
|
if (Object.prototype.hasOwnProperty.call(patch, "extraHeaders")) provider.extraHeaders = patch.extraHeaders ?? null;
|
|
2473
|
-
if (Object.prototype.hasOwnProperty.call(patch, "wireApi") && (spec?.supportsWireApi ||
|
|
2355
|
+
if (Object.prototype.hasOwnProperty.call(patch, "wireApi") && (spec?.supportsWireApi || !spec)) provider.wireApi = patch.wireApi ?? spec?.defaultWireApi ?? "auto";
|
|
2474
2356
|
if (Object.prototype.hasOwnProperty.call(patch, "models")) provider.models = normalizeModelList(patch.models ?? []);
|
|
2475
2357
|
if (Object.prototype.hasOwnProperty.call(patch, "modelConfig")) provider.modelConfig = normalizeProviderModelConfig(patch.modelConfig ?? {});
|
|
2476
2358
|
const next = ConfigSchema.parse(config);
|
|
2477
2359
|
saveConfig(next, configPath);
|
|
2478
2360
|
const uiHints = buildUiHints(next);
|
|
2479
|
-
const updated = next.providers[
|
|
2480
|
-
return toProviderView(next, updated,
|
|
2361
|
+
const updated = next.providers[providerId];
|
|
2362
|
+
return toProviderView(next, updated, providerId, uiHints, spec ?? void 0);
|
|
2481
2363
|
}
|
|
2482
|
-
function
|
|
2364
|
+
function createProvider(configPath, patch = {}) {
|
|
2483
2365
|
const config = loadConfigOrDefault(configPath);
|
|
2484
|
-
const providerName = findNextCustomProviderName(config);
|
|
2485
2366
|
const providers = config.providers;
|
|
2486
|
-
const
|
|
2487
|
-
|
|
2367
|
+
const requestedProviderType = normalizeProviderId(patch.providerType);
|
|
2368
|
+
const spec = requestedProviderType ? findServerBuiltinProviderByName(requestedProviderType) : void 0;
|
|
2369
|
+
const fallbackProviderId = spec ? spec.name : findNextCustomProviderName(config);
|
|
2370
|
+
const requestedProviderId = normalizeProviderId(patch.providerId);
|
|
2371
|
+
if (requestedProviderId && providers[requestedProviderId]) return null;
|
|
2372
|
+
const providerId = requestedProviderId ? requestedProviderId : findNextProviderId(config, fallbackProviderId);
|
|
2373
|
+
const generatedDisplayName = spec ? `${spec.displayName}${resolveProviderDisplayNameSuffix(providerId, spec.name)}` : resolveCustomProviderFallbackDisplayName(providerId);
|
|
2374
|
+
const defaultModels = spec ? buildProviderScopedModels(providerId, spec.defaultModels ?? []) : [];
|
|
2375
|
+
providers[providerId] = {
|
|
2488
2376
|
enabled: patch.enabled !== false,
|
|
2377
|
+
providerType: spec?.name ?? null,
|
|
2489
2378
|
displayName: normalizeOptionalDisplayName(patch.displayName) ?? generatedDisplayName,
|
|
2490
2379
|
apiKey: normalizeOptionalString(patch.apiKey) ?? "",
|
|
2491
|
-
apiBase: normalizeOptionalString(patch.apiBase),
|
|
2380
|
+
apiBase: normalizeOptionalString(patch.apiBase) ?? spec?.defaultApiBase ?? null,
|
|
2492
2381
|
extraHeaders: normalizeHeaders(patch.extraHeaders ?? null),
|
|
2493
|
-
wireApi: patch.wireApi ?? "auto",
|
|
2494
|
-
models: normalizeModelList(patch.models ?? []),
|
|
2495
|
-
modelConfig: normalizeProviderModelConfig(patch.modelConfig ?? {})
|
|
2382
|
+
wireApi: patch.wireApi ?? spec?.defaultWireApi ?? "auto",
|
|
2383
|
+
models: Object.prototype.hasOwnProperty.call(patch, "models") ? normalizeModelList(patch.models ?? []) : defaultModels,
|
|
2384
|
+
modelConfig: normalizeProviderModelConfig(patch.modelConfig ?? spec?.modelConfig ?? {})
|
|
2496
2385
|
};
|
|
2497
2386
|
const next = ConfigSchema.parse(config);
|
|
2498
2387
|
saveConfig(next, configPath);
|
|
2499
2388
|
const uiHints = buildUiHints(next);
|
|
2500
|
-
const created = next.providers[
|
|
2389
|
+
const created = next.providers[providerId];
|
|
2501
2390
|
return {
|
|
2502
|
-
|
|
2503
|
-
provider: toProviderView(next, created,
|
|
2391
|
+
providerId,
|
|
2392
|
+
provider: toProviderView(next, created, providerId, uiHints, spec)
|
|
2504
2393
|
};
|
|
2505
2394
|
}
|
|
2506
|
-
function
|
|
2507
|
-
if (!isCustomProviderName(providerName)) return null;
|
|
2395
|
+
function deleteProvider(configPath, providerId) {
|
|
2508
2396
|
const config = loadConfigOrDefault(configPath);
|
|
2509
2397
|
const providers = config.providers;
|
|
2510
|
-
if (!providers[
|
|
2511
|
-
delete providers[
|
|
2512
|
-
config.secrets.refs = clearSecretRefsByPrefix(config.secrets.refs, `providers.${
|
|
2398
|
+
if (!providers[providerId]) return null;
|
|
2399
|
+
delete providers[providerId];
|
|
2400
|
+
config.secrets.refs = clearSecretRefsByPrefix(config.secrets.refs, `providers.${providerId}`);
|
|
2513
2401
|
saveConfig(ConfigSchema.parse(config), configPath);
|
|
2514
2402
|
return true;
|
|
2515
2403
|
}
|
|
@@ -2549,32 +2437,35 @@ function buildScopedProviderModel(providerName, model, spec) {
|
|
|
2549
2437
|
if (!prefix) return trimmed;
|
|
2550
2438
|
return `${prefix}/${trimmed}`;
|
|
2551
2439
|
}
|
|
2552
|
-
function
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2440
|
+
function stripProviderIdPrefix(providerId, model) {
|
|
2441
|
+
const prefix = `${providerId}/`;
|
|
2442
|
+
if (!model.startsWith(prefix)) return model;
|
|
2443
|
+
return model.slice(prefix.length).trim() || model;
|
|
2444
|
+
}
|
|
2445
|
+
function resolveTestModel(config, providerId, requestedModel, provider, spec) {
|
|
2446
|
+
if (requestedModel) return spec ? requestedModel.replace(`${providerId}/`, `${spec.name}/`) : stripProviderIdPrefix(providerId, requestedModel);
|
|
2447
|
+
const providerModels = normalizeModelList(provider.models ?? []).map((modelId) => {
|
|
2448
|
+
const providerModel = stripProviderIdPrefix(providerId, modelId);
|
|
2449
|
+
return spec ? buildScopedProviderModel(spec.name, providerModel, spec) : providerModel;
|
|
2450
|
+
}).filter((modelId) => modelId.length > 0);
|
|
2561
2451
|
if (providerModels.length > 0) return providerModels[0];
|
|
2562
2452
|
const defaultModel = normalizeOptionalString(config.agents.defaults.model);
|
|
2563
2453
|
if (defaultModel) {
|
|
2564
2454
|
const routedProvider = getProviderName(config, defaultModel);
|
|
2565
|
-
if (!routedProvider || routedProvider ===
|
|
2455
|
+
if (!routedProvider || routedProvider === providerId) return spec ? defaultModel.replace(`${providerId}/`, `${spec.name}/`) : stripProviderIdPrefix(providerId, defaultModel);
|
|
2566
2456
|
}
|
|
2567
|
-
if (
|
|
2457
|
+
if (!spec) return null;
|
|
2568
2458
|
return normalizeModelList(spec?.defaultModels ?? [])[0] ?? null ?? defaultModel ?? null;
|
|
2569
2459
|
}
|
|
2570
2460
|
function stringifyError(error) {
|
|
2571
2461
|
return (error instanceof Error ? error.message : String(error)).replace(/\s+/g, " ").trim();
|
|
2572
2462
|
}
|
|
2573
|
-
async function testProviderConnection(configPath,
|
|
2463
|
+
async function testProviderConnection(configPath, providerId, patch, providerManager) {
|
|
2574
2464
|
const config = loadConfigOrDefault(configPath);
|
|
2575
|
-
const provider =
|
|
2465
|
+
const provider = config.providers[providerId];
|
|
2576
2466
|
if (!provider) return null;
|
|
2577
|
-
const
|
|
2467
|
+
const providerType = resolveProviderType(providerId, provider);
|
|
2468
|
+
const spec = findServerBuiltinProviderByName(providerType ?? "");
|
|
2578
2469
|
const hasApiKeyPatch = Object.prototype.hasOwnProperty.call(patch, "apiKey");
|
|
2579
2470
|
const providedApiKey = normalizeOptionalString(patch.apiKey);
|
|
2580
2471
|
const currentApiKey = normalizeOptionalString(provider.apiKey);
|
|
@@ -2584,32 +2475,31 @@ async function testProviderConnection(configPath, providerName, patch, providerM
|
|
|
2584
2475
|
const currentApiBase = normalizeOptionalString(provider.apiBase);
|
|
2585
2476
|
const apiBase = hasApiBasePatch ? patchedApiBase ?? spec?.defaultApiBase ?? null : currentApiBase ?? spec?.defaultApiBase ?? null;
|
|
2586
2477
|
const extraHeaders = Object.prototype.hasOwnProperty.call(patch, "extraHeaders") ? normalizeHeaders(patch.extraHeaders ?? null) : normalizeHeaders(provider.extraHeaders ?? null);
|
|
2587
|
-
const
|
|
2588
|
-
const wireApi = spec?.supportsWireApi || isCustom ? patch.wireApi ?? provider.wireApi ?? spec?.defaultWireApi ?? "auto" : null;
|
|
2478
|
+
const wireApi = spec?.supportsWireApi || !spec ? patch.wireApi ?? provider.wireApi ?? spec?.defaultWireApi ?? "auto" : null;
|
|
2589
2479
|
if (!apiKey && !spec?.isLocal) return {
|
|
2590
2480
|
success: false,
|
|
2591
|
-
provider:
|
|
2481
|
+
provider: providerId,
|
|
2592
2482
|
latencyMs: 0,
|
|
2593
2483
|
message: "API key is required before testing the connection."
|
|
2594
2484
|
};
|
|
2595
|
-
const model = resolveTestModel(config,
|
|
2485
|
+
const model = resolveTestModel(config, providerId, normalizeOptionalString(patch.model), provider, spec ?? void 0);
|
|
2596
2486
|
if (!model) return {
|
|
2597
2487
|
success: false,
|
|
2598
|
-
provider:
|
|
2488
|
+
provider: providerId,
|
|
2599
2489
|
latencyMs: 0,
|
|
2600
2490
|
message: "No test model found. Configure provider models or set a default model for this provider, then try again."
|
|
2601
2491
|
};
|
|
2602
2492
|
const startedAtMs = Date.now();
|
|
2603
2493
|
if (!providerManager) return {
|
|
2604
2494
|
success: false,
|
|
2605
|
-
provider:
|
|
2495
|
+
provider: providerId,
|
|
2606
2496
|
model,
|
|
2607
2497
|
latencyMs: Date.now() - startedAtMs,
|
|
2608
2498
|
message: "Provider manager is unavailable."
|
|
2609
2499
|
};
|
|
2610
2500
|
try {
|
|
2611
2501
|
await providerManager.testConnection({
|
|
2612
|
-
providerName,
|
|
2502
|
+
providerName: providerType,
|
|
2613
2503
|
apiKey,
|
|
2614
2504
|
apiBase,
|
|
2615
2505
|
defaultModel: model,
|
|
@@ -2623,7 +2513,7 @@ async function testProviderConnection(configPath, providerName, patch, providerM
|
|
|
2623
2513
|
});
|
|
2624
2514
|
return {
|
|
2625
2515
|
success: true,
|
|
2626
|
-
provider:
|
|
2516
|
+
provider: providerId,
|
|
2627
2517
|
model,
|
|
2628
2518
|
latencyMs: Date.now() - startedAtMs,
|
|
2629
2519
|
message: "Connection test passed."
|
|
@@ -2631,7 +2521,7 @@ async function testProviderConnection(configPath, providerName, patch, providerM
|
|
|
2631
2521
|
} catch (error) {
|
|
2632
2522
|
return {
|
|
2633
2523
|
success: false,
|
|
2634
|
-
provider:
|
|
2524
|
+
provider: providerId,
|
|
2635
2525
|
model,
|
|
2636
2526
|
latencyMs: Date.now() - startedAtMs,
|
|
2637
2527
|
message: stringifyError(error) || "Connection test failed."
|
|
@@ -3447,6 +3337,8 @@ var McpMarketplaceController = class {
|
|
|
3447
3337
|
//#endregion
|
|
3448
3338
|
//#region src/features/marketplace/utils/marketplace-installed.utils.ts
|
|
3449
3339
|
const getWorkspacePathFromConfig = NextclawCore.getWorkspacePathFromConfig;
|
|
3340
|
+
const MARKETPLACE_INSTALL_STATE_FILE = ".nextclaw-install.json";
|
|
3341
|
+
const LEGACY_MARKETPLACE_INSTALL_STATE_FILE = ".nextclaw-marketplace.json";
|
|
3450
3342
|
function createSkillsLoader(workspace) {
|
|
3451
3343
|
const ctor = NextclawCore.SkillsLoader;
|
|
3452
3344
|
if (!ctor) return null;
|
|
@@ -3460,14 +3352,21 @@ function collectInstalledSkillRecords(options) {
|
|
|
3460
3352
|
const metadata = skillsLoader?.getSkillMetadata?.(skill);
|
|
3461
3353
|
const description = readNonEmptyString(metadata?.description);
|
|
3462
3354
|
const descriptionZh = readNonEmptyString(metadata?.description_zh) ?? readNonEmptyString(metadata?.descriptionZh) ?? readNonEmptyString(MARKETPLACE_ZH_COPY_BY_SLUG[skill.name]?.description);
|
|
3355
|
+
const marketplaceState = readMarketplaceSkillInstallState(dirname(skill.path));
|
|
3356
|
+
const origin = marketplaceState ? "marketplace" : void 0;
|
|
3357
|
+
const catalogSlug = marketplaceState?.slug;
|
|
3358
|
+
const installedAt = marketplaceState?.installedAt;
|
|
3463
3359
|
return {
|
|
3464
3360
|
type: "skill",
|
|
3465
3361
|
id: skill.name,
|
|
3466
3362
|
spec: skill.name,
|
|
3467
3363
|
label: skill.name,
|
|
3468
|
-
|
|
3469
|
-
|
|
3364
|
+
description,
|
|
3365
|
+
descriptionZh,
|
|
3470
3366
|
source: skill.source,
|
|
3367
|
+
origin,
|
|
3368
|
+
catalogSlug,
|
|
3369
|
+
installedAt,
|
|
3471
3370
|
enabled,
|
|
3472
3371
|
runtimeStatus: enabled ? "enabled" : "disabled"
|
|
3473
3372
|
};
|
|
@@ -3477,6 +3376,20 @@ function collectInstalledSkillRecords(options) {
|
|
|
3477
3376
|
records
|
|
3478
3377
|
};
|
|
3479
3378
|
}
|
|
3379
|
+
function readMarketplaceSkillInstallState(destinationDir) {
|
|
3380
|
+
const statePath = [MARKETPLACE_INSTALL_STATE_FILE, LEGACY_MARKETPLACE_INSTALL_STATE_FILE].map((file) => join(destinationDir, file)).find((path) => existsSync(path));
|
|
3381
|
+
if (!statePath) return null;
|
|
3382
|
+
try {
|
|
3383
|
+
const parsed = JSON.parse(readFileSync(statePath, "utf8"));
|
|
3384
|
+
if (parsed.schemaVersion !== 1 || parsed.type !== "skill" || parsed.source !== "marketplace" || typeof parsed.slug !== "string") return null;
|
|
3385
|
+
return {
|
|
3386
|
+
slug: parsed.slug,
|
|
3387
|
+
installedAt: typeof parsed.installedAt === "string" ? parsed.installedAt : void 0
|
|
3388
|
+
};
|
|
3389
|
+
} catch {
|
|
3390
|
+
return null;
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3480
3393
|
function collectSkillMarketplaceInstalledView(options) {
|
|
3481
3394
|
const installed = collectInstalledSkillRecords(options);
|
|
3482
3395
|
return {
|
|
@@ -3531,9 +3444,24 @@ async function manageMarketplaceSkill(params) {
|
|
|
3531
3444
|
const { body, options } = params;
|
|
3532
3445
|
const action = body.action;
|
|
3533
3446
|
const targetId = typeof body.id === "string" && body.id.trim().length > 0 ? body.id.trim() : typeof body.spec === "string" && body.spec.trim().length > 0 ? body.spec.trim() : "";
|
|
3534
|
-
if (action !== "uninstall" || !targetId) throw new Error("INVALID_BODY:skill manage requires uninstall action and non-empty id/spec");
|
|
3447
|
+
if (action !== "update" && action !== "uninstall" || !targetId) throw new Error("INVALID_BODY:skill manage requires update/uninstall action and non-empty id/spec");
|
|
3535
3448
|
const installer = options.marketplace?.installer;
|
|
3536
3449
|
if (!installer) throw new Error("NOT_AVAILABLE:marketplace installer is not configured");
|
|
3450
|
+
if (action === "update") {
|
|
3451
|
+
if (!installer.updateSkill) throw new Error("NOT_AVAILABLE:skill update is not configured");
|
|
3452
|
+
const result = await installer.updateSkill({
|
|
3453
|
+
slug: targetId,
|
|
3454
|
+
force: body.force
|
|
3455
|
+
});
|
|
3456
|
+
emitConfigUpdated(options, "skills");
|
|
3457
|
+
return {
|
|
3458
|
+
type: "skill",
|
|
3459
|
+
action,
|
|
3460
|
+
id: targetId,
|
|
3461
|
+
message: result.message,
|
|
3462
|
+
output: result.output
|
|
3463
|
+
};
|
|
3464
|
+
}
|
|
3537
3465
|
if (!installer.uninstallSkill) throw new Error("NOT_AVAILABLE:skill uninstall is not configured");
|
|
3538
3466
|
const result = await installer.uninstallSkill(targetId);
|
|
3539
3467
|
emitConfigUpdated(options, "skills");
|
|
@@ -4402,6 +4330,13 @@ var ServiceAppsRoutesController = class {
|
|
|
4402
4330
|
return this.handleServiceAppError(c, error);
|
|
4403
4331
|
}
|
|
4404
4332
|
};
|
|
4333
|
+
deleteServiceApp = async (c) => {
|
|
4334
|
+
try {
|
|
4335
|
+
return c.json(ok(await this.params.serviceAppManager.deleteServiceApp(c.req.param("appId"))));
|
|
4336
|
+
} catch (error) {
|
|
4337
|
+
return this.handleServiceAppError(c, error);
|
|
4338
|
+
}
|
|
4339
|
+
};
|
|
4405
4340
|
requireBridgeSession = (c) => {
|
|
4406
4341
|
const token = c.req.raw.headers.get(PANEL_BRIDGE_SESSION_HEADER)?.trim();
|
|
4407
4342
|
if (!token) throw new Error("panel app bridge session is required");
|
|
@@ -4825,182 +4760,8 @@ var UiRouteRegistry = class {
|
|
|
4825
4760
|
ncpAsset.getAssetContent
|
|
4826
4761
|
]]);
|
|
4827
4762
|
};
|
|
4828
|
-
|
|
4829
|
-
const {
|
|
4830
|
-
this.mountRoutes([
|
|
4831
|
-
[
|
|
4832
|
-
"get",
|
|
4833
|
-
"/api/health",
|
|
4834
|
-
app.health
|
|
4835
|
-
],
|
|
4836
|
-
[
|
|
4837
|
-
"get",
|
|
4838
|
-
"/api/app/meta",
|
|
4839
|
-
app.appMeta
|
|
4840
|
-
],
|
|
4841
|
-
[
|
|
4842
|
-
"get",
|
|
4843
|
-
"/api/runtime/bootstrap-status",
|
|
4844
|
-
app.bootstrapStatus
|
|
4845
|
-
],
|
|
4846
|
-
[
|
|
4847
|
-
"get",
|
|
4848
|
-
"/api/auth/status",
|
|
4849
|
-
auth.getStatus
|
|
4850
|
-
],
|
|
4851
|
-
[
|
|
4852
|
-
"post",
|
|
4853
|
-
"/api/auth/setup",
|
|
4854
|
-
auth.setup
|
|
4855
|
-
],
|
|
4856
|
-
[
|
|
4857
|
-
"post",
|
|
4858
|
-
"/api/auth/login",
|
|
4859
|
-
auth.login
|
|
4860
|
-
],
|
|
4861
|
-
[
|
|
4862
|
-
"post",
|
|
4863
|
-
"/api/auth/logout",
|
|
4864
|
-
auth.logout
|
|
4865
|
-
],
|
|
4866
|
-
[
|
|
4867
|
-
"put",
|
|
4868
|
-
"/api/auth/password",
|
|
4869
|
-
auth.updatePassword
|
|
4870
|
-
],
|
|
4871
|
-
[
|
|
4872
|
-
"put",
|
|
4873
|
-
"/api/auth/enabled",
|
|
4874
|
-
auth.updateEnabled
|
|
4875
|
-
],
|
|
4876
|
-
[
|
|
4877
|
-
"post",
|
|
4878
|
-
"/api/auth/bridge",
|
|
4879
|
-
auth.issueBridgeSession
|
|
4880
|
-
],
|
|
4881
|
-
[
|
|
4882
|
-
"get",
|
|
4883
|
-
"/api/agents",
|
|
4884
|
-
agents.listAgents
|
|
4885
|
-
],
|
|
4886
|
-
[
|
|
4887
|
-
"post",
|
|
4888
|
-
"/api/agents",
|
|
4889
|
-
agents.createAgent
|
|
4890
|
-
],
|
|
4891
|
-
[
|
|
4892
|
-
"put",
|
|
4893
|
-
"/api/agents/:agentId",
|
|
4894
|
-
agents.updateAgent
|
|
4895
|
-
],
|
|
4896
|
-
[
|
|
4897
|
-
"delete",
|
|
4898
|
-
"/api/agents/:agentId",
|
|
4899
|
-
agents.deleteAgent
|
|
4900
|
-
],
|
|
4901
|
-
[
|
|
4902
|
-
"get",
|
|
4903
|
-
"/api/agents/:agentId/avatar",
|
|
4904
|
-
agents.getAgentAvatar
|
|
4905
|
-
]
|
|
4906
|
-
]);
|
|
4907
|
-
this.mountRoutes([
|
|
4908
|
-
[
|
|
4909
|
-
"get",
|
|
4910
|
-
"/api/config",
|
|
4911
|
-
config.getConfig
|
|
4912
|
-
],
|
|
4913
|
-
[
|
|
4914
|
-
"get",
|
|
4915
|
-
"/api/config/meta",
|
|
4916
|
-
config.getConfigMeta
|
|
4917
|
-
],
|
|
4918
|
-
[
|
|
4919
|
-
"get",
|
|
4920
|
-
"/api/config/schema",
|
|
4921
|
-
config.getConfigSchema
|
|
4922
|
-
],
|
|
4923
|
-
[
|
|
4924
|
-
"put",
|
|
4925
|
-
"/api/config/model",
|
|
4926
|
-
config.updateConfigModel
|
|
4927
|
-
],
|
|
4928
|
-
[
|
|
4929
|
-
"put",
|
|
4930
|
-
"/api/config/search",
|
|
4931
|
-
config.updateConfigSearch
|
|
4932
|
-
],
|
|
4933
|
-
[
|
|
4934
|
-
"put",
|
|
4935
|
-
"/api/config/providers/:provider",
|
|
4936
|
-
config.updateProvider
|
|
4937
|
-
],
|
|
4938
|
-
[
|
|
4939
|
-
"post",
|
|
4940
|
-
"/api/config/providers",
|
|
4941
|
-
config.createProvider
|
|
4942
|
-
],
|
|
4943
|
-
[
|
|
4944
|
-
"delete",
|
|
4945
|
-
"/api/config/providers/:provider",
|
|
4946
|
-
config.deleteProvider
|
|
4947
|
-
],
|
|
4948
|
-
[
|
|
4949
|
-
"post",
|
|
4950
|
-
"/api/config/providers/:provider/test",
|
|
4951
|
-
config.testProviderConnection
|
|
4952
|
-
],
|
|
4953
|
-
[
|
|
4954
|
-
"post",
|
|
4955
|
-
"/api/config/providers/:provider/auth/start",
|
|
4956
|
-
config.startProviderAuth
|
|
4957
|
-
],
|
|
4958
|
-
[
|
|
4959
|
-
"post",
|
|
4960
|
-
"/api/config/providers/:provider/auth/poll",
|
|
4961
|
-
config.pollProviderAuth
|
|
4962
|
-
],
|
|
4963
|
-
[
|
|
4964
|
-
"post",
|
|
4965
|
-
"/api/config/providers/:provider/auth/import-cli",
|
|
4966
|
-
config.importProviderAuthFromCli
|
|
4967
|
-
],
|
|
4968
|
-
[
|
|
4969
|
-
"put",
|
|
4970
|
-
"/api/config/channels/:channel",
|
|
4971
|
-
config.updateChannel
|
|
4972
|
-
],
|
|
4973
|
-
[
|
|
4974
|
-
"post",
|
|
4975
|
-
"/api/config/channels/:channel/auth/start",
|
|
4976
|
-
config.startChannelAuth
|
|
4977
|
-
],
|
|
4978
|
-
[
|
|
4979
|
-
"post",
|
|
4980
|
-
"/api/config/channels/:channel/auth/connect",
|
|
4981
|
-
config.connectChannelAuth
|
|
4982
|
-
],
|
|
4983
|
-
[
|
|
4984
|
-
"post",
|
|
4985
|
-
"/api/config/channels/:channel/auth/poll",
|
|
4986
|
-
config.pollChannelAuth
|
|
4987
|
-
],
|
|
4988
|
-
[
|
|
4989
|
-
"put",
|
|
4990
|
-
"/api/config/secrets",
|
|
4991
|
-
config.updateSecrets
|
|
4992
|
-
],
|
|
4993
|
-
[
|
|
4994
|
-
"put",
|
|
4995
|
-
"/api/config/runtime",
|
|
4996
|
-
config.updateRuntime
|
|
4997
|
-
],
|
|
4998
|
-
[
|
|
4999
|
-
"post",
|
|
5000
|
-
"/api/config/actions/:actionId/execute",
|
|
5001
|
-
config.executeAction
|
|
5002
|
-
]
|
|
5003
|
-
]);
|
|
4763
|
+
mountResourceRoutes = () => {
|
|
4764
|
+
const { ncpSession, panelApps, serviceApps, serverPath } = this.controllers;
|
|
5004
4765
|
this.mountRoutes([
|
|
5005
4766
|
[
|
|
5006
4767
|
"get",
|
|
@@ -5122,6 +4883,11 @@ var UiRouteRegistry = class {
|
|
|
5122
4883
|
"/api/service-apps/:appId",
|
|
5123
4884
|
serviceApps.getServiceApp
|
|
5124
4885
|
],
|
|
4886
|
+
[
|
|
4887
|
+
"delete",
|
|
4888
|
+
"/api/service-apps/:appId",
|
|
4889
|
+
serviceApps.deleteServiceApp
|
|
4890
|
+
],
|
|
5125
4891
|
[
|
|
5126
4892
|
"get",
|
|
5127
4893
|
"/api/service-actions",
|
|
@@ -5163,6 +4929,194 @@ var UiRouteRegistry = class {
|
|
|
5163
4929
|
serverPath.read
|
|
5164
4930
|
]
|
|
5165
4931
|
]);
|
|
4932
|
+
};
|
|
4933
|
+
register = () => {
|
|
4934
|
+
const { agents, app, auth, config, cron, ncpAsset, remote, runtimeControl, runtimeUpdate } = this.controllers;
|
|
4935
|
+
this.mountRoutes([
|
|
4936
|
+
[
|
|
4937
|
+
"get",
|
|
4938
|
+
"/api/health",
|
|
4939
|
+
app.health
|
|
4940
|
+
],
|
|
4941
|
+
[
|
|
4942
|
+
"get",
|
|
4943
|
+
"/api/app/meta",
|
|
4944
|
+
app.appMeta
|
|
4945
|
+
],
|
|
4946
|
+
[
|
|
4947
|
+
"get",
|
|
4948
|
+
"/api/runtime/bootstrap-status",
|
|
4949
|
+
app.bootstrapStatus
|
|
4950
|
+
],
|
|
4951
|
+
[
|
|
4952
|
+
"get",
|
|
4953
|
+
"/api/auth/status",
|
|
4954
|
+
auth.getStatus
|
|
4955
|
+
],
|
|
4956
|
+
[
|
|
4957
|
+
"post",
|
|
4958
|
+
"/api/auth/setup",
|
|
4959
|
+
auth.setup
|
|
4960
|
+
],
|
|
4961
|
+
[
|
|
4962
|
+
"post",
|
|
4963
|
+
"/api/auth/login",
|
|
4964
|
+
auth.login
|
|
4965
|
+
],
|
|
4966
|
+
[
|
|
4967
|
+
"post",
|
|
4968
|
+
"/api/auth/logout",
|
|
4969
|
+
auth.logout
|
|
4970
|
+
],
|
|
4971
|
+
[
|
|
4972
|
+
"put",
|
|
4973
|
+
"/api/auth/password",
|
|
4974
|
+
auth.updatePassword
|
|
4975
|
+
],
|
|
4976
|
+
[
|
|
4977
|
+
"put",
|
|
4978
|
+
"/api/auth/enabled",
|
|
4979
|
+
auth.updateEnabled
|
|
4980
|
+
],
|
|
4981
|
+
[
|
|
4982
|
+
"post",
|
|
4983
|
+
"/api/auth/bridge",
|
|
4984
|
+
auth.issueBridgeSession
|
|
4985
|
+
],
|
|
4986
|
+
[
|
|
4987
|
+
"get",
|
|
4988
|
+
"/api/agents",
|
|
4989
|
+
agents.listAgents
|
|
4990
|
+
],
|
|
4991
|
+
[
|
|
4992
|
+
"post",
|
|
4993
|
+
"/api/agents",
|
|
4994
|
+
agents.createAgent
|
|
4995
|
+
],
|
|
4996
|
+
[
|
|
4997
|
+
"put",
|
|
4998
|
+
"/api/agents/:agentId",
|
|
4999
|
+
agents.updateAgent
|
|
5000
|
+
],
|
|
5001
|
+
[
|
|
5002
|
+
"delete",
|
|
5003
|
+
"/api/agents/:agentId",
|
|
5004
|
+
agents.deleteAgent
|
|
5005
|
+
],
|
|
5006
|
+
[
|
|
5007
|
+
"get",
|
|
5008
|
+
"/api/agents/:agentId/avatar",
|
|
5009
|
+
agents.getAgentAvatar
|
|
5010
|
+
]
|
|
5011
|
+
]);
|
|
5012
|
+
this.mountRoutes([
|
|
5013
|
+
[
|
|
5014
|
+
"get",
|
|
5015
|
+
"/api/config",
|
|
5016
|
+
config.getConfig
|
|
5017
|
+
],
|
|
5018
|
+
[
|
|
5019
|
+
"get",
|
|
5020
|
+
"/api/config/meta",
|
|
5021
|
+
config.getConfigMeta
|
|
5022
|
+
],
|
|
5023
|
+
[
|
|
5024
|
+
"get",
|
|
5025
|
+
"/api/config/schema",
|
|
5026
|
+
config.getConfigSchema
|
|
5027
|
+
],
|
|
5028
|
+
[
|
|
5029
|
+
"get",
|
|
5030
|
+
"/api/providers",
|
|
5031
|
+
config.listProviders
|
|
5032
|
+
],
|
|
5033
|
+
[
|
|
5034
|
+
"get",
|
|
5035
|
+
"/api/provider-templates",
|
|
5036
|
+
config.listProviderTemplates
|
|
5037
|
+
],
|
|
5038
|
+
[
|
|
5039
|
+
"post",
|
|
5040
|
+
"/api/providers",
|
|
5041
|
+
config.createProvider
|
|
5042
|
+
],
|
|
5043
|
+
[
|
|
5044
|
+
"put",
|
|
5045
|
+
"/api/providers/:providerId",
|
|
5046
|
+
config.updateProvider
|
|
5047
|
+
],
|
|
5048
|
+
[
|
|
5049
|
+
"delete",
|
|
5050
|
+
"/api/providers/:providerId",
|
|
5051
|
+
config.deleteProvider
|
|
5052
|
+
],
|
|
5053
|
+
[
|
|
5054
|
+
"post",
|
|
5055
|
+
"/api/providers/:providerId/test",
|
|
5056
|
+
config.testProviderConnection
|
|
5057
|
+
],
|
|
5058
|
+
[
|
|
5059
|
+
"post",
|
|
5060
|
+
"/api/providers/:providerId/auth/start",
|
|
5061
|
+
config.startProviderAuth
|
|
5062
|
+
],
|
|
5063
|
+
[
|
|
5064
|
+
"post",
|
|
5065
|
+
"/api/providers/:providerId/auth/poll",
|
|
5066
|
+
config.pollProviderAuth
|
|
5067
|
+
],
|
|
5068
|
+
[
|
|
5069
|
+
"post",
|
|
5070
|
+
"/api/providers/:providerId/auth/import-cli",
|
|
5071
|
+
config.importProviderAuthFromCli
|
|
5072
|
+
],
|
|
5073
|
+
[
|
|
5074
|
+
"put",
|
|
5075
|
+
"/api/config/model",
|
|
5076
|
+
config.updateConfigModel
|
|
5077
|
+
],
|
|
5078
|
+
[
|
|
5079
|
+
"put",
|
|
5080
|
+
"/api/config/search",
|
|
5081
|
+
config.updateConfigSearch
|
|
5082
|
+
],
|
|
5083
|
+
[
|
|
5084
|
+
"put",
|
|
5085
|
+
"/api/config/channels/:channel",
|
|
5086
|
+
config.updateChannel
|
|
5087
|
+
],
|
|
5088
|
+
[
|
|
5089
|
+
"post",
|
|
5090
|
+
"/api/config/channels/:channel/auth/start",
|
|
5091
|
+
config.startChannelAuth
|
|
5092
|
+
],
|
|
5093
|
+
[
|
|
5094
|
+
"post",
|
|
5095
|
+
"/api/config/channels/:channel/auth/connect",
|
|
5096
|
+
config.connectChannelAuth
|
|
5097
|
+
],
|
|
5098
|
+
[
|
|
5099
|
+
"post",
|
|
5100
|
+
"/api/config/channels/:channel/auth/poll",
|
|
5101
|
+
config.pollChannelAuth
|
|
5102
|
+
],
|
|
5103
|
+
[
|
|
5104
|
+
"put",
|
|
5105
|
+
"/api/config/secrets",
|
|
5106
|
+
config.updateSecrets
|
|
5107
|
+
],
|
|
5108
|
+
[
|
|
5109
|
+
"put",
|
|
5110
|
+
"/api/config/runtime",
|
|
5111
|
+
config.updateRuntime
|
|
5112
|
+
],
|
|
5113
|
+
[
|
|
5114
|
+
"post",
|
|
5115
|
+
"/api/config/actions/:actionId/execute",
|
|
5116
|
+
config.executeAction
|
|
5117
|
+
]
|
|
5118
|
+
]);
|
|
5119
|
+
this.mountResourceRoutes();
|
|
5166
5120
|
this.mountNcpAgentRoutes(this.options.kernel, ncpAsset);
|
|
5167
5121
|
this.mountRoutes([
|
|
5168
5122
|
[
|
|
@@ -5317,7 +5271,7 @@ var UiRouteRegistry = class {
|
|
|
5317
5271
|
function createUiRouter(options, authServiceOverride) {
|
|
5318
5272
|
const app = new Hono();
|
|
5319
5273
|
const marketplaceBaseUrl = normalizeMarketplaceBaseUrl(options);
|
|
5320
|
-
const authService = authServiceOverride ?? options.authService ?? new UiAuthService(options.configPath);
|
|
5274
|
+
const authService = authServiceOverride ?? options.authService ?? new UiAuthService(options.kernel.accessManager ?? new AccessManager({ configPath: options.configPath }));
|
|
5321
5275
|
const controllers = createUiRouteControllers(options, authService, marketplaceBaseUrl);
|
|
5322
5276
|
app.notFound((c) => c.json(err("NOT_FOUND", "endpoint not found"), 404));
|
|
5323
5277
|
app.use("/api/*", async (c, next) => {
|
|
@@ -5431,7 +5385,7 @@ async function startUiServer(gateway) {
|
|
|
5431
5385
|
const app = new Hono();
|
|
5432
5386
|
app.use("/*", compress());
|
|
5433
5387
|
const corsPolicy = corsOrigins ?? DEFAULT_CORS_ORIGINS;
|
|
5434
|
-
const authService = new UiAuthService(gateway.configPath);
|
|
5388
|
+
const authService = new UiAuthService(gateway.kernel.accessManager ?? new AccessManager({ configPath: gateway.configPath }));
|
|
5435
5389
|
app.use("/api/*", async (c, next) => {
|
|
5436
5390
|
const allowOrigin = resolveAllowedCorsOrigin(c.req.header("origin")?.trim() ?? null, corsPolicy);
|
|
5437
5391
|
const allowHeaders = c.req.header("access-control-request-headers")?.trim() ?? null;
|
|
@@ -5479,6 +5433,6 @@ async function startUiServer(gateway) {
|
|
|
5479
5433
|
};
|
|
5480
5434
|
}
|
|
5481
5435
|
//#endregion
|
|
5482
|
-
export { ConfigRoutesController, PanelAppsRoutesController, RuntimeControlRoutesController, ServiceAppsRoutesController, buildConfigMeta, buildConfigSchemaView, buildConfigView,
|
|
5436
|
+
export { ConfigRoutesController, PanelAppsRoutesController, RuntimeControlRoutesController, ServiceAppsRoutesController, buildConfigMeta, buildConfigSchemaView, buildConfigView, buildProviderTemplatesView, buildProvidersView, createProvider, createUiRouter, deleteProvider, ensureUiBridgeSecret, executeConfigAction, getUiBridgeSecretPath, loadConfigOrDefault, readUiBridgeSecret, startUiServer, testProviderConnection, updateChannel, updateModel, updateProvider, updateRuntime, updateSearch, updateSecrets };
|
|
5483
5437
|
|
|
5484
5438
|
//# sourceMappingURL=index.js.map
|