@nextclaw/server 0.6.4 → 0.6.6
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 -9
- package/dist/index.js +469 -206
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -57,6 +57,7 @@ type ProviderConnectionTestResult = {
|
|
|
57
57
|
type ProviderAuthStartResult = {
|
|
58
58
|
provider: string;
|
|
59
59
|
kind: "device_code";
|
|
60
|
+
methodId?: string;
|
|
60
61
|
sessionId: string;
|
|
61
62
|
verificationUri: string;
|
|
62
63
|
userCode: string;
|
|
@@ -64,6 +65,9 @@ type ProviderAuthStartResult = {
|
|
|
64
65
|
intervalMs: number;
|
|
65
66
|
note?: string;
|
|
66
67
|
};
|
|
68
|
+
type ProviderAuthStartRequest = {
|
|
69
|
+
methodId?: string;
|
|
70
|
+
};
|
|
67
71
|
type ProviderAuthPollRequest = {
|
|
68
72
|
sessionId: string;
|
|
69
73
|
};
|
|
@@ -296,6 +300,21 @@ type ChatCapabilitiesView = {
|
|
|
296
300
|
stopSupported: boolean;
|
|
297
301
|
stopReason?: string;
|
|
298
302
|
};
|
|
303
|
+
type ChatCommandOptionView = {
|
|
304
|
+
name: string;
|
|
305
|
+
description: string;
|
|
306
|
+
type: "string" | "boolean" | "number";
|
|
307
|
+
required?: boolean;
|
|
308
|
+
};
|
|
309
|
+
type ChatCommandView = {
|
|
310
|
+
name: string;
|
|
311
|
+
description: string;
|
|
312
|
+
options?: ChatCommandOptionView[];
|
|
313
|
+
};
|
|
314
|
+
type ChatCommandsView = {
|
|
315
|
+
commands: ChatCommandView[];
|
|
316
|
+
total: number;
|
|
317
|
+
};
|
|
299
318
|
type ChatTurnStopRequest = {
|
|
300
319
|
runId: string;
|
|
301
320
|
sessionKey?: string;
|
|
@@ -402,6 +421,18 @@ type ProviderSpecView = {
|
|
|
402
421
|
en?: string;
|
|
403
422
|
zh?: string;
|
|
404
423
|
};
|
|
424
|
+
methods?: Array<{
|
|
425
|
+
id: string;
|
|
426
|
+
label?: {
|
|
427
|
+
en?: string;
|
|
428
|
+
zh?: string;
|
|
429
|
+
};
|
|
430
|
+
hint?: {
|
|
431
|
+
en?: string;
|
|
432
|
+
zh?: string;
|
|
433
|
+
};
|
|
434
|
+
}>;
|
|
435
|
+
defaultMethodId?: string;
|
|
405
436
|
supportsCliImport?: boolean;
|
|
406
437
|
};
|
|
407
438
|
defaultModels?: string[];
|
|
@@ -491,7 +522,9 @@ type ConfigActionExecuteResult = {
|
|
|
491
522
|
};
|
|
492
523
|
type MarketplaceItemType = "plugin" | "skill";
|
|
493
524
|
type MarketplaceSort = "relevance" | "updated";
|
|
494
|
-
type
|
|
525
|
+
type MarketplacePluginInstallKind = "npm";
|
|
526
|
+
type MarketplaceSkillInstallKind = "builtin" | "marketplace";
|
|
527
|
+
type MarketplaceInstallKind = MarketplacePluginInstallKind | MarketplaceSkillInstallKind;
|
|
495
528
|
type MarketplaceInstallSpec = {
|
|
496
529
|
kind: MarketplaceInstallKind;
|
|
497
530
|
spec: string;
|
|
@@ -522,7 +555,7 @@ type MarketplaceSkillContentView = {
|
|
|
522
555
|
slug: string;
|
|
523
556
|
name: string;
|
|
524
557
|
install: MarketplaceInstallSpec;
|
|
525
|
-
source: "
|
|
558
|
+
source: "builtin" | "marketplace" | "remote";
|
|
526
559
|
raw: string;
|
|
527
560
|
metadataRaw?: string;
|
|
528
561
|
bodyRaw: string;
|
|
@@ -578,11 +611,9 @@ type MarketplaceInstalledView = {
|
|
|
578
611
|
};
|
|
579
612
|
type MarketplaceInstallSkillParams = {
|
|
580
613
|
slug: string;
|
|
581
|
-
kind?:
|
|
614
|
+
kind?: MarketplaceSkillInstallKind;
|
|
582
615
|
skill?: string;
|
|
583
616
|
installPath?: string;
|
|
584
|
-
version?: string;
|
|
585
|
-
registry?: string;
|
|
586
617
|
force?: boolean;
|
|
587
618
|
};
|
|
588
619
|
type MarketplacePluginInstallRequest = {
|
|
@@ -592,11 +623,9 @@ type MarketplacePluginInstallRequest = {
|
|
|
592
623
|
type MarketplaceSkillInstallRequest = {
|
|
593
624
|
type?: "skill";
|
|
594
625
|
spec: string;
|
|
595
|
-
kind?:
|
|
626
|
+
kind?: MarketplaceSkillInstallKind;
|
|
596
627
|
skill?: string;
|
|
597
628
|
installPath?: string;
|
|
598
|
-
version?: string;
|
|
599
|
-
registry?: string;
|
|
600
629
|
force?: boolean;
|
|
601
630
|
};
|
|
602
631
|
type MarketplacePluginInstallResult = {
|
|
@@ -679,6 +708,11 @@ type UiServerEvent = {
|
|
|
679
708
|
payload: {
|
|
680
709
|
run: ChatRunView;
|
|
681
710
|
};
|
|
711
|
+
} | {
|
|
712
|
+
type: "session.updated";
|
|
713
|
+
payload: {
|
|
714
|
+
sessionKey: string;
|
|
715
|
+
};
|
|
682
716
|
} | {
|
|
683
717
|
type: "config.reload.started";
|
|
684
718
|
payload?: Record<string, unknown>;
|
|
@@ -758,4 +792,4 @@ declare function deleteSession(configPath: string, key: string): boolean;
|
|
|
758
792
|
declare function updateRuntime(configPath: string, patch: RuntimeConfigUpdate): Pick<ConfigView, "agents" | "bindings" | "session">;
|
|
759
793
|
declare function updateSecrets(configPath: string, patch: SecretsConfigUpdate): SecretsView;
|
|
760
794
|
|
|
761
|
-
export { type AgentBindingView, type AgentProfileView, type ApiError, type ApiResponse, type AppMetaView, type BindingPeerView, type ChannelSpecView, type ChatCapabilitiesView, type ChatRunListView, type ChatRunState, type ChatRunView, type ChatTurnRequest, type ChatTurnResult, type ChatTurnStopRequest, type ChatTurnStopResult, 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 ProviderAuthImportResult, type ProviderAuthPollRequest, type ProviderAuthPollResult, type ProviderAuthStartResult, 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 };
|
|
795
|
+
export { type AgentBindingView, type AgentProfileView, type ApiError, type ApiResponse, type AppMetaView, type BindingPeerView, type ChannelSpecView, type ChatCapabilitiesView, type ChatCommandOptionView, type ChatCommandView, type ChatCommandsView, type ChatRunListView, type ChatRunState, type ChatRunView, type ChatTurnRequest, type ChatTurnResult, type ChatTurnStopRequest, type ChatTurnStopResult, 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 MarketplacePluginInstallKind, type MarketplacePluginInstallRequest, type MarketplacePluginInstallResult, type MarketplacePluginManageAction, type MarketplacePluginManageRequest, type MarketplacePluginManageResult, type MarketplaceRecommendationView, type MarketplaceSkillContentView, type MarketplaceSkillInstallKind, type MarketplaceSkillInstallRequest, type MarketplaceSkillInstallResult, type MarketplaceSkillManageAction, type MarketplaceSkillManageRequest, type MarketplaceSkillManageResult, type MarketplaceSort, type ProviderAuthImportResult, type ProviderAuthPollRequest, type ProviderAuthPollResult, type ProviderAuthStartRequest, type ProviderAuthStartResult, 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,12 +5,11 @@ 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 as
|
|
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 as readFile2 } from "fs/promises";
|
|
14
13
|
import * as NextclawCore from "@nextclaw/core";
|
|
15
14
|
import { buildPluginStatusReport } from "@nextclaw/openclaw-compat";
|
|
16
15
|
|
|
@@ -29,7 +28,96 @@ import {
|
|
|
29
28
|
SessionManager,
|
|
30
29
|
getWorkspacePathFromConfig
|
|
31
30
|
} from "@nextclaw/core";
|
|
31
|
+
|
|
32
|
+
// src/ui/provider-overrides.ts
|
|
32
33
|
import { findBuiltinProviderByName, listBuiltinProviders } from "@nextclaw/runtime";
|
|
34
|
+
var MINIMAX_PORTAL_PROVIDER_SPEC = {
|
|
35
|
+
name: "minimax-portal",
|
|
36
|
+
keywords: ["minimax-portal", "minimax"],
|
|
37
|
+
envKey: "MINIMAX_PORTAL_TOKEN",
|
|
38
|
+
displayName: "MiniMax Portal",
|
|
39
|
+
modelPrefix: "minimax-portal",
|
|
40
|
+
litellmPrefix: "minimax-portal",
|
|
41
|
+
skipPrefixes: ["minimax-portal/"],
|
|
42
|
+
envExtras: [],
|
|
43
|
+
isGateway: false,
|
|
44
|
+
isLocal: false,
|
|
45
|
+
detectByKeyPrefix: "",
|
|
46
|
+
detectByBaseKeyword: "",
|
|
47
|
+
defaultApiBase: "https://api.minimax.io/v1",
|
|
48
|
+
defaultModels: ["minimax-portal/MiniMax-M2.5", "minimax-portal/MiniMax-M2.5-highspeed"],
|
|
49
|
+
stripModelPrefix: false,
|
|
50
|
+
modelOverrides: [],
|
|
51
|
+
logo: "minimax.svg",
|
|
52
|
+
apiBaseHelp: {
|
|
53
|
+
zh: "OAuth Global \u9ED8\u8BA4\u4F7F\u7528 https://api.minimax.io/v1\uFF1BOAuth \u4E2D\u56FD\u533A\u9ED8\u8BA4\u4F7F\u7528 https://api.minimaxi.com/v1\u3002",
|
|
54
|
+
en: "OAuth Global uses https://api.minimax.io/v1 by default; OAuth CN uses https://api.minimaxi.com/v1."
|
|
55
|
+
},
|
|
56
|
+
auth: {
|
|
57
|
+
kind: "device_code",
|
|
58
|
+
protocol: "minimax_user_code",
|
|
59
|
+
displayName: "MiniMax OAuth",
|
|
60
|
+
baseUrl: "https://api.minimax.io",
|
|
61
|
+
deviceCodePath: "/oauth/code",
|
|
62
|
+
tokenPath: "/oauth/token",
|
|
63
|
+
clientId: "78257093-7e40-4613-99e0-527b14b39113",
|
|
64
|
+
scope: "group_id profile model.completion",
|
|
65
|
+
grantType: "urn:ietf:params:oauth:grant-type:user_code",
|
|
66
|
+
usePkce: true,
|
|
67
|
+
defaultMethodId: "cn",
|
|
68
|
+
methods: [
|
|
69
|
+
{
|
|
70
|
+
id: "global",
|
|
71
|
+
label: {
|
|
72
|
+
zh: "Global\uFF08\u6D77\u5916\uFF09",
|
|
73
|
+
en: "Global"
|
|
74
|
+
},
|
|
75
|
+
hint: {
|
|
76
|
+
zh: "\u9002\u7528\u4E8E\u6D77\u5916\u7528\u6237\uFF0C\u9ED8\u8BA4 API Base \u4E3A https://api.minimax.io/v1\u3002",
|
|
77
|
+
en: "For international users. Default API base: https://api.minimax.io/v1."
|
|
78
|
+
},
|
|
79
|
+
baseUrl: "https://api.minimax.io",
|
|
80
|
+
defaultApiBase: "https://api.minimax.io/v1"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "cn",
|
|
84
|
+
label: {
|
|
85
|
+
zh: "\u4E2D\u56FD\u533A\uFF08CN\uFF09",
|
|
86
|
+
en: "China Mainland (CN)"
|
|
87
|
+
},
|
|
88
|
+
hint: {
|
|
89
|
+
zh: "\u9002\u7528\u4E8E\u4E2D\u56FD\u533A\u7528\u6237\uFF0C\u9ED8\u8BA4 API Base \u4E3A https://api.minimaxi.com/v1\u3002",
|
|
90
|
+
en: "For Mainland China users. Default API base: https://api.minimaxi.com/v1."
|
|
91
|
+
},
|
|
92
|
+
baseUrl: "https://api.minimaxi.com",
|
|
93
|
+
defaultApiBase: "https://api.minimaxi.com/v1"
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
note: {
|
|
97
|
+
zh: "\u901A\u8FC7\u6D4F\u89C8\u5668\u5B8C\u6210 MiniMax OAuth \u6388\u6743\u540E\u5373\u53EF\u4F7F\u7528\uFF0C\u65E0\u9700\u624B\u52A8\u586B\u5199 API Key\u3002",
|
|
98
|
+
en: "Complete MiniMax OAuth in browser to use this provider without manually entering an API key."
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
var SERVER_BUILTIN_PROVIDER_OVERRIDES = [MINIMAX_PORTAL_PROVIDER_SPEC];
|
|
103
|
+
var SERVER_BUILTIN_PROVIDER_OVERRIDE_MAP = new Map(
|
|
104
|
+
SERVER_BUILTIN_PROVIDER_OVERRIDES.map((provider) => [provider.name, provider])
|
|
105
|
+
);
|
|
106
|
+
function listServerBuiltinProviders() {
|
|
107
|
+
const merged = /* @__PURE__ */ new Map();
|
|
108
|
+
for (const provider of listBuiltinProviders()) {
|
|
109
|
+
merged.set(provider.name, provider);
|
|
110
|
+
}
|
|
111
|
+
for (const provider of SERVER_BUILTIN_PROVIDER_OVERRIDES) {
|
|
112
|
+
merged.set(provider.name, provider);
|
|
113
|
+
}
|
|
114
|
+
return Array.from(merged.values());
|
|
115
|
+
}
|
|
116
|
+
function findServerBuiltinProviderByName(name) {
|
|
117
|
+
return SERVER_BUILTIN_PROVIDER_OVERRIDE_MAP.get(name) ?? findBuiltinProviderByName(name);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/ui/config.ts
|
|
33
121
|
var MASK_MIN_LENGTH = 8;
|
|
34
122
|
var EXTRA_SENSITIVE_PATH_PATTERNS = [/authorization/i, /cookie/i, /session/i, /bearer/i];
|
|
35
123
|
var PREFERRED_PROVIDER_ORDER = [
|
|
@@ -47,7 +135,7 @@ var PREFERRED_PROVIDER_ORDER = [
|
|
|
47
135
|
var PREFERRED_PROVIDER_ORDER_INDEX = new Map(
|
|
48
136
|
PREFERRED_PROVIDER_ORDER.map((name, index) => [name, index])
|
|
49
137
|
);
|
|
50
|
-
var BUILTIN_PROVIDERS =
|
|
138
|
+
var BUILTIN_PROVIDERS = listServerBuiltinProviders();
|
|
51
139
|
var BUILTIN_PROVIDER_NAMES = new Set(BUILTIN_PROVIDERS.map((spec) => spec.name));
|
|
52
140
|
var CUSTOM_PROVIDER_WIRE_API_OPTIONS = ["auto", "chat", "responses"];
|
|
53
141
|
var CUSTOM_PROVIDER_PREFIX = "custom-";
|
|
@@ -108,7 +196,7 @@ function ensureProviderConfig(config, providerName) {
|
|
|
108
196
|
if (isCustomProviderName(providerName)) {
|
|
109
197
|
return null;
|
|
110
198
|
}
|
|
111
|
-
const spec =
|
|
199
|
+
const spec = findServerBuiltinProviderByName(providerName);
|
|
112
200
|
if (!spec) {
|
|
113
201
|
return null;
|
|
114
202
|
}
|
|
@@ -376,7 +464,7 @@ function buildConfigView(config) {
|
|
|
376
464
|
const uiHints = buildUiHints(config);
|
|
377
465
|
const providers = {};
|
|
378
466
|
for (const [name, provider] of Object.entries(config.providers)) {
|
|
379
|
-
const spec =
|
|
467
|
+
const spec = findServerBuiltinProviderByName(name);
|
|
380
468
|
providers[name] = toProviderView(config, provider, name, uiHints, spec);
|
|
381
469
|
}
|
|
382
470
|
return {
|
|
@@ -425,6 +513,12 @@ function buildConfigMeta(config) {
|
|
|
425
513
|
kind: spec.auth.kind,
|
|
426
514
|
displayName: spec.auth.displayName,
|
|
427
515
|
note: spec.auth.note,
|
|
516
|
+
methods: spec.auth.methods?.map((method) => ({
|
|
517
|
+
id: method.id,
|
|
518
|
+
label: method.label,
|
|
519
|
+
hint: method.hint
|
|
520
|
+
})),
|
|
521
|
+
defaultMethodId: spec.auth.defaultMethodId,
|
|
428
522
|
supportsCliImport: Boolean(spec.auth.cliCredential)
|
|
429
523
|
} : void 0,
|
|
430
524
|
defaultModels: normalizeModelList(spec.defaultModels ?? []),
|
|
@@ -555,7 +649,7 @@ function updateProvider(configPath, providerName, patch) {
|
|
|
555
649
|
if (!provider) {
|
|
556
650
|
return null;
|
|
557
651
|
}
|
|
558
|
-
const spec =
|
|
652
|
+
const spec = findServerBuiltinProviderByName(providerName);
|
|
559
653
|
const isCustom = isCustomProviderName(providerName);
|
|
560
654
|
if (Object.prototype.hasOwnProperty.call(patch, "displayName") && isCustom) {
|
|
561
655
|
provider.displayName = normalizeOptionalDisplayName(patch.displayName) ?? "";
|
|
@@ -690,7 +784,7 @@ async function testProviderConnection(configPath, providerName, patch) {
|
|
|
690
784
|
if (!provider) {
|
|
691
785
|
return null;
|
|
692
786
|
}
|
|
693
|
-
const spec =
|
|
787
|
+
const spec = findServerBuiltinProviderByName(providerName);
|
|
694
788
|
const hasApiKeyPatch = Object.prototype.hasOwnProperty.call(patch, "apiKey");
|
|
695
789
|
const providedApiKey = normalizeOptionalString(patch.apiKey);
|
|
696
790
|
const currentApiKey = normalizeOptionalString(provider.apiKey);
|
|
@@ -1038,7 +1132,6 @@ import {
|
|
|
1038
1132
|
loadConfig as loadConfig2,
|
|
1039
1133
|
saveConfig as saveConfig2
|
|
1040
1134
|
} from "@nextclaw/core";
|
|
1041
|
-
import { findBuiltinProviderByName as findBuiltinProviderByName2 } from "@nextclaw/runtime";
|
|
1042
1135
|
var authSessions = /* @__PURE__ */ new Map();
|
|
1043
1136
|
var DEFAULT_AUTH_INTERVAL_MS = 2e3;
|
|
1044
1137
|
var MAX_AUTH_INTERVAL_MS = 1e4;
|
|
@@ -1048,6 +1141,12 @@ function normalizePositiveInt(value, fallback) {
|
|
|
1048
1141
|
}
|
|
1049
1142
|
return Math.floor(value);
|
|
1050
1143
|
}
|
|
1144
|
+
function normalizePositiveFloat(value) {
|
|
1145
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
1146
|
+
return null;
|
|
1147
|
+
}
|
|
1148
|
+
return value;
|
|
1149
|
+
}
|
|
1051
1150
|
function toBase64Url(buffer) {
|
|
1052
1151
|
return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
1053
1152
|
}
|
|
@@ -1074,6 +1173,113 @@ function resolveDeviceCodeEndpoints(baseUrl, deviceCodePath, tokenPath) {
|
|
|
1074
1173
|
function resolveAuthNote(params) {
|
|
1075
1174
|
return params.zh ?? params.en;
|
|
1076
1175
|
}
|
|
1176
|
+
function resolveLocalizedMethodLabel(method, fallbackId) {
|
|
1177
|
+
return method.label?.zh ?? method.label?.en ?? fallbackId;
|
|
1178
|
+
}
|
|
1179
|
+
function resolveLocalizedMethodHint(method) {
|
|
1180
|
+
return method.hint?.zh ?? method.hint?.en;
|
|
1181
|
+
}
|
|
1182
|
+
function normalizeMethodId(value) {
|
|
1183
|
+
if (typeof value !== "string") {
|
|
1184
|
+
return void 0;
|
|
1185
|
+
}
|
|
1186
|
+
const trimmed = value.trim();
|
|
1187
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
1188
|
+
}
|
|
1189
|
+
function resolveAuthMethod(auth, requestedMethodId) {
|
|
1190
|
+
const protocol = auth.protocol ?? "rfc8628";
|
|
1191
|
+
const methods = (auth.methods ?? []).filter((entry) => normalizeMethodId(entry.id));
|
|
1192
|
+
const cleanRequestedMethodId = normalizeMethodId(requestedMethodId);
|
|
1193
|
+
if (methods.length === 0) {
|
|
1194
|
+
if (cleanRequestedMethodId) {
|
|
1195
|
+
throw new Error(`provider auth method is not supported: ${cleanRequestedMethodId}`);
|
|
1196
|
+
}
|
|
1197
|
+
return {
|
|
1198
|
+
protocol,
|
|
1199
|
+
baseUrl: auth.baseUrl,
|
|
1200
|
+
deviceCodePath: auth.deviceCodePath,
|
|
1201
|
+
tokenPath: auth.tokenPath,
|
|
1202
|
+
clientId: auth.clientId,
|
|
1203
|
+
scope: auth.scope,
|
|
1204
|
+
grantType: auth.grantType,
|
|
1205
|
+
usePkce: Boolean(auth.usePkce)
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
let selectedMethod = methods.find((entry) => normalizeMethodId(entry.id) === cleanRequestedMethodId);
|
|
1209
|
+
if (!selectedMethod) {
|
|
1210
|
+
const fallbackMethodId = normalizeMethodId(auth.defaultMethodId) ?? normalizeMethodId(methods[0]?.id);
|
|
1211
|
+
selectedMethod = methods.find((entry) => normalizeMethodId(entry.id) === fallbackMethodId) ?? methods[0];
|
|
1212
|
+
}
|
|
1213
|
+
const methodId = normalizeMethodId(selectedMethod?.id);
|
|
1214
|
+
if (!selectedMethod || !methodId) {
|
|
1215
|
+
throw new Error("provider auth method is not configured");
|
|
1216
|
+
}
|
|
1217
|
+
if (cleanRequestedMethodId && methodId !== cleanRequestedMethodId) {
|
|
1218
|
+
throw new Error(`provider auth method is not supported: ${cleanRequestedMethodId}`);
|
|
1219
|
+
}
|
|
1220
|
+
return {
|
|
1221
|
+
id: methodId,
|
|
1222
|
+
protocol,
|
|
1223
|
+
baseUrl: selectedMethod.baseUrl ?? auth.baseUrl,
|
|
1224
|
+
deviceCodePath: selectedMethod.deviceCodePath ?? auth.deviceCodePath,
|
|
1225
|
+
tokenPath: selectedMethod.tokenPath ?? auth.tokenPath,
|
|
1226
|
+
clientId: selectedMethod.clientId ?? auth.clientId,
|
|
1227
|
+
scope: selectedMethod.scope ?? auth.scope,
|
|
1228
|
+
grantType: selectedMethod.grantType ?? auth.grantType,
|
|
1229
|
+
usePkce: selectedMethod.usePkce ?? Boolean(auth.usePkce),
|
|
1230
|
+
defaultApiBase: selectedMethod.defaultApiBase
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
function parseExpiresAtMs(value, fallbackFromNowMs) {
|
|
1234
|
+
const normalized = normalizePositiveFloat(value);
|
|
1235
|
+
if (normalized === null) {
|
|
1236
|
+
return Date.now() + fallbackFromNowMs;
|
|
1237
|
+
}
|
|
1238
|
+
if (normalized >= 1e12) {
|
|
1239
|
+
return Math.floor(normalized);
|
|
1240
|
+
}
|
|
1241
|
+
if (normalized >= 1e9) {
|
|
1242
|
+
return Math.floor(normalized * 1e3);
|
|
1243
|
+
}
|
|
1244
|
+
return Date.now() + Math.floor(normalized * 1e3);
|
|
1245
|
+
}
|
|
1246
|
+
function parsePollIntervalMs(value, fallbackMs) {
|
|
1247
|
+
const normalized = normalizePositiveFloat(value);
|
|
1248
|
+
if (normalized === null) {
|
|
1249
|
+
return fallbackMs;
|
|
1250
|
+
}
|
|
1251
|
+
if (normalized <= 30) {
|
|
1252
|
+
return Math.floor(normalized * 1e3);
|
|
1253
|
+
}
|
|
1254
|
+
return Math.floor(normalized);
|
|
1255
|
+
}
|
|
1256
|
+
function buildMinimaxErrorMessage(payload, fallback) {
|
|
1257
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
1258
|
+
return fallback;
|
|
1259
|
+
}
|
|
1260
|
+
const record = payload;
|
|
1261
|
+
if (typeof record.error_description === "string" && record.error_description.trim()) {
|
|
1262
|
+
return record.error_description.trim();
|
|
1263
|
+
}
|
|
1264
|
+
if (typeof record.error === "string" && record.error.trim()) {
|
|
1265
|
+
return record.error.trim();
|
|
1266
|
+
}
|
|
1267
|
+
const baseMessage = record.base_resp?.status_msg;
|
|
1268
|
+
if (typeof baseMessage === "string" && baseMessage.trim()) {
|
|
1269
|
+
return baseMessage.trim();
|
|
1270
|
+
}
|
|
1271
|
+
return fallback;
|
|
1272
|
+
}
|
|
1273
|
+
function classifyMiniMaxErrorStatus(message) {
|
|
1274
|
+
const normalized = message.toLowerCase();
|
|
1275
|
+
if (normalized.includes("deny") || normalized.includes("rejected")) {
|
|
1276
|
+
return "denied";
|
|
1277
|
+
}
|
|
1278
|
+
if (normalized.includes("expired") || normalized.includes("timeout") || normalized.includes("timed out")) {
|
|
1279
|
+
return "expired";
|
|
1280
|
+
}
|
|
1281
|
+
return "error";
|
|
1282
|
+
}
|
|
1077
1283
|
function resolveHomePath(inputPath) {
|
|
1078
1284
|
const trimmed = inputPath.trim();
|
|
1079
1285
|
if (!trimmed) {
|
|
@@ -1138,70 +1344,125 @@ function setProviderApiKey(params) {
|
|
|
1138
1344
|
const next = ConfigSchema2.parse(config);
|
|
1139
1345
|
saveConfig2(next, params.configPath);
|
|
1140
1346
|
}
|
|
1141
|
-
async function startProviderAuth(configPath, providerName) {
|
|
1347
|
+
async function startProviderAuth(configPath, providerName, options) {
|
|
1142
1348
|
cleanupExpiredAuthSessions();
|
|
1143
|
-
const spec =
|
|
1349
|
+
const spec = findServerBuiltinProviderByName(providerName);
|
|
1144
1350
|
if (!spec?.auth || spec.auth.kind !== "device_code") {
|
|
1145
1351
|
return null;
|
|
1146
1352
|
}
|
|
1353
|
+
const resolvedMethod = resolveAuthMethod(spec.auth, options?.methodId);
|
|
1147
1354
|
const { deviceCodeEndpoint, tokenEndpoint } = resolveDeviceCodeEndpoints(
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1355
|
+
resolvedMethod.baseUrl,
|
|
1356
|
+
resolvedMethod.deviceCodePath,
|
|
1357
|
+
resolvedMethod.tokenPath
|
|
1151
1358
|
);
|
|
1152
|
-
const pkce =
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1359
|
+
const pkce = resolvedMethod.usePkce ? buildPkce() : null;
|
|
1360
|
+
let authorizationCode = "";
|
|
1361
|
+
let tokenCodeField = "device_code";
|
|
1362
|
+
let userCode = "";
|
|
1363
|
+
let verificationUri = "";
|
|
1364
|
+
let intervalMs = DEFAULT_AUTH_INTERVAL_MS;
|
|
1365
|
+
let expiresAtMs = Date.now() + 6e5;
|
|
1366
|
+
if (resolvedMethod.protocol === "minimax_user_code") {
|
|
1367
|
+
if (!pkce) {
|
|
1368
|
+
throw new Error("MiniMax OAuth requires PKCE");
|
|
1369
|
+
}
|
|
1370
|
+
const state = toBase64Url(randomBytes(16));
|
|
1371
|
+
const body = new URLSearchParams({
|
|
1372
|
+
response_type: "code",
|
|
1373
|
+
client_id: resolvedMethod.clientId,
|
|
1374
|
+
scope: resolvedMethod.scope,
|
|
1375
|
+
code_challenge: pkce.challenge,
|
|
1376
|
+
code_challenge_method: "S256",
|
|
1377
|
+
state
|
|
1378
|
+
});
|
|
1379
|
+
const response = await fetch(deviceCodeEndpoint, {
|
|
1380
|
+
method: "POST",
|
|
1381
|
+
headers: {
|
|
1382
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1383
|
+
Accept: "application/json",
|
|
1384
|
+
"x-request-id": randomUUID()
|
|
1385
|
+
},
|
|
1386
|
+
body
|
|
1387
|
+
});
|
|
1388
|
+
const payload = await response.json().catch(() => ({}));
|
|
1389
|
+
if (!response.ok) {
|
|
1390
|
+
throw new Error(buildMinimaxErrorMessage(payload, response.statusText || "MiniMax OAuth start failed"));
|
|
1391
|
+
}
|
|
1392
|
+
if (payload.state && payload.state !== state) {
|
|
1393
|
+
throw new Error("MiniMax OAuth state mismatch");
|
|
1394
|
+
}
|
|
1395
|
+
authorizationCode = payload.user_code?.trim() ?? "";
|
|
1396
|
+
userCode = authorizationCode;
|
|
1397
|
+
verificationUri = payload.verification_uri?.trim() ?? "";
|
|
1398
|
+
if (!authorizationCode || !verificationUri) {
|
|
1399
|
+
throw new Error("provider auth payload is incomplete");
|
|
1400
|
+
}
|
|
1401
|
+
tokenCodeField = "user_code";
|
|
1402
|
+
intervalMs = Math.min(parsePollIntervalMs(payload.interval, DEFAULT_AUTH_INTERVAL_MS), MAX_AUTH_INTERVAL_MS);
|
|
1403
|
+
expiresAtMs = parseExpiresAtMs(payload.expired_in, 6e5);
|
|
1404
|
+
} else {
|
|
1405
|
+
const body = new URLSearchParams({
|
|
1406
|
+
client_id: resolvedMethod.clientId,
|
|
1407
|
+
scope: resolvedMethod.scope
|
|
1408
|
+
});
|
|
1409
|
+
if (pkce) {
|
|
1410
|
+
body.set("code_challenge", pkce.challenge);
|
|
1411
|
+
body.set("code_challenge_method", "S256");
|
|
1412
|
+
}
|
|
1413
|
+
const response = await fetch(deviceCodeEndpoint, {
|
|
1414
|
+
method: "POST",
|
|
1415
|
+
headers: {
|
|
1416
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1417
|
+
Accept: "application/json"
|
|
1418
|
+
},
|
|
1419
|
+
body
|
|
1420
|
+
});
|
|
1421
|
+
const payload = await response.json().catch(() => ({}));
|
|
1422
|
+
if (!response.ok) {
|
|
1423
|
+
const message = payload.error_description || payload.error || response.statusText || "device code auth failed";
|
|
1424
|
+
throw new Error(message);
|
|
1425
|
+
}
|
|
1426
|
+
authorizationCode = payload.device_code?.trim() ?? "";
|
|
1427
|
+
userCode = payload.user_code?.trim() ?? "";
|
|
1428
|
+
verificationUri = payload.verification_uri_complete?.trim() || payload.verification_uri?.trim() || "";
|
|
1429
|
+
if (!authorizationCode || !userCode || !verificationUri) {
|
|
1430
|
+
throw new Error("provider auth payload is incomplete");
|
|
1431
|
+
}
|
|
1432
|
+
intervalMs = normalizePositiveInt(payload.interval, DEFAULT_AUTH_INTERVAL_MS / 1e3) * 1e3;
|
|
1433
|
+
const expiresInSec = normalizePositiveInt(payload.expires_in, 600);
|
|
1434
|
+
expiresAtMs = Date.now() + expiresInSec * 1e3;
|
|
1160
1435
|
}
|
|
1161
|
-
const response = await fetch(deviceCodeEndpoint, {
|
|
1162
|
-
method: "POST",
|
|
1163
|
-
headers: {
|
|
1164
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
1165
|
-
Accept: "application/json"
|
|
1166
|
-
},
|
|
1167
|
-
body
|
|
1168
|
-
});
|
|
1169
|
-
const payload = await response.json().catch(() => ({}));
|
|
1170
|
-
if (!response.ok) {
|
|
1171
|
-
const message = payload.error_description || payload.error || response.statusText || "device code auth failed";
|
|
1172
|
-
throw new Error(message);
|
|
1173
|
-
}
|
|
1174
|
-
const deviceCode = payload.device_code?.trim() ?? "";
|
|
1175
|
-
const userCode = payload.user_code?.trim() ?? "";
|
|
1176
|
-
const verificationUri = payload.verification_uri_complete?.trim() || payload.verification_uri?.trim() || "";
|
|
1177
|
-
if (!deviceCode || !userCode || !verificationUri) {
|
|
1178
|
-
throw new Error("provider auth payload is incomplete");
|
|
1179
|
-
}
|
|
1180
|
-
const intervalMs = normalizePositiveInt(payload.interval, DEFAULT_AUTH_INTERVAL_MS / 1e3) * 1e3;
|
|
1181
|
-
const expiresInSec = normalizePositiveInt(payload.expires_in, 600);
|
|
1182
|
-
const expiresAtMs = Date.now() + expiresInSec * 1e3;
|
|
1183
1436
|
const sessionId = randomUUID();
|
|
1184
1437
|
authSessions.set(sessionId, {
|
|
1185
1438
|
sessionId,
|
|
1186
1439
|
provider: providerName,
|
|
1187
1440
|
configPath,
|
|
1188
|
-
|
|
1441
|
+
authorizationCode,
|
|
1442
|
+
tokenCodeField,
|
|
1443
|
+
protocol: resolvedMethod.protocol,
|
|
1444
|
+
methodId: resolvedMethod.id,
|
|
1189
1445
|
codeVerifier: pkce?.verifier,
|
|
1190
1446
|
tokenEndpoint,
|
|
1191
|
-
clientId:
|
|
1192
|
-
grantType:
|
|
1447
|
+
clientId: resolvedMethod.clientId,
|
|
1448
|
+
grantType: resolvedMethod.grantType,
|
|
1449
|
+
defaultApiBase: resolvedMethod.defaultApiBase ?? spec.defaultApiBase,
|
|
1193
1450
|
expiresAtMs,
|
|
1194
1451
|
intervalMs
|
|
1195
1452
|
});
|
|
1453
|
+
const methodConfig = (spec.auth.methods ?? []).find((entry) => normalizeMethodId(entry.id) === resolvedMethod.id);
|
|
1454
|
+
const methodLabel = methodConfig ? resolveLocalizedMethodLabel(methodConfig, resolvedMethod.id ?? "") : void 0;
|
|
1455
|
+
const methodHint = methodConfig ? resolveLocalizedMethodHint(methodConfig) : void 0;
|
|
1196
1456
|
return {
|
|
1197
1457
|
provider: providerName,
|
|
1198
1458
|
kind: "device_code",
|
|
1459
|
+
methodId: resolvedMethod.id,
|
|
1199
1460
|
sessionId,
|
|
1200
1461
|
verificationUri,
|
|
1201
1462
|
userCode,
|
|
1202
1463
|
expiresAt: new Date(expiresAtMs).toISOString(),
|
|
1203
1464
|
intervalMs,
|
|
1204
|
-
note: resolveAuthNote(spec.auth.note ?? {})
|
|
1465
|
+
note: methodHint ?? methodLabel ?? resolveAuthNote(spec.auth.note ?? {})
|
|
1205
1466
|
};
|
|
1206
1467
|
}
|
|
1207
1468
|
async function pollProviderAuth(params) {
|
|
@@ -1220,9 +1481,9 @@ async function pollProviderAuth(params) {
|
|
|
1220
1481
|
}
|
|
1221
1482
|
const body = new URLSearchParams({
|
|
1222
1483
|
grant_type: session.grantType,
|
|
1223
|
-
client_id: session.clientId
|
|
1224
|
-
device_code: session.deviceCode
|
|
1484
|
+
client_id: session.clientId
|
|
1225
1485
|
});
|
|
1486
|
+
body.set(session.tokenCodeField, session.authorizationCode);
|
|
1226
1487
|
if (session.codeVerifier) {
|
|
1227
1488
|
body.set("code_verifier", session.codeVerifier);
|
|
1228
1489
|
}
|
|
@@ -1234,17 +1495,47 @@ async function pollProviderAuth(params) {
|
|
|
1234
1495
|
},
|
|
1235
1496
|
body
|
|
1236
1497
|
});
|
|
1237
|
-
|
|
1238
|
-
if (
|
|
1239
|
-
const
|
|
1240
|
-
|
|
1498
|
+
let accessToken = "";
|
|
1499
|
+
if (session.protocol === "minimax_user_code") {
|
|
1500
|
+
const raw = await response.text();
|
|
1501
|
+
let payload = {};
|
|
1502
|
+
if (raw) {
|
|
1503
|
+
try {
|
|
1504
|
+
payload = JSON.parse(raw);
|
|
1505
|
+
} catch {
|
|
1506
|
+
payload = {};
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
if (!response.ok) {
|
|
1510
|
+
const message = buildMinimaxErrorMessage(payload, raw || response.statusText || "authorization failed");
|
|
1241
1511
|
return {
|
|
1242
1512
|
provider: params.providerName,
|
|
1243
|
-
status: "
|
|
1244
|
-
|
|
1513
|
+
status: "error",
|
|
1514
|
+
message
|
|
1245
1515
|
};
|
|
1246
1516
|
}
|
|
1247
|
-
|
|
1517
|
+
const status = payload.status?.trim().toLowerCase();
|
|
1518
|
+
if (status === "success") {
|
|
1519
|
+
accessToken = payload.access_token?.trim() ?? "";
|
|
1520
|
+
if (!accessToken) {
|
|
1521
|
+
return {
|
|
1522
|
+
provider: params.providerName,
|
|
1523
|
+
status: "error",
|
|
1524
|
+
message: "provider token response missing access token"
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
} else if (status === "error") {
|
|
1528
|
+
const message = buildMinimaxErrorMessage(payload, "authorization failed");
|
|
1529
|
+
const classified = classifyMiniMaxErrorStatus(message);
|
|
1530
|
+
if (classified === "denied" || classified === "expired") {
|
|
1531
|
+
authSessions.delete(params.sessionId);
|
|
1532
|
+
}
|
|
1533
|
+
return {
|
|
1534
|
+
provider: params.providerName,
|
|
1535
|
+
status: classified,
|
|
1536
|
+
message
|
|
1537
|
+
};
|
|
1538
|
+
} else {
|
|
1248
1539
|
const nextPollMs = Math.min(Math.floor(session.intervalMs * 1.5), MAX_AUTH_INTERVAL_MS);
|
|
1249
1540
|
session.intervalMs = nextPollMs;
|
|
1250
1541
|
authSessions.set(params.sessionId, session);
|
|
@@ -1254,42 +1545,63 @@ async function pollProviderAuth(params) {
|
|
|
1254
1545
|
nextPollMs
|
|
1255
1546
|
};
|
|
1256
1547
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1548
|
+
} else {
|
|
1549
|
+
const payload = await response.json().catch(() => ({}));
|
|
1550
|
+
if (!response.ok) {
|
|
1551
|
+
const errorCode = payload.error?.trim().toLowerCase();
|
|
1552
|
+
if (errorCode === "authorization_pending") {
|
|
1553
|
+
return {
|
|
1554
|
+
provider: params.providerName,
|
|
1555
|
+
status: "pending",
|
|
1556
|
+
nextPollMs: session.intervalMs
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
if (errorCode === "slow_down") {
|
|
1560
|
+
const nextPollMs = Math.min(Math.floor(session.intervalMs * 1.5), MAX_AUTH_INTERVAL_MS);
|
|
1561
|
+
session.intervalMs = nextPollMs;
|
|
1562
|
+
authSessions.set(params.sessionId, session);
|
|
1563
|
+
return {
|
|
1564
|
+
provider: params.providerName,
|
|
1565
|
+
status: "pending",
|
|
1566
|
+
nextPollMs
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
if (errorCode === "access_denied") {
|
|
1570
|
+
authSessions.delete(params.sessionId);
|
|
1571
|
+
return {
|
|
1572
|
+
provider: params.providerName,
|
|
1573
|
+
status: "denied",
|
|
1574
|
+
message: payload.error_description || "authorization denied"
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
if (errorCode === "expired_token") {
|
|
1578
|
+
authSessions.delete(params.sessionId);
|
|
1579
|
+
return {
|
|
1580
|
+
provider: params.providerName,
|
|
1581
|
+
status: "expired",
|
|
1582
|
+
message: payload.error_description || "authorization session expired"
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1259
1585
|
return {
|
|
1260
1586
|
provider: params.providerName,
|
|
1261
|
-
status: "
|
|
1262
|
-
message: payload.error_description || "authorization
|
|
1587
|
+
status: "error",
|
|
1588
|
+
message: payload.error_description || payload.error || response.statusText || "authorization failed"
|
|
1263
1589
|
};
|
|
1264
1590
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1591
|
+
accessToken = payload.access_token?.trim() ?? "";
|
|
1592
|
+
if (!accessToken) {
|
|
1267
1593
|
return {
|
|
1268
1594
|
provider: params.providerName,
|
|
1269
|
-
status: "
|
|
1270
|
-
message:
|
|
1595
|
+
status: "error",
|
|
1596
|
+
message: "provider token response missing access token"
|
|
1271
1597
|
};
|
|
1272
1598
|
}
|
|
1273
|
-
return {
|
|
1274
|
-
provider: params.providerName,
|
|
1275
|
-
status: "error",
|
|
1276
|
-
message: payload.error_description || payload.error || response.statusText || "authorization failed"
|
|
1277
|
-
};
|
|
1278
|
-
}
|
|
1279
|
-
const accessToken = payload.access_token?.trim();
|
|
1280
|
-
if (!accessToken) {
|
|
1281
|
-
return {
|
|
1282
|
-
provider: params.providerName,
|
|
1283
|
-
status: "error",
|
|
1284
|
-
message: "provider token response missing access token"
|
|
1285
|
-
};
|
|
1286
1599
|
}
|
|
1287
|
-
const spec = findBuiltinProviderByName2(params.providerName);
|
|
1288
1600
|
setProviderApiKey({
|
|
1289
1601
|
configPath: params.configPath,
|
|
1290
1602
|
provider: params.providerName,
|
|
1291
1603
|
accessToken,
|
|
1292
|
-
defaultApiBase:
|
|
1604
|
+
defaultApiBase: session.defaultApiBase
|
|
1293
1605
|
});
|
|
1294
1606
|
authSessions.delete(params.sessionId);
|
|
1295
1607
|
return {
|
|
@@ -1298,7 +1610,7 @@ async function pollProviderAuth(params) {
|
|
|
1298
1610
|
};
|
|
1299
1611
|
}
|
|
1300
1612
|
async function importProviderAuthFromCli(configPath, providerName) {
|
|
1301
|
-
const spec =
|
|
1613
|
+
const spec = findServerBuiltinProviderByName(providerName);
|
|
1302
1614
|
if (!spec?.auth || spec.auth.kind !== "device_code" || !spec.auth.cliCredential) {
|
|
1303
1615
|
return null;
|
|
1304
1616
|
}
|
|
@@ -1999,98 +2311,23 @@ function isSupportedMarketplaceSkillItem(item, knownSkillNames) {
|
|
|
1999
2311
|
if (item.type !== "skill") {
|
|
2000
2312
|
return false;
|
|
2001
2313
|
}
|
|
2002
|
-
if (item.install.kind === "
|
|
2314
|
+
if (item.install.kind === "marketplace") {
|
|
2003
2315
|
return true;
|
|
2004
2316
|
}
|
|
2005
2317
|
return item.install.kind === "builtin" && knownSkillNames.has(item.install.spec);
|
|
2006
2318
|
}
|
|
2007
|
-
function
|
|
2008
|
-
const
|
|
2009
|
-
|
|
2010
|
-
if (!match) {
|
|
2011
|
-
return { bodyRaw: normalized };
|
|
2012
|
-
}
|
|
2013
|
-
return {
|
|
2014
|
-
metadataRaw: match[1]?.trim() || void 0,
|
|
2015
|
-
bodyRaw: match[2] ?? ""
|
|
2016
|
-
};
|
|
2017
|
-
}
|
|
2018
|
-
async function loadLocalSkillMarkdown(options, skillName) {
|
|
2019
|
-
const config = loadConfigOrDefault(options.configPath);
|
|
2020
|
-
const loader = createSkillsLoader(getWorkspacePathFromConfig3(config));
|
|
2021
|
-
if (!loader) {
|
|
2022
|
-
return null;
|
|
2023
|
-
}
|
|
2024
|
-
const skillInfo = loader.listSkills(false).find((skill) => skill.name === skillName);
|
|
2025
|
-
if (!skillInfo) {
|
|
2026
|
-
return null;
|
|
2027
|
-
}
|
|
2028
|
-
try {
|
|
2029
|
-
const raw = await readFile2(skillInfo.path, "utf-8");
|
|
2030
|
-
return {
|
|
2031
|
-
raw,
|
|
2032
|
-
source: skillInfo.source
|
|
2033
|
-
};
|
|
2034
|
-
} catch {
|
|
2035
|
-
return null;
|
|
2036
|
-
}
|
|
2037
|
-
}
|
|
2038
|
-
function parseGitSkillSpec(rawSpec) {
|
|
2039
|
-
const spec = rawSpec.trim();
|
|
2040
|
-
if (!spec) {
|
|
2041
|
-
return null;
|
|
2042
|
-
}
|
|
2043
|
-
const segments = spec.split("/").filter(Boolean);
|
|
2044
|
-
if (segments.length < 3) {
|
|
2045
|
-
return null;
|
|
2046
|
-
}
|
|
2047
|
-
return {
|
|
2048
|
-
owner: segments[0] ?? "",
|
|
2049
|
-
repo: segments[1] ?? "",
|
|
2050
|
-
skillPath: segments.slice(2).join("/")
|
|
2051
|
-
};
|
|
2052
|
-
}
|
|
2053
|
-
async function fetchTextWithFallback(urls) {
|
|
2054
|
-
for (const url of urls) {
|
|
2055
|
-
try {
|
|
2056
|
-
const response = await fetch(url, {
|
|
2057
|
-
method: "GET",
|
|
2058
|
-
headers: {
|
|
2059
|
-
Accept: "text/plain, text/markdown, application/json"
|
|
2060
|
-
}
|
|
2061
|
-
});
|
|
2062
|
-
if (!response.ok) {
|
|
2063
|
-
continue;
|
|
2064
|
-
}
|
|
2065
|
-
const text = await response.text();
|
|
2066
|
-
if (text.trim().length === 0) {
|
|
2067
|
-
continue;
|
|
2068
|
-
}
|
|
2069
|
-
return { text, url };
|
|
2070
|
-
} catch {
|
|
2319
|
+
function findUnsupportedSkillInstallKind(items) {
|
|
2320
|
+
for (const item of items) {
|
|
2321
|
+
if (item.type !== "skill") {
|
|
2071
2322
|
continue;
|
|
2072
2323
|
}
|
|
2324
|
+
const kind = item.install.kind;
|
|
2325
|
+
if (kind !== "builtin" && kind !== "marketplace") {
|
|
2326
|
+
return kind;
|
|
2327
|
+
}
|
|
2073
2328
|
}
|
|
2074
2329
|
return null;
|
|
2075
2330
|
}
|
|
2076
|
-
async function loadGitSkillMarkdownFromSpec(rawSpec) {
|
|
2077
|
-
const parsed = parseGitSkillSpec(rawSpec);
|
|
2078
|
-
if (!parsed) {
|
|
2079
|
-
return null;
|
|
2080
|
-
}
|
|
2081
|
-
const candidates = [
|
|
2082
|
-
`https://raw.githubusercontent.com/${parsed.owner}/${parsed.repo}/main/${parsed.skillPath}/SKILL.md`,
|
|
2083
|
-
`https://raw.githubusercontent.com/${parsed.owner}/${parsed.repo}/master/${parsed.skillPath}/SKILL.md`
|
|
2084
|
-
];
|
|
2085
|
-
const result = await fetchTextWithFallback(candidates);
|
|
2086
|
-
if (!result) {
|
|
2087
|
-
return null;
|
|
2088
|
-
}
|
|
2089
|
-
return {
|
|
2090
|
-
raw: result.text,
|
|
2091
|
-
sourceUrl: result.url
|
|
2092
|
-
};
|
|
2093
|
-
}
|
|
2094
2331
|
async function loadPluginReadmeFromNpm(spec) {
|
|
2095
2332
|
const encodedSpec = encodeURIComponent(spec);
|
|
2096
2333
|
const registryUrl = `https://registry.npmjs.org/${encodedSpec}`;
|
|
@@ -2124,40 +2361,6 @@ async function loadPluginReadmeFromNpm(spec) {
|
|
|
2124
2361
|
return null;
|
|
2125
2362
|
}
|
|
2126
2363
|
}
|
|
2127
|
-
async function buildSkillContentView(options, item) {
|
|
2128
|
-
const local = await loadLocalSkillMarkdown(options, item.install.spec);
|
|
2129
|
-
if (local) {
|
|
2130
|
-
const split = splitMarkdownFrontmatter(local.raw);
|
|
2131
|
-
return {
|
|
2132
|
-
type: "skill",
|
|
2133
|
-
slug: item.slug,
|
|
2134
|
-
name: item.name,
|
|
2135
|
-
install: item.install,
|
|
2136
|
-
source: local.source,
|
|
2137
|
-
raw: local.raw,
|
|
2138
|
-
metadataRaw: split.metadataRaw,
|
|
2139
|
-
bodyRaw: split.bodyRaw
|
|
2140
|
-
};
|
|
2141
|
-
}
|
|
2142
|
-
if (item.install.kind === "git") {
|
|
2143
|
-
const remote = await loadGitSkillMarkdownFromSpec(item.install.spec);
|
|
2144
|
-
if (remote) {
|
|
2145
|
-
const split = splitMarkdownFrontmatter(remote.raw);
|
|
2146
|
-
return {
|
|
2147
|
-
type: "skill",
|
|
2148
|
-
slug: item.slug,
|
|
2149
|
-
name: item.name,
|
|
2150
|
-
install: item.install,
|
|
2151
|
-
source: "git",
|
|
2152
|
-
raw: remote.raw,
|
|
2153
|
-
metadataRaw: split.metadataRaw,
|
|
2154
|
-
bodyRaw: split.bodyRaw,
|
|
2155
|
-
sourceUrl: remote.sourceUrl
|
|
2156
|
-
};
|
|
2157
|
-
}
|
|
2158
|
-
}
|
|
2159
|
-
return null;
|
|
2160
|
-
}
|
|
2161
2364
|
async function buildPluginContentView(item) {
|
|
2162
2365
|
if (item.install.kind === "npm") {
|
|
2163
2366
|
const npm = await loadPluginReadmeFromNpm(item.install.spec);
|
|
@@ -2276,8 +2479,6 @@ async function installMarketplaceSkill(params) {
|
|
|
2276
2479
|
kind: params.body.kind,
|
|
2277
2480
|
skill: params.body.skill,
|
|
2278
2481
|
installPath: params.body.installPath,
|
|
2279
|
-
version: params.body.version,
|
|
2280
|
-
registry: params.body.registry,
|
|
2281
2482
|
force: params.body.force
|
|
2282
2483
|
});
|
|
2283
2484
|
params.options.publish({ type: "config.updated", payload: { path: "skills" } });
|
|
@@ -2504,8 +2705,16 @@ function registerSkillMarketplaceRoutes(app, options, marketplaceBaseUrl) {
|
|
|
2504
2705
|
if (!result.ok) {
|
|
2505
2706
|
return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
|
|
2506
2707
|
}
|
|
2708
|
+
const normalizedItems = result.data.items.map((item) => normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(item)));
|
|
2709
|
+
const unsupportedKind = findUnsupportedSkillInstallKind(normalizedItems);
|
|
2710
|
+
if (unsupportedKind) {
|
|
2711
|
+
return c.json(
|
|
2712
|
+
err("MARKETPLACE_CONTRACT_MISMATCH", `unsupported skill install kind from marketplace api: ${unsupportedKind}`),
|
|
2713
|
+
502
|
|
2714
|
+
);
|
|
2715
|
+
}
|
|
2507
2716
|
const knownSkillNames = collectKnownSkillNames(options);
|
|
2508
|
-
const filteredItems =
|
|
2717
|
+
const filteredItems = normalizedItems.filter((item) => isSupportedMarketplaceSkillItem(item, knownSkillNames));
|
|
2509
2718
|
const pageSize = Math.min(100, toPositiveInt(query.pageSize, 20));
|
|
2510
2719
|
const requestedPage = toPositiveInt(query.page, 1);
|
|
2511
2720
|
const totalPages = filteredItems.length === 0 ? 0 : Math.ceil(filteredItems.length / pageSize);
|
|
@@ -2531,6 +2740,13 @@ function registerSkillMarketplaceRoutes(app, options, marketplaceBaseUrl) {
|
|
|
2531
2740
|
}
|
|
2532
2741
|
const knownSkillNames = collectKnownSkillNames(options);
|
|
2533
2742
|
const sanitized = normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(result.data));
|
|
2743
|
+
const unsupportedKind = findUnsupportedSkillInstallKind([sanitized]);
|
|
2744
|
+
if (unsupportedKind) {
|
|
2745
|
+
return c.json(
|
|
2746
|
+
err("MARKETPLACE_CONTRACT_MISMATCH", `unsupported skill install kind from marketplace api: ${unsupportedKind}`),
|
|
2747
|
+
502
|
|
2748
|
+
);
|
|
2749
|
+
}
|
|
2534
2750
|
if (!isSupportedMarketplaceSkillItem(sanitized, knownSkillNames)) {
|
|
2535
2751
|
return c.json(err("NOT_FOUND", "marketplace item not supported by nextclaw"), 404);
|
|
2536
2752
|
}
|
|
@@ -2547,14 +2763,24 @@ function registerSkillMarketplaceRoutes(app, options, marketplaceBaseUrl) {
|
|
|
2547
2763
|
}
|
|
2548
2764
|
const knownSkillNames = collectKnownSkillNames(options);
|
|
2549
2765
|
const sanitized = normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(result.data));
|
|
2766
|
+
const unsupportedKind = findUnsupportedSkillInstallKind([sanitized]);
|
|
2767
|
+
if (unsupportedKind) {
|
|
2768
|
+
return c.json(
|
|
2769
|
+
err("MARKETPLACE_CONTRACT_MISMATCH", `unsupported skill install kind from marketplace api: ${unsupportedKind}`),
|
|
2770
|
+
502
|
|
2771
|
+
);
|
|
2772
|
+
}
|
|
2550
2773
|
if (!isSupportedMarketplaceSkillItem(sanitized, knownSkillNames)) {
|
|
2551
2774
|
return c.json(err("NOT_FOUND", "marketplace item not supported by nextclaw"), 404);
|
|
2552
2775
|
}
|
|
2553
|
-
const
|
|
2554
|
-
|
|
2555
|
-
|
|
2776
|
+
const contentResult = await fetchMarketplaceData({
|
|
2777
|
+
baseUrl: marketplaceBaseUrl,
|
|
2778
|
+
path: `/api/v1/skills/items/${slug}/content`
|
|
2779
|
+
});
|
|
2780
|
+
if (!contentResult.ok) {
|
|
2781
|
+
return c.json(err("MARKETPLACE_UNAVAILABLE", contentResult.message), contentResult.status);
|
|
2556
2782
|
}
|
|
2557
|
-
return c.json(ok(
|
|
2783
|
+
return c.json(ok(contentResult.data));
|
|
2558
2784
|
});
|
|
2559
2785
|
app.post("/api/marketplace/skills/install", async (c) => {
|
|
2560
2786
|
const body = await readJson(c.req.raw);
|
|
@@ -2727,8 +2953,20 @@ function createUiRouter(options) {
|
|
|
2727
2953
|
});
|
|
2728
2954
|
app.post("/api/config/providers/:provider/auth/start", async (c) => {
|
|
2729
2955
|
const provider = c.req.param("provider");
|
|
2956
|
+
let payload = {};
|
|
2957
|
+
const rawBody = await c.req.raw.text();
|
|
2958
|
+
if (rawBody.trim().length > 0) {
|
|
2959
|
+
try {
|
|
2960
|
+
payload = JSON.parse(rawBody);
|
|
2961
|
+
} catch {
|
|
2962
|
+
return c.json(err("INVALID_BODY", "invalid json body"), 400);
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
const methodId = typeof payload.methodId === "string" ? payload.methodId.trim() : void 0;
|
|
2730
2966
|
try {
|
|
2731
|
-
const result = await startProviderAuth(options.configPath, provider
|
|
2967
|
+
const result = await startProviderAuth(options.configPath, provider, {
|
|
2968
|
+
methodId
|
|
2969
|
+
});
|
|
2732
2970
|
if (!result) {
|
|
2733
2971
|
return c.json(err("NOT_SUPPORTED", `provider auth is not supported: ${provider}`), 404);
|
|
2734
2972
|
}
|
|
@@ -2814,6 +3052,31 @@ function createUiRouter(options) {
|
|
|
2814
3052
|
return c.json(err("CHAT_RUNTIME_FAILED", String(error)), 500);
|
|
2815
3053
|
}
|
|
2816
3054
|
});
|
|
3055
|
+
app.get("/api/chat/commands", async (c) => {
|
|
3056
|
+
try {
|
|
3057
|
+
const config = loadConfigOrDefault(options.configPath);
|
|
3058
|
+
const registry = new NextclawCore.CommandRegistry(config);
|
|
3059
|
+
const commands = registry.listSlashCommands().map((command) => ({
|
|
3060
|
+
name: command.name,
|
|
3061
|
+
description: command.description,
|
|
3062
|
+
...Array.isArray(command.options) && command.options.length > 0 ? {
|
|
3063
|
+
options: command.options.map((option) => ({
|
|
3064
|
+
name: option.name,
|
|
3065
|
+
description: option.description,
|
|
3066
|
+
type: option.type,
|
|
3067
|
+
...option.required === true ? { required: true } : {}
|
|
3068
|
+
}))
|
|
3069
|
+
} : {}
|
|
3070
|
+
}));
|
|
3071
|
+
const payload = {
|
|
3072
|
+
commands,
|
|
3073
|
+
total: commands.length
|
|
3074
|
+
};
|
|
3075
|
+
return c.json(ok(payload));
|
|
3076
|
+
} catch (error) {
|
|
3077
|
+
return c.json(err("CHAT_COMMANDS_FAILED", String(error)), 500);
|
|
3078
|
+
}
|
|
3079
|
+
});
|
|
2817
3080
|
app.post("/api/chat/turn", async (c) => {
|
|
2818
3081
|
if (!options.chatRuntime) {
|
|
2819
3082
|
return c.json(err("NOT_AVAILABLE", "chat runtime unavailable"), 503);
|
|
@@ -3419,7 +3682,7 @@ function startUiServer(options) {
|
|
|
3419
3682
|
join,
|
|
3420
3683
|
getContent: async (path) => {
|
|
3421
3684
|
try {
|
|
3422
|
-
return await
|
|
3685
|
+
return await readFile2(path);
|
|
3423
3686
|
} catch {
|
|
3424
3687
|
return null;
|
|
3425
3688
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/server",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Nextclaw UI/API server.",
|
|
6
6
|
"type": "module",
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
],
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@hono/node-server": "^1.13.3",
|
|
18
|
-
"@nextclaw/
|
|
19
|
-
"@nextclaw/runtime": "^0.1.1",
|
|
18
|
+
"@nextclaw/runtime": "^0.1.2",
|
|
20
19
|
"hono": "^4.6.2",
|
|
21
20
|
"ws": "^8.18.0",
|
|
22
|
-
"@nextclaw/core": "^0.7.
|
|
21
|
+
"@nextclaw/core": "^0.7.3",
|
|
22
|
+
"@nextclaw/openclaw-compat": "0.2.1"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/node": "^20.17.6",
|