@yvhitxcel/opencode-remote 0.16.2 → 0.16.3
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/core/config.js +1 -1
- package/dist/core/router.js +54 -16
- package/dist/feishu/commands.js +60 -26
- package/dist/feishu/handler.js +1 -1
- package/dist/opencode/client.js +41 -1
- package/dist/plugins/agents/opencode/index.js +33 -7
- package/dist/weixin/commands.js +60 -36
- package/dist/weixin/handler.js +1 -1
- package/package.json +1 -1
package/dist/core/config.js
CHANGED
|
@@ -26,6 +26,7 @@ export function loadConfig() {
|
|
|
26
26
|
|
|
27
27
|
const appSecret = content.match(/FEISHU_APP_SECRET=(.+)/)?.[1]?.trim();
|
|
28
28
|
if (appSecret) config.feishuAppSecret = appSecret;
|
|
29
|
+
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
// 2. Read ./.env (local project, lower priority)
|
|
@@ -56,6 +57,5 @@ export function loadConfig() {
|
|
|
56
57
|
if (process.env.SESSION_IDLE_TIMEOUT_MS) config.sessionIdleTimeoutMs = parseInt(process.env.SESSION_IDLE_TIMEOUT_MS, 10);
|
|
57
58
|
if (process.env.CLEANUP_INTERVAL_MS) config.cleanupIntervalMs = parseInt(process.env.CLEANUP_INTERVAL_MS, 10);
|
|
58
59
|
if (process.env.APPROVAL_TIMEOUT_MS) config.approvalTimeoutMs = parseInt(process.env.APPROVAL_TIMEOUT_MS, 10);
|
|
59
|
-
|
|
60
60
|
return config;
|
|
61
61
|
}
|
package/dist/core/router.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Message router - full command definitions shared across all platforms
|
|
2
2
|
import { registry } from './registry.js';
|
|
3
|
-
import { initOpenCode,
|
|
3
|
+
import { initOpenCode, checkConnection, resumeSession, shareSession, setThreadModel, getThreadModel, getRecentModels, pushRecentModel } from '../opencode/client.js';
|
|
4
4
|
import { formatTaskCompletion } from './notifications.js';
|
|
5
5
|
|
|
6
6
|
const demoModeMap = new Map();
|
|
@@ -499,30 +499,68 @@ export async function routeMessage(parsed, ctx) {
|
|
|
499
499
|
|
|
500
500
|
case 'model': {
|
|
501
501
|
try {
|
|
502
|
+
const opencode = await initOpenCode();
|
|
503
|
+
if (!opencode) return '❌ OpenCode 不可用';
|
|
504
|
+
|
|
502
505
|
if (parsed.arg) {
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
+
const arg = parsed.arg.trim();
|
|
507
|
+
|
|
508
|
+
// Search mode: /model <keyword>
|
|
509
|
+
if (!arg.includes('/')) {
|
|
510
|
+
const result = await opencode.client.provider.list();
|
|
511
|
+
if (result.error || !result.data?.all) return '❌ 无法获取模型列表';
|
|
512
|
+
const q = arg.toLowerCase();
|
|
513
|
+
const matches = [];
|
|
514
|
+
for (const p of result.data.all) {
|
|
515
|
+
for (const mid of Object.keys(p.models || {})) {
|
|
516
|
+
const label = `${p.id}/${mid}`;
|
|
517
|
+
if (label.toLowerCase().includes(q)) {
|
|
518
|
+
matches.push(label);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (matches.length === 0) return `🔍 未找到包含 "${arg}" 的模型`;
|
|
523
|
+
matches.sort().splice(30);
|
|
524
|
+
let msg = `🔍 搜索 "${arg}" (${matches.length > 30 ? '30+' : matches.length} 个):\n`;
|
|
525
|
+
for (const m of matches.slice(0, 30)) {
|
|
526
|
+
msg += ` ${m}\n`;
|
|
527
|
+
}
|
|
528
|
+
msg += '\n切换: /model <provider>/<modelID>';
|
|
529
|
+
return msg;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Switch mode: /model <provider>/<modelID>
|
|
533
|
+
const entry = setThreadModel(ctx.threadId, arg);
|
|
534
|
+
if (entry) {
|
|
535
|
+
return `✅ 已切换模型至: ${entry.providerID}/${entry.modelID}`;
|
|
536
|
+
}
|
|
537
|
+
return '❌ 格式错误,请使用: /model <provider>/<modelID>';
|
|
506
538
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
539
|
+
|
|
540
|
+
// No arg: show current + recent
|
|
541
|
+
const current = getThreadModel(ctx.threadId);
|
|
542
|
+
let msg = current
|
|
543
|
+
? `🧠 当前模型: ${current.providerID}/${current.modelID}\n━━━━━━━━━━━━━━━━\n\n`
|
|
544
|
+
: '━━━━━━━━━━━━━━━━\n\n';
|
|
545
|
+
const recent = getRecentModels();
|
|
546
|
+
if (recent.length > 0) {
|
|
547
|
+
msg += '最近使用:\n';
|
|
548
|
+
for (const r of recent) {
|
|
549
|
+
const mark = (current && r.providerID === current.providerID && r.modelID === current.modelID) ? ' ←' : '';
|
|
550
|
+
msg += ` ${r.providerID}/${r.modelID}${mark}\n`;
|
|
516
551
|
}
|
|
517
|
-
|
|
552
|
+
msg += '\n';
|
|
553
|
+
}
|
|
554
|
+
if (!current) {
|
|
555
|
+
msg += '提示: 用 /model <关键词> 搜索模型,/model <provider>/<modelID> 切换\n';
|
|
556
|
+
} else {
|
|
557
|
+
msg += '用法:\n /model <关键词> — 搜索\n /model <provider>/<modelID> — 切换';
|
|
518
558
|
}
|
|
519
|
-
msg += '\n用法: /model <provider/model>';
|
|
520
559
|
return msg;
|
|
521
560
|
} catch (e) {
|
|
522
561
|
return `❌ 模型操作失败: ${e.message}`;
|
|
523
562
|
}
|
|
524
563
|
}
|
|
525
|
-
|
|
526
564
|
case 'diagnose': {
|
|
527
565
|
const diag = ['🔍 诊断报告\n'];
|
|
528
566
|
const qiniuOk = !!process.env.QINIU_ACCESS_KEY;
|
package/dist/feishu/commands.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getOrCreateSession } from '../core/session.js';
|
|
2
2
|
import { splitMessage } from '../core/notifications.js';
|
|
3
3
|
import { EMOJI } from '../core/types.js';
|
|
4
|
-
import { initOpenCode, createSession, sendMessage, checkConnection, abortSession, resumeSession, revertSessionMessage, unrevertSession,
|
|
4
|
+
import { initOpenCode, createSession, sendMessage, checkConnection, abortSession, resumeSession, revertSessionMessage, unrevertSession, setThreadModel, getThreadModel, getRecentModels } from '../opencode/client.js';
|
|
5
5
|
import { claimOwnership } from '../core/auth.js';
|
|
6
6
|
import { COMMAND_ALIASES, detectCommand, getHelpText, DEMO_RESPONSES, setDemoMode, isDemoMode } from '../core/router.js';
|
|
7
7
|
import { registry } from '../core/registry.js';
|
|
@@ -110,43 +110,77 @@ async function handleCommand(adapter, ctx, command, arg, openCodeSessions) {
|
|
|
110
110
|
try {
|
|
111
111
|
if (arg) {
|
|
112
112
|
const modelStr = arg.trim();
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
113
|
+
|
|
114
|
+
// Search mode: /model <keyword>
|
|
115
|
+
if (!modelStr.includes('/')) {
|
|
116
|
+
const opencode = await initOpenCode();
|
|
117
|
+
if (!opencode) {
|
|
118
|
+
await adapter.reply(ctx.threadId, '❌ OpenCode 不可用');
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
const result = await opencode.client.provider.list();
|
|
122
|
+
if (result.error || !result.data?.all) {
|
|
123
|
+
await adapter.reply(ctx.threadId, '❌ 无法获取模型列表');
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
const q = modelStr.toLowerCase();
|
|
127
|
+
const matches = [];
|
|
128
|
+
for (const p of result.data.all) {
|
|
129
|
+
for (const mid of Object.keys(p.models || {})) {
|
|
130
|
+
if (`${p.id}/${mid}`.toLowerCase().includes(q)) {
|
|
131
|
+
matches.push(`${p.id}/${mid}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (matches.length === 0) {
|
|
136
|
+
await adapter.reply(ctx.threadId, `🔍 未找到包含 "${modelStr}" 的模型`);
|
|
137
|
+
return true;
|
|
118
138
|
}
|
|
119
|
-
|
|
139
|
+
matches.sort();
|
|
140
|
+
let msg = `🔍 搜索 "${modelStr}" (${matches.length} 个):\n`;
|
|
141
|
+
for (const m of matches.slice(0, 30)) {
|
|
142
|
+
msg += ` ${m}\n`;
|
|
143
|
+
}
|
|
144
|
+
msg += '\n切换: /model <provider>/<modelID>';
|
|
145
|
+
const msgs = splitMessage(msg);
|
|
146
|
+
for (const m of msgs) await adapter.reply(ctx.threadId, m);
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const entry = setThreadModel(ctx.threadId, modelStr);
|
|
151
|
+
if (entry) {
|
|
152
|
+
await adapter.reply(ctx.threadId, `✅ 已切换模型至: ${entry.providerID}/${entry.modelID}`);
|
|
120
153
|
} else {
|
|
121
|
-
await adapter.reply(ctx.threadId, '❌
|
|
154
|
+
await adapter.reply(ctx.threadId, '❌ 格式错误,请使用: /model <provider>/<modelID>');
|
|
122
155
|
}
|
|
123
156
|
return true;
|
|
124
157
|
}
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
msg += ` ${p.id}/${mid}\n`;
|
|
158
|
+
const current = getThreadModel(ctx.threadId);
|
|
159
|
+
let msg = current
|
|
160
|
+
? `🧠 当前模型: ${current.providerID}/${current.modelID}\n\n`
|
|
161
|
+
: '';
|
|
162
|
+
|
|
163
|
+
const recent = getRecentModels();
|
|
164
|
+
if (recent.length > 0) {
|
|
165
|
+
msg += '最近使用:\n';
|
|
166
|
+
for (const r of recent) {
|
|
167
|
+
const mark = (current && r.providerID === current.providerID && r.modelID === current.modelID) ? ' ←' : '';
|
|
168
|
+
msg += ` ${r.providerID}/${r.modelID}${mark}\n`;
|
|
137
169
|
}
|
|
138
|
-
if (modelIds.length > 5) msg += ` ...还有 ${modelIds.length - 5} 个\n`;
|
|
139
170
|
msg += '\n';
|
|
140
171
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
172
|
+
if (!current) {
|
|
173
|
+
msg += '提示: 用 /model <关键词> 搜索模型,/model <provider>/<modelID> 切换\n';
|
|
174
|
+
} else {
|
|
175
|
+
msg += '用法: /model <关键词> — 搜索\n /model <provider>/<modelID> — 切换';
|
|
145
176
|
}
|
|
177
|
+
const msgs = splitMessage(msg);
|
|
178
|
+
for (const m of msgs) await adapter.reply(ctx.threadId, m);
|
|
179
|
+
return true;
|
|
146
180
|
} catch (e) {
|
|
147
181
|
await adapter.reply(ctx.threadId, `❌ 模型操作失败: ${e.message}`);
|
|
182
|
+
return true;
|
|
148
183
|
}
|
|
149
|
-
return true;
|
|
150
184
|
}
|
|
151
185
|
case 'oc':
|
|
152
186
|
case 'cc':
|
package/dist/feishu/handler.js
CHANGED
|
@@ -292,7 +292,7 @@ async function forwardToOpenCode(adapter, ctx, text, openCodeSessions, session,
|
|
|
292
292
|
adapter.updateMessage(ctx.threadId, '', `⏳ 重试中 (${status.attempt})...`).catch(() => {});
|
|
293
293
|
}
|
|
294
294
|
},
|
|
295
|
-
});
|
|
295
|
+
}, ctx.threadId);
|
|
296
296
|
session.taskStartTime = null;
|
|
297
297
|
session.currentTool = null;
|
|
298
298
|
if (session.modifiedFiles instanceof Set) {
|
package/dist/opencode/client.js
CHANGED
|
@@ -11,6 +11,41 @@ import { homedir } from 'os';
|
|
|
11
11
|
const CONFIG_DIR = join(homedir(), '.opencode-remote');
|
|
12
12
|
const CONFIG_FILE = join(CONFIG_DIR, '.env');
|
|
13
13
|
|
|
14
|
+
const threadModels = new Map();
|
|
15
|
+
const recentModels = [];
|
|
16
|
+
|
|
17
|
+
export function setThreadModel(threadId, modelStr) {
|
|
18
|
+
if (!modelStr || !modelStr.includes('/')) {
|
|
19
|
+
threadModels.delete(threadId);
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const parts = modelStr.split('/');
|
|
23
|
+
const entry = { providerID: parts[0], modelID: parts.slice(1).join('/') };
|
|
24
|
+
threadModels.set(threadId, entry);
|
|
25
|
+
pushRecent(entry);
|
|
26
|
+
return entry;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getThreadModel(threadId) {
|
|
30
|
+
return threadModels.get(threadId);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getRecentModels() {
|
|
34
|
+
return [...recentModels];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function pushRecentModel(entry) {
|
|
38
|
+
pushRecent(entry);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function pushRecent(entry) {
|
|
42
|
+
const key = `${entry.providerID}/${entry.modelID}`;
|
|
43
|
+
const idx = recentModels.findIndex(e => `${e.providerID}/${e.modelID}` === key);
|
|
44
|
+
if (idx !== -1) recentModels.splice(idx, 1);
|
|
45
|
+
recentModels.unshift(entry);
|
|
46
|
+
if (recentModels.length > 5) recentModels.length = 5;
|
|
47
|
+
}
|
|
48
|
+
|
|
14
49
|
// Find opencode.exe binary
|
|
15
50
|
function findOpenCodeExe() {
|
|
16
51
|
const isWindows = platform() === 'win32';
|
|
@@ -349,7 +384,7 @@ export async function createSession(_threadId, title = `Remote control session`)
|
|
|
349
384
|
}
|
|
350
385
|
}
|
|
351
386
|
// Send message - use promptAsync then poll for response
|
|
352
|
-
export async function sendMessage(session, message, callbacks) {
|
|
387
|
+
export async function sendMessage(session, message, callbacks, threadId) {
|
|
353
388
|
const TIMEOUT_MS = 5 * 60 * 1000; // 5 minute timeout
|
|
354
389
|
const POLL_INTERVAL = 2000; // 2 seconds between polls
|
|
355
390
|
|
|
@@ -381,6 +416,11 @@ export async function sendMessage(session, message, callbacks) {
|
|
|
381
416
|
const promptBody = {
|
|
382
417
|
parts: [{ type: 'text', text: message }]
|
|
383
418
|
};
|
|
419
|
+
// Inject local model preference if set
|
|
420
|
+
if (threadId && threadModels.has(threadId)) {
|
|
421
|
+
session.model = threadModels.get(threadId);
|
|
422
|
+
pushRecent(session.model);
|
|
423
|
+
}
|
|
384
424
|
// Per-message model override if set on session
|
|
385
425
|
if (session.model?.providerID && session.model?.modelID) {
|
|
386
426
|
promptBody.model = {
|
|
@@ -22,7 +22,7 @@ export class OpenCodeAgentAdapter {
|
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
async sendPrompt(_sessionId, prompt, history) {
|
|
25
|
+
async sendPrompt(_sessionId, prompt, history, options = {}) {
|
|
26
26
|
const contextualPrompt = this.buildContextualPrompt(prompt, history);
|
|
27
27
|
return this.callOpenCode(contextualPrompt);
|
|
28
28
|
}
|
|
@@ -45,7 +45,7 @@ export class OpenCodeAgentAdapter {
|
|
|
45
45
|
.find(l => /Error|error|ERROR|^\d{3}/.test(l));
|
|
46
46
|
return first || null;
|
|
47
47
|
}
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
callOpenCode(prompt) {
|
|
50
50
|
return new Promise((resolve) => {
|
|
51
51
|
const proc = spawn('opencode', ['run', '--format', 'json', prompt], {
|
|
@@ -56,11 +56,33 @@ export class OpenCodeAgentAdapter {
|
|
|
56
56
|
let stdout = '';
|
|
57
57
|
let stderr = '';
|
|
58
58
|
let fullText = '';
|
|
59
|
+
let resigned = false;
|
|
60
|
+
|
|
61
|
+
const STUCK_PATTERNS = [
|
|
62
|
+
'Free usage exceeded', 'quota exceeded', 'rate limit',
|
|
63
|
+
'retrying in', 'retry attempt',
|
|
64
|
+
'429', '401', '403', '402', 'Payment Required',
|
|
65
|
+
'subscription required', 'insufficient_quota',
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const checkStuck = (stderrText) => {
|
|
69
|
+
if (resigned) return;
|
|
70
|
+
for (const pattern of STUCK_PATTERNS) {
|
|
71
|
+
if (stderrText.toLowerCase().includes(pattern.toLowerCase())) {
|
|
72
|
+
resigned = true;
|
|
73
|
+
proc.kill();
|
|
74
|
+
const detail = this.extractErrorMessage('', stderrText);
|
|
75
|
+
resolve(`❌ OpenCode 无法继续: ${detail || pattern}`);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
};
|
|
59
81
|
|
|
60
82
|
proc.stdout?.on('data', (data) => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
83
|
+
const chunk = data.toString();
|
|
84
|
+
stdout += chunk;
|
|
85
|
+
const lines = chunk.split('\n');
|
|
64
86
|
for (const line of lines) {
|
|
65
87
|
if (!line.trim()) continue;
|
|
66
88
|
try {
|
|
@@ -70,15 +92,19 @@ export class OpenCodeAgentAdapter {
|
|
|
70
92
|
}
|
|
71
93
|
});
|
|
72
94
|
|
|
73
|
-
proc.stderr?.on('data', (data) => {
|
|
95
|
+
proc.stderr?.on('data', (data) => {
|
|
96
|
+
stderr += data.toString();
|
|
97
|
+
checkStuck(stderr);
|
|
98
|
+
});
|
|
74
99
|
|
|
75
100
|
proc.on('close', (code) => {
|
|
101
|
+
if (resigned) return;
|
|
76
102
|
if (code !== 0) {
|
|
77
103
|
const detail = this.extractErrorMessage(stdout, stderr);
|
|
78
104
|
const hint = detail
|
|
79
105
|
? `: ${detail}`
|
|
80
106
|
: '。请运行 `opencode auth login` 配置认证。';
|
|
81
|
-
resolve(`❌ OpenCode
|
|
107
|
+
resolve(`❌ OpenCode 错误 (exit code ${code})${hint}`);
|
|
82
108
|
} else {
|
|
83
109
|
resolve(fullText || '完成');
|
|
84
110
|
}
|
package/dist/weixin/commands.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { detectCommand, COMMAND_ALIASES, getHelpText, DEMO_RESPONSES, setDemoMode, isDemoMode } from '../core/router.js';
|
|
2
2
|
import { getOrCreateSession, saveSessionMapping, sessionManager } from '../core/session.js';
|
|
3
3
|
import { splitMessage } from '../core/notifications.js';
|
|
4
|
-
import { initOpenCode, checkConnection, abortSession, resumeSession, revertSessionMessage, unrevertSession,
|
|
4
|
+
import { initOpenCode, checkConnection, abortSession, resumeSession, revertSessionMessage, unrevertSession, setThreadModel, getThreadModel, getRecentModels } from '../opencode/client.js';
|
|
5
5
|
import { claimOwnership } from '../core/auth.js';
|
|
6
6
|
import { registry } from '../core/registry.js';
|
|
7
7
|
import { uploadToQiniu, findBuildOutputs, formatSize, deleteFromQiniu } from '../core/qiniu.js';
|
|
@@ -658,58 +658,82 @@ async function handleCommand(adapter, ctx, command, arg, openCodeSessions) {
|
|
|
658
658
|
|
|
659
659
|
case 'model': {
|
|
660
660
|
try {
|
|
661
|
-
const opencode = await initOpenCode();
|
|
662
|
-
if (!opencode) {
|
|
663
|
-
await adapter.reply(ctx.threadId, '❌ 无法连接 OpenCode');
|
|
664
|
-
return true;
|
|
665
|
-
}
|
|
666
661
|
if (arg) {
|
|
667
662
|
const modelStr = arg.trim();
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
663
|
+
|
|
664
|
+
// Search mode: /model <keyword>
|
|
665
|
+
if (!modelStr.includes('/')) {
|
|
666
|
+
const opencode = await initOpenCode();
|
|
667
|
+
if (!opencode) {
|
|
668
|
+
await adapter.reply(ctx.threadId, '❌ OpenCode 不可用');
|
|
669
|
+
return true;
|
|
670
|
+
}
|
|
671
|
+
const result = await opencode.client.provider.list();
|
|
672
|
+
if (result.error || !result.data?.all) {
|
|
673
|
+
await adapter.reply(ctx.threadId, '❌ 无法获取模型列表');
|
|
674
|
+
return true;
|
|
675
|
+
}
|
|
676
|
+
const q = modelStr.toLowerCase();
|
|
677
|
+
const matches = [];
|
|
678
|
+
for (const p of result.data.all) {
|
|
679
|
+
for (const mid of Object.keys(p.models || {})) {
|
|
680
|
+
if (`${p.id}/${mid}`.toLowerCase().includes(q)) {
|
|
681
|
+
matches.push(`${p.id}/${mid}`);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
if (matches.length === 0) {
|
|
686
|
+
await adapter.reply(ctx.threadId, `🔍 未找到包含 "${modelStr}" 的模型`);
|
|
687
|
+
return true;
|
|
673
688
|
}
|
|
674
|
-
|
|
689
|
+
matches.sort();
|
|
690
|
+
let msg = `🔍 搜索 "${modelStr}" (${matches.length} 个):\n`;
|
|
691
|
+
for (const m of matches.slice(0, 30)) {
|
|
692
|
+
msg += ` ${m}\n`;
|
|
693
|
+
}
|
|
694
|
+
msg += '\n切换: /model <provider>/<modelID>';
|
|
695
|
+
const msgs = splitMessage(msg);
|
|
696
|
+
for (const m of msgs) await adapter.reply(ctx.threadId, m);
|
|
697
|
+
return true;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const entry = setThreadModel(ctx.threadId, modelStr);
|
|
701
|
+
if (entry) {
|
|
702
|
+
await adapter.reply(ctx.threadId, `✅ 已切换模型至: ${entry.providerID}/${entry.modelID}`);
|
|
675
703
|
} else {
|
|
676
|
-
await adapter.reply(ctx.threadId,
|
|
704
|
+
await adapter.reply(ctx.threadId, '❌ 格式错误,请使用: /model <provider>/<modelID>');
|
|
677
705
|
}
|
|
678
706
|
return true;
|
|
679
707
|
}
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
const m = p.models[mid];
|
|
692
|
-
const tags = [];
|
|
693
|
-
if (m.reasoning) tags.push('推理');
|
|
694
|
-
if (m.attachment) tags.push('附件');
|
|
695
|
-
msg += ` ${p.id}/${mid}${tags.length ? ` (${tags.join(', ')})` : ''}\n`;
|
|
708
|
+
const current = getThreadModel(ctx.threadId);
|
|
709
|
+
let msg = current
|
|
710
|
+
? `🧠 当前模型: ${current.providerID}/${current.modelID}\n\n`
|
|
711
|
+
: '';
|
|
712
|
+
|
|
713
|
+
const recent = getRecentModels();
|
|
714
|
+
if (recent.length > 0) {
|
|
715
|
+
msg += '最近使用:\n';
|
|
716
|
+
for (const r of recent) {
|
|
717
|
+
const mark = (current && r.providerID === current.providerID && r.modelID === current.modelID) ? ' ←' : '';
|
|
718
|
+
msg += ` ${r.providerID}/${r.modelID}${mark}\n`;
|
|
696
719
|
}
|
|
697
|
-
if (modelIds.length > 8) msg += ` ...还有 ${modelIds.length - 8} 个\n`;
|
|
698
720
|
msg += '\n';
|
|
699
721
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
722
|
+
if (!current) {
|
|
723
|
+
msg += '提示: 用 /model <关键词> 搜索模型,/model <provider>/<modelID> 切换\n';
|
|
724
|
+
} else {
|
|
725
|
+
msg += '用法: /model <关键词> — 搜索\n /model <provider>/<modelID> — 切换';
|
|
704
726
|
}
|
|
727
|
+
const msgs = splitMessage(msg);
|
|
728
|
+
for (const m of msgs) await adapter.reply(ctx.threadId, m);
|
|
729
|
+
return true;
|
|
705
730
|
} catch (e) {
|
|
706
731
|
await adapter.reply(ctx.threadId, `❌ 模型操作失败: ${e.message}`);
|
|
732
|
+
return true;
|
|
707
733
|
}
|
|
708
|
-
return true;
|
|
709
734
|
}
|
|
710
735
|
|
|
711
736
|
|
|
712
|
-
|
|
713
737
|
case 'demo': {
|
|
714
738
|
const argText = (arg || '').trim().toLowerCase();
|
|
715
739
|
if (argText === 'off' || argText === 'exit' || argText === 'stop') {
|
package/dist/weixin/handler.js
CHANGED
|
@@ -138,7 +138,7 @@ async function forwardToOpenCode(adapter, ctx, text, openCodeSessions, session,
|
|
|
138
138
|
onStatusChange: (status) => {
|
|
139
139
|
if (status.hasToolActivity) hasToolActivity = true;
|
|
140
140
|
},
|
|
141
|
-
}).catch((e) => {
|
|
141
|
+
}, ctx.threadId).catch((e) => {
|
|
142
142
|
console.error('[forwardToOpenCode] Task error:', e.message);
|
|
143
143
|
return '';
|
|
144
144
|
});
|
package/package.json
CHANGED