feat: add MiniMax coding plans support to /connect (#684)

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
Charles Packer
2026-01-26 13:24:47 -08:00
committed by GitHub
parent aa8a58df3f
commit 83cdccd930
3 changed files with 311 additions and 5 deletions

View File

@@ -8,6 +8,12 @@ import {
startLocalOAuthServer,
startOpenAIOAuth,
} from "../../auth/openai-oauth";
import {
createOrUpdateMinimaxProvider,
getMinimaxProvider,
MINIMAX_PROVIDER_NAME,
removeMinimaxProvider,
} from "../../providers/minimax-provider";
import {
checkOpenAICodexEligibility,
createOrUpdateOpenAICodexProvider,
@@ -110,18 +116,18 @@ export async function handleConnect(
ctx.buffersRef,
ctx.refreshDerived,
msg,
"Usage: /connect <provider> [options]\n\nAvailable providers:\n \u2022 codex - Connect via OAuth to authenticate with ChatGPT Plus/Pro\n \u2022 zai <api_key> - Connect to Zai with your API key",
"Usage: /connect <provider> [options]\n\nAvailable providers:\n \u2022 codex - Connect via OAuth to authenticate with ChatGPT Plus/Pro\n \u2022 zai <api_key> - Connect to zAI with your API key\n \u2022 minimax <api_key> - Connect to MiniMax with your API key",
false,
);
return;
}
if (provider !== "codex" && provider !== "zai") {
if (provider !== "codex" && provider !== "zai" && provider !== "minimax") {
addCommandResult(
ctx.buffersRef,
ctx.refreshDerived,
msg,
`Error: Unknown provider "${provider}"\n\nAvailable providers: codex, zai\nUsage: /connect <provider> [options]`,
`Error: Unknown provider "${provider}"\n\nAvailable providers: codex, zai, minimax\nUsage: /connect <provider> [options]`,
false,
);
return;
@@ -133,6 +139,12 @@ export async function handleConnect(
return;
}
// MiniMax is handled separately in App.tsx, but add a fallback just in case
if (provider === "minimax") {
await handleConnectMinimax(ctx, msg);
return;
}
// Handle /connect codex
await handleConnectCodex(ctx, msg);
}
@@ -372,6 +384,137 @@ async function handleConnectCodex(
}
}
/**
* Handle /disconnect minimax
*/
async function handleDisconnectMinimax(
ctx: ConnectCommandContext,
msg: string,
): Promise<void> {
// Check if MiniMax provider exists
const existing = await getMinimaxProvider();
if (!existing) {
addCommandResult(
ctx.buffersRef,
ctx.refreshDerived,
msg,
"Not currently connected to MiniMax.\n\nUse /connect minimax <api_key> to connect.",
false,
);
return;
}
// Show running status
const cmdId = addCommandResult(
ctx.buffersRef,
ctx.refreshDerived,
msg,
"Disconnecting from MiniMax...",
true,
"running",
);
ctx.setCommandRunning(true);
try {
// Remove provider from Letta
await removeMinimaxProvider();
updateCommandResult(
ctx.buffersRef,
ctx.refreshDerived,
cmdId,
msg,
`\u2713 Disconnected from MiniMax.\n\n` +
`Provider '${MINIMAX_PROVIDER_NAME}' removed from Letta.`,
true,
"finished",
);
} catch (error) {
updateCommandResult(
ctx.buffersRef,
ctx.refreshDerived,
cmdId,
msg,
`\u2717 Failed to disconnect from MiniMax: ${getErrorMessage(error)}`,
false,
"finished",
);
} finally {
ctx.setCommandRunning(false);
}
}
/**
* Handle /connect minimax command
* Usage: /connect minimax <api_key>
*
* Creates the minimax-coding-plan provider with the provided API key
*/
export async function handleConnectMinimax(
ctx: ConnectCommandContext,
msg: string,
): Promise<void> {
const parts = msg.trim().split(/\s+/);
// Join all remaining parts in case the API key got split
const apiKey = parts.slice(2).join("");
// If no API key provided, show usage
if (!apiKey || apiKey.length === 0) {
addCommandResult(
ctx.buffersRef,
ctx.refreshDerived,
msg,
"Usage: /connect minimax <api_key>\n\n" +
"Connect to MiniMax by providing your API key.\n\n" +
"Example: /connect minimax <api_key>...",
false,
);
return;
}
// Show running status
const cmdId = addCommandResult(
ctx.buffersRef,
ctx.refreshDerived,
msg,
"Creating MiniMax coding plan provider...",
true,
"running",
);
ctx.setCommandRunning(true);
try {
// Create or update the MiniMax provider with the API key
await createOrUpdateMinimaxProvider(apiKey);
updateCommandResult(
ctx.buffersRef,
ctx.refreshDerived,
cmdId,
msg,
`\u2713 Successfully connected to MiniMax!\n\n` +
`Provider '${MINIMAX_PROVIDER_NAME}' created in Letta.\n\n` +
`The models are populated in /model \u2192 "All Available Models"`,
true,
"finished",
);
} catch (error) {
updateCommandResult(
ctx.buffersRef,
ctx.refreshDerived,
cmdId,
msg,
`\u2717 Failed to create MiniMax provider: ${getErrorMessage(error)}`,
false,
"finished",
);
} finally {
ctx.setCommandRunning(false);
}
}
/**
* Handle /disconnect command
* Usage: /disconnect <provider>
@@ -389,7 +532,7 @@ export async function handleDisconnect(
ctx.buffersRef,
ctx.refreshDerived,
msg,
"Usage: /disconnect <provider>\n\nAvailable providers: codex, claude, zai",
"Usage: /disconnect <provider>\n\nAvailable providers: codex, claude, zai, minimax",
false,
);
return;
@@ -401,6 +544,12 @@ export async function handleDisconnect(
return;
}
// Handle /disconnect minimax
if (provider === "minimax") {
await handleDisconnectMinimax(ctx, msg);
return;
}
// Handle /disconnect codex
if (provider === "codex") {
await handleDisconnectCodex(ctx, msg);
@@ -418,7 +567,7 @@ export async function handleDisconnect(
ctx.buffersRef,
ctx.refreshDerived,
msg,
`Error: Unknown provider "${provider}"\n\nAvailable providers: codex, claude, zai\nUsage: /disconnect <provider>`,
`Error: Unknown provider "${provider}"\n\nAvailable providers: codex, claude, zai, minimax\nUsage: /disconnect <provider>`,
false,
);
}

View File

@@ -45,6 +45,13 @@ export const BYOK_PROVIDERS = [
providerType: "zai",
providerName: "lc-zai",
},
{
id: "minimax",
displayName: "MiniMax API",
description: "Connect a MiniMax key or coding plan",
providerType: "minimax",
providerName: "lc-minimax",
},
{
id: "gemini",
displayName: "Gemini API",

View File

@@ -0,0 +1,150 @@
/**
* Direct API calls to Letta for managing MiniMax provider
*/
import { LETTA_CLOUD_API_URL } from "../auth/oauth";
import { settingsManager } from "../settings-manager";
// Provider name constant for MiniMax coding plan
export const MINIMAX_PROVIDER_NAME = "minimax-coding-plan";
interface ProviderResponse {
id: string;
name: string;
provider_type: string;
api_key?: string;
base_url?: string;
}
/**
* Get the Letta API base URL and auth token
*/
async function getLettaConfig(): Promise<{ baseUrl: string; apiKey: string }> {
const settings = await settingsManager.getSettingsWithSecureTokens();
const baseUrl =
process.env.LETTA_BASE_URL ||
settings.env?.LETTA_BASE_URL ||
LETTA_CLOUD_API_URL;
const apiKey = process.env.LETTA_API_KEY || settings.env?.LETTA_API_KEY || "";
return { baseUrl, apiKey };
}
/**
* Make a request to the Letta providers API
*/
async function providersRequest<T>(
method: "GET" | "POST" | "PATCH" | "DELETE",
path: string,
body?: Record<string, unknown>,
): Promise<T> {
const { baseUrl, apiKey } = await getLettaConfig();
const url = `${baseUrl}${path}`;
const response = await fetch(url, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
"X-Letta-Source": "letta-code",
},
...(body && { body: JSON.stringify(body) }),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Provider API error (${response.status}): ${errorText}`);
}
// Handle empty responses (e.g., DELETE)
const text = await response.text();
if (!text) {
return {} as T;
}
return JSON.parse(text) as T;
}
/**
* List all providers
*/
async function listProviders(): Promise<ProviderResponse[]> {
try {
const response = await providersRequest<ProviderResponse[]>(
"GET",
"/v1/providers",
);
return response;
} catch {
return [];
}
}
/**
* Get the minimax-coding-plan provider if it exists
*/
export async function getMinimaxProvider(): Promise<ProviderResponse | null> {
const providers = await listProviders();
return providers.find((p) => p.name === MINIMAX_PROVIDER_NAME) || null;
}
/**
* Create the MiniMax coding plan provider with the given API key
*/
export async function createMinimaxProvider(
apiKey: string,
): Promise<ProviderResponse> {
return providersRequest<ProviderResponse>("POST", "/v1/providers", {
name: MINIMAX_PROVIDER_NAME,
provider_type: "minimax",
api_key: apiKey,
});
}
/**
* Update an existing MiniMax provider with a new API key
*/
export async function updateMinimaxProvider(
providerId: string,
apiKey: string,
): Promise<ProviderResponse> {
return providersRequest<ProviderResponse>(
"PATCH",
`/v1/providers/${providerId}`,
{
api_key: apiKey,
},
);
}
/**
* Create or update the MiniMax coding plan provider
* If provider exists, updates it with the new API key
* If not, creates a new provider
*/
export async function createOrUpdateMinimaxProvider(
apiKey: string,
): Promise<ProviderResponse> {
const existing = await getMinimaxProvider();
if (existing) {
return updateMinimaxProvider(existing.id, apiKey);
}
return createMinimaxProvider(apiKey);
}
/**
* Delete the MiniMax provider by ID
*/
async function deleteMinimaxProvider(providerId: string): Promise<void> {
await providersRequest<void>("DELETE", `/v1/providers/${providerId}`);
}
/**
* Remove the MiniMax provider (called on /disconnect minimax)
*/
export async function removeMinimaxProvider(): Promise<void> {
const existing = await getMinimaxProvider();
if (existing) {
await deleteMinimaxProvider(existing.id);
}
}