@nextclaw/server 0.5.25 → 0.5.27
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 +43 -1
- package/dist/index.js +564 -31
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ type ApiResponse<T> = {
|
|
|
15
15
|
error: ApiError;
|
|
16
16
|
};
|
|
17
17
|
type ProviderConfigView = {
|
|
18
|
+
displayName?: string;
|
|
18
19
|
apiKeySet: boolean;
|
|
19
20
|
apiKeyMasked?: string;
|
|
20
21
|
apiBase?: string | null;
|
|
@@ -23,6 +24,7 @@ type ProviderConfigView = {
|
|
|
23
24
|
models?: string[];
|
|
24
25
|
};
|
|
25
26
|
type ProviderConfigUpdate = {
|
|
27
|
+
displayName?: string | null;
|
|
26
28
|
apiKey?: string | null;
|
|
27
29
|
apiBase?: string | null;
|
|
28
30
|
extraHeaders?: Record<string, string> | null;
|
|
@@ -32,6 +34,15 @@ type ProviderConfigUpdate = {
|
|
|
32
34
|
type ProviderConnectionTestRequest = ProviderConfigUpdate & {
|
|
33
35
|
model?: string | null;
|
|
34
36
|
};
|
|
37
|
+
type ProviderCreateRequest = ProviderConfigUpdate;
|
|
38
|
+
type ProviderCreateResult = {
|
|
39
|
+
name: string;
|
|
40
|
+
provider: ProviderConfigView;
|
|
41
|
+
};
|
|
42
|
+
type ProviderDeleteResult = {
|
|
43
|
+
deleted: boolean;
|
|
44
|
+
provider: string;
|
|
45
|
+
};
|
|
35
46
|
type ProviderConnectionTestResult = {
|
|
36
47
|
success: boolean;
|
|
37
48
|
provider: string;
|
|
@@ -288,6 +299,7 @@ type ConfigView = {
|
|
|
288
299
|
type ProviderSpecView = {
|
|
289
300
|
name: string;
|
|
290
301
|
displayName?: string;
|
|
302
|
+
isCustom?: boolean;
|
|
291
303
|
modelPrefix?: string;
|
|
292
304
|
keywords: string[];
|
|
293
305
|
envKey: string;
|
|
@@ -387,12 +399,14 @@ type MarketplaceInstallSpec = {
|
|
|
387
399
|
spec: string;
|
|
388
400
|
command: string;
|
|
389
401
|
};
|
|
402
|
+
type MarketplaceLocalizedTextMap = Record<string, string>;
|
|
390
403
|
type MarketplaceItemSummary = {
|
|
391
404
|
id: string;
|
|
392
405
|
slug: string;
|
|
393
406
|
type: MarketplaceItemType;
|
|
394
407
|
name: string;
|
|
395
408
|
summary: string;
|
|
409
|
+
summaryI18n: MarketplaceLocalizedTextMap;
|
|
396
410
|
tags: string[];
|
|
397
411
|
author: string;
|
|
398
412
|
install: MarketplaceInstallSpec;
|
|
@@ -400,10 +414,33 @@ type MarketplaceItemSummary = {
|
|
|
400
414
|
};
|
|
401
415
|
type MarketplaceItemView = MarketplaceItemSummary & {
|
|
402
416
|
description?: string;
|
|
417
|
+
descriptionI18n?: MarketplaceLocalizedTextMap;
|
|
403
418
|
sourceRepo?: string;
|
|
404
419
|
homepage?: string;
|
|
405
420
|
publishedAt: string;
|
|
406
421
|
};
|
|
422
|
+
type MarketplaceSkillContentView = {
|
|
423
|
+
type: "skill";
|
|
424
|
+
slug: string;
|
|
425
|
+
name: string;
|
|
426
|
+
install: MarketplaceInstallSpec;
|
|
427
|
+
source: "workspace" | "builtin" | "git" | "remote";
|
|
428
|
+
raw: string;
|
|
429
|
+
metadataRaw?: string;
|
|
430
|
+
bodyRaw: string;
|
|
431
|
+
sourceUrl?: string;
|
|
432
|
+
};
|
|
433
|
+
type MarketplacePluginContentView = {
|
|
434
|
+
type: "plugin";
|
|
435
|
+
slug: string;
|
|
436
|
+
name: string;
|
|
437
|
+
install: MarketplaceInstallSpec;
|
|
438
|
+
source: "npm" | "repo" | "remote";
|
|
439
|
+
raw?: string;
|
|
440
|
+
bodyRaw?: string;
|
|
441
|
+
metadataRaw?: string;
|
|
442
|
+
sourceUrl?: string;
|
|
443
|
+
};
|
|
407
444
|
type MarketplaceListView = {
|
|
408
445
|
total: number;
|
|
409
446
|
page: number;
|
|
@@ -597,6 +634,11 @@ declare function updateModel(configPath: string, patch: {
|
|
|
597
634
|
maxTokens?: number;
|
|
598
635
|
}): ConfigView;
|
|
599
636
|
declare function updateProvider(configPath: string, providerName: string, patch: ProviderConfigUpdate): ProviderConfigView | null;
|
|
637
|
+
declare function createCustomProvider(configPath: string, patch?: ProviderConfigUpdate): {
|
|
638
|
+
name: string;
|
|
639
|
+
provider: ProviderConfigView;
|
|
640
|
+
};
|
|
641
|
+
declare function deleteCustomProvider(configPath: string, providerName: string): boolean | null;
|
|
600
642
|
declare function testProviderConnection(configPath: string, providerName: string, patch: ProviderConnectionTestRequest): Promise<ProviderConnectionTestResult | null>;
|
|
601
643
|
declare function updateChannel(configPath: string, channelName: string, patch: Record<string, unknown>): Record<string, unknown> | null;
|
|
602
644
|
declare function listSessions(configPath: string, query?: {
|
|
@@ -610,4 +652,4 @@ declare function deleteSession(configPath: string, key: string): boolean;
|
|
|
610
652
|
declare function updateRuntime(configPath: string, patch: RuntimeConfigUpdate): Pick<ConfigView, "agents" | "bindings" | "session">;
|
|
611
653
|
declare function updateSecrets(configPath: string, patch: SecretsConfigUpdate): SecretsView;
|
|
612
654
|
|
|
613
|
-
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 };
|
|
655
|
+
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 MarketplaceLocalizedTextMap, type MarketplacePluginContentView, type MarketplacePluginInstallRequest, type MarketplacePluginInstallResult, type MarketplacePluginManageAction, type MarketplacePluginManageRequest, type MarketplacePluginManageResult, type MarketplaceRecommendationView, type MarketplaceSkillContentView, type MarketplaceSkillInstallRequest, type MarketplaceSkillInstallResult, type MarketplaceSkillManageAction, type MarketplaceSkillManageRequest, type MarketplaceSkillManageResult, type MarketplaceSort, type ProviderConfigUpdate, type ProviderConfigView, type ProviderConnectionTestRequest, type ProviderConnectionTestResult, type ProviderCreateRequest, type ProviderCreateResult, type ProviderDeleteResult, 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, createCustomProvider, createUiRouter, deleteCustomProvider, deleteSession, executeConfigAction, getSessionHistory, listSessions, loadConfigOrDefault, patchSession, startUiServer, testProviderConnection, updateChannel, updateModel, updateProvider, updateRuntime, updateSecrets };
|
package/dist/index.js
CHANGED
|
@@ -5,11 +5,12 @@ import { cors } from "hono/cors";
|
|
|
5
5
|
import { serve } from "@hono/node-server";
|
|
6
6
|
import { WebSocketServer, WebSocket } from "ws";
|
|
7
7
|
import { existsSync, readFileSync } from "fs";
|
|
8
|
-
import { readFile, stat } from "fs/promises";
|
|
8
|
+
import { readFile as readFile2, stat } from "fs/promises";
|
|
9
9
|
import { join } from "path";
|
|
10
10
|
|
|
11
11
|
// src/ui/router.ts
|
|
12
12
|
import { Hono } from "hono";
|
|
13
|
+
import { readFile } from "fs/promises";
|
|
13
14
|
import * as NextclawCore from "@nextclaw/core";
|
|
14
15
|
import { buildPluginStatusReport } from "@nextclaw/openclaw-compat";
|
|
15
16
|
|
|
@@ -34,7 +35,7 @@ var MASK_MIN_LENGTH = 8;
|
|
|
34
35
|
var EXTRA_SENSITIVE_PATH_PATTERNS = [/authorization/i, /cookie/i, /session/i, /bearer/i];
|
|
35
36
|
var PROVIDER_TEST_MODEL_FALLBACKS = {
|
|
36
37
|
openai: "gpt-5-mini",
|
|
37
|
-
deepseek: "deepseek-
|
|
38
|
+
deepseek: "deepseek-chat",
|
|
38
39
|
gemini: "gemini-3-flash-preview",
|
|
39
40
|
zhipu: "glm-5",
|
|
40
41
|
dashscope: "qwen3.5-flash",
|
|
@@ -59,6 +60,53 @@ var PREFERRED_PROVIDER_ORDER = [
|
|
|
59
60
|
var PREFERRED_PROVIDER_ORDER_INDEX = new Map(
|
|
60
61
|
PREFERRED_PROVIDER_ORDER.map((name, index) => [name, index])
|
|
61
62
|
);
|
|
63
|
+
var BUILTIN_PROVIDER_NAMES = new Set(PROVIDERS.map((spec) => spec.name));
|
|
64
|
+
var CUSTOM_PROVIDER_WIRE_API_OPTIONS = ["auto", "chat", "responses"];
|
|
65
|
+
var CUSTOM_PROVIDER_PREFIX = "custom-";
|
|
66
|
+
function normalizeOptionalDisplayName(value) {
|
|
67
|
+
if (typeof value !== "string") {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const trimmed = value.trim();
|
|
71
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
72
|
+
}
|
|
73
|
+
function isCustomProviderName(name) {
|
|
74
|
+
return name.trim().length > 0 && !BUILTIN_PROVIDER_NAMES.has(name);
|
|
75
|
+
}
|
|
76
|
+
function resolveCustomProviderFallbackDisplayName(name) {
|
|
77
|
+
if (name.startsWith(CUSTOM_PROVIDER_PREFIX)) {
|
|
78
|
+
const suffix = name.slice(CUSTOM_PROVIDER_PREFIX.length);
|
|
79
|
+
if (/^\d+$/.test(suffix)) {
|
|
80
|
+
return `Custom ${suffix}`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return name;
|
|
84
|
+
}
|
|
85
|
+
function resolveProviderDisplayName(providerName, provider, spec) {
|
|
86
|
+
const configDisplayName = normalizeOptionalDisplayName(provider?.displayName);
|
|
87
|
+
if (isCustomProviderName(providerName)) {
|
|
88
|
+
return configDisplayName ?? resolveCustomProviderFallbackDisplayName(providerName);
|
|
89
|
+
}
|
|
90
|
+
return spec?.displayName ?? configDisplayName ?? spec?.name;
|
|
91
|
+
}
|
|
92
|
+
function listCustomProviderNames(config) {
|
|
93
|
+
return Object.keys(config.providers).filter((name) => isCustomProviderName(name));
|
|
94
|
+
}
|
|
95
|
+
function findNextCustomProviderName(config) {
|
|
96
|
+
const providers = config.providers;
|
|
97
|
+
let index = 1;
|
|
98
|
+
while (providers[`${CUSTOM_PROVIDER_PREFIX}${index}`]) {
|
|
99
|
+
index += 1;
|
|
100
|
+
}
|
|
101
|
+
return `${CUSTOM_PROVIDER_PREFIX}${index}`;
|
|
102
|
+
}
|
|
103
|
+
function clearSecretRefsByPrefix(config, pathPrefix) {
|
|
104
|
+
for (const key of Object.keys(config.secrets.refs)) {
|
|
105
|
+
if (key === pathPrefix || key.startsWith(`${pathPrefix}.`)) {
|
|
106
|
+
delete config.secrets.refs[key];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
62
110
|
var DOCS_BASE_URL = "https://docs.nextclaw.io";
|
|
63
111
|
var CHANNEL_TUTORIAL_URLS = {
|
|
64
112
|
feishu: {
|
|
@@ -295,14 +343,16 @@ function toProviderView(config, provider, providerName, uiHints, spec) {
|
|
|
295
343
|
uiHints
|
|
296
344
|
) : null;
|
|
297
345
|
const view = {
|
|
346
|
+
displayName: resolveProviderDisplayName(providerName, provider, spec),
|
|
298
347
|
apiKeySet: masked.apiKeySet || apiKeyRefSet,
|
|
299
348
|
apiKeyMasked: masked.apiKeyMasked ?? (apiKeyRefSet ? "****" : void 0),
|
|
300
349
|
apiBase: provider.apiBase ?? null,
|
|
301
350
|
extraHeaders: extraHeaders && Object.keys(extraHeaders).length > 0 ? extraHeaders : null,
|
|
302
351
|
models: normalizeModelList(provider.models ?? [])
|
|
303
352
|
};
|
|
304
|
-
|
|
305
|
-
|
|
353
|
+
const supportsWireApi = Boolean(spec?.supportsWireApi) || isCustomProviderName(providerName);
|
|
354
|
+
if (supportsWireApi) {
|
|
355
|
+
view.wireApi = provider.wireApi ?? spec?.defaultWireApi ?? "auto";
|
|
306
356
|
}
|
|
307
357
|
return view;
|
|
308
358
|
}
|
|
@@ -340,20 +390,25 @@ function clearSecretRef(config, path) {
|
|
|
340
390
|
}
|
|
341
391
|
}
|
|
342
392
|
function buildConfigMeta(config) {
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
393
|
+
const configProviders = config.providers;
|
|
394
|
+
const builtinProviders = PROVIDERS.map((spec) => {
|
|
395
|
+
const providerConfig = configProviders[spec.name];
|
|
396
|
+
return {
|
|
397
|
+
name: spec.name,
|
|
398
|
+
displayName: resolveProviderDisplayName(spec.name, providerConfig, spec),
|
|
399
|
+
isCustom: false,
|
|
400
|
+
modelPrefix: spec.modelPrefix,
|
|
401
|
+
keywords: spec.keywords,
|
|
402
|
+
envKey: spec.envKey,
|
|
403
|
+
isGateway: spec.isGateway,
|
|
404
|
+
isLocal: spec.isLocal,
|
|
405
|
+
defaultApiBase: spec.defaultApiBase,
|
|
406
|
+
defaultModels: normalizeModelList(spec.defaultModels ?? []),
|
|
407
|
+
supportsWireApi: spec.supportsWireApi,
|
|
408
|
+
wireApiOptions: spec.wireApiOptions,
|
|
409
|
+
defaultWireApi: spec.defaultWireApi
|
|
410
|
+
};
|
|
411
|
+
}).sort((left, right) => {
|
|
357
412
|
const leftRank = PREFERRED_PROVIDER_ORDER_INDEX.get(left.name);
|
|
358
413
|
const rightRank = PREFERRED_PROVIDER_ORDER_INDEX.get(right.name);
|
|
359
414
|
if (leftRank !== void 0 && rightRank !== void 0) {
|
|
@@ -367,6 +422,26 @@ function buildConfigMeta(config) {
|
|
|
367
422
|
}
|
|
368
423
|
return left.name.localeCompare(right.name);
|
|
369
424
|
});
|
|
425
|
+
const customProviders = listCustomProviderNames(config).sort((left, right) => left.localeCompare(right, void 0, { numeric: true, sensitivity: "base" })).map((name) => {
|
|
426
|
+
const providerConfig = configProviders[name];
|
|
427
|
+
const displayName = resolveProviderDisplayName(name, providerConfig);
|
|
428
|
+
return {
|
|
429
|
+
name,
|
|
430
|
+
displayName,
|
|
431
|
+
isCustom: true,
|
|
432
|
+
modelPrefix: name,
|
|
433
|
+
keywords: normalizeModelList([name, displayName ?? ""]),
|
|
434
|
+
envKey: "OPENAI_API_KEY",
|
|
435
|
+
isGateway: false,
|
|
436
|
+
isLocal: false,
|
|
437
|
+
defaultApiBase: void 0,
|
|
438
|
+
defaultModels: [],
|
|
439
|
+
supportsWireApi: true,
|
|
440
|
+
wireApiOptions: CUSTOM_PROVIDER_WIRE_API_OPTIONS,
|
|
441
|
+
defaultWireApi: "auto"
|
|
442
|
+
};
|
|
443
|
+
});
|
|
444
|
+
const providers = [...customProviders, ...builtinProviders];
|
|
370
445
|
const channels = Object.keys(config.channels).map((name) => {
|
|
371
446
|
const tutorialUrls = CHANNEL_TUTORIAL_URLS[name];
|
|
372
447
|
const tutorialUrl = tutorialUrls?.default ?? tutorialUrls?.en ?? tutorialUrls?.zh;
|
|
@@ -457,6 +532,10 @@ function updateProvider(configPath, providerName, patch) {
|
|
|
457
532
|
return null;
|
|
458
533
|
}
|
|
459
534
|
const spec = findProviderByName(providerName);
|
|
535
|
+
const isCustom = isCustomProviderName(providerName);
|
|
536
|
+
if (Object.prototype.hasOwnProperty.call(patch, "displayName") && isCustom) {
|
|
537
|
+
provider.displayName = normalizeOptionalDisplayName(patch.displayName) ?? "";
|
|
538
|
+
}
|
|
460
539
|
if (Object.prototype.hasOwnProperty.call(patch, "apiKey")) {
|
|
461
540
|
provider.apiKey = patch.apiKey ?? "";
|
|
462
541
|
clearSecretRef(config, `providers.${providerName}.apiKey`);
|
|
@@ -467,8 +546,8 @@ function updateProvider(configPath, providerName, patch) {
|
|
|
467
546
|
if (Object.prototype.hasOwnProperty.call(patch, "extraHeaders")) {
|
|
468
547
|
provider.extraHeaders = patch.extraHeaders ?? null;
|
|
469
548
|
}
|
|
470
|
-
if (Object.prototype.hasOwnProperty.call(patch, "wireApi") && spec?.supportsWireApi) {
|
|
471
|
-
provider.wireApi = patch.wireApi ?? spec
|
|
549
|
+
if (Object.prototype.hasOwnProperty.call(patch, "wireApi") && (spec?.supportsWireApi || isCustom)) {
|
|
550
|
+
provider.wireApi = patch.wireApi ?? spec?.defaultWireApi ?? "auto";
|
|
472
551
|
}
|
|
473
552
|
if (Object.prototype.hasOwnProperty.call(patch, "models")) {
|
|
474
553
|
provider.models = normalizeModelList(patch.models ?? []);
|
|
@@ -479,6 +558,43 @@ function updateProvider(configPath, providerName, patch) {
|
|
|
479
558
|
const updated = next.providers[providerName];
|
|
480
559
|
return toProviderView(next, updated, providerName, uiHints, spec ?? void 0);
|
|
481
560
|
}
|
|
561
|
+
function createCustomProvider(configPath, patch = {}) {
|
|
562
|
+
const config = loadConfigOrDefault(configPath);
|
|
563
|
+
const providerName = findNextCustomProviderName(config);
|
|
564
|
+
const providers = config.providers;
|
|
565
|
+
const generatedDisplayName = resolveCustomProviderFallbackDisplayName(providerName);
|
|
566
|
+
providers[providerName] = {
|
|
567
|
+
displayName: normalizeOptionalDisplayName(patch.displayName) ?? generatedDisplayName,
|
|
568
|
+
apiKey: normalizeOptionalString(patch.apiKey) ?? "",
|
|
569
|
+
apiBase: normalizeOptionalString(patch.apiBase),
|
|
570
|
+
extraHeaders: normalizeHeaders(patch.extraHeaders ?? null),
|
|
571
|
+
wireApi: patch.wireApi ?? "auto",
|
|
572
|
+
models: normalizeModelList(patch.models ?? [])
|
|
573
|
+
};
|
|
574
|
+
const next = ConfigSchema.parse(config);
|
|
575
|
+
saveConfig(next, configPath);
|
|
576
|
+
const uiHints = buildUiHints(next);
|
|
577
|
+
const created = next.providers[providerName];
|
|
578
|
+
return {
|
|
579
|
+
name: providerName,
|
|
580
|
+
provider: toProviderView(next, created, providerName, uiHints)
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
function deleteCustomProvider(configPath, providerName) {
|
|
584
|
+
if (!isCustomProviderName(providerName)) {
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
const config = loadConfigOrDefault(configPath);
|
|
588
|
+
const providers = config.providers;
|
|
589
|
+
if (!providers[providerName]) {
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
delete providers[providerName];
|
|
593
|
+
clearSecretRefsByPrefix(config, `providers.${providerName}`);
|
|
594
|
+
const next = ConfigSchema.parse(config);
|
|
595
|
+
saveConfig(next, configPath);
|
|
596
|
+
return true;
|
|
597
|
+
}
|
|
482
598
|
function normalizeOptionalString(value) {
|
|
483
599
|
if (typeof value !== "string") {
|
|
484
600
|
return null;
|
|
@@ -496,10 +612,37 @@ function normalizeHeaders(input) {
|
|
|
496
612
|
}
|
|
497
613
|
return Object.fromEntries(entries);
|
|
498
614
|
}
|
|
499
|
-
function
|
|
615
|
+
function buildScopedProviderModel(providerName, model, spec) {
|
|
616
|
+
const trimmed = model.trim();
|
|
617
|
+
if (!trimmed) {
|
|
618
|
+
return "";
|
|
619
|
+
}
|
|
620
|
+
if (trimmed.includes("/")) {
|
|
621
|
+
return trimmed;
|
|
622
|
+
}
|
|
623
|
+
if (isCustomProviderName(providerName)) {
|
|
624
|
+
return trimmed;
|
|
625
|
+
}
|
|
626
|
+
const prefix = (spec?.modelPrefix ?? providerName).trim();
|
|
627
|
+
if (!prefix) {
|
|
628
|
+
return trimmed;
|
|
629
|
+
}
|
|
630
|
+
return `${prefix}/${trimmed}`;
|
|
631
|
+
}
|
|
632
|
+
function resolveTestModel(config, providerName, requestedModel, provider, spec) {
|
|
500
633
|
if (requestedModel) {
|
|
634
|
+
if (isCustomProviderName(providerName)) {
|
|
635
|
+
const prefix = `${providerName}/`;
|
|
636
|
+
if (requestedModel.startsWith(prefix)) {
|
|
637
|
+
return requestedModel.slice(prefix.length) || null;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
501
640
|
return requestedModel;
|
|
502
641
|
}
|
|
642
|
+
const providerModels = normalizeModelList(provider.models ?? []).map((modelId) => buildScopedProviderModel(providerName, modelId, spec)).filter((modelId) => modelId.length > 0);
|
|
643
|
+
if (providerModels.length > 0) {
|
|
644
|
+
return providerModels[0];
|
|
645
|
+
}
|
|
503
646
|
const defaultModel = normalizeOptionalString(config.agents.defaults.model);
|
|
504
647
|
if (defaultModel) {
|
|
505
648
|
const routedProvider = getProviderName(config, defaultModel);
|
|
@@ -507,6 +650,9 @@ function resolveTestModel(config, providerName, requestedModel) {
|
|
|
507
650
|
return defaultModel;
|
|
508
651
|
}
|
|
509
652
|
}
|
|
653
|
+
if (isCustomProviderName(providerName)) {
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
510
656
|
return PROVIDER_TEST_MODEL_FALLBACKS[providerName] ?? defaultModel ?? null;
|
|
511
657
|
}
|
|
512
658
|
function stringifyError(error) {
|
|
@@ -530,7 +676,8 @@ async function testProviderConnection(configPath, providerName, patch) {
|
|
|
530
676
|
const apiBase = hasApiBasePatch ? patchedApiBase ?? spec?.defaultApiBase ?? null : currentApiBase ?? spec?.defaultApiBase ?? null;
|
|
531
677
|
const hasHeadersPatch = Object.prototype.hasOwnProperty.call(patch, "extraHeaders");
|
|
532
678
|
const extraHeaders = hasHeadersPatch ? normalizeHeaders(patch.extraHeaders ?? null) : normalizeHeaders(provider.extraHeaders ?? null);
|
|
533
|
-
const
|
|
679
|
+
const isCustom = isCustomProviderName(providerName);
|
|
680
|
+
const wireApi = spec?.supportsWireApi || isCustom ? patch.wireApi ?? provider.wireApi ?? spec?.defaultWireApi ?? "auto" : null;
|
|
534
681
|
if (!apiKey && !spec?.isLocal) {
|
|
535
682
|
return {
|
|
536
683
|
success: false,
|
|
@@ -540,13 +687,13 @@ async function testProviderConnection(configPath, providerName, patch) {
|
|
|
540
687
|
};
|
|
541
688
|
}
|
|
542
689
|
const requestedModel = normalizeOptionalString(patch.model);
|
|
543
|
-
const model = resolveTestModel(config, providerName, requestedModel);
|
|
690
|
+
const model = resolveTestModel(config, providerName, requestedModel, provider, spec ?? void 0);
|
|
544
691
|
if (!model) {
|
|
545
692
|
return {
|
|
546
693
|
success: false,
|
|
547
694
|
provider: providerName,
|
|
548
695
|
latencyMs: 0,
|
|
549
|
-
message: "No test model found.
|
|
696
|
+
message: "No test model found. Configure provider models or set a default model for this provider, then try again."
|
|
550
697
|
};
|
|
551
698
|
}
|
|
552
699
|
const probe = new LiteLLMProvider({
|
|
@@ -1287,6 +1434,141 @@ function sanitizeMarketplaceItem(item) {
|
|
|
1287
1434
|
delete next.metrics;
|
|
1288
1435
|
return next;
|
|
1289
1436
|
}
|
|
1437
|
+
var MARKETPLACE_ZH_COPY_BY_SLUG = {
|
|
1438
|
+
weather: {
|
|
1439
|
+
summary: "NextClaw \u5185\u7F6E\u6280\u80FD\uFF0C\u7528\u4E8E\u5929\u6C14\u67E5\u8BE2\u5DE5\u4F5C\u6D41\u3002",
|
|
1440
|
+
description: "\u5728 NextClaw \u4E2D\u63D0\u4F9B\u5FEB\u901F\u5929\u6C14\u67E5\u8BE2\u5DE5\u4F5C\u6D41\u3002"
|
|
1441
|
+
},
|
|
1442
|
+
summarize: {
|
|
1443
|
+
summary: "NextClaw \u5185\u7F6E\u6280\u80FD\uFF0C\u7528\u4E8E\u7ED3\u6784\u5316\u6458\u8981\u3002",
|
|
1444
|
+
description: "\u5728 NextClaw \u4E2D\u63D0\u4F9B\u6587\u4EF6\u4E0E\u957F\u6587\u672C\u7684\u6458\u8981\u5DE5\u4F5C\u6D41\u3002"
|
|
1445
|
+
},
|
|
1446
|
+
github: {
|
|
1447
|
+
summary: "NextClaw \u5185\u7F6E\u6280\u80FD\uFF0C\u7528\u4E8E GitHub \u5DE5\u4F5C\u6D41\u3002",
|
|
1448
|
+
description: "\u5728 NextClaw \u4E2D\u63D0\u4F9B Issue\u3001PR \u4E0E\u4ED3\u5E93\u76F8\u5173\u5DE5\u4F5C\u6D41\u6307\u5F15\u3002"
|
|
1449
|
+
},
|
|
1450
|
+
tmux: {
|
|
1451
|
+
summary: "NextClaw \u5185\u7F6E\u6280\u80FD\uFF0C\u7528\u4E8E\u7EC8\u7AEF/Tmux \u534F\u4F5C\u5DE5\u4F5C\u6D41\u3002",
|
|
1452
|
+
description: "\u5728 NextClaw \u4E2D\u63D0\u4F9B\u57FA\u4E8E Tmux \u7684\u4EFB\u52A1\u6267\u884C\u5DE5\u4F5C\u6D41\u6307\u5F15\u3002"
|
|
1453
|
+
},
|
|
1454
|
+
gog: {
|
|
1455
|
+
summary: "NextClaw \u5185\u7F6E\u6280\u80FD\uFF0C\u7528\u4E8E\u56FE\u8C31\u5BFC\u5411\u751F\u6210\u5DE5\u4F5C\u6D41\u3002",
|
|
1456
|
+
description: "\u5728 NextClaw \u4E2D\u63D0\u4F9B\u56FE\u8C31\u4E0E\u89C4\u5212\u5BFC\u5411\u5DE5\u4F5C\u6D41\u6307\u5F15\u3002"
|
|
1457
|
+
},
|
|
1458
|
+
pdf: {
|
|
1459
|
+
summary: "Anthropic \u6280\u80FD\uFF0C\u7528\u4E8E PDF \u8BFB\u53D6/\u5408\u5E76/\u62C6\u5206/OCR \u5DE5\u4F5C\u6D41\u3002",
|
|
1460
|
+
description: "\u4F7F\u7528\u8BE5\u6280\u80FD\u53EF\u8BFB\u53D6\u3001\u63D0\u53D6\u3001\u5408\u5E76\u3001\u62C6\u5206\u3001\u65CB\u8F6C\u5E76\u5BF9 PDF \u6267\u884C OCR \u5904\u7406\u3002"
|
|
1461
|
+
},
|
|
1462
|
+
docx: {
|
|
1463
|
+
summary: "Anthropic \u6280\u80FD\uFF0C\u7528\u4E8E\u521B\u5EFA\u548C\u7F16\u8F91 Word \u6587\u6863\u3002",
|
|
1464
|
+
description: "\u4F7F\u7528\u8BE5\u6280\u80FD\u53EF\u521B\u5EFA\u3001\u8BFB\u53D6\u3001\u7F16\u8F91\u5E76\u91CD\u6784 .docx \u6587\u6863\u3002"
|
|
1465
|
+
},
|
|
1466
|
+
pptx: {
|
|
1467
|
+
summary: "Anthropic \u6280\u80FD\uFF0C\u7528\u4E8E\u6F14\u793A\u6587\u7A3F\u64CD\u4F5C\u3002",
|
|
1468
|
+
description: "\u4F7F\u7528\u8BE5\u6280\u80FD\u53EF\u521B\u5EFA\u3001\u89E3\u6790\u3001\u7F16\u8F91\u5E76\u91CD\u7EC4 .pptx \u6F14\u793A\u6587\u7A3F\u3002"
|
|
1469
|
+
},
|
|
1470
|
+
xlsx: {
|
|
1471
|
+
summary: "Anthropic \u6280\u80FD\uFF0C\u7528\u4E8E\u8868\u683C\u6587\u6863\u5DE5\u4F5C\u6D41\u3002",
|
|
1472
|
+
description: "\u4F7F\u7528\u8BE5\u6280\u80FD\u53EF\u6253\u5F00\u3001\u7F16\u8F91\u3001\u6E05\u6D17\u5E76\u8F6C\u6362 .xlsx \u4E0E .csv \u7B49\u8868\u683C\u6587\u4EF6\u3002"
|
|
1473
|
+
},
|
|
1474
|
+
bird: {
|
|
1475
|
+
summary: "OpenClaw \u793E\u533A\u6280\u80FD\uFF0C\u7528\u4E8E X/Twitter \u8BFB\u53D6/\u641C\u7D22/\u53D1\u5E03\u5DE5\u4F5C\u6D41\u3002",
|
|
1476
|
+
description: "\u4F7F\u7528 bird CLI \u5728\u4EE3\u7406\u5DE5\u4F5C\u6D41\u4E2D\u8BFB\u53D6\u7EBF\u7A0B\u3001\u641C\u7D22\u5E16\u5B50\u5E76\u8D77\u8349\u63A8\u6587/\u56DE\u590D\u3002"
|
|
1477
|
+
},
|
|
1478
|
+
"cloudflare-deploy": {
|
|
1479
|
+
summary: "OpenAI \u7CBE\u9009\u6280\u80FD\uFF0C\u7528\u4E8E\u5728 Cloudflare \u4E0A\u90E8\u7F72\u5E94\u7528\u4E0E\u57FA\u7840\u8BBE\u65BD\u3002",
|
|
1480
|
+
description: "\u4F7F\u7528\u8BE5\u6280\u80FD\u53EF\u9009\u62E9 Cloudflare \u4EA7\u54C1\u5E76\u90E8\u7F72 Workers\u3001Pages \u53CA\u76F8\u5173\u670D\u52A1\u3002"
|
|
1481
|
+
},
|
|
1482
|
+
"channel-plugin-discord": {
|
|
1483
|
+
summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E Discord \u6E20\u9053\u96C6\u6210\u3002",
|
|
1484
|
+
description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B Discord \u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
|
|
1485
|
+
},
|
|
1486
|
+
"channel-plugin-telegram": {
|
|
1487
|
+
summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E Telegram \u6E20\u9053\u96C6\u6210\u3002",
|
|
1488
|
+
description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B Telegram \u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
|
|
1489
|
+
},
|
|
1490
|
+
"channel-plugin-slack": {
|
|
1491
|
+
summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E Slack \u6E20\u9053\u96C6\u6210\u3002",
|
|
1492
|
+
description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B Slack \u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
|
|
1493
|
+
},
|
|
1494
|
+
"channel-plugin-wecom": {
|
|
1495
|
+
summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E\u4F01\u4E1A\u5FAE\u4FE1\u6E20\u9053\u96C6\u6210\u3002",
|
|
1496
|
+
description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B\u4F01\u4E1A\u5FAE\u4FE1\u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
|
|
1497
|
+
},
|
|
1498
|
+
"channel-plugin-email": {
|
|
1499
|
+
summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E Email \u6E20\u9053\u96C6\u6210\u3002",
|
|
1500
|
+
description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B Email \u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
|
|
1501
|
+
},
|
|
1502
|
+
"channel-plugin-whatsapp": {
|
|
1503
|
+
summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E WhatsApp \u6E20\u9053\u96C6\u6210\u3002",
|
|
1504
|
+
description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B WhatsApp \u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
|
|
1505
|
+
},
|
|
1506
|
+
"channel-plugin-clawbay": {
|
|
1507
|
+
summary: "Clawbay \u5B98\u65B9\u6E20\u9053\u63D2\u4EF6\uFF0C\u7528\u4E8E NextClaw \u96C6\u6210\u3002",
|
|
1508
|
+
description: "\u901A\u8FC7\u63D2\u4EF6\u8FD0\u884C\u65F6\u4E3A NextClaw \u63D0\u4F9B Clawbay \u6E20\u9053\u80FD\u529B\u3002"
|
|
1509
|
+
}
|
|
1510
|
+
};
|
|
1511
|
+
function readLocalizedMap(value) {
|
|
1512
|
+
const localized = {};
|
|
1513
|
+
if (!isRecord(value)) {
|
|
1514
|
+
return localized;
|
|
1515
|
+
}
|
|
1516
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1517
|
+
if (typeof entry !== "string" || entry.trim().length === 0) {
|
|
1518
|
+
continue;
|
|
1519
|
+
}
|
|
1520
|
+
localized[key] = entry.trim();
|
|
1521
|
+
}
|
|
1522
|
+
return localized;
|
|
1523
|
+
}
|
|
1524
|
+
function normalizeLocaleTag(value) {
|
|
1525
|
+
return value.trim().toLowerCase().replace(/_/g, "-");
|
|
1526
|
+
}
|
|
1527
|
+
function pickLocaleFamilyValue(localized, localeFamily) {
|
|
1528
|
+
const normalizedFamily = normalizeLocaleTag(localeFamily).split("-")[0];
|
|
1529
|
+
if (!normalizedFamily) {
|
|
1530
|
+
return void 0;
|
|
1531
|
+
}
|
|
1532
|
+
let familyMatch;
|
|
1533
|
+
for (const [locale, text] of Object.entries(localized)) {
|
|
1534
|
+
const normalizedLocale = normalizeLocaleTag(locale);
|
|
1535
|
+
if (!normalizedLocale) {
|
|
1536
|
+
continue;
|
|
1537
|
+
}
|
|
1538
|
+
if (normalizedLocale === normalizedFamily) {
|
|
1539
|
+
return text;
|
|
1540
|
+
}
|
|
1541
|
+
if (!familyMatch && normalizedLocale.startsWith(`${normalizedFamily}-`)) {
|
|
1542
|
+
familyMatch = text;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
return familyMatch;
|
|
1546
|
+
}
|
|
1547
|
+
function normalizeLocalizedTextMap(primaryText, localized, zhFallback) {
|
|
1548
|
+
const next = readLocalizedMap(localized);
|
|
1549
|
+
if (!next.en) {
|
|
1550
|
+
next.en = pickLocaleFamilyValue(next, "en") ?? primaryText;
|
|
1551
|
+
}
|
|
1552
|
+
if (!next.zh) {
|
|
1553
|
+
next.zh = pickLocaleFamilyValue(next, "zh") ?? (zhFallback && zhFallback.trim().length > 0 ? zhFallback.trim() : next.en);
|
|
1554
|
+
}
|
|
1555
|
+
return next;
|
|
1556
|
+
}
|
|
1557
|
+
function normalizeMarketplaceItemForUi(item) {
|
|
1558
|
+
const zhCopy = MARKETPLACE_ZH_COPY_BY_SLUG[item.slug];
|
|
1559
|
+
const next = {
|
|
1560
|
+
...item,
|
|
1561
|
+
summaryI18n: normalizeLocalizedTextMap(item.summary, item.summaryI18n, zhCopy?.summary)
|
|
1562
|
+
};
|
|
1563
|
+
if ("description" in item && typeof item.description === "string" && item.description.trim().length > 0) {
|
|
1564
|
+
next.descriptionI18n = normalizeLocalizedTextMap(
|
|
1565
|
+
item.description,
|
|
1566
|
+
item.descriptionI18n,
|
|
1567
|
+
zhCopy?.description
|
|
1568
|
+
);
|
|
1569
|
+
}
|
|
1570
|
+
return next;
|
|
1571
|
+
}
|
|
1290
1572
|
function toPositiveInt(raw, fallback) {
|
|
1291
1573
|
if (!raw) {
|
|
1292
1574
|
return fallback;
|
|
@@ -1314,6 +1596,192 @@ function isSupportedMarketplaceSkillItem(item, knownSkillNames) {
|
|
|
1314
1596
|
}
|
|
1315
1597
|
return item.install.kind === "builtin" && knownSkillNames.has(item.install.spec);
|
|
1316
1598
|
}
|
|
1599
|
+
function splitMarkdownFrontmatter(raw) {
|
|
1600
|
+
const normalized = raw.replace(/\r\n/g, "\n");
|
|
1601
|
+
const match = normalized.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
1602
|
+
if (!match) {
|
|
1603
|
+
return { bodyRaw: normalized };
|
|
1604
|
+
}
|
|
1605
|
+
return {
|
|
1606
|
+
metadataRaw: match[1]?.trim() || void 0,
|
|
1607
|
+
bodyRaw: match[2] ?? ""
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
async function loadLocalSkillMarkdown(options, skillName) {
|
|
1611
|
+
const config = loadConfigOrDefault(options.configPath);
|
|
1612
|
+
const loader = createSkillsLoader(getWorkspacePathFromConfig3(config));
|
|
1613
|
+
if (!loader) {
|
|
1614
|
+
return null;
|
|
1615
|
+
}
|
|
1616
|
+
const skillInfo = loader.listSkills(false).find((skill) => skill.name === skillName);
|
|
1617
|
+
if (!skillInfo) {
|
|
1618
|
+
return null;
|
|
1619
|
+
}
|
|
1620
|
+
try {
|
|
1621
|
+
const raw = await readFile(skillInfo.path, "utf-8");
|
|
1622
|
+
return {
|
|
1623
|
+
raw,
|
|
1624
|
+
source: skillInfo.source
|
|
1625
|
+
};
|
|
1626
|
+
} catch {
|
|
1627
|
+
return null;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
function parseGitSkillSpec(rawSpec) {
|
|
1631
|
+
const spec = rawSpec.trim();
|
|
1632
|
+
if (!spec) {
|
|
1633
|
+
return null;
|
|
1634
|
+
}
|
|
1635
|
+
const segments = spec.split("/").filter(Boolean);
|
|
1636
|
+
if (segments.length < 3) {
|
|
1637
|
+
return null;
|
|
1638
|
+
}
|
|
1639
|
+
return {
|
|
1640
|
+
owner: segments[0] ?? "",
|
|
1641
|
+
repo: segments[1] ?? "",
|
|
1642
|
+
skillPath: segments.slice(2).join("/")
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
async function fetchTextWithFallback(urls) {
|
|
1646
|
+
for (const url of urls) {
|
|
1647
|
+
try {
|
|
1648
|
+
const response = await fetch(url, {
|
|
1649
|
+
method: "GET",
|
|
1650
|
+
headers: {
|
|
1651
|
+
Accept: "text/plain, text/markdown, application/json"
|
|
1652
|
+
}
|
|
1653
|
+
});
|
|
1654
|
+
if (!response.ok) {
|
|
1655
|
+
continue;
|
|
1656
|
+
}
|
|
1657
|
+
const text = await response.text();
|
|
1658
|
+
if (text.trim().length === 0) {
|
|
1659
|
+
continue;
|
|
1660
|
+
}
|
|
1661
|
+
return { text, url };
|
|
1662
|
+
} catch {
|
|
1663
|
+
continue;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
return null;
|
|
1667
|
+
}
|
|
1668
|
+
async function loadGitSkillMarkdownFromSpec(rawSpec) {
|
|
1669
|
+
const parsed = parseGitSkillSpec(rawSpec);
|
|
1670
|
+
if (!parsed) {
|
|
1671
|
+
return null;
|
|
1672
|
+
}
|
|
1673
|
+
const candidates = [
|
|
1674
|
+
`https://raw.githubusercontent.com/${parsed.owner}/${parsed.repo}/main/${parsed.skillPath}/SKILL.md`,
|
|
1675
|
+
`https://raw.githubusercontent.com/${parsed.owner}/${parsed.repo}/master/${parsed.skillPath}/SKILL.md`
|
|
1676
|
+
];
|
|
1677
|
+
const result = await fetchTextWithFallback(candidates);
|
|
1678
|
+
if (!result) {
|
|
1679
|
+
return null;
|
|
1680
|
+
}
|
|
1681
|
+
return {
|
|
1682
|
+
raw: result.text,
|
|
1683
|
+
sourceUrl: result.url
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
async function loadPluginReadmeFromNpm(spec) {
|
|
1687
|
+
const encodedSpec = encodeURIComponent(spec);
|
|
1688
|
+
const registryUrl = `https://registry.npmjs.org/${encodedSpec}`;
|
|
1689
|
+
try {
|
|
1690
|
+
const response = await fetch(registryUrl, {
|
|
1691
|
+
headers: {
|
|
1692
|
+
Accept: "application/json"
|
|
1693
|
+
}
|
|
1694
|
+
});
|
|
1695
|
+
if (!response.ok) {
|
|
1696
|
+
return null;
|
|
1697
|
+
}
|
|
1698
|
+
const payload = await response.json();
|
|
1699
|
+
const readme = typeof payload.readme === "string" ? payload.readme : "";
|
|
1700
|
+
const latest = isRecord(payload["dist-tags"]) && typeof payload["dist-tags"].latest === "string" ? payload["dist-tags"].latest : void 0;
|
|
1701
|
+
const metadata = {
|
|
1702
|
+
name: typeof payload.name === "string" ? payload.name : spec,
|
|
1703
|
+
version: latest,
|
|
1704
|
+
description: typeof payload.description === "string" ? payload.description : void 0,
|
|
1705
|
+
homepage: typeof payload.homepage === "string" ? payload.homepage : void 0
|
|
1706
|
+
};
|
|
1707
|
+
if (readme.trim().length === 0) {
|
|
1708
|
+
return null;
|
|
1709
|
+
}
|
|
1710
|
+
return {
|
|
1711
|
+
readme,
|
|
1712
|
+
sourceUrl: registryUrl,
|
|
1713
|
+
metadataRaw: JSON.stringify(metadata, null, 2)
|
|
1714
|
+
};
|
|
1715
|
+
} catch {
|
|
1716
|
+
return null;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
async function buildSkillContentView(options, item) {
|
|
1720
|
+
const local = await loadLocalSkillMarkdown(options, item.install.spec);
|
|
1721
|
+
if (local) {
|
|
1722
|
+
const split = splitMarkdownFrontmatter(local.raw);
|
|
1723
|
+
return {
|
|
1724
|
+
type: "skill",
|
|
1725
|
+
slug: item.slug,
|
|
1726
|
+
name: item.name,
|
|
1727
|
+
install: item.install,
|
|
1728
|
+
source: local.source,
|
|
1729
|
+
raw: local.raw,
|
|
1730
|
+
metadataRaw: split.metadataRaw,
|
|
1731
|
+
bodyRaw: split.bodyRaw
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
if (item.install.kind === "git") {
|
|
1735
|
+
const remote = await loadGitSkillMarkdownFromSpec(item.install.spec);
|
|
1736
|
+
if (remote) {
|
|
1737
|
+
const split = splitMarkdownFrontmatter(remote.raw);
|
|
1738
|
+
return {
|
|
1739
|
+
type: "skill",
|
|
1740
|
+
slug: item.slug,
|
|
1741
|
+
name: item.name,
|
|
1742
|
+
install: item.install,
|
|
1743
|
+
source: "git",
|
|
1744
|
+
raw: remote.raw,
|
|
1745
|
+
metadataRaw: split.metadataRaw,
|
|
1746
|
+
bodyRaw: split.bodyRaw,
|
|
1747
|
+
sourceUrl: remote.sourceUrl
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
return null;
|
|
1752
|
+
}
|
|
1753
|
+
async function buildPluginContentView(item) {
|
|
1754
|
+
if (item.install.kind === "npm") {
|
|
1755
|
+
const npm = await loadPluginReadmeFromNpm(item.install.spec);
|
|
1756
|
+
if (npm) {
|
|
1757
|
+
return {
|
|
1758
|
+
type: "plugin",
|
|
1759
|
+
slug: item.slug,
|
|
1760
|
+
name: item.name,
|
|
1761
|
+
install: item.install,
|
|
1762
|
+
source: "npm",
|
|
1763
|
+
raw: npm.readme,
|
|
1764
|
+
bodyRaw: npm.readme,
|
|
1765
|
+
metadataRaw: npm.metadataRaw,
|
|
1766
|
+
sourceUrl: npm.sourceUrl
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
return {
|
|
1771
|
+
type: "plugin",
|
|
1772
|
+
slug: item.slug,
|
|
1773
|
+
name: item.name,
|
|
1774
|
+
install: item.install,
|
|
1775
|
+
source: "remote",
|
|
1776
|
+
bodyRaw: item.description || item.summary || "",
|
|
1777
|
+
metadataRaw: JSON.stringify({
|
|
1778
|
+
name: item.name,
|
|
1779
|
+
author: item.author,
|
|
1780
|
+
sourceRepo: item.sourceRepo,
|
|
1781
|
+
homepage: item.homepage
|
|
1782
|
+
}, null, 2)
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1317
1785
|
async function fetchAllMarketplaceItems(params) {
|
|
1318
1786
|
const allItems = [];
|
|
1319
1787
|
let remotePage = 1;
|
|
@@ -1492,7 +1960,7 @@ function registerPluginMarketplaceRoutes(app, options, marketplaceBaseUrl) {
|
|
|
1492
1960
|
if (!result.ok) {
|
|
1493
1961
|
return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
|
|
1494
1962
|
}
|
|
1495
|
-
const filteredItems = result.data.items.map((item) => sanitizeMarketplaceItem(item)).filter((item) => isSupportedMarketplacePluginItem(item));
|
|
1963
|
+
const filteredItems = result.data.items.map((item) => normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(item))).filter((item) => isSupportedMarketplacePluginItem(item));
|
|
1496
1964
|
const pageSize = Math.min(100, toPositiveInt(query.pageSize, 20));
|
|
1497
1965
|
const requestedPage = toPositiveInt(query.page, 1);
|
|
1498
1966
|
const totalPages = filteredItems.length === 0 ? 0 : Math.ceil(filteredItems.length / pageSize);
|
|
@@ -1516,12 +1984,28 @@ function registerPluginMarketplaceRoutes(app, options, marketplaceBaseUrl) {
|
|
|
1516
1984
|
if (!result.ok) {
|
|
1517
1985
|
return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
|
|
1518
1986
|
}
|
|
1519
|
-
const sanitized = sanitizeMarketplaceItem(result.data);
|
|
1987
|
+
const sanitized = normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(result.data));
|
|
1520
1988
|
if (!isSupportedMarketplacePluginItem(sanitized)) {
|
|
1521
1989
|
return c.json(err("NOT_FOUND", "marketplace item not supported by nextclaw"), 404);
|
|
1522
1990
|
}
|
|
1523
1991
|
return c.json(ok(sanitized));
|
|
1524
1992
|
});
|
|
1993
|
+
app.get("/api/marketplace/plugins/items/:slug/content", async (c) => {
|
|
1994
|
+
const slug = encodeURIComponent(c.req.param("slug"));
|
|
1995
|
+
const result = await fetchMarketplaceData({
|
|
1996
|
+
baseUrl: marketplaceBaseUrl,
|
|
1997
|
+
path: `/api/v1/plugins/items/${slug}`
|
|
1998
|
+
});
|
|
1999
|
+
if (!result.ok) {
|
|
2000
|
+
return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
|
|
2001
|
+
}
|
|
2002
|
+
const sanitized = normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(result.data));
|
|
2003
|
+
if (!isSupportedMarketplacePluginItem(sanitized)) {
|
|
2004
|
+
return c.json(err("NOT_FOUND", "marketplace item not supported by nextclaw"), 404);
|
|
2005
|
+
}
|
|
2006
|
+
const content = await buildPluginContentView(sanitized);
|
|
2007
|
+
return c.json(ok(content));
|
|
2008
|
+
});
|
|
1525
2009
|
app.post("/api/marketplace/plugins/install", async (c) => {
|
|
1526
2010
|
const body = await readJson(c.req.raw);
|
|
1527
2011
|
if (!body.ok || !body.data || typeof body.data !== "object") {
|
|
@@ -1585,7 +2069,7 @@ function registerPluginMarketplaceRoutes(app, options, marketplaceBaseUrl) {
|
|
|
1585
2069
|
if (!result.ok) {
|
|
1586
2070
|
return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
|
|
1587
2071
|
}
|
|
1588
|
-
const filteredItems = result.data.items.map((item) => sanitizeMarketplaceItem(item)).filter((item) => isSupportedMarketplacePluginItem(item));
|
|
2072
|
+
const filteredItems = result.data.items.map((item) => normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(item))).filter((item) => isSupportedMarketplacePluginItem(item));
|
|
1589
2073
|
return c.json(ok({
|
|
1590
2074
|
...result.data,
|
|
1591
2075
|
total: filteredItems.length,
|
|
@@ -1613,7 +2097,7 @@ function registerSkillMarketplaceRoutes(app, options, marketplaceBaseUrl) {
|
|
|
1613
2097
|
return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
|
|
1614
2098
|
}
|
|
1615
2099
|
const knownSkillNames = collectKnownSkillNames(options);
|
|
1616
|
-
const filteredItems = result.data.items.map((item) => sanitizeMarketplaceItem(item)).filter((item) => isSupportedMarketplaceSkillItem(item, knownSkillNames));
|
|
2100
|
+
const filteredItems = result.data.items.map((item) => normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(item))).filter((item) => isSupportedMarketplaceSkillItem(item, knownSkillNames));
|
|
1617
2101
|
const pageSize = Math.min(100, toPositiveInt(query.pageSize, 20));
|
|
1618
2102
|
const requestedPage = toPositiveInt(query.page, 1);
|
|
1619
2103
|
const totalPages = filteredItems.length === 0 ? 0 : Math.ceil(filteredItems.length / pageSize);
|
|
@@ -1638,12 +2122,32 @@ function registerSkillMarketplaceRoutes(app, options, marketplaceBaseUrl) {
|
|
|
1638
2122
|
return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
|
|
1639
2123
|
}
|
|
1640
2124
|
const knownSkillNames = collectKnownSkillNames(options);
|
|
1641
|
-
const sanitized = sanitizeMarketplaceItem(result.data);
|
|
2125
|
+
const sanitized = normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(result.data));
|
|
1642
2126
|
if (!isSupportedMarketplaceSkillItem(sanitized, knownSkillNames)) {
|
|
1643
2127
|
return c.json(err("NOT_FOUND", "marketplace item not supported by nextclaw"), 404);
|
|
1644
2128
|
}
|
|
1645
2129
|
return c.json(ok(sanitized));
|
|
1646
2130
|
});
|
|
2131
|
+
app.get("/api/marketplace/skills/items/:slug/content", async (c) => {
|
|
2132
|
+
const slug = encodeURIComponent(c.req.param("slug"));
|
|
2133
|
+
const result = await fetchMarketplaceData({
|
|
2134
|
+
baseUrl: marketplaceBaseUrl,
|
|
2135
|
+
path: `/api/v1/skills/items/${slug}`
|
|
2136
|
+
});
|
|
2137
|
+
if (!result.ok) {
|
|
2138
|
+
return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
|
|
2139
|
+
}
|
|
2140
|
+
const knownSkillNames = collectKnownSkillNames(options);
|
|
2141
|
+
const sanitized = normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(result.data));
|
|
2142
|
+
if (!isSupportedMarketplaceSkillItem(sanitized, knownSkillNames)) {
|
|
2143
|
+
return c.json(err("NOT_FOUND", "marketplace item not supported by nextclaw"), 404);
|
|
2144
|
+
}
|
|
2145
|
+
const content = await buildSkillContentView(options, sanitized);
|
|
2146
|
+
if (!content) {
|
|
2147
|
+
return c.json(err("NOT_FOUND", "skill markdown content not found"), 404);
|
|
2148
|
+
}
|
|
2149
|
+
return c.json(ok(content));
|
|
2150
|
+
});
|
|
1647
2151
|
app.post("/api/marketplace/skills/install", async (c) => {
|
|
1648
2152
|
const body = await readJson(c.req.raw);
|
|
1649
2153
|
if (!body.ok || !body.data || typeof body.data !== "object") {
|
|
@@ -1708,7 +2212,7 @@ function registerSkillMarketplaceRoutes(app, options, marketplaceBaseUrl) {
|
|
|
1708
2212
|
return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
|
|
1709
2213
|
}
|
|
1710
2214
|
const knownSkillNames = collectKnownSkillNames(options);
|
|
1711
|
-
const filteredItems = result.data.items.map((item) => sanitizeMarketplaceItem(item)).filter((item) => isSupportedMarketplaceSkillItem(item, knownSkillNames));
|
|
2215
|
+
const filteredItems = result.data.items.map((item) => normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(item))).filter((item) => isSupportedMarketplaceSkillItem(item, knownSkillNames));
|
|
1712
2216
|
return c.json(ok({
|
|
1713
2217
|
...result.data,
|
|
1714
2218
|
total: filteredItems.length,
|
|
@@ -1775,6 +2279,33 @@ function createUiRouter(options) {
|
|
|
1775
2279
|
options.publish({ type: "config.updated", payload: { path: `providers.${provider}` } });
|
|
1776
2280
|
return c.json(ok(result));
|
|
1777
2281
|
});
|
|
2282
|
+
app.post("/api/config/providers", async (c) => {
|
|
2283
|
+
const body = await readJson(c.req.raw);
|
|
2284
|
+
if (!body.ok) {
|
|
2285
|
+
return c.json(err("INVALID_BODY", "invalid json body"), 400);
|
|
2286
|
+
}
|
|
2287
|
+
const result = createCustomProvider(
|
|
2288
|
+
options.configPath,
|
|
2289
|
+
body.data
|
|
2290
|
+
);
|
|
2291
|
+
options.publish({ type: "config.updated", payload: { path: `providers.${result.name}` } });
|
|
2292
|
+
return c.json(ok({
|
|
2293
|
+
name: result.name,
|
|
2294
|
+
provider: result.provider
|
|
2295
|
+
}));
|
|
2296
|
+
});
|
|
2297
|
+
app.delete("/api/config/providers/:provider", async (c) => {
|
|
2298
|
+
const provider = c.req.param("provider");
|
|
2299
|
+
const result = deleteCustomProvider(options.configPath, provider);
|
|
2300
|
+
if (result === null) {
|
|
2301
|
+
return c.json(err("NOT_FOUND", `custom provider not found: ${provider}`), 404);
|
|
2302
|
+
}
|
|
2303
|
+
options.publish({ type: "config.updated", payload: { path: `providers.${provider}` } });
|
|
2304
|
+
return c.json(ok({
|
|
2305
|
+
deleted: true,
|
|
2306
|
+
provider
|
|
2307
|
+
}));
|
|
2308
|
+
});
|
|
1778
2309
|
app.post("/api/config/providers/:provider/test", async (c) => {
|
|
1779
2310
|
const provider = c.req.param("provider");
|
|
1780
2311
|
const body = await readJson(c.req.raw);
|
|
@@ -2153,7 +2684,7 @@ function startUiServer(options) {
|
|
|
2153
2684
|
join,
|
|
2154
2685
|
getContent: async (path) => {
|
|
2155
2686
|
try {
|
|
2156
|
-
return await
|
|
2687
|
+
return await readFile2(path);
|
|
2157
2688
|
} catch {
|
|
2158
2689
|
return null;
|
|
2159
2690
|
}
|
|
@@ -2203,7 +2734,9 @@ export {
|
|
|
2203
2734
|
buildConfigMeta,
|
|
2204
2735
|
buildConfigSchemaView,
|
|
2205
2736
|
buildConfigView,
|
|
2737
|
+
createCustomProvider,
|
|
2206
2738
|
createUiRouter,
|
|
2739
|
+
deleteCustomProvider,
|
|
2207
2740
|
deleteSession,
|
|
2208
2741
|
executeConfigAction,
|
|
2209
2742
|
getSessionHistory,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/server",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.27",
|
|
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.
|
|
18
|
+
"@nextclaw/openclaw-compat": "^0.1.32",
|
|
19
19
|
"hono": "^4.6.2",
|
|
20
20
|
"ws": "^8.18.0",
|
|
21
|
-
"@nextclaw/core": "^0.6.
|
|
21
|
+
"@nextclaw/core": "^0.6.43"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/node": "^20.17.6",
|