imtoagent 0.2.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/README.md +234 -0
- package/bin/imtoagent +453 -0
- package/index.ts +1129 -0
- package/modules/agent/claude-adapter.ts +258 -0
- package/modules/agent/claude.ts +160 -0
- package/modules/agent/codex-adapter.ts +232 -0
- package/modules/agent/codex-exec-server.ts +513 -0
- package/modules/agent/codex.ts +275 -0
- package/modules/agent/opencode-adapter.ts +308 -0
- package/modules/agent/opencode.ts +247 -0
- package/modules/bot-context.ts +26 -0
- package/modules/capabilities.ts +189 -0
- package/modules/cli/setup.ts +424 -0
- package/modules/core/config.ts +275 -0
- package/modules/core/error.ts +124 -0
- package/modules/core/index.ts +39 -0
- package/modules/core/runtime.ts +282 -0
- package/modules/core/session.ts +256 -0
- package/modules/core/stats.ts +92 -0
- package/modules/core/types.ts +250 -0
- package/modules/im/feishu.ts +731 -0
- package/modules/im/telegram.ts +639 -0
- package/modules/im/wechat.ts +1094 -0
- package/modules/im/wecom.ts +603 -0
- package/modules/media/feishu-inbound-adapter.ts +108 -0
- package/modules/media/index.ts +27 -0
- package/modules/media/media-store.ts +273 -0
- package/modules/media/resolver.ts +178 -0
- package/modules/media/telegram-inbound-adapter.ts +124 -0
- package/modules/media/types.ts +76 -0
- package/modules/prompt-builder.ts +123 -0
- package/modules/proxy/anthropic-proxy.ts +1083 -0
- package/modules/proxy/codex-proxy.ts +657 -0
- package/modules/rate-limiter.ts +58 -0
- package/modules/types.ts +144 -0
- package/modules/utils/backend-check.ts +121 -0
- package/modules/utils/paths.ts +218 -0
- package/package.json +53 -0
- package/scripts/postinstall.ts +70 -0
- package/templates/config.template.json +57 -0
- package/templates/opencode.template.json +28 -0
- package/templates/providers.template.json +19 -0
- package/templates/soul.template/identity.md +6 -0
- package/templates/soul.template/profile.md +11 -0
- package/templates/soul.template/rules.md +7 -0
- package/templates/soul.template/skills.md +3 -0
- package/templates/soul.template/workspace.md +4 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// ================================================================
|
|
2
|
+
// StatsTracker — 调用统计追踪
|
|
3
|
+
// ================================================================
|
|
4
|
+
// 统一管理 token/cost/duration 统计,替代各 Agent 中的重复逻辑
|
|
5
|
+
// ================================================================
|
|
6
|
+
|
|
7
|
+
import type { Session, StatsTracker, CallStats } from './types';
|
|
8
|
+
|
|
9
|
+
// ================================================================
|
|
10
|
+
// DefaultStatsTracker
|
|
11
|
+
// ================================================================
|
|
12
|
+
|
|
13
|
+
export class DefaultStatsTracker implements StatsTracker {
|
|
14
|
+
/**
|
|
15
|
+
* 重置单次调用的统计(在调用开始时)
|
|
16
|
+
*/
|
|
17
|
+
resetForCall(session: Session): void {
|
|
18
|
+
session.stats.calls += 1;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 累加统计(在调用成功后)
|
|
23
|
+
*/
|
|
24
|
+
accumulate(session: Session, usage: {
|
|
25
|
+
inputTokens: number;
|
|
26
|
+
outputTokens: number;
|
|
27
|
+
costUSD?: number;
|
|
28
|
+
durationMs?: number;
|
|
29
|
+
numTurns?: number;
|
|
30
|
+
}): void {
|
|
31
|
+
session.stats.totalInputTokens += usage.inputTokens || 0;
|
|
32
|
+
session.stats.totalOutputTokens += usage.outputTokens || 0;
|
|
33
|
+
session.stats.totalCostUSD += usage.costUSD || 0;
|
|
34
|
+
session.stats.totalDurationMs += usage.durationMs || 0;
|
|
35
|
+
if (usage.numTurns) {
|
|
36
|
+
session.stats.totalTurns += usage.numTurns;
|
|
37
|
+
}
|
|
38
|
+
session.lastUsed = Date.now();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 生成统计摘要字符串(用于发送给用户)
|
|
43
|
+
*/
|
|
44
|
+
formatSummary(session: Session): string {
|
|
45
|
+
const s = session.stats;
|
|
46
|
+
const parts: string[] = [];
|
|
47
|
+
|
|
48
|
+
// 调用次数
|
|
49
|
+
parts.push(`📊 调用 ${s.calls} 次`);
|
|
50
|
+
|
|
51
|
+
// Token 用量
|
|
52
|
+
const totalTokens = s.totalInputTokens + s.totalOutputTokens;
|
|
53
|
+
if (totalTokens > 0) {
|
|
54
|
+
parts.push(`Token ${this.formatTokens(totalTokens)}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 成本
|
|
58
|
+
if (s.totalCostUSD > 0) {
|
|
59
|
+
parts.push(`费用 $${s.totalCostUSD.toFixed(4)}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 耗时
|
|
63
|
+
if (s.totalDurationMs > 0) {
|
|
64
|
+
parts.push(`耗时 ${this.formatDuration(s.totalDurationMs)}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return parts.join(' | ');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 格式化 Token 数量 */
|
|
71
|
+
private formatTokens(count: number): string {
|
|
72
|
+
if (count >= 1_000_000) {
|
|
73
|
+
return `${(count / 1_000_000).toFixed(1)}M`;
|
|
74
|
+
}
|
|
75
|
+
if (count >= 1_000) {
|
|
76
|
+
return `${(count / 1_000).toFixed(1)}K`;
|
|
77
|
+
}
|
|
78
|
+
return `${count}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** 格式化时长 */
|
|
82
|
+
private formatDuration(ms: number): string {
|
|
83
|
+
const secs = ms / 1000;
|
|
84
|
+
if (secs >= 3600) {
|
|
85
|
+
return `${(secs / 3600).toFixed(1)}h`;
|
|
86
|
+
}
|
|
87
|
+
if (secs >= 60) {
|
|
88
|
+
return `${(secs / 60).toFixed(1)}m`;
|
|
89
|
+
}
|
|
90
|
+
return `${secs.toFixed(0)}s`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// ================================================================
|
|
2
|
+
// SDK 核心类型 — IMtoAgent 新模块只需实现 AgentAdapter
|
|
3
|
+
// ================================================================
|
|
4
|
+
|
|
5
|
+
import type { UnifiedBlock, IMCapabilities } from '../capabilities';
|
|
6
|
+
|
|
7
|
+
// ================================================================
|
|
8
|
+
// 统计
|
|
9
|
+
// ================================================================
|
|
10
|
+
|
|
11
|
+
/** 调用统计 */
|
|
12
|
+
export interface CallStats {
|
|
13
|
+
calls: number;
|
|
14
|
+
totalTurns: number;
|
|
15
|
+
totalInputTokens: number;
|
|
16
|
+
totalOutputTokens: number;
|
|
17
|
+
totalCostUSD: number;
|
|
18
|
+
totalDurationMs: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ================================================================
|
|
22
|
+
// Session
|
|
23
|
+
// ================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 统一 Session — 替代各 Agent 特有的 session 字段
|
|
27
|
+
*
|
|
28
|
+
* 与现有 .memory.json 格式保持向后兼容:
|
|
29
|
+
* - metadata 中存放 sdkSessionId / codexThreadId / ocSessionId 等
|
|
30
|
+
* - 直接字段存放通用属性
|
|
31
|
+
*/
|
|
32
|
+
export interface Session {
|
|
33
|
+
chatId: string;
|
|
34
|
+
userId: string;
|
|
35
|
+
cwd?: string;
|
|
36
|
+
startFresh: boolean;
|
|
37
|
+
|
|
38
|
+
// 后端会话 ID(通用,替代 sdkSessionId/codexThreadId/ocSessionId)
|
|
39
|
+
backendSessionId?: string;
|
|
40
|
+
|
|
41
|
+
// 各 Agent 特有的元数据(扩展用)
|
|
42
|
+
// 向后兼容:从旧 .memory.json 迁移时,sdkSessionId/codexThreadId/ocSessionId 存在这里
|
|
43
|
+
metadata: Record<string, any>;
|
|
44
|
+
|
|
45
|
+
// 统计
|
|
46
|
+
stats: CallStats;
|
|
47
|
+
|
|
48
|
+
// 元数据
|
|
49
|
+
lastUsed: number;
|
|
50
|
+
running: boolean;
|
|
51
|
+
permissionMode?: string;
|
|
52
|
+
codexMode?: string; // plan/auto
|
|
53
|
+
|
|
54
|
+
// 最近消息(向后兼容)
|
|
55
|
+
recentMessages: string[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ================================================================
|
|
59
|
+
// Agent 输入 / 输出
|
|
60
|
+
// ================================================================
|
|
61
|
+
|
|
62
|
+
/** 消息附件(图片/文件/语音等) */
|
|
63
|
+
export interface MessageAttachment {
|
|
64
|
+
/** 类型:image | file | audio */
|
|
65
|
+
type: 'image' | 'file' | 'audio';
|
|
66
|
+
/** 本地下载后的文件路径 */
|
|
67
|
+
localPath: string;
|
|
68
|
+
/** 原始文件名(如有) */
|
|
69
|
+
filename?: string;
|
|
70
|
+
/** 飞书 image_key / file_key(调试用) */
|
|
71
|
+
sourceKey?: string;
|
|
72
|
+
/** MIME 类型 */
|
|
73
|
+
mimeType?: string;
|
|
74
|
+
/** 语音时长(毫秒) */
|
|
75
|
+
durationMs?: number;
|
|
76
|
+
/** 预计算的 Agent 提示文本(由 MediaResolver 按文件类型生成,优先级高于 buildAttachmentHint 自动生成的) */
|
|
77
|
+
hint?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** 构建附件提示文本,注入到 Agent 输入消息中 */
|
|
81
|
+
export function buildAttachmentHint(attachments: MessageAttachment[]): string {
|
|
82
|
+
return attachments.map((att, i) => {
|
|
83
|
+
const icon = att.type === 'image' ? '🖼️' : att.type === 'audio' ? '🎵' : '📎';
|
|
84
|
+
const typeLabel = att.type === 'image' ? '图片' : att.type === 'audio' ? '语音' : '文件';
|
|
85
|
+
const detail = att.filename ? ` (${att.filename})` : '';
|
|
86
|
+
const dur = att.durationMs ? ` [时长: ${Math.round(att.durationMs / 1000)}秒]` : '';
|
|
87
|
+
const mimeInfo = att.mimeType ? ` [${att.mimeType}]` : '';
|
|
88
|
+
|
|
89
|
+
// 优先使用预计算的提示(由 MediaResolver 按文件类型生成)
|
|
90
|
+
let hint: string;
|
|
91
|
+
if (att.hint) {
|
|
92
|
+
hint = `\n> 💡 ${att.hint}`;
|
|
93
|
+
} else if (att.type === 'image') {
|
|
94
|
+
hint = `\n> 💡 图片已保存到本地,路径: \`${att.localPath}\`,格式: ${att.mimeType || '未知'},可使用查看图片工具读取`;
|
|
95
|
+
} else if (att.type === 'audio') {
|
|
96
|
+
hint = `\n> 💡 语音文件路径: \`${att.localPath}\`,可用语音识别工具处理`;
|
|
97
|
+
} else {
|
|
98
|
+
const ext = att.filename ? `,扩展名: ${att.filename.split('.').pop()}` : '';
|
|
99
|
+
hint = `\n> 💡 文件路径: \`${att.localPath}\`,可直接读取内容${ext}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return `${icon} [用户消息附带${typeLabel} #${i + 1}]${detail}${mimeInfo}${dur}${hint}`;
|
|
103
|
+
}).join('\n\n');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Agent 输入 */
|
|
107
|
+
export interface AgentInput {
|
|
108
|
+
chatId: string;
|
|
109
|
+
text: string;
|
|
110
|
+
/** 消息附带的附件/媒体(图片、文件、语音等,已下载到本地) */
|
|
111
|
+
attachments?: MessageAttachment[];
|
|
112
|
+
session: Session;
|
|
113
|
+
workingDir: string;
|
|
114
|
+
systemPrompt?: string;
|
|
115
|
+
model: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Agent 输出 */
|
|
119
|
+
export interface AgentOutput {
|
|
120
|
+
text?: string;
|
|
121
|
+
toolCalls?: Array<{ name: string; summary: string }>;
|
|
122
|
+
usage?: {
|
|
123
|
+
inputTokens: number;
|
|
124
|
+
outputTokens: number;
|
|
125
|
+
costUSD?: number;
|
|
126
|
+
durationMs?: number;
|
|
127
|
+
numTurns?: number;
|
|
128
|
+
};
|
|
129
|
+
error?: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ================================================================
|
|
133
|
+
// AgentAdapter — 新 Agent 模块只需实现这个接口
|
|
134
|
+
// ================================================================
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* AgentAdapter — 新 Agent 模块只需实现这个接口
|
|
138
|
+
*
|
|
139
|
+
* 职责:接收用户消息,调用后端 API,返回响应
|
|
140
|
+
* SDK Runtime 负责:session 管理、统计、格式化、错误处理、配置
|
|
141
|
+
*/
|
|
142
|
+
export interface AgentAdapter {
|
|
143
|
+
readonly name: string;
|
|
144
|
+
handleMessage(input: AgentInput): Promise<AgentOutput>;
|
|
145
|
+
healthCheck?(): Promise<boolean>;
|
|
146
|
+
cancel?(sessionId: string): Promise<void>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ================================================================
|
|
150
|
+
// Runtime 接口
|
|
151
|
+
// ================================================================
|
|
152
|
+
|
|
153
|
+
/** 消息处理上下文 */
|
|
154
|
+
export interface MessageContext {
|
|
155
|
+
chatId: string;
|
|
156
|
+
text: string;
|
|
157
|
+
/** 消息附带的附件/媒体 */
|
|
158
|
+
attachments?: MessageAttachment[];
|
|
159
|
+
userId: string;
|
|
160
|
+
workingDir: string;
|
|
161
|
+
model: string;
|
|
162
|
+
systemPrompt?: string;
|
|
163
|
+
reply: (text: string) => Promise<void>;
|
|
164
|
+
sendProgress: (text: string) => Promise<void>;
|
|
165
|
+
sendBlocks?: (blocks: UnifiedBlock[]) => Promise<void>;
|
|
166
|
+
/** IM 能力声明,用于 parseToBlocks 正确解析 */
|
|
167
|
+
imCaps?: IMCapabilities;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Session 管理器 */
|
|
171
|
+
export interface SessionManager {
|
|
172
|
+
/** 获取或创建 Session */
|
|
173
|
+
getOrCreate(botName: string, chatId: string, userId: string): Promise<Session>;
|
|
174
|
+
/** 持久化 Session */
|
|
175
|
+
persist(botName: string, session: Session): void;
|
|
176
|
+
/** 删除 Session */
|
|
177
|
+
delete(botName: string, chatId: string): void;
|
|
178
|
+
/** 清理空闲 Session */
|
|
179
|
+
cleanupIdle(botName: string, timeoutMs: number): void;
|
|
180
|
+
/** 列出所有活跃 Session */
|
|
181
|
+
listActive(botName: string): Session[];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** 错误处理器 */
|
|
185
|
+
export interface ErrorHandler {
|
|
186
|
+
handle(chatId: string, error: Error, ctx: ErrorContext): Promise<ErrorAction>;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** 配置管理器 */
|
|
190
|
+
export interface ConfigManager {
|
|
191
|
+
get<T>(path: string): T;
|
|
192
|
+
getBotConfig(name: string): BotConfig | null;
|
|
193
|
+
getProviderConfig(providerId: string): ProviderConfig | null;
|
|
194
|
+
getActiveModel(): string;
|
|
195
|
+
resolveModel(modelSpec: string): string;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** 统计追踪器 */
|
|
199
|
+
export interface StatsTracker {
|
|
200
|
+
resetForCall(session: Session): void;
|
|
201
|
+
accumulate(session: Session, usage: { inputTokens: number; outputTokens: number; costUSD?: number; durationMs?: number; numTurns?: number }): void;
|
|
202
|
+
formatSummary(session: Session): string;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ================================================================
|
|
206
|
+
// Runtime 配置
|
|
207
|
+
// ================================================================
|
|
208
|
+
|
|
209
|
+
export interface RuntimeConfig {
|
|
210
|
+
sessionManager: SessionManager;
|
|
211
|
+
errorHandler: ErrorHandler;
|
|
212
|
+
configManager: ConfigManager;
|
|
213
|
+
statsTracker: StatsTracker;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ================================================================
|
|
217
|
+
// 错误处理
|
|
218
|
+
// ================================================================
|
|
219
|
+
|
|
220
|
+
export type ErrorAction =
|
|
221
|
+
| { type: 'reply'; message: string }
|
|
222
|
+
| { type: 'retry'; maxAttempts: number }
|
|
223
|
+
| { type: 'fallback'; adapter: string };
|
|
224
|
+
|
|
225
|
+
export interface ErrorContext {
|
|
226
|
+
chatId: string;
|
|
227
|
+
backend: string;
|
|
228
|
+
attempt: number;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ================================================================
|
|
232
|
+
// 配置类型
|
|
233
|
+
// ================================================================
|
|
234
|
+
|
|
235
|
+
export interface BotConfig {
|
|
236
|
+
name: string;
|
|
237
|
+
backend: string; // 'claude' | 'codex' | 'opencode'
|
|
238
|
+
appId: string;
|
|
239
|
+
appSecret: string;
|
|
240
|
+
cwd?: string;
|
|
241
|
+
activeModel?: string;
|
|
242
|
+
modelAliases?: Record<string, string>;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export interface ProviderConfig {
|
|
246
|
+
baseUrl: string;
|
|
247
|
+
apiKey: string;
|
|
248
|
+
model: string;
|
|
249
|
+
format: 'anthropic' | 'openai';
|
|
250
|
+
}
|