@nextclaw/server 0.6.5 → 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 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 = listBuiltinProviders();
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 = findBuiltinProviderByName(providerName);
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 = findBuiltinProviderByName(name);
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 = findBuiltinProviderByName(providerName);
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 = findBuiltinProviderByName(providerName);
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 = findBuiltinProviderByName2(providerName);
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
- spec.auth.baseUrl,
1148
- spec.auth.deviceCodePath,
1149
- spec.auth.tokenPath
1355
+ resolvedMethod.baseUrl,
1356
+ resolvedMethod.deviceCodePath,
1357
+ resolvedMethod.tokenPath
1150
1358
  );
1151
- const pkce = spec.auth.usePkce ? buildPkce() : null;
1152
- const body = new URLSearchParams({
1153
- client_id: spec.auth.clientId,
1154
- scope: spec.auth.scope
1155
- });
1156
- if (pkce) {
1157
- body.set("code_challenge", pkce.challenge);
1158
- body.set("code_challenge_method", "S256");
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
- deviceCode,
1441
+ authorizationCode,
1442
+ tokenCodeField,
1443
+ protocol: resolvedMethod.protocol,
1444
+ methodId: resolvedMethod.id,
1188
1445
  codeVerifier: pkce?.verifier,
1189
1446
  tokenEndpoint,
1190
- clientId: spec.auth.clientId,
1191
- grantType: spec.auth.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
- const payload = await response.json().catch(() => ({}));
1237
- if (!response.ok) {
1238
- const errorCode = payload.error?.trim().toLowerCase();
1239
- if (errorCode === "authorization_pending") {
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: "pending",
1243
- nextPollMs: session.intervalMs
1513
+ status: "error",
1514
+ message
1244
1515
  };
1245
1516
  }
1246
- if (errorCode === "slow_down") {
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
- if (errorCode === "access_denied") {
1257
- authSessions.delete(params.sessionId);
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: "denied",
1261
- message: payload.error_description || "authorization denied"
1587
+ status: "error",
1588
+ message: payload.error_description || payload.error || response.statusText || "authorization failed"
1262
1589
  };
1263
1590
  }
1264
- if (errorCode === "expired_token") {
1265
- authSessions.delete(params.sessionId);
1591
+ accessToken = payload.access_token?.trim() ?? "";
1592
+ if (!accessToken) {
1266
1593
  return {
1267
1594
  provider: params.providerName,
1268
- status: "expired",
1269
- message: payload.error_description || "authorization session expired"
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: spec?.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 = findBuiltinProviderByName2(providerName);
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.5",
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/openclaw-compat": "^0.2.0",
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.2"
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",