@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.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, scryptSync, timingSafeEqual } from "node:crypto";
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/services/ui-auth.service.ts
176
+ //#region src/features/auth/utils/access-cookie.utils.ts
177
177
  const SESSION_COOKIE_NAME = "nextclaw_ui_session";
178
- const PASSWORD_MIN_LENGTH = 8;
179
- function normalizeUsername(value) {
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
- const key = rawKey?.trim();
188
- if (!key) continue;
189
- cookies[key] = decodeURIComponent(rawValue.join("=").trim());
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 cookies;
187
+ return null;
192
188
  }
193
- function buildSetCookie(params) {
194
- const { value, secure, maxAgeSeconds, expires } = params;
189
+ function buildAccessLoginCookie(params) {
190
+ const { expiresAt, secure, token } = params;
195
191
  const parts = [
196
- `${SESSION_COOKIE_NAME}=${encodeURIComponent(value)}`,
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 (typeof maxAgeSeconds === "number") parts.push(`Max-Age=${Math.max(0, Math.trunc(maxAgeSeconds))}`);
203
- if (expires) parts.push(`Expires=${expires}`);
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
- function hashPassword(password, salt) {
215
- return scryptSync(password, salt, 64).toString("hex");
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
- sessions = /* @__PURE__ */ new Map();
236
- constructor(configPath) {
237
- this.configPath = configPath;
230
+ constructor(accessManager) {
231
+ this.accessManager = accessManager;
238
232
  }
239
- loadCurrentConfig = () => {
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
- const auth = this.readAuthConfig();
267
- if (!auth.enabled || !this.isConfigured(auth)) return true;
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
- const auth = this.readAuthConfig();
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
- const sessionId = this.getSessionIdFromCookieHeader(rawCookieHeader);
278
- return Boolean(this.getValidSession(sessionId, username));
241
+ return this.accessManager.authenticateSession(readAccessSessionTokenFromCookieHeader(rawCookieHeader)) !== null;
279
242
  };
280
243
  getStatus = (request) => {
281
- const auth = this.readAuthConfig();
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
- createSession = (username) => {
293
- const sessionId = randomUUID();
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
- clearAllSessions = () => {
302
- this.sessions.clear();
249
+ login = (request, payload) => {
250
+ return this.toCookieResult(request, this.accessManager.loginWithPassword(payload));
303
251
  };
304
- deleteRequestSession = (request) => {
305
- const sessionId = this.getSessionIdFromCookieHeader(request.headers.get("cookie"));
306
- if (!sessionId) return;
307
- this.sessions.delete(sessionId);
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
- buildLoginCookie = (request, sessionId) => {
310
- return buildSetCookie({
311
- value: sessionId,
312
- secure: resolveSecureRequest(request.url, request.headers.get("x-forwarded-proto"))
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 auth = this.readAuthConfig();
317
- if (!auth.enabled || !this.isConfigured(auth)) return null;
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 buildSetCookie({
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
- setup = (request, payload) => {
332
- const config = this.loadCurrentConfig();
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
- login = (request, payload) => {
358
- const auth = this.readAuthConfig();
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
- enabled: true,
366
- configured: true,
367
- authenticated: true,
368
- username
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
- logout = (request) => {
374
- this.deleteRequestSession(request);
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
- updateEnabled = (request, payload) => {
407
- const config = this.loadCurrentConfig();
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: ["minimax-portal/MiniMax-M2.5", "minimax-portal/MiniMax-M2.5-highspeed"],
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]) providers[provider] = createDefaultProviderConfigFromSpec(findServerBuiltinProviderByName(provider));
1059
+ if (!providers[provider]) return;
1213
1060
  const target = providers[provider];
1214
1061
  target.apiKey = accessToken;
1215
- if (!target.apiBase && defaultApiBase) target.apiBase = defaultApiBase;
1062
+ if (defaultApiBase) target.apiBase = defaultApiBase;
1216
1063
  saveConfig(ConfigSchema.parse(config), configPath);
1217
1064
  }
1218
- async function startProviderAuth(configPath, providerName, options) {
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 spec = findServerBuiltinProviderByName(providerName);
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
- provider: providerName,
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: providerName,
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.provider !== providerName || session.configPath !== configPath) return null;
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: providerName,
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: providerName,
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: providerName,
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: providerName,
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: providerName,
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: providerName,
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: providerName,
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: providerName,
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: providerName,
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: providerName,
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: providerName,
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: providerName,
1307
+ provider: providerId,
1442
1308
  accessToken,
1443
1309
  defaultApiBase: session.defaultApiBase
1444
1310
  });
1445
1311
  authSessions.delete(sessionId);
1446
1312
  return {
1447
- provider: providerName,
1313
+ provider: providerId,
1448
1314
  status: "authorized"
1449
1315
  };
1450
1316
  }
1451
- async function importProviderAuthFromCli(configPath, providerName) {
1452
- const spec = findServerBuiltinProviderByName(providerName);
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: providerName,
1346
+ provider: providerId,
1479
1347
  accessToken,
1480
1348
  defaultApiBase: spec.defaultApiBase
1481
1349
  });
1482
1350
  return {
1483
- provider: providerName,
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 provider = c.req.param("provider");
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, provider, body.data);
1583
- if (!result) return c.json(err("NOT_FOUND", `unknown provider: ${provider}`), 404);
1584
- await this.publishConfigUpdates([`providers.${provider}`]);
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 = createCustomProvider(this.options.configPath, body.data);
1591
- await this.publishConfigUpdates([`providers.${result.name}`]);
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
- name: result.name,
1469
+ providerId: result.providerId,
1594
1470
  provider: result.provider
1595
1471
  }));
1596
1472
  };
1597
1473
  deleteProvider = async (c) => {
1598
- const provider = c.req.param("provider");
1599
- if (deleteCustomProvider(this.options.configPath, provider) === null) return c.json(err("NOT_FOUND", `custom provider not found: ${provider}`), 404);
1600
- await this.publishConfigUpdates([`providers.${provider}`]);
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
- provider
1479
+ providerId
1604
1480
  }));
1605
1481
  };
1606
1482
  testProviderConnection = async (c) => {
1607
- const provider = c.req.param("provider");
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("provider");
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("provider");
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("provider");
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 resolveProviderDisplayName(providerName, provider, spec) {
2039
- const configDisplayName = normalizeOptionalDisplayName(provider?.displayName);
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 ensureProviderConfig(config, providerName) {
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
- const existing = providers[providerName];
2055
- if (existing) return existing;
2056
- if (isCustomProviderName(providerName)) return null;
2057
- const spec = findServerBuiltinProviderByName(providerName);
2058
- if (!spec) return null;
2059
- const created = createDefaultProviderConfigFromSpec(spec);
2060
- providers[providerName] = created;
2061
- return created;
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, providerName, uiHints, spec) {
2227
- const apiKeyRefSet = hasSecretRef(config, `providers.${providerName}.apiKey`);
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.${providerName}.extraHeaders`, uiHints) : null;
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: resolveProviderDisplayName(providerName, provider, spec),
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) || isCustomProviderName(providerName)) view.wireApi = provider.wireApi ?? spec?.defaultWireApi ?? "auto";
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 [name, provider] of Object.entries(config.providers)) providers[name] = toProviderView(config, provider, name, uiHints, findServerBuiltinProviderByName(name));
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
- const configProviders = config.providers;
2327
- const builtinProviders = BUILTIN_PROVIDERS.map((spec) => {
2328
- const providerConfig = configProviders[spec.name];
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
- name: spec.name,
2331
- displayName: resolveProviderDisplayName(spec.name, providerConfig, spec),
2332
- isCustom: false,
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.name);
2361
- const rightRank = PREFERRED_PROVIDER_ORDER_INDEX.get(right.name);
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.name.localeCompare(right.name);
2366
- });
2367
- return {
2368
- providers: [...listCustomProviderNames(config).sort((left, right) => left.localeCompare(right, void 0, {
2369
- numeric: true,
2370
- sensitivity: "base"
2371
- })).map((name) => {
2372
- const providerConfig = configProviders[name];
2373
- const displayName = resolveProviderDisplayName(name, providerConfig);
2374
- return {
2375
- name,
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, providerName, patch) {
2340
+ function updateProvider(configPath, providerId, patch) {
2460
2341
  const config = loadConfigOrDefault(configPath);
2461
- const provider = ensureProviderConfig(config, providerName);
2342
+ const provider = config.providers[providerId];
2462
2343
  if (!provider) return null;
2463
- const spec = findServerBuiltinProviderByName(providerName);
2464
- const isCustom = isCustomProviderName(providerName);
2465
- if (Object.prototype.hasOwnProperty.call(patch, "displayName") && isCustom) provider.displayName = normalizeOptionalDisplayName(patch.displayName) ?? "";
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.${providerName}.apiKey`);
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 || isCustom)) provider.wireApi = patch.wireApi ?? spec?.defaultWireApi ?? "auto";
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[providerName];
2480
- return toProviderView(next, updated, providerName, uiHints, spec ?? void 0);
2361
+ const updated = next.providers[providerId];
2362
+ return toProviderView(next, updated, providerId, uiHints, spec ?? void 0);
2481
2363
  }
2482
- function createCustomProvider(configPath, patch = {}) {
2364
+ function createProvider(configPath, patch = {}) {
2483
2365
  const config = loadConfigOrDefault(configPath);
2484
- const providerName = findNextCustomProviderName(config);
2485
2366
  const providers = config.providers;
2486
- const generatedDisplayName = resolveCustomProviderFallbackDisplayName(providerName);
2487
- providers[providerName] = {
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[providerName];
2389
+ const created = next.providers[providerId];
2501
2390
  return {
2502
- name: providerName,
2503
- provider: toProviderView(next, created, providerName, uiHints)
2391
+ providerId,
2392
+ provider: toProviderView(next, created, providerId, uiHints, spec)
2504
2393
  };
2505
2394
  }
2506
- function deleteCustomProvider(configPath, providerName) {
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[providerName]) return null;
2511
- delete providers[providerName];
2512
- config.secrets.refs = clearSecretRefsByPrefix(config.secrets.refs, `providers.${providerName}`);
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 resolveTestModel(config, providerName, requestedModel, provider, spec) {
2553
- if (requestedModel) {
2554
- if (isCustomProviderName(providerName)) {
2555
- const prefix = `${providerName}/`;
2556
- if (requestedModel.startsWith(prefix)) return requestedModel.slice(prefix.length) || null;
2557
- }
2558
- return requestedModel;
2559
- }
2560
- const providerModels = normalizeModelList(provider.models ?? []).map((modelId) => buildScopedProviderModel(providerName, modelId, spec)).filter((modelId) => modelId.length > 0);
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 === providerName) return defaultModel;
2455
+ if (!routedProvider || routedProvider === providerId) return spec ? defaultModel.replace(`${providerId}/`, `${spec.name}/`) : stripProviderIdPrefix(providerId, defaultModel);
2566
2456
  }
2567
- if (isCustomProviderName(providerName)) return null;
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, providerName, patch, providerManager) {
2463
+ async function testProviderConnection(configPath, providerId, patch, providerManager) {
2574
2464
  const config = loadConfigOrDefault(configPath);
2575
- const provider = ensureProviderConfig(config, providerName);
2465
+ const provider = config.providers[providerId];
2576
2466
  if (!provider) return null;
2577
- const spec = findServerBuiltinProviderByName(providerName);
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 isCustom = isCustomProviderName(providerName);
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: providerName,
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, providerName, normalizeOptionalString(patch.model), provider, spec ?? void 0);
2485
+ const model = resolveTestModel(config, providerId, normalizeOptionalString(patch.model), provider, spec ?? void 0);
2596
2486
  if (!model) return {
2597
2487
  success: false,
2598
- provider: providerName,
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: providerName,
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: providerName,
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: providerName,
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
- ...description ? { description } : {},
3469
- ...descriptionZh ? { descriptionZh } : {},
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
- register = () => {
4829
- const { agents, app, auth, config, cron, ncpAsset, ncpSession, panelApps, serviceApps, remote, runtimeControl, runtimeUpdate, serverPath } = this.controllers;
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, createCustomProvider, createUiRouter, deleteCustomProvider, ensureUiBridgeSecret, executeConfigAction, getUiBridgeSecretPath, loadConfigOrDefault, readUiBridgeSecret, startUiServer, testProviderConnection, updateChannel, updateModel, updateProvider, updateRuntime, updateSearch, updateSecrets };
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