@z-qinghui/migpt-claw 1.0.0
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/LICENSE +21 -0
- package/README.md +690 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/setup-entry.d.ts +3 -0
- package/dist/setup-entry.js +7 -0
- package/dist/setup-entry.js.map +1 -0
- package/dist/src/channel.d.ts +10 -0
- package/dist/src/channel.js +444 -0
- package/dist/src/channel.js.map +1 -0
- package/dist/src/config.d.ts +125 -0
- package/dist/src/config.js +146 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/message.d.ts +51 -0
- package/dist/src/message.js +145 -0
- package/dist/src/message.js.map +1 -0
- package/dist/src/mi/account.d.ts +5 -0
- package/dist/src/mi/account.js +162 -0
- package/dist/src/mi/account.js.map +1 -0
- package/dist/src/mi/common.d.ts +15 -0
- package/dist/src/mi/common.js +80 -0
- package/dist/src/mi/common.js.map +1 -0
- package/dist/src/mi/index.d.ts +4 -0
- package/dist/src/mi/index.js +10 -0
- package/dist/src/mi/index.js.map +1 -0
- package/dist/src/mi/mina.d.ts +66 -0
- package/dist/src/mi/mina.js +225 -0
- package/dist/src/mi/mina.js.map +1 -0
- package/dist/src/mi/miot.d.ts +35 -0
- package/dist/src/mi/miot.js +168 -0
- package/dist/src/mi/miot.js.map +1 -0
- package/dist/src/mi/typing.d.ts +90 -0
- package/dist/src/mi/typing.js +1 -0
- package/dist/src/mi/typing.js.map +1 -0
- package/dist/src/onboarding.d.ts +5 -0
- package/dist/src/onboarding.js +118 -0
- package/dist/src/onboarding.js.map +1 -0
- package/dist/src/openclaw-plugin-sdk.d.d.ts +185 -0
- package/dist/src/openclaw-plugin-sdk.d.js +1 -0
- package/dist/src/openclaw-plugin-sdk.d.js.map +1 -0
- package/dist/src/outbound.d.ts +5 -0
- package/dist/src/outbound.js +108 -0
- package/dist/src/outbound.js.map +1 -0
- package/dist/src/runtime.d.ts +6 -0
- package/dist/src/runtime.js +15 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/service.d.ts +70 -0
- package/dist/src/service.js +200 -0
- package/dist/src/service.js.map +1 -0
- package/dist/src/speaker.d.ts +62 -0
- package/dist/src/speaker.js +211 -0
- package/dist/src/speaker.js.map +1 -0
- package/dist/src/tts/mimo.d.ts +50 -0
- package/dist/src/tts/mimo.js +214 -0
- package/dist/src/tts/mimo.js.map +1 -0
- package/dist/src/types.d.ts +30 -0
- package/dist/src/types.js +1 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/codec.d.ts +31 -0
- package/dist/src/utils/codec.js +144 -0
- package/dist/src/utils/codec.js.map +1 -0
- package/dist/src/utils/debug.d.ts +10 -0
- package/dist/src/utils/debug.js +15 -0
- package/dist/src/utils/debug.js.map +1 -0
- package/dist/src/utils/hash.d.ts +40 -0
- package/dist/src/utils/hash.js +75 -0
- package/dist/src/utils/hash.js.map +1 -0
- package/dist/src/utils/http.d.ts +24 -0
- package/dist/src/utils/http.js +151 -0
- package/dist/src/utils/http.js.map +1 -0
- package/dist/src/utils/index.d.ts +6 -0
- package/dist/src/utils/index.js +10 -0
- package/dist/src/utils/index.js.map +1 -0
- package/dist/src/utils/io.d.ts +26 -0
- package/dist/src/utils/io.js +53 -0
- package/dist/src/utils/io.js.map +1 -0
- package/dist/src/utils/parse.d.ts +26 -0
- package/dist/src/utils/parse.js +51 -0
- package/dist/src/utils/parse.js.map +1 -0
- package/index.ts +26 -0
- package/openclaw.plugin.json +344 -0
- package/package.json +106 -0
- package/setup-entry.ts +12 -0
- package/skills/migpt-volume/SKILL.md +182 -0
- package/skills/migpt-volume/index.ts +50 -0
- package/src/channel.ts +519 -0
- package/src/config.ts +299 -0
- package/src/message.ts +186 -0
- package/src/mi/account.ts +184 -0
- package/src/mi/common.ts +105 -0
- package/src/mi/index.ts +4 -0
- package/src/mi/mina.ts +261 -0
- package/src/mi/miot.ts +193 -0
- package/src/mi/typing.ts +93 -0
- package/src/onboarding.ts +136 -0
- package/src/openclaw-plugin-sdk.d.ts +185 -0
- package/src/outbound.ts +137 -0
- package/src/runtime.ts +14 -0
- package/src/service.ts +246 -0
- package/src/speaker.ts +264 -0
- package/src/tts/mimo.ts +300 -0
- package/src/types.ts +34 -0
- package/src/utils/codec.ts +206 -0
- package/src/utils/debug.ts +16 -0
- package/src/utils/hash.ts +104 -0
- package/src/utils/http.ts +193 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/io.ts +68 -0
- package/src/utils/parse.ts +64 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { ChannelOnboardingAdapter } from 'openclaw/plugin-sdk';
|
|
2
|
+
import { MiService } from './service.js';
|
|
3
|
+
import type { MiServiceConfig } from './service.js';
|
|
4
|
+
|
|
5
|
+
export const miGPTOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
6
|
+
async selectAccount({ accounts }: { accounts: string[] }) {
|
|
7
|
+
if (accounts.length === 0) {
|
|
8
|
+
return { action: 'create' };
|
|
9
|
+
}
|
|
10
|
+
if (accounts.length === 1) {
|
|
11
|
+
return { action: 'use', accountId: accounts[0] };
|
|
12
|
+
}
|
|
13
|
+
return { action: 'select' };
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
async promptCredentials() {
|
|
17
|
+
// 这些方法由 OpenClaw 框架在运行时提供
|
|
18
|
+
const answers: any = {};
|
|
19
|
+
|
|
20
|
+
answers.userId = await (this as any).prompt.input({
|
|
21
|
+
message: '请输入你的小米 ID(数字,在小米账号「个人信息」-「小米 ID」查看):',
|
|
22
|
+
validate: (v: string) => /^\d+$/.test(v) || '小米 ID 必须是数字',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const usePassToken = await (this as any).prompt.confirm({
|
|
26
|
+
message: '是否使用 passToken 登录?(推荐,可避免验证码)',
|
|
27
|
+
initial: false,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (usePassToken) {
|
|
31
|
+
answers.passToken = await (this as any).prompt.password({
|
|
32
|
+
message: '请输入 passToken:',
|
|
33
|
+
validate: (v: string) => !!v || 'passToken 不能为空',
|
|
34
|
+
});
|
|
35
|
+
} else {
|
|
36
|
+
answers.password = await (this as any).prompt.password({
|
|
37
|
+
message: '请输入小米账号密码:',
|
|
38
|
+
validate: (v: string) => !!v || '密码不能为空',
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
answers.deviceName = await (this as any).prompt.input({
|
|
43
|
+
message: '请输入小爱音箱在米家中设置的名称(如:客厅音箱):',
|
|
44
|
+
validate: (v: string) => !!v || '设备名称不能为空',
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return answers;
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
async validateCredentials({ input }: { input: any }) {
|
|
51
|
+
const config: MiServiceConfig = {
|
|
52
|
+
userId: input.userId,
|
|
53
|
+
password: input.password,
|
|
54
|
+
passToken: input.passToken,
|
|
55
|
+
debug: true,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const devices = await MiService.getDevices(config);
|
|
60
|
+
|
|
61
|
+
if (devices.length === 0) {
|
|
62
|
+
return {
|
|
63
|
+
valid: false,
|
|
64
|
+
error: '未找到任何设备,请检查账号凭证是否正确',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const deviceName = input.deviceName;
|
|
69
|
+
const matchedDevice = devices.find(
|
|
70
|
+
(d) => d.name.toLowerCase() === deviceName.toLowerCase() ||
|
|
71
|
+
d.did.toLowerCase() === deviceName.toLowerCase()
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (!matchedDevice) {
|
|
75
|
+
const deviceList = devices.map((d) => d.name).join(', ');
|
|
76
|
+
return {
|
|
77
|
+
valid: false,
|
|
78
|
+
error: `未找到设备 "${deviceName}"。可用设备:${deviceList}`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
valid: true,
|
|
84
|
+
data: {
|
|
85
|
+
did: matchedDevice.did,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
} catch (err: any) {
|
|
89
|
+
return {
|
|
90
|
+
valid: false,
|
|
91
|
+
error: err.message || '验证失败',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
applyConfig({ cfg, accountId, input, validatedData }: { cfg: any; accountId?: string; input: any; validatedData?: any }) {
|
|
97
|
+
const migptCfg = cfg.channels?.migpt ?? {};
|
|
98
|
+
|
|
99
|
+
const accountConfig = {
|
|
100
|
+
userId: input.userId,
|
|
101
|
+
password: input.password,
|
|
102
|
+
passToken: input.passToken,
|
|
103
|
+
devices: [validatedData?.did || input.deviceName],
|
|
104
|
+
enabled: true,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const isDefault = !accountId || accountId === 'main';
|
|
108
|
+
|
|
109
|
+
if (isDefault) {
|
|
110
|
+
return {
|
|
111
|
+
...cfg,
|
|
112
|
+
channels: {
|
|
113
|
+
...cfg.channels,
|
|
114
|
+
migpt: {
|
|
115
|
+
...migptCfg,
|
|
116
|
+
...accountConfig,
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
...cfg,
|
|
124
|
+
channels: {
|
|
125
|
+
...cfg.channels,
|
|
126
|
+
migpt: {
|
|
127
|
+
...migptCfg,
|
|
128
|
+
accounts: {
|
|
129
|
+
...migptCfg.accounts,
|
|
130
|
+
[accountId]: accountConfig,
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// Type declarations for openclaw/plugin-sdk
|
|
2
|
+
declare module 'openclaw/plugin-sdk/channel-core' {
|
|
3
|
+
export function defineSetupPluginEntry(plugin: any): any;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
declare module 'openclaw/plugin-sdk' {
|
|
7
|
+
export const DEFAULT_ACCOUNT_ID: string;
|
|
8
|
+
|
|
9
|
+
export function emptyPluginConfigSchema(): any;
|
|
10
|
+
|
|
11
|
+
export interface PluginRuntime {
|
|
12
|
+
channel: {
|
|
13
|
+
activity: {
|
|
14
|
+
record: (opts: { channel: string; accountId: string; direction: 'inbound' | 'outbound' }) => void;
|
|
15
|
+
};
|
|
16
|
+
reply: {
|
|
17
|
+
finalizeInboundContext: (opts: {
|
|
18
|
+
Body: string;
|
|
19
|
+
BodyForAgent?: string;
|
|
20
|
+
RawBody?: string;
|
|
21
|
+
CommandBody?: string;
|
|
22
|
+
From: string;
|
|
23
|
+
To: string;
|
|
24
|
+
SessionKey: string;
|
|
25
|
+
AccountId: string;
|
|
26
|
+
ChatType: 'direct' | 'group';
|
|
27
|
+
SenderId: string;
|
|
28
|
+
SenderName?: string;
|
|
29
|
+
Provider: string;
|
|
30
|
+
Surface: string;
|
|
31
|
+
MessageSid: string;
|
|
32
|
+
Timestamp: number;
|
|
33
|
+
OriginatingChannel: string;
|
|
34
|
+
OriginatingTo: string;
|
|
35
|
+
CommandAuthorized?: boolean;
|
|
36
|
+
MediaPaths?: string[];
|
|
37
|
+
MediaPath?: string;
|
|
38
|
+
MediaTypes?: string[];
|
|
39
|
+
MediaType?: string;
|
|
40
|
+
MediaUrls?: string[];
|
|
41
|
+
MediaUrl?: string;
|
|
42
|
+
ImageMediaTypes?: string[];
|
|
43
|
+
}) => any;
|
|
44
|
+
dispatchReplyWithBufferedBlockDispatcher: (opts: {
|
|
45
|
+
ctx: any;
|
|
46
|
+
cfg: any;
|
|
47
|
+
dispatcherOptions?: {
|
|
48
|
+
responsePrefix?: string;
|
|
49
|
+
deliver?: (payload: { text?: string; mediaUrls?: string[]; mediaUrl?: string }, info: { kind: string }) => Promise<void>;
|
|
50
|
+
};
|
|
51
|
+
}) => Promise<void>;
|
|
52
|
+
resolveEffectiveMessagesConfig: (cfg: any, agentId?: string) => any;
|
|
53
|
+
resolveEnvelopeFormatOptions: (cfg: any) => any;
|
|
54
|
+
formatInboundEnvelope: (opts: {
|
|
55
|
+
Body: string;
|
|
56
|
+
BodyForAgent?: string;
|
|
57
|
+
From: string;
|
|
58
|
+
To: string;
|
|
59
|
+
SessionKey: string;
|
|
60
|
+
ChatType: 'direct' | 'group';
|
|
61
|
+
SenderId: string;
|
|
62
|
+
SenderName?: string;
|
|
63
|
+
Provider: string;
|
|
64
|
+
Surface: string;
|
|
65
|
+
MessageSid: string;
|
|
66
|
+
Timestamp: number;
|
|
67
|
+
OriginatingChannel: string;
|
|
68
|
+
envelopeOptions: any;
|
|
69
|
+
}) => string;
|
|
70
|
+
};
|
|
71
|
+
routing: {
|
|
72
|
+
resolveAgentRoute: (opts: {
|
|
73
|
+
from: string;
|
|
74
|
+
to: string;
|
|
75
|
+
sessionKey: string;
|
|
76
|
+
accountId: string;
|
|
77
|
+
chatType: 'direct' | 'group';
|
|
78
|
+
provider: string;
|
|
79
|
+
}) => any;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface OpenClawPluginApi {
|
|
85
|
+
runtime: PluginRuntime;
|
|
86
|
+
logger: any;
|
|
87
|
+
registerChannel(options: { plugin: any }): void;
|
|
88
|
+
registerTool(tool: any): void;
|
|
89
|
+
registerGatewayMethod(name: string, handler: any): void;
|
|
90
|
+
registerHttpRoute(handler: any): void;
|
|
91
|
+
registerCli(handler: any, options?: any): void;
|
|
92
|
+
registerCommand(command: any): void;
|
|
93
|
+
registerService(service: any): void;
|
|
94
|
+
registerContextEngine(id: string, factory: any): void;
|
|
95
|
+
registerHook(event: string, handler: any, options?: any): void;
|
|
96
|
+
registerProvider(provider: any): void;
|
|
97
|
+
on(event: string, handler: any, options?: any): void;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface ChannelPlugin<T = any> {
|
|
101
|
+
id: string;
|
|
102
|
+
meta: {
|
|
103
|
+
id: string;
|
|
104
|
+
label: string;
|
|
105
|
+
selectionLabel: string;
|
|
106
|
+
docsPath?: string;
|
|
107
|
+
docsLabel?: string;
|
|
108
|
+
blurb: string;
|
|
109
|
+
aliases?: string[];
|
|
110
|
+
order?: number;
|
|
111
|
+
};
|
|
112
|
+
capabilities: {
|
|
113
|
+
chatTypes: string[];
|
|
114
|
+
polls?: boolean;
|
|
115
|
+
threads?: boolean;
|
|
116
|
+
media?: boolean;
|
|
117
|
+
reactions?: boolean;
|
|
118
|
+
edit?: boolean;
|
|
119
|
+
reply?: boolean;
|
|
120
|
+
blockStreaming?: boolean;
|
|
121
|
+
};
|
|
122
|
+
reload?: { configPrefixes: string[] };
|
|
123
|
+
onboarding?: ChannelOnboardingAdapter;
|
|
124
|
+
config: {
|
|
125
|
+
listAccountIds: (cfg: any) => string[];
|
|
126
|
+
resolveAccount: (cfg: any, accountId?: string) => T;
|
|
127
|
+
defaultAccountId: (cfg: any) => string;
|
|
128
|
+
setAccountEnabled: (opts: { cfg: any; accountId: string; enabled: boolean }) => any;
|
|
129
|
+
deleteAccount: (opts: { cfg: any; accountId: string }) => any;
|
|
130
|
+
isConfigured: (account: T) => boolean;
|
|
131
|
+
describeAccount: (account: T) => any;
|
|
132
|
+
resolveAllowFrom?: (opts: { cfg: any; accountId?: string }) => string[];
|
|
133
|
+
formatAllowFrom?: (opts: { allowFrom: Array<string | number> }) => string[];
|
|
134
|
+
};
|
|
135
|
+
setup?: {
|
|
136
|
+
resolveAccountId?: (opts: any) => string;
|
|
137
|
+
applyAccountConfig?: (opts: any) => any;
|
|
138
|
+
validateInput?: (opts: any) => string | null;
|
|
139
|
+
applyAccountName?: (opts: any) => any;
|
|
140
|
+
};
|
|
141
|
+
messaging?: {
|
|
142
|
+
normalizeTarget: (target: string) => { ok: boolean; to?: string; error?: string };
|
|
143
|
+
targetResolver: {
|
|
144
|
+
looksLikeId: (id: string) => boolean;
|
|
145
|
+
hint: string;
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
outbound: ChannelOutboundAdapter;
|
|
149
|
+
gateway?: {
|
|
150
|
+
startAccount: (ctx: any) => Promise<void>;
|
|
151
|
+
logoutAccount?: (opts: any) => Promise<any>;
|
|
152
|
+
};
|
|
153
|
+
status?: {
|
|
154
|
+
defaultRuntime: any;
|
|
155
|
+
buildChannelSummary?: (opts: any) => any;
|
|
156
|
+
probeAccount?: (opts: any) => Promise<any>;
|
|
157
|
+
buildAccountSnapshot?: (opts: any) => any;
|
|
158
|
+
};
|
|
159
|
+
pairing?: any;
|
|
160
|
+
security?: any;
|
|
161
|
+
groups?: any;
|
|
162
|
+
agentPrompt?: any;
|
|
163
|
+
directory?: any;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface ChannelOutboundAdapter {
|
|
167
|
+
deliveryMode: string;
|
|
168
|
+
chunker?: (text: string, limit: number) => string[];
|
|
169
|
+
chunkerMode?: string;
|
|
170
|
+
textChunkLimit?: number;
|
|
171
|
+
sendText: (opts: { to: string; text: string; accountId?: string; cfg: any; replyToId?: string }) => Promise<any>;
|
|
172
|
+
sendMedia?: (opts: { to: string; text?: string; mediaUrl: string; accountId?: string; cfg: any; replyToId?: string }) => Promise<any>;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface ChannelOnboardingAdapter {
|
|
176
|
+
selectAccount?: (opts: any) => Promise<any>;
|
|
177
|
+
promptCredentials?: () => Promise<any>;
|
|
178
|
+
validateCredentials?: (opts: any) => Promise<any>;
|
|
179
|
+
applyConfig?: (opts: any) => any;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface OpenClawConfig {
|
|
183
|
+
channels?: Record<string, any>;
|
|
184
|
+
}
|
|
185
|
+
}
|
package/src/outbound.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { ChannelOutboundAdapter } from 'openclaw/plugin-sdk';
|
|
2
|
+
import { MiService } from './service.js';
|
|
3
|
+
import { MiSpeaker } from './speaker.js';
|
|
4
|
+
import { resolveMiAccount } from './config.js';
|
|
5
|
+
import type { ExtendedOpenClawConfig } from './types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 文本分块函数
|
|
9
|
+
*/
|
|
10
|
+
function chunkText(text: string, limit: number): string[] {
|
|
11
|
+
if (text.length <= limit) return [text];
|
|
12
|
+
|
|
13
|
+
const chunks: string[] = [];
|
|
14
|
+
let remaining = text;
|
|
15
|
+
|
|
16
|
+
while (remaining.length > 0) {
|
|
17
|
+
if (remaining.length <= limit) {
|
|
18
|
+
chunks.push(remaining);
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 尝试在换行处分割
|
|
23
|
+
let splitAt = remaining.lastIndexOf('\n', limit);
|
|
24
|
+
if (splitAt <= 0 || splitAt < limit * 0.5) {
|
|
25
|
+
// 没找到合适的换行,尝试在空格处分割
|
|
26
|
+
splitAt = remaining.lastIndexOf(' ', limit);
|
|
27
|
+
}
|
|
28
|
+
if (splitAt <= 0 || splitAt < limit * 0.5) {
|
|
29
|
+
// 还是没找到,强制在 limit 处分割
|
|
30
|
+
splitAt = limit;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
chunks.push(remaining.slice(0, splitAt));
|
|
34
|
+
remaining = remaining.slice(splitAt).trimStart();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return chunks;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const miOutbound: ChannelOutboundAdapter = {
|
|
41
|
+
deliveryMode: 'direct',
|
|
42
|
+
chunker: chunkText,
|
|
43
|
+
chunkerMode: 'plain',
|
|
44
|
+
textChunkLimit: 200,
|
|
45
|
+
|
|
46
|
+
sendText: async ({ to, text, accountId, cfg }) => {
|
|
47
|
+
const account = resolveMiAccount(cfg as unknown as ExtendedOpenClawConfig, accountId);
|
|
48
|
+
|
|
49
|
+
if (!account.configured) {
|
|
50
|
+
return {
|
|
51
|
+
channel: 'migpt',
|
|
52
|
+
error: new Error('Account not configured')
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 初始化服务
|
|
57
|
+
const initSuccess = await MiService.init(account.config, to);
|
|
58
|
+
if (!initSuccess) {
|
|
59
|
+
return {
|
|
60
|
+
channel: 'migpt',
|
|
61
|
+
error: new Error('Failed to initialize MiService')
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 设置音量(如果配置了)
|
|
66
|
+
const volume = cfg.channels?.migpt?.volume;
|
|
67
|
+
if (volume && volume >= 6 && volume <= 100) {
|
|
68
|
+
await MiSpeaker.setVolume(volume);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 分块发送(如果启用流式)
|
|
72
|
+
const streaming = cfg.channels?.migpt?.streaming ?? true;
|
|
73
|
+
const chunkLimit = cfg.channels?.migpt?.textChunkLimit ?? 200;
|
|
74
|
+
|
|
75
|
+
if (streaming && text.length > chunkLimit) {
|
|
76
|
+
const chunks = chunkText(text, chunkLimit);
|
|
77
|
+
for (const chunk of chunks) {
|
|
78
|
+
const result = await MiSpeaker.play({ text: chunk });
|
|
79
|
+
if (!result.success) {
|
|
80
|
+
return {
|
|
81
|
+
channel: 'migpt',
|
|
82
|
+
error: new Error(result.error)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// 小块之间短暂暂停,避免过于紧凑
|
|
86
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
87
|
+
}
|
|
88
|
+
return { channel: 'migpt', messageId: Date.now().toString() };
|
|
89
|
+
} else {
|
|
90
|
+
const result = await MiSpeaker.play({ text });
|
|
91
|
+
if (!result.success) {
|
|
92
|
+
return {
|
|
93
|
+
channel: 'migpt',
|
|
94
|
+
error: new Error(result.error)
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return { channel: 'migpt', messageId: Date.now().toString() };
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
sendMedia: async ({ to, text, mediaUrl, accountId, cfg }) => {
|
|
102
|
+
const account = resolveMiAccount(cfg as unknown as ExtendedOpenClawConfig, accountId);
|
|
103
|
+
|
|
104
|
+
if (!account.configured) {
|
|
105
|
+
return {
|
|
106
|
+
channel: 'migpt',
|
|
107
|
+
error: new Error('Account not configured')
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const initSuccess = await MiService.init(account.config, to);
|
|
112
|
+
if (!initSuccess) {
|
|
113
|
+
return {
|
|
114
|
+
channel: 'migpt',
|
|
115
|
+
error: new Error('Failed to initialize MiService')
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 发送文本说明(如果有)
|
|
120
|
+
if (text?.trim()) {
|
|
121
|
+
await MiSpeaker.play({ text });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 播放音频 URL
|
|
125
|
+
if (mediaUrl) {
|
|
126
|
+
const result = await MiSpeaker.play({ url: mediaUrl });
|
|
127
|
+
if (!result.success) {
|
|
128
|
+
return {
|
|
129
|
+
channel: 'migpt',
|
|
130
|
+
error: new Error(result.error)
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return { channel: 'migpt', messageId: Date.now().toString() };
|
|
136
|
+
},
|
|
137
|
+
};
|
package/src/runtime.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
|
2
|
+
|
|
3
|
+
let runtime: PluginRuntime | null = null;
|
|
4
|
+
|
|
5
|
+
export function setMiGPTRuntime(next: PluginRuntime) {
|
|
6
|
+
runtime = next;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getMiGPTRuntime(): PluginRuntime {
|
|
10
|
+
if (!runtime) {
|
|
11
|
+
throw new Error("MiGPT runtime not initialized");
|
|
12
|
+
}
|
|
13
|
+
return runtime;
|
|
14
|
+
}
|