@nextclaw/server 0.5.21 → 0.5.23

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 CHANGED
@@ -20,12 +20,24 @@ type ProviderConfigView = {
20
20
  apiBase?: string | null;
21
21
  extraHeaders?: Record<string, string> | null;
22
22
  wireApi?: "auto" | "chat" | "responses" | null;
23
+ models?: string[];
23
24
  };
24
25
  type ProviderConfigUpdate = {
25
26
  apiKey?: string | null;
26
27
  apiBase?: string | null;
27
28
  extraHeaders?: Record<string, string> | null;
28
29
  wireApi?: "auto" | "chat" | "responses" | null;
30
+ models?: string[] | null;
31
+ };
32
+ type ProviderConnectionTestRequest = ProviderConfigUpdate & {
33
+ model?: string | null;
34
+ };
35
+ type ProviderConnectionTestResult = {
36
+ success: boolean;
37
+ provider: string;
38
+ model?: string;
39
+ latencyMs: number;
40
+ message: string;
29
41
  };
30
42
  type AgentProfileView = {
31
43
  id: string;
@@ -276,11 +288,13 @@ type ConfigView = {
276
288
  type ProviderSpecView = {
277
289
  name: string;
278
290
  displayName?: string;
291
+ modelPrefix?: string;
279
292
  keywords: string[];
280
293
  envKey: string;
281
294
  isGateway?: boolean;
282
295
  isLocal?: boolean;
283
296
  defaultApiBase?: string;
297
+ defaultModels?: string[];
284
298
  supportsWireApi?: boolean;
285
299
  wireApiOptions?: Array<"auto" | "chat" | "responses">;
286
300
  defaultWireApi?: "auto" | "chat" | "responses";
@@ -578,6 +592,7 @@ declare function updateModel(configPath: string, patch: {
578
592
  maxTokens?: number;
579
593
  }): ConfigView;
580
594
  declare function updateProvider(configPath: string, providerName: string, patch: ProviderConfigUpdate): ProviderConfigView | null;
595
+ declare function testProviderConnection(configPath: string, providerName: string, patch: ProviderConnectionTestRequest): Promise<ProviderConnectionTestResult | null>;
581
596
  declare function updateChannel(configPath: string, channelName: string, patch: Record<string, unknown>): Record<string, unknown> | null;
582
597
  declare function listSessions(configPath: string, query?: {
583
598
  q?: string;
@@ -590,4 +605,4 @@ declare function deleteSession(configPath: string, key: string): boolean;
590
605
  declare function updateRuntime(configPath: string, patch: RuntimeConfigUpdate): Pick<ConfigView, "agents" | "bindings" | "session">;
591
606
  declare function updateSecrets(configPath: string, patch: SecretsConfigUpdate): SecretsView;
592
607
 
593
- export { type AgentBindingView, type AgentProfileView, type ApiError, type ApiResponse, type BindingPeerView, type ChannelSpecView, type ChatTurnRequest, type ChatTurnResult, type ChatTurnStreamEvent, type ChatTurnView, type ConfigActionExecuteRequest, type ConfigActionExecuteResult, type ConfigActionManifest, type ConfigActionType, type ConfigMetaView, type ConfigSchemaResponse, type ConfigUiHint, type ConfigUiHints, type ConfigView, type CronActionResult, type CronEnableRequest, type CronJobStateView, type CronJobView, type CronListView, type CronPayloadView, type CronRunRequest, type CronScheduleView, type MarketplaceApiConfig, type MarketplaceInstallKind, type MarketplaceInstallSkillParams, type MarketplaceInstallSpec, type MarketplaceInstalledRecord, type MarketplaceInstalledView, type MarketplaceInstaller, type MarketplaceItemSummary, type MarketplaceItemType, type MarketplaceItemView, type MarketplaceListView, type MarketplacePluginInstallRequest, type MarketplacePluginInstallResult, type MarketplacePluginManageAction, type MarketplacePluginManageRequest, type MarketplacePluginManageResult, type MarketplaceRecommendationView, type MarketplaceSkillInstallRequest, type MarketplaceSkillInstallResult, type MarketplaceSkillManageAction, type MarketplaceSkillManageRequest, type MarketplaceSkillManageResult, type MarketplaceSort, type ProviderConfigUpdate, type ProviderConfigView, type ProviderSpecView, type RuntimeConfigUpdate, type SecretProviderEnvView, type SecretProviderExecView, type SecretProviderFileView, type SecretProviderView, type SecretRefView, type SecretSourceView, type SecretsConfigUpdate, type SecretsView, type SessionConfigView, type SessionEntryView, type SessionEventView, type SessionHistoryView, type SessionMessageView, type SessionPatchUpdate, type SessionsListView, type UiChatRuntime, type UiServerEvent, type UiServerHandle, type UiServerOptions, buildConfigMeta, buildConfigSchemaView, buildConfigView, createUiRouter, deleteSession, executeConfigAction, getSessionHistory, listSessions, loadConfigOrDefault, patchSession, startUiServer, updateChannel, updateModel, updateProvider, updateRuntime, updateSecrets };
608
+ export { type AgentBindingView, type AgentProfileView, type ApiError, type ApiResponse, type BindingPeerView, type ChannelSpecView, type ChatTurnRequest, type ChatTurnResult, type ChatTurnStreamEvent, type ChatTurnView, type ConfigActionExecuteRequest, type ConfigActionExecuteResult, type ConfigActionManifest, type ConfigActionType, type ConfigMetaView, type ConfigSchemaResponse, type ConfigUiHint, type ConfigUiHints, type ConfigView, type CronActionResult, type CronEnableRequest, type CronJobStateView, type CronJobView, type CronListView, type CronPayloadView, type CronRunRequest, type CronScheduleView, type MarketplaceApiConfig, type MarketplaceInstallKind, type MarketplaceInstallSkillParams, type MarketplaceInstallSpec, type MarketplaceInstalledRecord, type MarketplaceInstalledView, type MarketplaceInstaller, type MarketplaceItemSummary, type MarketplaceItemType, type MarketplaceItemView, type MarketplaceListView, type MarketplacePluginInstallRequest, type MarketplacePluginInstallResult, type MarketplacePluginManageAction, type MarketplacePluginManageRequest, type MarketplacePluginManageResult, type MarketplaceRecommendationView, type MarketplaceSkillInstallRequest, type MarketplaceSkillInstallResult, type MarketplaceSkillManageAction, type MarketplaceSkillManageRequest, type MarketplaceSkillManageResult, type MarketplaceSort, type ProviderConfigUpdate, type ProviderConfigView, type ProviderConnectionTestRequest, type ProviderConnectionTestResult, type ProviderSpecView, type RuntimeConfigUpdate, type SecretProviderEnvView, type SecretProviderExecView, type SecretProviderFileView, type SecretProviderView, type SecretRefView, type SecretSourceView, type SecretsConfigUpdate, type SecretsView, type SessionConfigView, type SessionEntryView, type SessionEventView, type SessionHistoryView, type SessionMessageView, type SessionPatchUpdate, type SessionsListView, type UiChatRuntime, type UiServerEvent, type UiServerHandle, type UiServerOptions, buildConfigMeta, buildConfigSchemaView, buildConfigView, createUiRouter, deleteSession, executeConfigAction, getSessionHistory, listSessions, loadConfigOrDefault, patchSession, startUiServer, testProviderConnection, updateChannel, updateModel, updateProvider, updateRuntime, updateSecrets };
package/dist/index.js CHANGED
@@ -19,9 +19,11 @@ import {
19
19
  saveConfig,
20
20
  ConfigSchema,
21
21
  probeFeishu,
22
+ LiteLLMProvider,
22
23
  PROVIDERS,
23
24
  buildConfigSchema,
24
25
  findProviderByName,
26
+ getProviderName,
25
27
  getPackageVersion,
26
28
  hasSecretRef,
27
29
  isSensitiveConfigPath,
@@ -30,6 +32,33 @@ import {
30
32
  } from "@nextclaw/core";
31
33
  var MASK_MIN_LENGTH = 8;
32
34
  var EXTRA_SENSITIVE_PATH_PATTERNS = [/authorization/i, /cookie/i, /session/i, /bearer/i];
35
+ var PROVIDER_TEST_MODEL_FALLBACKS = {
36
+ openai: "gpt-5-mini",
37
+ deepseek: "deepseek-v3.2",
38
+ gemini: "gemini-3-flash-preview",
39
+ zhipu: "glm-5",
40
+ dashscope: "qwen3.5-flash",
41
+ moonshot: "kimi-k2.5",
42
+ minimax: "MiniMax-M2.5",
43
+ groq: "llama-3.1-8b-instant",
44
+ openrouter: "openai/gpt-5.3-codex",
45
+ aihubmix: "gpt-5.3-codex",
46
+ anthropic: "claude-opus-4-6"
47
+ };
48
+ var PREFERRED_PROVIDER_ORDER = [
49
+ "openai",
50
+ "anthropic",
51
+ "gemini",
52
+ "openrouter",
53
+ "dashscope",
54
+ "deepseek",
55
+ "minimax",
56
+ "moonshot",
57
+ "zhipu"
58
+ ];
59
+ var PREFERRED_PROVIDER_ORDER_INDEX = new Map(
60
+ PREFERRED_PROVIDER_ORDER.map((name, index) => [name, index])
61
+ );
33
62
  function matchesExtraSensitivePath(path) {
34
63
  if (path === "session" || path.startsWith("session.")) {
35
64
  return false;
@@ -231,6 +260,23 @@ function maskApiKey(value) {
231
260
  apiKeyMasked: `${value.slice(0, 2)}****${value.slice(-4)}`
232
261
  };
233
262
  }
263
+ function normalizeModelList(input) {
264
+ if (!input || input.length === 0) {
265
+ return [];
266
+ }
267
+ const deduped = /* @__PURE__ */ new Set();
268
+ for (const item of input) {
269
+ if (typeof item !== "string") {
270
+ continue;
271
+ }
272
+ const trimmed = item.trim();
273
+ if (!trimmed) {
274
+ continue;
275
+ }
276
+ deduped.add(trimmed);
277
+ }
278
+ return [...deduped];
279
+ }
234
280
  function toProviderView(config, provider, providerName, uiHints, spec) {
235
281
  const apiKeyPath = `providers.${providerName}.apiKey`;
236
282
  const apiKeyRefSet = hasSecretRef(config, apiKeyPath);
@@ -244,7 +290,8 @@ function toProviderView(config, provider, providerName, uiHints, spec) {
244
290
  apiKeySet: masked.apiKeySet || apiKeyRefSet,
245
291
  apiKeyMasked: masked.apiKeyMasked ?? (apiKeyRefSet ? "****" : void 0),
246
292
  apiBase: provider.apiBase ?? null,
247
- extraHeaders: extraHeaders && Object.keys(extraHeaders).length > 0 ? extraHeaders : null
293
+ extraHeaders: extraHeaders && Object.keys(extraHeaders).length > 0 ? extraHeaders : null,
294
+ models: normalizeModelList(provider.models ?? [])
248
295
  };
249
296
  if (spec?.supportsWireApi) {
250
297
  view.wireApi = provider.wireApi ?? spec.defaultWireApi ?? "auto";
@@ -288,15 +335,30 @@ function buildConfigMeta(config) {
288
335
  const providers = PROVIDERS.map((spec) => ({
289
336
  name: spec.name,
290
337
  displayName: spec.displayName,
338
+ modelPrefix: spec.modelPrefix,
291
339
  keywords: spec.keywords,
292
340
  envKey: spec.envKey,
293
341
  isGateway: spec.isGateway,
294
342
  isLocal: spec.isLocal,
295
343
  defaultApiBase: spec.defaultApiBase,
344
+ defaultModels: normalizeModelList(spec.defaultModels ?? []),
296
345
  supportsWireApi: spec.supportsWireApi,
297
346
  wireApiOptions: spec.wireApiOptions,
298
347
  defaultWireApi: spec.defaultWireApi
299
- }));
348
+ })).sort((left, right) => {
349
+ const leftRank = PREFERRED_PROVIDER_ORDER_INDEX.get(left.name);
350
+ const rightRank = PREFERRED_PROVIDER_ORDER_INDEX.get(right.name);
351
+ if (leftRank !== void 0 && rightRank !== void 0) {
352
+ return leftRank - rightRank;
353
+ }
354
+ if (leftRank !== void 0) {
355
+ return -1;
356
+ }
357
+ if (rightRank !== void 0) {
358
+ return 1;
359
+ }
360
+ return left.name.localeCompare(right.name);
361
+ });
300
362
  const channels = Object.keys(config.channels).map((name) => ({
301
363
  name,
302
364
  displayName: name,
@@ -394,12 +456,117 @@ function updateProvider(configPath, providerName, patch) {
394
456
  if (Object.prototype.hasOwnProperty.call(patch, "wireApi") && spec?.supportsWireApi) {
395
457
  provider.wireApi = patch.wireApi ?? spec.defaultWireApi ?? "auto";
396
458
  }
459
+ if (Object.prototype.hasOwnProperty.call(patch, "models")) {
460
+ provider.models = normalizeModelList(patch.models ?? []);
461
+ }
397
462
  const next = ConfigSchema.parse(config);
398
463
  saveConfig(next, configPath);
399
464
  const uiHints = buildUiHints(next);
400
465
  const updated = next.providers[providerName];
401
466
  return toProviderView(next, updated, providerName, uiHints, spec ?? void 0);
402
467
  }
468
+ function normalizeOptionalString(value) {
469
+ if (typeof value !== "string") {
470
+ return null;
471
+ }
472
+ const trimmed = value.trim();
473
+ return trimmed.length > 0 ? trimmed : null;
474
+ }
475
+ function normalizeHeaders(input) {
476
+ if (!input) {
477
+ return null;
478
+ }
479
+ const entries = Object.entries(input).map(([key, value]) => [key.trim(), String(value ?? "").trim()]).filter(([key, value]) => key.length > 0 && value.length > 0);
480
+ if (entries.length === 0) {
481
+ return null;
482
+ }
483
+ return Object.fromEntries(entries);
484
+ }
485
+ function resolveTestModel(config, providerName, requestedModel) {
486
+ if (requestedModel) {
487
+ return requestedModel;
488
+ }
489
+ const defaultModel = normalizeOptionalString(config.agents.defaults.model);
490
+ if (defaultModel) {
491
+ const routedProvider = getProviderName(config, defaultModel);
492
+ if (!routedProvider || routedProvider === providerName) {
493
+ return defaultModel;
494
+ }
495
+ }
496
+ return PROVIDER_TEST_MODEL_FALLBACKS[providerName] ?? defaultModel ?? null;
497
+ }
498
+ function stringifyError(error) {
499
+ const raw = error instanceof Error ? error.message : String(error);
500
+ return raw.replace(/\s+/g, " ").trim();
501
+ }
502
+ async function testProviderConnection(configPath, providerName, patch) {
503
+ const config = loadConfigOrDefault(configPath);
504
+ const provider = config.providers[providerName];
505
+ if (!provider) {
506
+ return null;
507
+ }
508
+ const spec = findProviderByName(providerName);
509
+ const hasApiKeyPatch = Object.prototype.hasOwnProperty.call(patch, "apiKey");
510
+ const providedApiKey = normalizeOptionalString(patch.apiKey);
511
+ const currentApiKey = normalizeOptionalString(provider.apiKey);
512
+ const apiKey = hasApiKeyPatch ? providedApiKey : currentApiKey;
513
+ const hasApiBasePatch = Object.prototype.hasOwnProperty.call(patch, "apiBase");
514
+ const patchedApiBase = normalizeOptionalString(patch.apiBase);
515
+ const currentApiBase = normalizeOptionalString(provider.apiBase);
516
+ const apiBase = hasApiBasePatch ? patchedApiBase ?? spec?.defaultApiBase ?? null : currentApiBase ?? spec?.defaultApiBase ?? null;
517
+ const hasHeadersPatch = Object.prototype.hasOwnProperty.call(patch, "extraHeaders");
518
+ const extraHeaders = hasHeadersPatch ? normalizeHeaders(patch.extraHeaders ?? null) : normalizeHeaders(provider.extraHeaders ?? null);
519
+ const wireApi = spec?.supportsWireApi ? patch.wireApi ?? provider.wireApi ?? spec.defaultWireApi ?? "auto" : null;
520
+ if (!apiKey && !spec?.isLocal) {
521
+ return {
522
+ success: false,
523
+ provider: providerName,
524
+ latencyMs: 0,
525
+ message: "API key is required before testing the connection."
526
+ };
527
+ }
528
+ const requestedModel = normalizeOptionalString(patch.model);
529
+ const model = resolveTestModel(config, providerName, requestedModel);
530
+ if (!model) {
531
+ return {
532
+ success: false,
533
+ provider: providerName,
534
+ latencyMs: 0,
535
+ message: "No test model found. Set a default model first, then try again."
536
+ };
537
+ }
538
+ const probe = new LiteLLMProvider({
539
+ apiKey,
540
+ apiBase,
541
+ defaultModel: model,
542
+ extraHeaders,
543
+ providerName,
544
+ wireApi
545
+ });
546
+ const startedAtMs = Date.now();
547
+ try {
548
+ await probe.chat({
549
+ model,
550
+ messages: [{ role: "user", content: "ping" }],
551
+ maxTokens: 8
552
+ });
553
+ return {
554
+ success: true,
555
+ provider: providerName,
556
+ model,
557
+ latencyMs: Date.now() - startedAtMs,
558
+ message: "Connection test passed."
559
+ };
560
+ } catch (error) {
561
+ return {
562
+ success: false,
563
+ provider: providerName,
564
+ model,
565
+ latencyMs: Date.now() - startedAtMs,
566
+ message: stringifyError(error) || "Connection test failed."
567
+ };
568
+ }
569
+ }
403
570
  function updateChannel(configPath, channelName, patch) {
404
571
  const config = loadConfigOrDefault(configPath);
405
572
  const channel = config.channels[channelName];
@@ -1594,6 +1761,22 @@ function createUiRouter(options) {
1594
1761
  options.publish({ type: "config.updated", payload: { path: `providers.${provider}` } });
1595
1762
  return c.json(ok(result));
1596
1763
  });
1764
+ app.post("/api/config/providers/:provider/test", async (c) => {
1765
+ const provider = c.req.param("provider");
1766
+ const body = await readJson(c.req.raw);
1767
+ if (!body.ok) {
1768
+ return c.json(err("INVALID_BODY", "invalid json body"), 400);
1769
+ }
1770
+ const result = await testProviderConnection(
1771
+ options.configPath,
1772
+ provider,
1773
+ body.data
1774
+ );
1775
+ if (!result) {
1776
+ return c.json(err("NOT_FOUND", `unknown provider: ${provider}`), 404);
1777
+ }
1778
+ return c.json(ok(result));
1779
+ });
1597
1780
  app.put("/api/config/channels/:channel", async (c) => {
1598
1781
  const channel = c.req.param("channel");
1599
1782
  const body = await readJson(c.req.raw);
@@ -2014,6 +2197,7 @@ export {
2014
2197
  loadConfigOrDefault,
2015
2198
  patchSession,
2016
2199
  startUiServer,
2200
+ testProviderConnection,
2017
2201
  updateChannel,
2018
2202
  updateModel,
2019
2203
  updateProvider,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/server",
3
- "version": "0.5.21",
3
+ "version": "0.5.23",
4
4
  "private": false,
5
5
  "description": "Nextclaw UI/API server.",
6
6
  "type": "module",
@@ -15,10 +15,10 @@
15
15
  ],
16
16
  "dependencies": {
17
17
  "@hono/node-server": "^1.13.3",
18
- "@nextclaw/openclaw-compat": "^0.1.29",
18
+ "@nextclaw/openclaw-compat": "^0.1.30",
19
19
  "hono": "^4.6.2",
20
20
  "ws": "^8.18.0",
21
- "@nextclaw/core": "^0.6.38"
21
+ "@nextclaw/core": "^0.6.39"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/node": "^20.17.6",