@nextclaw/server 0.6.5 → 0.6.7
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 +22 -1
- package/dist/index.js +404 -79
- 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
|
};
|
|
@@ -417,6 +421,18 @@ type ProviderSpecView = {
|
|
|
417
421
|
en?: string;
|
|
418
422
|
zh?: string;
|
|
419
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;
|
|
420
436
|
supportsCliImport?: boolean;
|
|
421
437
|
};
|
|
422
438
|
defaultModels?: string[];
|
|
@@ -692,6 +708,11 @@ type UiServerEvent = {
|
|
|
692
708
|
payload: {
|
|
693
709
|
run: ChatRunView;
|
|
694
710
|
};
|
|
711
|
+
} | {
|
|
712
|
+
type: "session.updated";
|
|
713
|
+
payload: {
|
|
714
|
+
sessionKey: string;
|
|
715
|
+
};
|
|
695
716
|
} | {
|
|
696
717
|
type: "config.reload.started";
|
|
697
718
|
payload?: Record<string, unknown>;
|
|
@@ -771,4 +792,4 @@ declare function deleteSession(configPath: string, key: string): boolean;
|
|
|
771
792
|
declare function updateRuntime(configPath: string, patch: RuntimeConfigUpdate): Pick<ConfigView, "agents" | "bindings" | "session">;
|
|
772
793
|
declare function updateSecrets(configPath: string, patch: SecretsConfigUpdate): SecretsView;
|
|
773
794
|
|
|
774
|
-
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 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
|
@@ -28,7 +28,96 @@ import {
|
|
|
28
28
|
SessionManager,
|
|
29
29
|
getWorkspacePathFromConfig
|
|
30
30
|
} from "@nextclaw/core";
|
|
31
|
+
|
|
32
|
+
// src/ui/provider-overrides.ts
|
|
31
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
|
|
32
121
|
var MASK_MIN_LENGTH = 8;
|
|
33
122
|
var EXTRA_SENSITIVE_PATH_PATTERNS = [/authorization/i, /cookie/i, /session/i, /bearer/i];
|
|
34
123
|
var PREFERRED_PROVIDER_ORDER = [
|
|
@@ -46,7 +135,7 @@ var PREFERRED_PROVIDER_ORDER = [
|
|
|
46
135
|
var PREFERRED_PROVIDER_ORDER_INDEX = new Map(
|
|
47
136
|
PREFERRED_PROVIDER_ORDER.map((name, index) => [name, index])
|
|
48
137
|
);
|
|
49
|
-
var BUILTIN_PROVIDERS =
|
|
138
|
+
var BUILTIN_PROVIDERS = listServerBuiltinProviders();
|
|
50
139
|
var BUILTIN_PROVIDER_NAMES = new Set(BUILTIN_PROVIDERS.map((spec) => spec.name));
|
|
51
140
|
var CUSTOM_PROVIDER_WIRE_API_OPTIONS = ["auto", "chat", "responses"];
|
|
52
141
|
var CUSTOM_PROVIDER_PREFIX = "custom-";
|
|
@@ -107,7 +196,7 @@ function ensureProviderConfig(config, providerName) {
|
|
|
107
196
|
if (isCustomProviderName(providerName)) {
|
|
108
197
|
return null;
|
|
109
198
|
}
|
|
110
|
-
const spec =
|
|
199
|
+
const spec = findServerBuiltinProviderByName(providerName);
|
|
111
200
|
if (!spec) {
|
|
112
201
|
return null;
|
|
113
202
|
}
|
|
@@ -375,7 +464,7 @@ function buildConfigView(config) {
|
|
|
375
464
|
const uiHints = buildUiHints(config);
|
|
376
465
|
const providers = {};
|
|
377
466
|
for (const [name, provider] of Object.entries(config.providers)) {
|
|
378
|
-
const spec =
|
|
467
|
+
const spec = findServerBuiltinProviderByName(name);
|
|
379
468
|
providers[name] = toProviderView(config, provider, name, uiHints, spec);
|
|
380
469
|
}
|
|
381
470
|
return {
|
|
@@ -424,6 +513,12 @@ function buildConfigMeta(config) {
|
|
|
424
513
|
kind: spec.auth.kind,
|
|
425
514
|
displayName: spec.auth.displayName,
|
|
426
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,
|
|
427
522
|
supportsCliImport: Boolean(spec.auth.cliCredential)
|
|
428
523
|
} : void 0,
|
|
429
524
|
defaultModels: normalizeModelList(spec.defaultModels ?? []),
|
|
@@ -554,7 +649,7 @@ function updateProvider(configPath, providerName, patch) {
|
|
|
554
649
|
if (!provider) {
|
|
555
650
|
return null;
|
|
556
651
|
}
|
|
557
|
-
const spec =
|
|
652
|
+
const spec = findServerBuiltinProviderByName(providerName);
|
|
558
653
|
const isCustom = isCustomProviderName(providerName);
|
|
559
654
|
if (Object.prototype.hasOwnProperty.call(patch, "displayName") && isCustom) {
|
|
560
655
|
provider.displayName = normalizeOptionalDisplayName(patch.displayName) ?? "";
|
|
@@ -689,7 +784,7 @@ async function testProviderConnection(configPath, providerName, patch) {
|
|
|
689
784
|
if (!provider) {
|
|
690
785
|
return null;
|
|
691
786
|
}
|
|
692
|
-
const spec =
|
|
787
|
+
const spec = findServerBuiltinProviderByName(providerName);
|
|
693
788
|
const hasApiKeyPatch = Object.prototype.hasOwnProperty.call(patch, "apiKey");
|
|
694
789
|
const providedApiKey = normalizeOptionalString(patch.apiKey);
|
|
695
790
|
const currentApiKey = normalizeOptionalString(provider.apiKey);
|
|
@@ -1037,7 +1132,6 @@ import {
|
|
|
1037
1132
|
loadConfig as loadConfig2,
|
|
1038
1133
|
saveConfig as saveConfig2
|
|
1039
1134
|
} from "@nextclaw/core";
|
|
1040
|
-
import { findBuiltinProviderByName as findBuiltinProviderByName2 } from "@nextclaw/runtime";
|
|
1041
1135
|
var authSessions = /* @__PURE__ */ new Map();
|
|
1042
1136
|
var DEFAULT_AUTH_INTERVAL_MS = 2e3;
|
|
1043
1137
|
var MAX_AUTH_INTERVAL_MS = 1e4;
|
|
@@ -1047,6 +1141,12 @@ function normalizePositiveInt(value, fallback) {
|
|
|
1047
1141
|
}
|
|
1048
1142
|
return Math.floor(value);
|
|
1049
1143
|
}
|
|
1144
|
+
function normalizePositiveFloat(value) {
|
|
1145
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
1146
|
+
return null;
|
|
1147
|
+
}
|
|
1148
|
+
return value;
|
|
1149
|
+
}
|
|
1050
1150
|
function toBase64Url(buffer) {
|
|
1051
1151
|
return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
1052
1152
|
}
|
|
@@ -1073,6 +1173,113 @@ function resolveDeviceCodeEndpoints(baseUrl, deviceCodePath, tokenPath) {
|
|
|
1073
1173
|
function resolveAuthNote(params) {
|
|
1074
1174
|
return params.zh ?? params.en;
|
|
1075
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
|
+
}
|
|
1076
1283
|
function resolveHomePath(inputPath) {
|
|
1077
1284
|
const trimmed = inputPath.trim();
|
|
1078
1285
|
if (!trimmed) {
|
|
@@ -1137,70 +1344,125 @@ function setProviderApiKey(params) {
|
|
|
1137
1344
|
const next = ConfigSchema2.parse(config);
|
|
1138
1345
|
saveConfig2(next, params.configPath);
|
|
1139
1346
|
}
|
|
1140
|
-
async function startProviderAuth(configPath, providerName) {
|
|
1347
|
+
async function startProviderAuth(configPath, providerName, options) {
|
|
1141
1348
|
cleanupExpiredAuthSessions();
|
|
1142
|
-
const spec =
|
|
1349
|
+
const spec = findServerBuiltinProviderByName(providerName);
|
|
1143
1350
|
if (!spec?.auth || spec.auth.kind !== "device_code") {
|
|
1144
1351
|
return null;
|
|
1145
1352
|
}
|
|
1353
|
+
const resolvedMethod = resolveAuthMethod(spec.auth, options?.methodId);
|
|
1146
1354
|
const { deviceCodeEndpoint, tokenEndpoint } = resolveDeviceCodeEndpoints(
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1355
|
+
resolvedMethod.baseUrl,
|
|
1356
|
+
resolvedMethod.deviceCodePath,
|
|
1357
|
+
resolvedMethod.tokenPath
|
|
1150
1358
|
);
|
|
1151
|
-
const pkce =
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
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;
|
|
1159
1435
|
}
|
|
1160
|
-
const response = await fetch(deviceCodeEndpoint, {
|
|
1161
|
-
method: "POST",
|
|
1162
|
-
headers: {
|
|
1163
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
1164
|
-
Accept: "application/json"
|
|
1165
|
-
},
|
|
1166
|
-
body
|
|
1167
|
-
});
|
|
1168
|
-
const payload = await response.json().catch(() => ({}));
|
|
1169
|
-
if (!response.ok) {
|
|
1170
|
-
const message = payload.error_description || payload.error || response.statusText || "device code auth failed";
|
|
1171
|
-
throw new Error(message);
|
|
1172
|
-
}
|
|
1173
|
-
const deviceCode = payload.device_code?.trim() ?? "";
|
|
1174
|
-
const userCode = payload.user_code?.trim() ?? "";
|
|
1175
|
-
const verificationUri = payload.verification_uri_complete?.trim() || payload.verification_uri?.trim() || "";
|
|
1176
|
-
if (!deviceCode || !userCode || !verificationUri) {
|
|
1177
|
-
throw new Error("provider auth payload is incomplete");
|
|
1178
|
-
}
|
|
1179
|
-
const intervalMs = normalizePositiveInt(payload.interval, DEFAULT_AUTH_INTERVAL_MS / 1e3) * 1e3;
|
|
1180
|
-
const expiresInSec = normalizePositiveInt(payload.expires_in, 600);
|
|
1181
|
-
const expiresAtMs = Date.now() + expiresInSec * 1e3;
|
|
1182
1436
|
const sessionId = randomUUID();
|
|
1183
1437
|
authSessions.set(sessionId, {
|
|
1184
1438
|
sessionId,
|
|
1185
1439
|
provider: providerName,
|
|
1186
1440
|
configPath,
|
|
1187
|
-
|
|
1441
|
+
authorizationCode,
|
|
1442
|
+
tokenCodeField,
|
|
1443
|
+
protocol: resolvedMethod.protocol,
|
|
1444
|
+
methodId: resolvedMethod.id,
|
|
1188
1445
|
codeVerifier: pkce?.verifier,
|
|
1189
1446
|
tokenEndpoint,
|
|
1190
|
-
clientId:
|
|
1191
|
-
grantType:
|
|
1447
|
+
clientId: resolvedMethod.clientId,
|
|
1448
|
+
grantType: resolvedMethod.grantType,
|
|
1449
|
+
defaultApiBase: resolvedMethod.defaultApiBase ?? spec.defaultApiBase,
|
|
1192
1450
|
expiresAtMs,
|
|
1193
1451
|
intervalMs
|
|
1194
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;
|
|
1195
1456
|
return {
|
|
1196
1457
|
provider: providerName,
|
|
1197
1458
|
kind: "device_code",
|
|
1459
|
+
methodId: resolvedMethod.id,
|
|
1198
1460
|
sessionId,
|
|
1199
1461
|
verificationUri,
|
|
1200
1462
|
userCode,
|
|
1201
1463
|
expiresAt: new Date(expiresAtMs).toISOString(),
|
|
1202
1464
|
intervalMs,
|
|
1203
|
-
note: resolveAuthNote(spec.auth.note ?? {})
|
|
1465
|
+
note: methodHint ?? methodLabel ?? resolveAuthNote(spec.auth.note ?? {})
|
|
1204
1466
|
};
|
|
1205
1467
|
}
|
|
1206
1468
|
async function pollProviderAuth(params) {
|
|
@@ -1219,9 +1481,9 @@ async function pollProviderAuth(params) {
|
|
|
1219
1481
|
}
|
|
1220
1482
|
const body = new URLSearchParams({
|
|
1221
1483
|
grant_type: session.grantType,
|
|
1222
|
-
client_id: session.clientId
|
|
1223
|
-
device_code: session.deviceCode
|
|
1484
|
+
client_id: session.clientId
|
|
1224
1485
|
});
|
|
1486
|
+
body.set(session.tokenCodeField, session.authorizationCode);
|
|
1225
1487
|
if (session.codeVerifier) {
|
|
1226
1488
|
body.set("code_verifier", session.codeVerifier);
|
|
1227
1489
|
}
|
|
@@ -1233,17 +1495,47 @@ async function pollProviderAuth(params) {
|
|
|
1233
1495
|
},
|
|
1234
1496
|
body
|
|
1235
1497
|
});
|
|
1236
|
-
|
|
1237
|
-
if (
|
|
1238
|
-
const
|
|
1239
|
-
|
|
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");
|
|
1240
1511
|
return {
|
|
1241
1512
|
provider: params.providerName,
|
|
1242
|
-
status: "
|
|
1243
|
-
|
|
1513
|
+
status: "error",
|
|
1514
|
+
message
|
|
1244
1515
|
};
|
|
1245
1516
|
}
|
|
1246
|
-
|
|
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 {
|
|
1247
1539
|
const nextPollMs = Math.min(Math.floor(session.intervalMs * 1.5), MAX_AUTH_INTERVAL_MS);
|
|
1248
1540
|
session.intervalMs = nextPollMs;
|
|
1249
1541
|
authSessions.set(params.sessionId, session);
|
|
@@ -1253,42 +1545,63 @@ async function pollProviderAuth(params) {
|
|
|
1253
1545
|
nextPollMs
|
|
1254
1546
|
};
|
|
1255
1547
|
}
|
|
1256
|
-
|
|
1257
|
-
|
|
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
|
+
}
|
|
1258
1585
|
return {
|
|
1259
1586
|
provider: params.providerName,
|
|
1260
|
-
status: "
|
|
1261
|
-
message: payload.error_description || "authorization
|
|
1587
|
+
status: "error",
|
|
1588
|
+
message: payload.error_description || payload.error || response.statusText || "authorization failed"
|
|
1262
1589
|
};
|
|
1263
1590
|
}
|
|
1264
|
-
|
|
1265
|
-
|
|
1591
|
+
accessToken = payload.access_token?.trim() ?? "";
|
|
1592
|
+
if (!accessToken) {
|
|
1266
1593
|
return {
|
|
1267
1594
|
provider: params.providerName,
|
|
1268
|
-
status: "
|
|
1269
|
-
message:
|
|
1595
|
+
status: "error",
|
|
1596
|
+
message: "provider token response missing access token"
|
|
1270
1597
|
};
|
|
1271
1598
|
}
|
|
1272
|
-
return {
|
|
1273
|
-
provider: params.providerName,
|
|
1274
|
-
status: "error",
|
|
1275
|
-
message: payload.error_description || payload.error || response.statusText || "authorization failed"
|
|
1276
|
-
};
|
|
1277
|
-
}
|
|
1278
|
-
const accessToken = payload.access_token?.trim();
|
|
1279
|
-
if (!accessToken) {
|
|
1280
|
-
return {
|
|
1281
|
-
provider: params.providerName,
|
|
1282
|
-
status: "error",
|
|
1283
|
-
message: "provider token response missing access token"
|
|
1284
|
-
};
|
|
1285
1599
|
}
|
|
1286
|
-
const spec = findBuiltinProviderByName2(params.providerName);
|
|
1287
1600
|
setProviderApiKey({
|
|
1288
1601
|
configPath: params.configPath,
|
|
1289
1602
|
provider: params.providerName,
|
|
1290
1603
|
accessToken,
|
|
1291
|
-
defaultApiBase:
|
|
1604
|
+
defaultApiBase: session.defaultApiBase
|
|
1292
1605
|
});
|
|
1293
1606
|
authSessions.delete(params.sessionId);
|
|
1294
1607
|
return {
|
|
@@ -1297,7 +1610,7 @@ async function pollProviderAuth(params) {
|
|
|
1297
1610
|
};
|
|
1298
1611
|
}
|
|
1299
1612
|
async function importProviderAuthFromCli(configPath, providerName) {
|
|
1300
|
-
const spec =
|
|
1613
|
+
const spec = findServerBuiltinProviderByName(providerName);
|
|
1301
1614
|
if (!spec?.auth || spec.auth.kind !== "device_code" || !spec.auth.cliCredential) {
|
|
1302
1615
|
return null;
|
|
1303
1616
|
}
|
|
@@ -2640,8 +2953,20 @@ function createUiRouter(options) {
|
|
|
2640
2953
|
});
|
|
2641
2954
|
app.post("/api/config/providers/:provider/auth/start", async (c) => {
|
|
2642
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;
|
|
2643
2966
|
try {
|
|
2644
|
-
const result = await startProviderAuth(options.configPath, provider
|
|
2967
|
+
const result = await startProviderAuth(options.configPath, provider, {
|
|
2968
|
+
methodId
|
|
2969
|
+
});
|
|
2645
2970
|
if (!result) {
|
|
2646
2971
|
return c.json(err("NOT_SUPPORTED", `provider auth is not supported: ${provider}`), 404);
|
|
2647
2972
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/server",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.7",
|
|
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/openclaw-compat": "^0.2.0",
|
|
19
|
-
"@nextclaw/runtime": "^0.1.1",
|
|
20
18
|
"hono": "^4.6.2",
|
|
21
19
|
"ws": "^8.18.0",
|
|
22
|
-
"@nextclaw/
|
|
20
|
+
"@nextclaw/openclaw-compat": "0.2.2",
|
|
21
|
+
"@nextclaw/runtime": "0.1.2",
|
|
22
|
+
"@nextclaw/core": "0.7.3"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/node": "^20.17.6",
|