@spacex110/core 0.1.15 → 0.1.17
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 +337 -0
- package/dist/index.js +2553 -1893
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +19 -0
- package/dist/index.umd.js.map +1 -0
- package/package.json +1 -1
- package/src/channel-manager.ts +190 -2
- package/src/index.ts +18 -0
- package/src/multimodal-normalize.ts +170 -0
- package/src/providers.ts +2 -2
- package/src/strategies.ts +735 -1
- package/src/types.ts +177 -0
- package/dist/channel-manager.d.ts +0 -276
package/package.json
CHANGED
package/src/channel-manager.ts
CHANGED
|
@@ -15,10 +15,28 @@
|
|
|
15
15
|
* 10. 事件系统:on / off / emit
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import type {
|
|
18
|
+
import type {
|
|
19
|
+
AIConfig,
|
|
20
|
+
Provider,
|
|
21
|
+
Model,
|
|
22
|
+
ModelCapability,
|
|
23
|
+
StorageAdapter,
|
|
24
|
+
ApiFormat,
|
|
25
|
+
ChatMessage,
|
|
26
|
+
MultimodalChatOptions,
|
|
27
|
+
MultimodalChatStreamOptions,
|
|
28
|
+
MultimodalChatResult,
|
|
29
|
+
MultimodalStreamDelta,
|
|
30
|
+
} from './types';
|
|
19
31
|
import { getProvider, getAllProviders, getProvidersByFormat } from './providers';
|
|
20
32
|
import { defaultStorageAdapter } from './storage';
|
|
21
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
sendDirectChat,
|
|
35
|
+
sendDirectChatStream,
|
|
36
|
+
sendMultimodalChat,
|
|
37
|
+
sendMultimodalChatStream,
|
|
38
|
+
testDirectConnection,
|
|
39
|
+
} from './strategies';
|
|
22
40
|
import { fetchModels } from './api';
|
|
23
41
|
import { getStaticModels } from './models';
|
|
24
42
|
|
|
@@ -64,6 +82,17 @@ export interface ChannelManagerOptions {
|
|
|
64
82
|
proxyUrl?: string;
|
|
65
83
|
}
|
|
66
84
|
|
|
85
|
+
/**
|
|
86
|
+
* 多模态聊天结果(通道管理器层)。
|
|
87
|
+
* 在底层 MultimodalChatResult 上附加 channelId 与 model,便于上层定位调用方。
|
|
88
|
+
*/
|
|
89
|
+
export interface MultimodalChatManagerResult extends MultimodalChatResult {
|
|
90
|
+
/** 调用方通道 ID */
|
|
91
|
+
channelId: string;
|
|
92
|
+
/** 实际请求的模型 ID */
|
|
93
|
+
model: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
67
96
|
/** 通道查询过滤条件 */
|
|
68
97
|
export interface ChannelFilter {
|
|
69
98
|
/** 按 Provider ID 过滤 */
|
|
@@ -775,6 +804,165 @@ export class AIChannelManager {
|
|
|
775
804
|
return chatResult;
|
|
776
805
|
}
|
|
777
806
|
|
|
807
|
+
// ============ Multimodal Chat ============
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* 私有:根据聊天结果更新通道状态。复用 chatWith / chatWithStream 的逻辑。
|
|
811
|
+
*/
|
|
812
|
+
private updateChannelStatusAfterChat(
|
|
813
|
+
channelId: string,
|
|
814
|
+
success: boolean,
|
|
815
|
+
message: string | undefined
|
|
816
|
+
): void {
|
|
817
|
+
const index = this.channels.findIndex(c => c.id === channelId);
|
|
818
|
+
if (index < 0) return;
|
|
819
|
+
if (success) {
|
|
820
|
+
this.channels[index].lastUsedAt = Date.now();
|
|
821
|
+
this.channels[index].status = 'connected';
|
|
822
|
+
this.channels[index].lastError = undefined;
|
|
823
|
+
} else {
|
|
824
|
+
this.channels[index].status = 'error';
|
|
825
|
+
this.channels[index].lastError = message;
|
|
826
|
+
}
|
|
827
|
+
this.saveChannel(this.channels[index]);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* 使用当前激活通道发送多模态聊天。
|
|
832
|
+
* messages 支持 string(纯文本)或 ContentPart[](多模态)。
|
|
833
|
+
*/
|
|
834
|
+
async chatMultimodal(
|
|
835
|
+
message: ChatMessage | ChatMessage[],
|
|
836
|
+
options?: {
|
|
837
|
+
messages?: ChatMessage[];
|
|
838
|
+
maxTokens?: number;
|
|
839
|
+
outputModalities?: Array<'text' | 'image'>;
|
|
840
|
+
temperature?: number;
|
|
841
|
+
timeoutMs?: number;
|
|
842
|
+
}
|
|
843
|
+
): Promise<MultimodalChatManagerResult> {
|
|
844
|
+
const channel = this.getActiveChannel();
|
|
845
|
+
if (!channel) {
|
|
846
|
+
throw new Error('No active channel. Please set an active channel first.');
|
|
847
|
+
}
|
|
848
|
+
return this.chatMultimodalWith(channel.id, message, options);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* 使用指定通道发送多模态聊天。
|
|
853
|
+
* 与 chatMultimodal 区别:可显式指定 channelId。
|
|
854
|
+
*/
|
|
855
|
+
async chatMultimodalWith(
|
|
856
|
+
channelId: string,
|
|
857
|
+
message: ChatMessage | ChatMessage[],
|
|
858
|
+
options?: {
|
|
859
|
+
messages?: ChatMessage[];
|
|
860
|
+
maxTokens?: number;
|
|
861
|
+
outputModalities?: Array<'text' | 'image'>;
|
|
862
|
+
temperature?: number;
|
|
863
|
+
timeoutMs?: number;
|
|
864
|
+
}
|
|
865
|
+
): Promise<MultimodalChatManagerResult> {
|
|
866
|
+
const channel = this.getChannel(channelId);
|
|
867
|
+
if (!channel) {
|
|
868
|
+
throw new Error(`Channel not found: ${channelId}`);
|
|
869
|
+
}
|
|
870
|
+
const provider = getProvider(channel.providerId);
|
|
871
|
+
if (!provider) {
|
|
872
|
+
throw new Error(`Provider not found: ${channel.providerId}`);
|
|
873
|
+
}
|
|
874
|
+
const messages: ChatMessage[] = options?.messages
|
|
875
|
+
|| (Array.isArray(message) ? message : [message]);
|
|
876
|
+
const result = await sendMultimodalChat({
|
|
877
|
+
apiFormat: channel.apiFormat,
|
|
878
|
+
baseUrl: channel.baseUrl || provider.baseUrl,
|
|
879
|
+
apiKey: channel.apiKey,
|
|
880
|
+
model: channel.model,
|
|
881
|
+
messages,
|
|
882
|
+
maxTokens: options?.maxTokens,
|
|
883
|
+
outputModalities: options?.outputModalities,
|
|
884
|
+
temperature: options?.temperature,
|
|
885
|
+
timeoutMs: options?.timeoutMs,
|
|
886
|
+
});
|
|
887
|
+
const out: MultimodalChatManagerResult = {
|
|
888
|
+
...result,
|
|
889
|
+
channelId: channel.id,
|
|
890
|
+
model: channel.model,
|
|
891
|
+
};
|
|
892
|
+
this.updateChannelStatusAfterChat(channel.id, result.success, result.message);
|
|
893
|
+
this.emit('chat', out);
|
|
894
|
+
return out;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* 使用当前激活通道发送多模态流式聊天。
|
|
899
|
+
* onDelta 每次收到「累计完整状态」({ text, images })。
|
|
900
|
+
*/
|
|
901
|
+
async chatMultimodalStream(
|
|
902
|
+
message: ChatMessage | ChatMessage[],
|
|
903
|
+
options: {
|
|
904
|
+
messages?: ChatMessage[];
|
|
905
|
+
maxTokens?: number;
|
|
906
|
+
outputModalities?: Array<'text' | 'image'>;
|
|
907
|
+
temperature?: number;
|
|
908
|
+
timeoutMs?: number;
|
|
909
|
+
onDelta: (state: MultimodalStreamDelta) => void;
|
|
910
|
+
}
|
|
911
|
+
): Promise<MultimodalChatManagerResult> {
|
|
912
|
+
const channel = this.getActiveChannel();
|
|
913
|
+
if (!channel) {
|
|
914
|
+
throw new Error('No active channel. Please set an active channel first.');
|
|
915
|
+
}
|
|
916
|
+
return this.chatMultimodalStreamWith(channel.id, message, options);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* 使用指定通道发送多模态流式聊天。
|
|
921
|
+
*/
|
|
922
|
+
async chatMultimodalStreamWith(
|
|
923
|
+
channelId: string,
|
|
924
|
+
message: ChatMessage | ChatMessage[],
|
|
925
|
+
options: {
|
|
926
|
+
messages?: ChatMessage[];
|
|
927
|
+
maxTokens?: number;
|
|
928
|
+
outputModalities?: Array<'text' | 'image'>;
|
|
929
|
+
temperature?: number;
|
|
930
|
+
timeoutMs?: number;
|
|
931
|
+
onDelta: (state: MultimodalStreamDelta) => void;
|
|
932
|
+
}
|
|
933
|
+
): Promise<MultimodalChatManagerResult> {
|
|
934
|
+
const channel = this.getChannel(channelId);
|
|
935
|
+
if (!channel) {
|
|
936
|
+
throw new Error(`Channel not found: ${channelId}`);
|
|
937
|
+
}
|
|
938
|
+
const provider = getProvider(channel.providerId);
|
|
939
|
+
if (!provider) {
|
|
940
|
+
throw new Error(`Provider not found: ${channel.providerId}`);
|
|
941
|
+
}
|
|
942
|
+
const messages: ChatMessage[] = options.messages
|
|
943
|
+
|| (Array.isArray(message) ? message : [message]);
|
|
944
|
+
const result = await sendMultimodalChatStream({
|
|
945
|
+
apiFormat: channel.apiFormat,
|
|
946
|
+
baseUrl: channel.baseUrl || provider.baseUrl,
|
|
947
|
+
apiKey: channel.apiKey,
|
|
948
|
+
model: channel.model,
|
|
949
|
+
messages,
|
|
950
|
+
maxTokens: options.maxTokens,
|
|
951
|
+
outputModalities: options.outputModalities,
|
|
952
|
+
temperature: options.temperature,
|
|
953
|
+
timeoutMs: options.timeoutMs,
|
|
954
|
+
onDelta: options.onDelta,
|
|
955
|
+
});
|
|
956
|
+
const out: MultimodalChatManagerResult = {
|
|
957
|
+
...result,
|
|
958
|
+
channelId: channel.id,
|
|
959
|
+
model: channel.model,
|
|
960
|
+
};
|
|
961
|
+
this.updateChannelStatusAfterChat(channel.id, result.success, result.message);
|
|
962
|
+
this.emit('chat', out);
|
|
963
|
+
return out;
|
|
964
|
+
}
|
|
965
|
+
|
|
778
966
|
/** 测试指定通道的连接 */
|
|
779
967
|
async testChannel(id: string): Promise<{ success: boolean; latencyMs?: number; message?: string }> {
|
|
780
968
|
const channel = this.getChannel(id);
|
package/src/index.ts
CHANGED
|
@@ -27,6 +27,17 @@ export type {
|
|
|
27
27
|
FetcherActionType,
|
|
28
28
|
// Model capability
|
|
29
29
|
ModelCapability,
|
|
30
|
+
// Multimodal types
|
|
31
|
+
ContentPart,
|
|
32
|
+
ChatMessage,
|
|
33
|
+
NormalizedPart,
|
|
34
|
+
NormalizedMessage,
|
|
35
|
+
ContentResponsePart,
|
|
36
|
+
ResponseMessage,
|
|
37
|
+
MultimodalChatOptions,
|
|
38
|
+
MultimodalChatStreamOptions,
|
|
39
|
+
MultimodalChatResult,
|
|
40
|
+
MultimodalStreamDelta,
|
|
30
41
|
} from './types';
|
|
31
42
|
|
|
32
43
|
export {
|
|
@@ -78,14 +89,20 @@ export {
|
|
|
78
89
|
type ProviderStrategy,
|
|
79
90
|
strategyRegistry,
|
|
80
91
|
getStrategy,
|
|
92
|
+
guessMimeFromDataUri,
|
|
81
93
|
sendDirectChat,
|
|
82
94
|
sendDirectChatStream,
|
|
95
|
+
sendMultimodalChat,
|
|
96
|
+
sendMultimodalChatStream,
|
|
83
97
|
testDirectConnection,
|
|
84
98
|
type DirectChatOptions,
|
|
85
99
|
type DirectChatStreamOptions,
|
|
86
100
|
type DirectChatResult,
|
|
87
101
|
} from './strategies';
|
|
88
102
|
|
|
103
|
+
// Multimodal Normalize
|
|
104
|
+
export { normalizeMessages, normalizePart, fetchRemoteToDataUri } from './multimodal-normalize';
|
|
105
|
+
|
|
89
106
|
// Channel Manager (Headless)
|
|
90
107
|
export {
|
|
91
108
|
AIChannelManager,
|
|
@@ -93,5 +110,6 @@ export {
|
|
|
93
110
|
type ChannelManagerOptions,
|
|
94
111
|
type ChannelFilter,
|
|
95
112
|
type ChatResult,
|
|
113
|
+
type MultimodalChatManagerResult,
|
|
96
114
|
type ChannelEventType,
|
|
97
115
|
} from './channel-manager';
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 多模态消息标准化层
|
|
3
|
+
* 把调用方传入的 ChatMessage[](content: string | ContentPart[])转换为各协议策略都能消费的 NormalizedMessage[]。
|
|
4
|
+
*
|
|
5
|
+
* 关键设计:
|
|
6
|
+
* - 文本直接透传
|
|
7
|
+
* - 图片/音频统一转换为 data URI(含 MIME 推断)
|
|
8
|
+
* - HTTP/HTTPS URL 远程拉取并转为 base64 data URI(失败时保留 URL 透传并在 meta 中标记)
|
|
9
|
+
* - 裸 base64 视为缺少 MIME,按输入块的 type 推测
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
ChatMessage,
|
|
14
|
+
ContentPart,
|
|
15
|
+
NormalizedMessage,
|
|
16
|
+
NormalizedPart,
|
|
17
|
+
} from './types';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 把一个 ContentPart 转成 NormalizedPart。
|
|
21
|
+
* - text:透传
|
|
22
|
+
* - image_url / image / inline_data:归一化为 image + data URI
|
|
23
|
+
* - audio:归一化为 audio + data URI
|
|
24
|
+
*/
|
|
25
|
+
export async function normalizePart(p: ContentPart): Promise<NormalizedPart> {
|
|
26
|
+
// 纯文本块
|
|
27
|
+
if (p.type === 'text') {
|
|
28
|
+
return { type: 'text', data: p.text };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let dataUri: string;
|
|
32
|
+
let mime: string | undefined;
|
|
33
|
+
let meta: Record<string, unknown> | undefined;
|
|
34
|
+
|
|
35
|
+
// OpenAI 风格 image_url
|
|
36
|
+
if (p.type === 'image_url') {
|
|
37
|
+
dataUri = p.image_url.url;
|
|
38
|
+
if (p.image_url.detail) meta = { detail: p.image_url.detail };
|
|
39
|
+
}
|
|
40
|
+
// Anthropic 风格 image(base64 或 url)
|
|
41
|
+
else if (p.type === 'image') {
|
|
42
|
+
if (p.source.type === 'base64') {
|
|
43
|
+
dataUri = `data:${p.source.media_type};base64,${p.source.data}`;
|
|
44
|
+
mime = p.source.media_type;
|
|
45
|
+
} else {
|
|
46
|
+
// source.type === 'url'
|
|
47
|
+
dataUri = (p.source as { type: 'url'; url: string }).url;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Gemini 风格 inline_data
|
|
51
|
+
else if (p.type === 'inline_data') {
|
|
52
|
+
dataUri = `data:${p.inline_data.mime_type};base64,${p.inline_data.data}`;
|
|
53
|
+
mime = p.inline_data.mime_type;
|
|
54
|
+
}
|
|
55
|
+
// OpenAI 风格 input_audio
|
|
56
|
+
else if (p.type === 'audio') {
|
|
57
|
+
dataUri = `data:audio/${p.input_audio.format};base64,${p.input_audio.data}`;
|
|
58
|
+
mime = `audio/${p.input_audio.format}`;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
throw new Error(`未知的 ContentPart 类型: ${(p as { type: string }).type}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// HTTP/HTTPS 远程 URL:尝试拉取并转换为 data URI
|
|
65
|
+
if (/^https?:\/\//i.test(dataUri)) {
|
|
66
|
+
const remote = await fetchRemoteToDataUri(dataUri);
|
|
67
|
+
return {
|
|
68
|
+
type: 'image',
|
|
69
|
+
data: remote.dataUri,
|
|
70
|
+
mimeType: remote.mimeType,
|
|
71
|
+
meta: { ...meta, remoteUrlOnly: remote.fallback ? dataUri : undefined },
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// data URI:直接解析 MIME
|
|
76
|
+
if (/^data:/i.test(dataUri)) {
|
|
77
|
+
const m = /^data:([^;,]+)/.exec(dataUri);
|
|
78
|
+
return {
|
|
79
|
+
type: p.type === 'audio' ? 'audio' : 'image',
|
|
80
|
+
data: dataUri,
|
|
81
|
+
mimeType: mime || m?.[1],
|
|
82
|
+
meta,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 兜底:当作裸 base64 + 推断 MIME
|
|
87
|
+
const fallbackMime =
|
|
88
|
+
mime || (p.type === 'audio' ? 'audio/mpeg' : 'image/png');
|
|
89
|
+
return {
|
|
90
|
+
type: p.type === 'audio' ? 'audio' : 'image',
|
|
91
|
+
data: `data:${fallbackMime};base64,${dataUri}`,
|
|
92
|
+
mimeType: fallbackMime,
|
|
93
|
+
meta,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 远程图片 → data URI。
|
|
99
|
+
* 拉取失败时返回原始 URL(标记 fallback=true),让协议策略自行决定是否接受 URL。
|
|
100
|
+
*/
|
|
101
|
+
export async function fetchRemoteToDataUri(
|
|
102
|
+
url: string
|
|
103
|
+
): Promise<{ dataUri: string; mimeType?: string; fallback: boolean }> {
|
|
104
|
+
try {
|
|
105
|
+
const resp = await fetch(url);
|
|
106
|
+
if (!resp.ok) {
|
|
107
|
+
return { dataUri: url, fallback: true };
|
|
108
|
+
}
|
|
109
|
+
const mimeType = resp.headers.get('content-type') || undefined;
|
|
110
|
+
const buf = await resp.arrayBuffer();
|
|
111
|
+
// 在 Node 与浏览器中都能用的 base64 转换
|
|
112
|
+
const b64 = arrayBufferToBase64(buf);
|
|
113
|
+
const dataUri = `data:${mimeType || 'image/png'};base64,${b64}`;
|
|
114
|
+
return { dataUri, mimeType, fallback: false };
|
|
115
|
+
} catch {
|
|
116
|
+
return { dataUri: url, fallback: true };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* ArrayBuffer → base64。Node 与浏览器通用。
|
|
122
|
+
*/
|
|
123
|
+
function arrayBufferToBase64(buf: ArrayBuffer): string {
|
|
124
|
+
// Node 环境:使用 globalThis 上的 Buffer(避免引入 @types/node)
|
|
125
|
+
const g = globalThis as unknown as {
|
|
126
|
+
Buffer?: { from(data: ArrayBuffer): { toString(encoding: string): string } };
|
|
127
|
+
btoa?: (data: string) => string;
|
|
128
|
+
};
|
|
129
|
+
if (g.Buffer && typeof g.Buffer.from === 'function') {
|
|
130
|
+
return g.Buffer.from(buf).toString('base64');
|
|
131
|
+
}
|
|
132
|
+
// 浏览器:用 btoa
|
|
133
|
+
let binary = '';
|
|
134
|
+
const bytes = new Uint8Array(buf);
|
|
135
|
+
const chunk = 0x8000;
|
|
136
|
+
for (let i = 0; i < bytes.length; i += chunk) {
|
|
137
|
+
binary += String.fromCharCode.apply(
|
|
138
|
+
null,
|
|
139
|
+
Array.from(bytes.subarray(i, i + chunk)) as number[]
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return g.btoa ? g.btoa(binary) : binary;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 把 ChatMessage[] 转为 NormalizedMessage[]。
|
|
147
|
+
*
|
|
148
|
+
* 规则:
|
|
149
|
+
* - content: string → 一个 text part
|
|
150
|
+
* - content: ContentPart[] → 逐个 normalizePart
|
|
151
|
+
* - role: 'tool' → 在统一层视为 'user'(避免各策略重复处理 tool role)
|
|
152
|
+
*/
|
|
153
|
+
export async function normalizeMessages(
|
|
154
|
+
messages: ChatMessage[]
|
|
155
|
+
): Promise<NormalizedMessage[]> {
|
|
156
|
+
const out: NormalizedMessage[] = [];
|
|
157
|
+
for (const m of messages) {
|
|
158
|
+
const role: NormalizedMessage['role'] = m.role === 'tool' ? 'user' : m.role;
|
|
159
|
+
if (typeof m.content === 'string') {
|
|
160
|
+
out.push({ role, parts: [{ type: 'text', data: m.content }] });
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const parts: NormalizedPart[] = [];
|
|
164
|
+
for (const p of m.content) {
|
|
165
|
+
parts.push(await normalizePart(p));
|
|
166
|
+
}
|
|
167
|
+
out.push({ role, parts });
|
|
168
|
+
}
|
|
169
|
+
return out;
|
|
170
|
+
}
|
package/src/providers.ts
CHANGED
|
@@ -133,7 +133,7 @@ export const PROVIDERS: Record<string, Provider> = {
|
|
|
133
133
|
baseUrl: 'https://api.minimax.io/v1',
|
|
134
134
|
needsApiKey: true,
|
|
135
135
|
apiFormat: 'openai',
|
|
136
|
-
supportsModelsApi:
|
|
136
|
+
supportsModelsApi: true,
|
|
137
137
|
icon: `${ICON_CDN_BASE}/minimax.svg`,
|
|
138
138
|
},
|
|
139
139
|
// MiniMax 中文站(同服务商,独立域名;模型列表与海外版一致)
|
|
@@ -143,7 +143,7 @@ export const PROVIDERS: Record<string, Provider> = {
|
|
|
143
143
|
baseUrl: 'https://api.minimaxi.com/v1',
|
|
144
144
|
needsApiKey: true,
|
|
145
145
|
apiFormat: 'openai',
|
|
146
|
-
supportsModelsApi:
|
|
146
|
+
supportsModelsApi: true,
|
|
147
147
|
icon: `${ICON_CDN_BASE}/minimax.svg`,
|
|
148
148
|
},
|
|
149
149
|
[PROVIDER_ID.XAI]: {
|