@zhin.js/agent 0.1.0 → 0.1.2
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/lib/cron-engine.d.ts +16 -1
- package/lib/cron-engine.d.ts.map +1 -1
- package/lib/cron-engine.js +47 -13
- package/lib/cron-engine.js.map +1 -1
- package/lib/discover-skills.d.ts +3 -1
- package/lib/discover-skills.d.ts.map +1 -1
- package/lib/discover-skills.js +7 -9
- package/lib/discover-skills.js.map +1 -1
- package/lib/discover-tools.d.ts +1 -6
- package/lib/discover-tools.d.ts.map +1 -1
- package/lib/discover-tools.js +2 -6
- package/lib/discover-tools.js.map +1 -1
- package/lib/index.d.ts +2 -4
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -2
- package/lib/index.js.map +1 -1
- package/lib/init/create-zhin-agent.d.ts.map +1 -1
- package/lib/init/create-zhin-agent.js +31 -20
- package/lib/init/create-zhin-agent.js.map +1 -1
- package/lib/init/register-ai-trigger.d.ts.map +1 -1
- package/lib/init/register-ai-trigger.js +10 -3
- package/lib/init/register-ai-trigger.js.map +1 -1
- package/lib/init/register-builtin-tools.d.ts.map +1 -1
- package/lib/init/register-builtin-tools.js +46 -14
- package/lib/init/register-builtin-tools.js.map +1 -1
- package/lib/init/register-db-models.d.ts.map +1 -1
- package/lib/init/register-db-models.js +1 -3
- package/lib/init/register-db-models.js.map +1 -1
- package/lib/init/register-db-upgrade.d.ts.map +1 -1
- package/lib/init/register-db-upgrade.js +1 -8
- package/lib/init/register-db-upgrade.js.map +1 -1
- package/lib/init/register-management-tools.d.ts.map +1 -1
- package/lib/init/register-management-tools.js +33 -20
- package/lib/init/register-management-tools.js.map +1 -1
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +0 -8
- package/lib/service.js.map +1 -1
- package/lib/zhin-agent/builtin-tools.d.ts +0 -2
- package/lib/zhin-agent/builtin-tools.d.ts.map +1 -1
- package/lib/zhin-agent/builtin-tools.js +0 -55
- package/lib/zhin-agent/builtin-tools.js.map +1 -1
- package/lib/zhin-agent/config.d.ts +2 -1
- package/lib/zhin-agent/config.d.ts.map +1 -1
- package/lib/zhin-agent/config.js +1 -1
- package/lib/zhin-agent/config.js.map +1 -1
- package/lib/zhin-agent/index.d.ts +1 -6
- package/lib/zhin-agent/index.d.ts.map +1 -1
- package/lib/zhin-agent/index.js +26 -34
- package/lib/zhin-agent/index.js.map +1 -1
- package/lib/zhin-agent/prompt.d.ts.map +1 -1
- package/lib/zhin-agent/prompt.js +31 -76
- package/lib/zhin-agent/prompt.js.map +1 -1
- package/lib/zhin-agent/tool-collector.d.ts.map +1 -1
- package/lib/zhin-agent/tool-collector.js +7 -7
- package/lib/zhin-agent/tool-collector.js.map +1 -1
- package/package.json +7 -4
- package/CHANGELOG.md +0 -190
- package/lib/follow-up.d.ts +0 -131
- package/lib/follow-up.d.ts.map +0 -1
- package/lib/follow-up.js +0 -265
- package/lib/follow-up.js.map +0 -1
- package/src/agent.ts +0 -6
- package/src/bootstrap.ts +0 -309
- package/src/builtin-tools.ts +0 -958
- package/src/compaction.ts +0 -28
- package/src/context-manager.ts +0 -15
- package/src/conversation-memory.ts +0 -5
- package/src/cron-engine.ts +0 -338
- package/src/discover-agents.ts +0 -138
- package/src/discover-skills.ts +0 -325
- package/src/discover-tools.ts +0 -302
- package/src/discovery-utils.ts +0 -96
- package/src/file-policy.ts +0 -333
- package/src/follow-up.ts +0 -357
- package/src/hooks.ts +0 -223
- package/src/index.ts +0 -183
- package/src/init/create-zhin-agent.ts +0 -161
- package/src/init/register-ai-service.ts +0 -53
- package/src/init/register-ai-trigger.ts +0 -253
- package/src/init/register-builtin-tools.ts +0 -308
- package/src/init/register-db-models.ts +0 -31
- package/src/init/register-db-upgrade.ts +0 -77
- package/src/init/register-management-tools.ts +0 -71
- package/src/init/register-message-recorder.ts +0 -31
- package/src/init/register-tool-service.ts +0 -9
- package/src/init/shared-refs.ts +0 -20
- package/src/init/types.ts +0 -18
- package/src/init.ts +0 -50
- package/src/output.ts +0 -15
- package/src/rate-limiter.ts +0 -5
- package/src/service.ts +0 -228
- package/src/session.ts +0 -13
- package/src/storage.ts +0 -9
- package/src/subagent.ts +0 -209
- package/src/tone-detector.ts +0 -5
- package/src/tools.ts +0 -214
- package/src/user-profile.ts +0 -182
- package/src/zhin-agent/builtin-tools.ts +0 -247
- package/src/zhin-agent/config.ts +0 -124
- package/src/zhin-agent/exec-policy.ts +0 -285
- package/src/zhin-agent/index.ts +0 -633
- package/src/zhin-agent/prompt.ts +0 -305
- package/src/zhin-agent/tool-collector.ts +0 -249
- package/tests/ai/follow-up.test.ts +0 -175
- package/tests/ai/integration.test.ts +0 -582
- package/tests/ai/multimodal.test.ts +0 -106
- package/tests/ai/setup.ts +0 -186
- package/tests/ai/subagent.test.ts +0 -270
- package/tests/ai/tools-builtin.test.ts +0 -310
- package/tests/ai/user-profile.test.ts +0 -73
- package/tests/ai/zhin-agent.test.ts +0 -306
- package/tests/exec-policy.test.ts +0 -355
- package/tests/file-policy.test.ts +0 -405
- package/tsconfig.json +0 -22
package/src/index.ts
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @zhin.js/agent — AI Agent composition
|
|
3
|
-
* Composes providers, tools, skills from @zhin.js/core into session, ZhinAgent, init.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// Re-export types from core for convenience
|
|
7
|
-
export type {
|
|
8
|
-
AIConfig,
|
|
9
|
-
AIProvider,
|
|
10
|
-
ChatMessage,
|
|
11
|
-
ContentPart,
|
|
12
|
-
AgentTool,
|
|
13
|
-
AgentConfig,
|
|
14
|
-
AgentResult,
|
|
15
|
-
} from '@zhin.js/core';
|
|
16
|
-
|
|
17
|
-
export { Agent, createAgent, formatToolTitle } from './agent.js';
|
|
18
|
-
export type { AgentState, AgentEvents } from './agent.js';
|
|
19
|
-
|
|
20
|
-
export { AIService } from './service.js';
|
|
21
|
-
|
|
22
|
-
export {
|
|
23
|
-
SessionManager,
|
|
24
|
-
MemorySessionManager,
|
|
25
|
-
DatabaseSessionManager,
|
|
26
|
-
createMemorySessionManager,
|
|
27
|
-
createDatabaseSessionManager,
|
|
28
|
-
AI_SESSION_MODEL,
|
|
29
|
-
} from './session.js';
|
|
30
|
-
|
|
31
|
-
export {
|
|
32
|
-
ContextManager,
|
|
33
|
-
createContextManager,
|
|
34
|
-
CHAT_MESSAGE_MODEL,
|
|
35
|
-
CONTEXT_SUMMARY_MODEL,
|
|
36
|
-
} from './context-manager.js';
|
|
37
|
-
export type { ContextConfig, MessageRecord } from './context-manager.js';
|
|
38
|
-
|
|
39
|
-
export { ZhinAgent } from './zhin-agent/index.js';
|
|
40
|
-
export type { ZhinAgentConfig, OnChunkCallback } from './zhin-agent/index.js';
|
|
41
|
-
|
|
42
|
-
export { PERM_MAP, DEFAULT_CONFIG as ZHIN_AGENT_DEFAULT_CONFIG, SECTION_SEP } from './zhin-agent/config.js';
|
|
43
|
-
export {
|
|
44
|
-
checkExecPolicy, applyExecPolicyToTools, resolveExecAllowlist, EXEC_PRESETS,
|
|
45
|
-
isDangerousCommand, stripEnvVarPrefix, stripSafeWrappers, splitCompoundCommand, extractCommandName,
|
|
46
|
-
type ExecPolicyResult,
|
|
47
|
-
} from './zhin-agent/exec-policy.js';
|
|
48
|
-
export { collectRelevantTools, toAgentTool } from './zhin-agent/tool-collector.js';
|
|
49
|
-
export { buildRichSystemPrompt, buildContextHint, buildEnhancedPersona, buildUserMessageWithHistory, contentToText } from './zhin-agent/prompt.js';
|
|
50
|
-
export type { RichSystemPromptContext } from './zhin-agent/prompt.js';
|
|
51
|
-
export { createChatHistoryTool, createUserProfileTool, createScheduleFollowUpTool, createSpawnTaskTool } from './zhin-agent/builtin-tools.js';
|
|
52
|
-
|
|
53
|
-
export {
|
|
54
|
-
ConversationMemory,
|
|
55
|
-
AI_MESSAGE_MODEL,
|
|
56
|
-
AI_SUMMARY_MODEL,
|
|
57
|
-
} from './conversation-memory.js';
|
|
58
|
-
export type { ConversationMemoryConfig } from './conversation-memory.js';
|
|
59
|
-
|
|
60
|
-
export { UserProfileStore, AI_USER_PROFILE_MODEL } from './user-profile.js';
|
|
61
|
-
|
|
62
|
-
export { RateLimiter } from './rate-limiter.js';
|
|
63
|
-
export type { RateLimitConfig, RateLimitResult } from './rate-limiter.js';
|
|
64
|
-
|
|
65
|
-
export { FollowUpManager, AI_FOLLOWUP_MODEL } from './follow-up.js';
|
|
66
|
-
export type { FollowUpRecord, FollowUpSender } from './follow-up.js';
|
|
67
|
-
|
|
68
|
-
export { SubagentManager } from './subagent.js';
|
|
69
|
-
export type {
|
|
70
|
-
SubagentOrigin,
|
|
71
|
-
SubagentResultSender,
|
|
72
|
-
SpawnOptions,
|
|
73
|
-
SubagentManagerOptions,
|
|
74
|
-
} from './subagent.js';
|
|
75
|
-
|
|
76
|
-
export {
|
|
77
|
-
PersistentCronEngine,
|
|
78
|
-
readCronJobsFile,
|
|
79
|
-
writeCronJobsFile,
|
|
80
|
-
getCronJobsFilePath,
|
|
81
|
-
generateCronJobId,
|
|
82
|
-
createCronTools,
|
|
83
|
-
setCronManager,
|
|
84
|
-
getCronManager,
|
|
85
|
-
CRON_JOBS_FILENAME,
|
|
86
|
-
} from './cron-engine.js';
|
|
87
|
-
export type {
|
|
88
|
-
CronJobRecord,
|
|
89
|
-
CronRunner,
|
|
90
|
-
AddCronFn,
|
|
91
|
-
PersistentCronEngineOptions,
|
|
92
|
-
CronManager,
|
|
93
|
-
} from './cron-engine.js';
|
|
94
|
-
|
|
95
|
-
export {
|
|
96
|
-
estimateTokens,
|
|
97
|
-
estimateMessagesTokens,
|
|
98
|
-
splitMessagesByTokenShare,
|
|
99
|
-
chunkMessagesByMaxTokens,
|
|
100
|
-
computeAdaptiveChunkRatio,
|
|
101
|
-
resolveContextWindowTokens,
|
|
102
|
-
evaluateContextWindowGuard,
|
|
103
|
-
summarizeWithFallback,
|
|
104
|
-
summarizeInStages,
|
|
105
|
-
pruneHistoryForContext,
|
|
106
|
-
compactSession,
|
|
107
|
-
DEFAULT_CONTEXT_TOKENS,
|
|
108
|
-
} from './compaction.js';
|
|
109
|
-
export type { ContextWindowSource, ContextWindowInfo, ContextWindowGuardResult, PruneResult } from './compaction.js';
|
|
110
|
-
|
|
111
|
-
export {
|
|
112
|
-
loadBootstrapFiles,
|
|
113
|
-
buildContextFiles,
|
|
114
|
-
buildBootstrapContextSection,
|
|
115
|
-
loadSoulPersona,
|
|
116
|
-
loadToolsGuide,
|
|
117
|
-
loadAgentsMemory,
|
|
118
|
-
clearBootstrapCache,
|
|
119
|
-
} from './bootstrap.js';
|
|
120
|
-
export type { BootstrapFile, ContextFile } from './bootstrap.js';
|
|
121
|
-
|
|
122
|
-
export {
|
|
123
|
-
registerAIHook,
|
|
124
|
-
unregisterAIHook,
|
|
125
|
-
triggerAIHook,
|
|
126
|
-
createAIHookEvent,
|
|
127
|
-
clearAIHooks,
|
|
128
|
-
getRegisteredAIHookKeys,
|
|
129
|
-
} from './hooks.js';
|
|
130
|
-
export type {
|
|
131
|
-
AIHookEvent,
|
|
132
|
-
AIHookEventType,
|
|
133
|
-
AIHookHandler,
|
|
134
|
-
MessageReceivedEvent,
|
|
135
|
-
MessageSentEvent,
|
|
136
|
-
SessionCompactEvent,
|
|
137
|
-
SessionNewEvent,
|
|
138
|
-
AgentBootstrapEvent,
|
|
139
|
-
ToolCallEvent,
|
|
140
|
-
} from './hooks.js';
|
|
141
|
-
|
|
142
|
-
export { detectTone } from './tone-detector.js';
|
|
143
|
-
export type { Tone } from './tone-detector.js';
|
|
144
|
-
|
|
145
|
-
export {
|
|
146
|
-
parseOutput,
|
|
147
|
-
renderToPlainText,
|
|
148
|
-
renderToSatori,
|
|
149
|
-
} from './output.js';
|
|
150
|
-
export type {
|
|
151
|
-
OutputElement,
|
|
152
|
-
TextElement,
|
|
153
|
-
ImageElement,
|
|
154
|
-
AudioElement,
|
|
155
|
-
VideoElement,
|
|
156
|
-
CardElement,
|
|
157
|
-
FileElement,
|
|
158
|
-
CardField,
|
|
159
|
-
CardButton,
|
|
160
|
-
} from './output.js';
|
|
161
|
-
|
|
162
|
-
export {
|
|
163
|
-
calculatorTool,
|
|
164
|
-
timeTool,
|
|
165
|
-
searchTool,
|
|
166
|
-
codeRunnerTool,
|
|
167
|
-
httpTool,
|
|
168
|
-
memoryTool,
|
|
169
|
-
getBuiltinTools,
|
|
170
|
-
getAllBuiltinTools,
|
|
171
|
-
} from './tools.js';
|
|
172
|
-
|
|
173
|
-
export { initAgentModule } from './init.js';
|
|
174
|
-
|
|
175
|
-
export {
|
|
176
|
-
MemoryStorageBackend,
|
|
177
|
-
DatabaseStorageBackend,
|
|
178
|
-
createSwappableBackend,
|
|
179
|
-
} from './storage.js';
|
|
180
|
-
export type {
|
|
181
|
-
StorageBackend,
|
|
182
|
-
DbModel,
|
|
183
|
-
} from './storage.js';
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Create ZhinAgent global brain and wire up sub-systems
|
|
3
|
-
* (follow-up sender, subagent manager, cron engine, scheduler).
|
|
4
|
-
*/
|
|
5
|
-
import * as path from 'path';
|
|
6
|
-
import { getPlugin, Scheduler, getScheduler, setScheduler, type MessageType, type SendOptions } from '@zhin.js/core';
|
|
7
|
-
import { ModelRegistry, computeTierScore } from '@zhin.js/ai';
|
|
8
|
-
import { ZhinAgent } from '../zhin-agent/index.js';
|
|
9
|
-
import { createBuiltinTools } from '../builtin-tools.js';
|
|
10
|
-
import { collectPluginSkillSearchRoots } from '../discovery-utils.js';
|
|
11
|
-
import { resolveSkillInstructionMaxChars, DEFAULT_CONFIG } from '../zhin-agent/config.js';
|
|
12
|
-
import { PersistentCronEngine, setCronManager } from '../cron-engine.js';
|
|
13
|
-
import type { AIServiceRefs } from './shared-refs.js';
|
|
14
|
-
|
|
15
|
-
export function createZhinAgentContext(refs: AIServiceRefs): void {
|
|
16
|
-
const plugin = getPlugin();
|
|
17
|
-
const { useContext, root, logger } = plugin;
|
|
18
|
-
|
|
19
|
-
useContext('ai', (ai) => {
|
|
20
|
-
if (!ai.isReady()) {
|
|
21
|
-
logger.warn('AI Service not ready, ZhinAgent not created');
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const provider = ai.getProvider();
|
|
26
|
-
const agentConfig = ai.getAgentConfig();
|
|
27
|
-
const agent = new ZhinAgent(provider, agentConfig);
|
|
28
|
-
refs.zhinAgent = agent;
|
|
29
|
-
|
|
30
|
-
const skillRegistry = root.inject('skill');
|
|
31
|
-
if (skillRegistry) agent.setSkillRegistry(skillRegistry);
|
|
32
|
-
|
|
33
|
-
// Model Registry: discover models and wire to agent
|
|
34
|
-
const dataDir = path.join(process.cwd(), 'data');
|
|
35
|
-
const modelRegistry = new ModelRegistry(dataDir);
|
|
36
|
-
const hadCache = modelRegistry.loadCache();
|
|
37
|
-
agent.setModelRegistry(modelRegistry);
|
|
38
|
-
ai.setModelRegistry(modelRegistry);
|
|
39
|
-
// Discover models in background (don't block startup)
|
|
40
|
-
(async () => {
|
|
41
|
-
try {
|
|
42
|
-
const discovered = await modelRegistry.discover(provider);
|
|
43
|
-
modelRegistry.saveCache();
|
|
44
|
-
if (discovered.length > 0) {
|
|
45
|
-
provider.models = discovered
|
|
46
|
-
.sort((a, b) => computeTierScore(b.id) - computeTierScore(a.id))
|
|
47
|
-
.map(m => m.id);
|
|
48
|
-
}
|
|
49
|
-
if (hadCache) {
|
|
50
|
-
logger.debug(`ModelRegistry: refreshed ${discovered.length} models from ${provider.name}`);
|
|
51
|
-
} else {
|
|
52
|
-
logger.info(`ModelRegistry: discovered ${discovered.length} models from ${provider.name}`);
|
|
53
|
-
}
|
|
54
|
-
} catch (e) {
|
|
55
|
-
logger.warn(`ModelRegistry: discovery failed for ${provider.name}: ${(e as Error).message}`);
|
|
56
|
-
}
|
|
57
|
-
})();
|
|
58
|
-
|
|
59
|
-
// Follow-up reminder sender
|
|
60
|
-
agent.setFollowUpSender(async (record) => {
|
|
61
|
-
const adapter = root.inject(record.platform) as { sendMessage?: (opts: SendOptions) => Promise<string> } | undefined;
|
|
62
|
-
if (!adapter || typeof adapter.sendMessage !== 'function') {
|
|
63
|
-
logger.warn(`[跟进提醒] 找不到适配器: ${record.platform}`);
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
await adapter.sendMessage({
|
|
67
|
-
context: record.platform,
|
|
68
|
-
bot: record.bot_id,
|
|
69
|
-
id: record.scene_id,
|
|
70
|
-
type: record.scene_type as MessageType,
|
|
71
|
-
content: `⏰ 定时提醒:${record.message}`,
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// Subagent manager for background tasks
|
|
76
|
-
agent.initSubagentManager(() => {
|
|
77
|
-
const modelName = provider.models[0] || '';
|
|
78
|
-
const fullConfig = { ...DEFAULT_CONFIG, ...agentConfig } as Required<import('../zhin-agent/config.js').ZhinAgentConfig>;
|
|
79
|
-
const zhinTools = createBuiltinTools({
|
|
80
|
-
plugin,
|
|
81
|
-
skillInstructionMaxChars: resolveSkillInstructionMaxChars(fullConfig, modelName),
|
|
82
|
-
pluginSkillRootsResolver: () => collectPluginSkillSearchRoots(root),
|
|
83
|
-
});
|
|
84
|
-
return zhinTools.map(zt => {
|
|
85
|
-
const t = zt.toTool();
|
|
86
|
-
return {
|
|
87
|
-
name: t.name,
|
|
88
|
-
description: t.description,
|
|
89
|
-
parameters: t.parameters,
|
|
90
|
-
execute: t.execute as (args: Record<string, any>) => Promise<unknown>,
|
|
91
|
-
tags: t.tags,
|
|
92
|
-
keywords: t.keywords,
|
|
93
|
-
};
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
agent.setSubagentSender(async (origin, content) => {
|
|
97
|
-
const adapter = root.inject(origin.platform) as { sendMessage?: (opts: SendOptions) => Promise<string> } | undefined;
|
|
98
|
-
if (!adapter || typeof adapter.sendMessage !== 'function') {
|
|
99
|
-
logger.warn(`[子任务] 找不到适配器: ${origin.platform}`);
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
await adapter.sendMessage({
|
|
103
|
-
context: origin.platform,
|
|
104
|
-
bot: origin.botId,
|
|
105
|
-
id: origin.sceneId,
|
|
106
|
-
type: origin.sceneType as MessageType,
|
|
107
|
-
content,
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// Persistent cron engine
|
|
112
|
-
let cronEngine: PersistentCronEngine | null = null;
|
|
113
|
-
const cronFeature = root.inject('cron') as import('@zhin.js/core').CronFeature | undefined;
|
|
114
|
-
if (cronFeature && typeof cronFeature.add === 'function') {
|
|
115
|
-
const addCron: import('../cron-engine.js').AddCronFn = (c) => cronFeature.add(c, 'cron-engine');
|
|
116
|
-
const runner = async (prompt: string) => {
|
|
117
|
-
if (!refs.zhinAgent) return;
|
|
118
|
-
await refs.zhinAgent.process(prompt, {
|
|
119
|
-
platform: 'cron',
|
|
120
|
-
senderId: 'system',
|
|
121
|
-
sceneId: 'cron',
|
|
122
|
-
});
|
|
123
|
-
};
|
|
124
|
-
cronEngine = new PersistentCronEngine({ dataDir, addCron, runner });
|
|
125
|
-
cronEngine.load();
|
|
126
|
-
setCronManager({ cronFeature, engine: cronEngine });
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Unified scheduler (at/every/cron)
|
|
130
|
-
const scheduler = new Scheduler({
|
|
131
|
-
storePath: path.join(dataDir, 'scheduler-jobs.json'),
|
|
132
|
-
workspace: process.cwd(),
|
|
133
|
-
onJob: async (job) => {
|
|
134
|
-
if (!refs.zhinAgent) return;
|
|
135
|
-
await refs.zhinAgent.process(job.payload.message, {
|
|
136
|
-
platform: 'cron',
|
|
137
|
-
senderId: 'system',
|
|
138
|
-
sceneId: 'scheduler',
|
|
139
|
-
});
|
|
140
|
-
},
|
|
141
|
-
});
|
|
142
|
-
setScheduler(scheduler);
|
|
143
|
-
scheduler.start().catch((e) => logger.warn('Scheduler start failed: ' + (e as Error).message));
|
|
144
|
-
|
|
145
|
-
logger.debug('ZhinAgent created');
|
|
146
|
-
return () => {
|
|
147
|
-
setCronManager(null);
|
|
148
|
-
if (cronEngine) {
|
|
149
|
-
cronEngine.unload();
|
|
150
|
-
cronEngine = null;
|
|
151
|
-
}
|
|
152
|
-
const s = getScheduler();
|
|
153
|
-
if (s) {
|
|
154
|
-
s.stop();
|
|
155
|
-
setScheduler(null);
|
|
156
|
-
}
|
|
157
|
-
agent.dispose();
|
|
158
|
-
refs.zhinAgent = null;
|
|
159
|
-
};
|
|
160
|
-
});
|
|
161
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Register AIService as a plugin context.
|
|
3
|
-
*/
|
|
4
|
-
import './types.js';
|
|
5
|
-
import { getPlugin, type Plugin } from '@zhin.js/core';
|
|
6
|
-
import type { AIConfig } from '@zhin.js/core';
|
|
7
|
-
import { AIService } from '../service.js';
|
|
8
|
-
import type { AIServiceRefs } from './shared-refs.js';
|
|
9
|
-
|
|
10
|
-
export function registerAIService(refs: AIServiceRefs): void {
|
|
11
|
-
const plugin = getPlugin();
|
|
12
|
-
const { provide, root, logger } = plugin;
|
|
13
|
-
|
|
14
|
-
provide<'ai'>({
|
|
15
|
-
name: 'ai',
|
|
16
|
-
description: 'AI Service - Multi-model LLM integration',
|
|
17
|
-
async mounted(_p: Plugin) {
|
|
18
|
-
const configService = root.inject('config');
|
|
19
|
-
const appConfig =
|
|
20
|
-
configService?.getPrimary<{ ai?: AIConfig }>() || {};
|
|
21
|
-
const config = appConfig.ai || {};
|
|
22
|
-
|
|
23
|
-
if (config.enabled === false) {
|
|
24
|
-
logger.info('AI Service is disabled');
|
|
25
|
-
return undefined as unknown as AIService;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const service = new AIService(config);
|
|
29
|
-
refs.aiService = service;
|
|
30
|
-
service.setPlugin(root);
|
|
31
|
-
|
|
32
|
-
const providers = service.listProviders();
|
|
33
|
-
if (providers.length === 0) {
|
|
34
|
-
logger.warn(
|
|
35
|
-
'No AI providers configured. Please add API keys in zhin.config (yml/json/toml)',
|
|
36
|
-
);
|
|
37
|
-
} else {
|
|
38
|
-
logger.info(
|
|
39
|
-
`AI Service started with providers: ${providers.join(', ')}`,
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return service;
|
|
44
|
-
},
|
|
45
|
-
async dispose(service) {
|
|
46
|
-
if (service) {
|
|
47
|
-
service.dispose();
|
|
48
|
-
refs.aiService = null;
|
|
49
|
-
logger.info('AI Service stopped');
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
});
|
|
53
|
-
}
|
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Register AI trigger handler via MessageDispatcher or fallback middleware.
|
|
3
|
-
*/
|
|
4
|
-
import './types.js';
|
|
5
|
-
import { getPlugin, Message, shouldTriggerAI, inferSenderPermissions, parseRichMediaContent, mergeAITriggerConfig } from '@zhin.js/core';
|
|
6
|
-
import type { Tool, ToolContext } from '@zhin.js/core';
|
|
7
|
-
import type { ContentPart } from '@zhin.js/core';
|
|
8
|
-
import type { OutputElement } from '@zhin.js/ai';
|
|
9
|
-
import type { AIServiceRefs } from './shared-refs.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Extract multimodal ContentPart[] from a Message's structured $content segments.
|
|
13
|
-
* Handles image, video, audio, and face/sticker types.
|
|
14
|
-
* Falls back to raw string parsing for image URLs when $content has no media segments.
|
|
15
|
-
*/
|
|
16
|
-
function extractMediaParts(message: Message<any>): ContentPart[] {
|
|
17
|
-
const parts: ContentPart[] = [];
|
|
18
|
-
|
|
19
|
-
// 1. Extract from structured $content segments
|
|
20
|
-
if (Array.isArray(message.$content)) {
|
|
21
|
-
for (const seg of message.$content) {
|
|
22
|
-
if (typeof seg === 'string' || !seg || !seg.type) continue;
|
|
23
|
-
const { type, data } = seg;
|
|
24
|
-
switch (type) {
|
|
25
|
-
case 'image': {
|
|
26
|
-
const url = data?.url || data?.file || data?.src;
|
|
27
|
-
if (url) parts.push({ type: 'image_url', image_url: { url } });
|
|
28
|
-
break;
|
|
29
|
-
}
|
|
30
|
-
case 'video': {
|
|
31
|
-
const url = data?.url || data?.file || data?.src;
|
|
32
|
-
if (url) parts.push({ type: 'video_url', video_url: { url } });
|
|
33
|
-
break;
|
|
34
|
-
}
|
|
35
|
-
case 'audio':
|
|
36
|
-
case 'record':
|
|
37
|
-
case 'voice': {
|
|
38
|
-
const dataStr = data?.data || data?.base64;
|
|
39
|
-
if (dataStr) {
|
|
40
|
-
const fmt = data?.format === 'wav' ? 'wav' : 'mp3';
|
|
41
|
-
parts.push({ type: 'audio', audio: { data: dataStr, format: fmt } });
|
|
42
|
-
} else {
|
|
43
|
-
const url = data?.url || data?.file || data?.src;
|
|
44
|
-
if (url) {
|
|
45
|
-
// Audio URL: describe as text since most LLMs can't play audio URLs directly
|
|
46
|
-
parts.push({ type: 'text', text: `[用户发送了一段语音: ${url}]` });
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
break;
|
|
50
|
-
}
|
|
51
|
-
case 'face':
|
|
52
|
-
case 'sticker':
|
|
53
|
-
case 'emoji': {
|
|
54
|
-
const id = String(data?.id ?? data?.face_id ?? '');
|
|
55
|
-
const text = data?.text || data?.name || data?.describe;
|
|
56
|
-
if (id) parts.push({ type: 'face', face: { id, text } });
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// 2. Fallback: parse image URLs from $raw for adapters that don't use structured $content
|
|
64
|
-
if (parts.length === 0) {
|
|
65
|
-
const raw = typeof message.$raw === 'string' ? message.$raw : JSON.stringify(message.$raw || '');
|
|
66
|
-
|
|
67
|
-
const xmlMatches = raw.match(/<image[^>]+url="([^"]+)"/g);
|
|
68
|
-
if (xmlMatches) {
|
|
69
|
-
for (const m of xmlMatches) {
|
|
70
|
-
const urlMatch = m.match(/url="([^"]+)"/);
|
|
71
|
-
if (urlMatch) parts.push({ type: 'image_url', image_url: { url: urlMatch[1] } });
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const cqMatches = raw.match(/\[CQ:image[^\]]*url=([^\],]+)/g);
|
|
76
|
-
if (cqMatches) {
|
|
77
|
-
for (const m of cqMatches) {
|
|
78
|
-
const urlMatch = m.match(/url=([^\],]+)/);
|
|
79
|
-
if (urlMatch) parts.push({ type: 'image_url', image_url: { url: urlMatch[1] } });
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return parts;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function renderOutput(elements: OutputElement[]): string {
|
|
88
|
-
const parts: string[] = [];
|
|
89
|
-
for (const el of elements) {
|
|
90
|
-
switch (el.type) {
|
|
91
|
-
case 'text':
|
|
92
|
-
if (el.content) parts.push(el.content);
|
|
93
|
-
break;
|
|
94
|
-
case 'image':
|
|
95
|
-
parts.push(`<image url="${el.url}"/>`);
|
|
96
|
-
break;
|
|
97
|
-
case 'audio':
|
|
98
|
-
parts.push(`<audio url="${el.url}"/>`);
|
|
99
|
-
break;
|
|
100
|
-
case 'video':
|
|
101
|
-
parts.push(`<video url="${el.url}"/>`);
|
|
102
|
-
break;
|
|
103
|
-
case 'card': {
|
|
104
|
-
const cp = [`📋 ${el.title}`];
|
|
105
|
-
if (el.description) cp.push(el.description);
|
|
106
|
-
if (el.fields?.length)
|
|
107
|
-
for (const f of el.fields) cp.push(` ${f.label}: ${f.value}`);
|
|
108
|
-
if (el.imageUrl) cp.push(`<image url="${el.imageUrl}"/>`);
|
|
109
|
-
parts.push(cp.join('\n'));
|
|
110
|
-
break;
|
|
111
|
-
}
|
|
112
|
-
case 'file':
|
|
113
|
-
parts.push(`📎 ${el.name}: ${el.url}`);
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return parts.join('\n') || '';
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export function registerAITrigger(refs: AIServiceRefs): void {
|
|
121
|
-
const plugin = getPlugin();
|
|
122
|
-
const { useContext, root, logger } = plugin;
|
|
123
|
-
|
|
124
|
-
useContext('ai', (ai) => {
|
|
125
|
-
const rawConfig = ai.getTriggerConfig();
|
|
126
|
-
const triggerConfig = mergeAITriggerConfig(rawConfig);
|
|
127
|
-
if (!triggerConfig.enabled) {
|
|
128
|
-
logger.info('AI Trigger is disabled');
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const dispatcherSvc = root.inject('dispatcher') as
|
|
133
|
-
| { replyWithPolish?: (m: Message<any>, s: 'ai' | 'command', c: unknown) => Promise<unknown> }
|
|
134
|
-
| undefined;
|
|
135
|
-
|
|
136
|
-
const handleAIMessage = async (
|
|
137
|
-
message: Message<any>,
|
|
138
|
-
content: string,
|
|
139
|
-
) => {
|
|
140
|
-
const replyOutbound = async (payload: unknown) => {
|
|
141
|
-
if (dispatcherSvc && typeof dispatcherSvc.replyWithPolish === 'function') {
|
|
142
|
-
return dispatcherSvc.replyWithPolish(message, 'ai', payload as any);
|
|
143
|
-
}
|
|
144
|
-
return message.$reply(payload as any);
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const t0 = performance.now();
|
|
148
|
-
if (!ai.isReady()) return;
|
|
149
|
-
if (triggerConfig.thinkingMessage)
|
|
150
|
-
await replyOutbound(triggerConfig.thinkingMessage);
|
|
151
|
-
|
|
152
|
-
const permissions = inferSenderPermissions(message, triggerConfig);
|
|
153
|
-
|
|
154
|
-
// 从 bot 配置中查找 owner(bots[].owner)
|
|
155
|
-
const adapterInstance = root.inject(message.$adapter) as
|
|
156
|
-
| { bots?: Map<string, { $config?: Record<string, any> }> }
|
|
157
|
-
| undefined;
|
|
158
|
-
const botConfig = adapterInstance?.bots?.get(message.$bot)?.$config as Record<string, any> | undefined;
|
|
159
|
-
const botOwner: string | undefined = botConfig?.owner;
|
|
160
|
-
|
|
161
|
-
// 用 bot 级别 owner 覆盖权限判断
|
|
162
|
-
const isOwner = botOwner ? String(message.$sender.id) === String(botOwner) : permissions.isOwner;
|
|
163
|
-
const permissionLevel = isOwner ? 'owner' as const : permissions.permissionLevel;
|
|
164
|
-
|
|
165
|
-
const toolContext: ToolContext = {
|
|
166
|
-
platform: message.$adapter,
|
|
167
|
-
botId: message.$bot,
|
|
168
|
-
sceneId: message.$channel?.id || message.$sender.id,
|
|
169
|
-
senderId: message.$sender.id,
|
|
170
|
-
message,
|
|
171
|
-
scope: permissions.scope,
|
|
172
|
-
senderPermissionLevel: permissionLevel,
|
|
173
|
-
isGroupAdmin: permissions.isGroupAdmin,
|
|
174
|
-
isGroupOwner: permissions.isGroupOwner,
|
|
175
|
-
isBotAdmin: isOwner || permissions.isBotAdmin,
|
|
176
|
-
isOwner,
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
const tCollect = performance.now();
|
|
180
|
-
const toolService = root.inject('tool');
|
|
181
|
-
let externalTools: Tool[] = [];
|
|
182
|
-
if (toolService) {
|
|
183
|
-
externalTools = toolService.collectAll(root);
|
|
184
|
-
externalTools = toolService.filterByContext(externalTools, toolContext);
|
|
185
|
-
}
|
|
186
|
-
logger.debug(`[AI Handler] 工具收集: ${externalTools.length} 个, ${(performance.now() - tCollect).toFixed(0)}ms`);
|
|
187
|
-
|
|
188
|
-
try {
|
|
189
|
-
const timeout = new Promise<never>((_, rej) =>
|
|
190
|
-
setTimeout(() => rej(new Error('AI 响应超时')), triggerConfig.timeout),
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
let responseText: string;
|
|
194
|
-
if (refs.zhinAgent) {
|
|
195
|
-
const mediaParts = extractMediaParts(message);
|
|
196
|
-
let elements: OutputElement[];
|
|
197
|
-
if (mediaParts.length > 0) {
|
|
198
|
-
const parts: ContentPart[] = [];
|
|
199
|
-
if (content) parts.push({ type: 'text', text: content });
|
|
200
|
-
parts.push(...mediaParts);
|
|
201
|
-
elements = await Promise.race([
|
|
202
|
-
refs.zhinAgent.processMultimodal(parts, toolContext),
|
|
203
|
-
timeout,
|
|
204
|
-
]);
|
|
205
|
-
} else {
|
|
206
|
-
elements = await Promise.race([
|
|
207
|
-
refs.zhinAgent.process(content, toolContext, externalTools),
|
|
208
|
-
timeout,
|
|
209
|
-
]);
|
|
210
|
-
}
|
|
211
|
-
responseText = renderOutput(elements);
|
|
212
|
-
} else {
|
|
213
|
-
const response = await Promise.race([
|
|
214
|
-
ai.process(content, toolContext, externalTools),
|
|
215
|
-
timeout,
|
|
216
|
-
]);
|
|
217
|
-
responseText = typeof response === 'string' ? response : '';
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (responseText) await replyOutbound(parseRichMediaContent(responseText));
|
|
221
|
-
logger.info(`[AI Handler] 总耗时: ${(performance.now() - t0).toFixed(0)}ms`);
|
|
222
|
-
} catch (error) {
|
|
223
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
224
|
-
logger.warn(`[AI Handler] 失败 (${(performance.now() - t0).toFixed(0)}ms): ${msg}`);
|
|
225
|
-
await replyOutbound(triggerConfig.errorTemplate.replace('{error}', msg));
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
const dispatcher = root.inject('dispatcher');
|
|
230
|
-
|
|
231
|
-
if (dispatcher && typeof dispatcher.setAIHandler === 'function') {
|
|
232
|
-
dispatcher.setAITriggerMatcher((message: Message<any>) =>
|
|
233
|
-
shouldTriggerAI(message, triggerConfig),
|
|
234
|
-
);
|
|
235
|
-
dispatcher.setAIHandler(handleAIMessage);
|
|
236
|
-
logger.debug('AI Handler registered via MessageDispatcher');
|
|
237
|
-
return () => { logger.info('AI Handler unregistered'); };
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const aiMw = async (
|
|
241
|
-
message: Message<any>,
|
|
242
|
-
next: () => Promise<void>,
|
|
243
|
-
) => {
|
|
244
|
-
const { triggered, content } = shouldTriggerAI(message, triggerConfig);
|
|
245
|
-
if (!triggered) return await next();
|
|
246
|
-
await handleAIMessage(message, content);
|
|
247
|
-
await next();
|
|
248
|
-
};
|
|
249
|
-
const dispose = root.addMiddleware(aiMw);
|
|
250
|
-
logger.debug('AI Trigger middleware registered (fallback mode)');
|
|
251
|
-
return () => { dispose(); };
|
|
252
|
-
});
|
|
253
|
-
}
|