foliko 1.1.93 → 2.0.1
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/.claude/settings.local.json +2 -1
- package/CLAUDE.md +56 -30
- package/REFACTORING_PLAN.md +645 -0
- package/docs/architecture.md +131 -0
- package/docs/migration.md +57 -0
- package/docs/public-api.md +138 -0
- package/docs/usage.md +385 -0
- package/examples/ambient-example.js +20 -137
- package/examples/basic.js +21 -48
- package/examples/bootstrap.js +16 -74
- package/examples/mcp-example.js +6 -29
- package/examples/skill-example.js +6 -19
- package/examples/workflow.js +8 -56
- package/package.json +8 -4
- package/plugins/README.md +49 -0
- package/plugins/{ambient-agent → ambient}/EventWatcher.js +1 -1
- package/plugins/{ambient-agent → ambient}/ExplorerLoop.js +3 -3
- package/plugins/{ambient-agent → ambient}/GoalManager.js +2 -2
- package/plugins/ambient/README.md +14 -0
- package/plugins/{ambient-agent → ambient}/Reflector.js +1 -1
- package/plugins/{ambient-agent → ambient}/StateStore.js +1 -1
- package/plugins/{ambient-agent → ambient}/index.js +2 -2
- package/plugins/{ai-plugin.js → core/ai/index.js} +14 -30
- package/plugins/{audit-plugin.js → core/audit/index.js} +3 -30
- package/plugins/{coordinator-plugin.js → core/coordinator/index.js} +3 -35
- package/plugins/core/default/bootstrap.js +224 -0
- package/plugins/core/default/config.js +222 -0
- package/plugins/core/default/index.js +58 -0
- package/plugins/core/mcp/index.js +1 -0
- package/plugins/{python-plugin-loader.js → core/python-loader/index.js} +7 -187
- package/plugins/{rules-plugin.js → core/rules/index.js} +121 -64
- package/plugins/{scheduler-plugin.js → core/scheduler/index.js} +12 -114
- package/plugins/{session-plugin.js → core/session/index.js} +9 -73
- package/{src/capabilities/skill-manager.js → plugins/core/skill-manager/index.js} +64 -18
- package/plugins/{storage-plugin.js → core/storage/index.js} +5 -29
- package/plugins/{subagent-plugin.js → core/sub-agent/index.js} +10 -171
- package/plugins/{think-plugin.js → core/think/index.js} +24 -91
- package/{src/capabilities/workflow-engine.js → plugins/core/workflow/index.js} +87 -85
- package/plugins/default-plugins.js +6 -720
- package/plugins/{data-splitter-plugin.js → executors/data-splitter/index.js} +9 -83
- package/plugins/{extension-executor-plugin.js → executors/extension/index.js} +13 -97
- package/plugins/{python-executor-plugin.js → executors/python/index.js} +6 -31
- package/plugins/{shell-executor-plugin.js → executors/shell/index.js} +2 -5
- package/plugins/install/README.md +9 -0
- package/plugins/{install-plugin.js → install/index.js} +3 -3
- package/plugins/{file-system-plugin.js → io/file-system/index.js} +34 -236
- package/plugins/{web-plugin.js → io/web/index.js} +11 -113
- package/plugins/memory/README.md +13 -0
- package/plugins/{memory-plugin.js → memory/index.js} +4 -18
- package/plugins/messaging/email/README.md +19 -0
- package/plugins/{email → messaging/email}/index.js +3 -3
- package/plugins/{feishu-plugin.js → messaging/feishu/index.js} +4 -4
- package/plugins/{qq-plugin.js → messaging/qq/index.js} +6 -17
- package/plugins/{telegram-plugin.js → messaging/telegram/index.js} +4 -4
- package/plugins/{weixin-plugin.js → messaging/weixin/index.js} +16 -16
- package/plugins/{plugin-manager-plugin.js → plugin-manager/index.js} +36 -180
- package/plugins/{tools-plugin.js → tools/index.js} +68 -116
- package/plugins/trading/README.md +15 -0
- package/plugins/{gate-trading.js → trading/index.js} +8 -8
- package/{examples → sandbox}/test-concurrent-chat.js +2 -2
- package/{examples → sandbox}/test-long-chat.js +2 -2
- package/{examples → sandbox}/test-session-chat.js +2 -2
- package/{examples → sandbox}/test-web-plugin.js +1 -1
- package/{examples → sandbox}/test-weixin-feishu.js +2 -2
- package/src/agent/base.js +56 -0
- package/src/{core/agent-chat.js → agent/chat.js} +11 -11
- package/src/{core/coordinator-manager.js → agent/coordinator.js} +3 -3
- package/src/agent/index.js +111 -0
- package/src/agent/main.js +337 -0
- package/src/agent/prompt.js +78 -0
- package/src/agent/sub.js +198 -0
- package/src/agent/worker.js +104 -0
- package/{cli/bin/foliko.js → src/cli/bin.js} +1 -1
- package/{cli/src → src/cli}/commands/chat.js +25 -21
- package/{cli/src → src/cli}/index.js +1 -0
- package/{cli/src → src/cli}/ui/chat-ui-old.js +40 -178
- package/{cli/src → src/cli}/ui/chat-ui.js +3 -3
- package/{cli/src → src/cli}/ui/components/footer-bar.js +1 -1
- package/src/common/errors.js +402 -0
- package/src/{utils → common}/logger.js +33 -0
- package/src/{utils/chat-queue.js → common/queue.js} +2 -2
- package/src/config/plugin-config.js +50 -0
- package/src/context/agent.js +32 -0
- package/src/context/compaction-prompts.js +170 -0
- package/src/context/compaction-utils.js +191 -0
- package/src/context/compressor.js +413 -0
- package/src/context/index.js +9 -0
- package/src/{core/context-manager.js → context/manager.js} +1 -1
- package/src/context/request.js +50 -0
- package/src/context/session.js +33 -0
- package/src/context/storage.js +30 -0
- package/src/executors/mcp-client.js +153 -0
- package/src/executors/mcp-desc.js +236 -0
- package/src/executors/mcp-executor.js +91 -956
- package/src/{core → framework}/command-registry.js +1 -1
- package/src/framework/framework.js +300 -0
- package/src/framework/index.js +18 -0
- package/src/framework/lifecycle.js +203 -0
- package/src/framework/loader.js +78 -0
- package/src/framework/registry.js +86 -0
- package/src/{core/ui-extension-context.js → framework/ui-extension.js} +1 -1
- package/src/index.js +130 -15
- package/src/llm/index.js +26 -0
- package/src/llm/provider.js +212 -0
- package/src/llm/registry.js +11 -0
- package/src/{core/token-counter.js → llm/tokens.js} +4 -37
- package/src/{core/plugin-base.js → plugin/base.js} +10 -136
- package/src/plugin/index.js +14 -0
- package/src/plugin/loader.js +101 -0
- package/src/plugin/manager.js +484 -0
- package/src/{core → session}/branch-summary-auto.js +2 -2
- package/src/{core/chat-session.js → session/chat.js} +2 -2
- package/src/session/index.js +7 -0
- package/src/{core/session-manager.js → session/session.js} +2 -2
- package/src/session/ttl.js +92 -0
- package/src/{core/jsonl-storage.js → storage/jsonl.js} +1 -1
- package/src/tool/executor.js +85 -0
- package/src/tool/index.js +15 -0
- package/src/tool/registry.js +143 -0
- package/src/{core/tool-router.js → tool/router.js} +17 -124
- package/src/tool/schema.js +108 -0
- package/src/utils/data-splitter.js +1 -1
- package/src/utils/download.js +1 -1
- package/src/utils/index.js +6 -6
- package/src/utils/message-validator.js +1 -1
- package/tests/core/context-storage.test.js +46 -0
- package/tests/core/llm.test.js +54 -0
- package/tests/core/plugin.test.js +42 -0
- package/tests/core/tool.test.js +60 -0
- package/tests/setup.js +10 -0
- package/tests/smoke.test.js +58 -0
- package/vitest.config.js +9 -0
- package/cli/src/daemon.js +0 -149
- package/docs/CONTEXT_DESIGN.md +0 -1596
- package/docs/ai-sdk-optimization.md +0 -655
- package/docs/features.md +0 -120
- package/docs/qq-bot.md +0 -976
- package/docs/quick-reference.md +0 -160
- package/docs/user-manual.md +0 -1391
- package/images/geometric_shapes.jpg +0 -0
- package/images/sunset_mountain_lake.jpg +0 -0
- package/skills/poster-guide/SKILL.md +0 -792
- package/src/capabilities/index.js +0 -11
- package/src/core/agent.js +0 -808
- package/src/core/context-compressor.js +0 -959
- package/src/core/enhanced-context-compressor.js +0 -210
- package/src/core/framework.js +0 -1422
- package/src/core/index.js +0 -30
- package/src/core/plugin-manager.js +0 -961
- package/src/core/provider-registry.js +0 -159
- package/src/core/provider.js +0 -156
- package/src/core/request-context.js +0 -98
- package/src/core/subagent.js +0 -442
- package/src/core/system-prompt-builder.js +0 -120
- package/src/core/tool-executor.js +0 -202
- package/src/core/tool-registry.js +0 -517
- package/src/core/worker-agent.js +0 -192
- package/src/executors/executor-base.js +0 -58
- package/src/utils/error-boundary.js +0 -363
- package/src/utils/error.js +0 -374
- package/system.md +0 -1645
- package/website_v2/README.md +0 -57
- package/website_v2/SPEC.md +0 -1
- package/website_v2/docs/api.html +0 -128
- package/website_v2/docs/configuration.html +0 -147
- package/website_v2/docs/plugin-development.html +0 -129
- package/website_v2/docs/project-structure.html +0 -89
- package/website_v2/docs/skill-development.html +0 -85
- package/website_v2/index.html +0 -489
- package/website_v2/scripts/main.js +0 -93
- package/website_v2/styles/animations.css +0 -8
- package/website_v2/styles/docs.css +0 -83
- package/website_v2/styles/main.css +0 -417
- package/xhs_auth.json +0 -268
- package//346/265/267/346/212/245/346/217/222/344/273/266.md +0 -621
- /package/plugins/{ambient-agent → ambient}/constants.js +0 -0
- /package/plugins/{email → messaging/email}/constants.js +0 -0
- /package/plugins/{email → messaging/email}/handlers.js +0 -0
- /package/plugins/{email → messaging/email}/monitor.js +0 -0
- /package/plugins/{email → messaging/email}/parser.js +0 -0
- /package/plugins/{email → messaging/email}/reply.js +0 -0
- /package/plugins/{email → messaging/email}/utils.js +0 -0
- /package/{examples → sandbox}/test-chat.js +0 -0
- /package/{examples → sandbox}/test-mcp.js +0 -0
- /package/{examples → sandbox}/test-reload.js +0 -0
- /package/{examples → sandbox}/test-telegram.js +0 -0
- /package/{examples → sandbox}/test-tg-bot.js +0 -0
- /package/{examples → sandbox}/test-tg-simple.js +0 -0
- /package/{examples → sandbox}/test-tg.js +0 -0
- /package/{examples → sandbox}/test-think.js +0 -0
- /package/src/{core/sub-agent-config.js → agent/sub-config.js} +0 -0
- /package/{cli/src → src/cli}/commands/daemon.js +0 -0
- /package/{cli/src → src/cli}/commands/list.js +0 -0
- /package/{cli/src → src/cli}/commands/plugin.js +0 -0
- /package/{cli/src → src/cli}/ui/components/agent-mention-provider.js +0 -0
- /package/{cli/src → src/cli}/ui/components/chained-autocomplete-provider.js +0 -0
- /package/{cli/src → src/cli}/ui/components/message-bubble.js +0 -0
- /package/{cli/src → src/cli}/ui/components/status-bar.js +0 -0
- /package/{cli/src → src/cli}/utils/ansi.js +0 -0
- /package/{cli/src → src/cli}/utils/config.js +0 -0
- /package/{cli/src → src/cli}/utils/markdown.js +0 -0
- /package/{cli/src → src/cli}/utils/plugin-config.js +0 -0
- /package/{cli/src → src/cli}/utils/render-diff.js +0 -0
- /package/src/{utils/circuit-breaker.js → common/circuit.js} +0 -0
- /package/src/{core → common}/constants.js +0 -0
- /package/src/{utils/edit-diff.js → common/diff.js} +0 -0
- /package/src/{utils/event-emitter.js → common/events.js} +0 -0
- /package/src/{utils → common}/id.js +0 -0
- /package/src/{utils → common}/retry.js +0 -0
- /package/src/{core/notification-manager.js → notification/manager.js} +0 -0
- /package/src/{core/session-entry.js → session/entry.js} +0 -0
- /package/src/{core/storage-manager.js → storage/manager.js} +0 -0
package/src/index.js
CHANGED
|
@@ -1,25 +1,140 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Foliko Framework
|
|
3
3
|
* 简约的插件化 Agent 框架
|
|
4
|
+
*
|
|
5
|
+
* Public API Facade
|
|
6
|
+
* 提供简洁的入口函数,覆盖 80% 使用场景
|
|
4
7
|
*/
|
|
5
8
|
|
|
6
|
-
|
|
9
|
+
'use strict';
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
// 加载工作目录 .env 配置(优先于系统环境变量)
|
|
12
|
+
require('dotenv').config({ override: true });
|
|
9
13
|
|
|
10
|
-
|
|
14
|
+
// ============================================================
|
|
15
|
+
// Framework
|
|
16
|
+
// ============================================================
|
|
17
|
+
const { Framework } = require('./framework/framework');
|
|
18
|
+
const { bootstrap, ready, destroy } = require('./framework/lifecycle');
|
|
11
19
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
Plugin,
|
|
17
|
-
PluginManager,
|
|
18
|
-
ToolRegistry,
|
|
19
|
-
EventEmitter,
|
|
20
|
+
// ============================================================
|
|
21
|
+
// Agent
|
|
22
|
+
// ============================================================
|
|
23
|
+
const { createAgent, MainAgent, SubAgent, WorkerAgent } = require('./agent');
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
// ============================================================
|
|
26
|
+
// Plugin
|
|
27
|
+
// ============================================================
|
|
28
|
+
const { Plugin, PluginManager } = require('./plugin');
|
|
29
|
+
|
|
30
|
+
// ============================================================
|
|
31
|
+
// Tool
|
|
32
|
+
// ============================================================
|
|
33
|
+
const { ToolRegistry, ToolExecutor } = require('./tool');
|
|
34
|
+
|
|
35
|
+
// ============================================================
|
|
36
|
+
// LLM
|
|
37
|
+
// ============================================================
|
|
38
|
+
const llm = require('./llm');
|
|
39
|
+
|
|
40
|
+
// ============================================================
|
|
41
|
+
// Common
|
|
42
|
+
// ============================================================
|
|
43
|
+
const { EventEmitter } = require('./common/events');
|
|
44
|
+
const { Logger } = require('./common/logger');
|
|
45
|
+
|
|
46
|
+
// ============================================================
|
|
47
|
+
// Public API
|
|
48
|
+
// ============================================================
|
|
49
|
+
|
|
50
|
+
// --- 入门三件套 ---
|
|
51
|
+
|
|
52
|
+
/** 加载 .foliko 配置并启动框架 */
|
|
53
|
+
exports.bootstrap = async (config = {}) => {
|
|
54
|
+
const fw = new Framework(config);
|
|
55
|
+
await bootstrap(fw, config);
|
|
56
|
+
return fw;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** 构造一个未启动的 Framework(支持链式 .use()) */
|
|
60
|
+
exports.createApp = (config = {}) => {
|
|
61
|
+
const fw = new Framework(config);
|
|
62
|
+
fw.use = (plugin) => { fw.loadPlugin(plugin); return fw; };
|
|
63
|
+
return fw;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** 显式构造 Agent */
|
|
67
|
+
exports.createAgent = createAgent;
|
|
68
|
+
|
|
69
|
+
// --- 框架生命周期 ---
|
|
70
|
+
|
|
71
|
+
exports.ready = ready;
|
|
72
|
+
exports.destroy = destroy;
|
|
73
|
+
|
|
74
|
+
// --- 上下文管理 ---
|
|
75
|
+
|
|
76
|
+
exports.runInSession = (framework, sessionId, options, fn) =>
|
|
77
|
+
framework.runInSession(sessionId, options, fn);
|
|
78
|
+
|
|
79
|
+
exports.runInRequest = (framework, context, fn) =>
|
|
80
|
+
framework.runWithRequestContext(context, fn);
|
|
81
|
+
|
|
82
|
+
// --- 快捷工具注册 ---
|
|
83
|
+
|
|
84
|
+
exports.registerTool = (framework, toolDef) => {
|
|
85
|
+
framework.registerTool(toolDef);
|
|
86
|
+
return framework;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
exports.use = (app, plugin) => {
|
|
90
|
+
if (app?.loadPlugin) return app.loadPlugin(plugin);
|
|
91
|
+
throw new Error('use() requires a Framework instance as first argument');
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// --- 快捷插件工厂 ---
|
|
95
|
+
|
|
96
|
+
exports.withAI = (config) => {
|
|
97
|
+
const AIPlugin = require('../plugins/core/ai');
|
|
98
|
+
return new AIPlugin(config);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
exports.withShell = (config = {}) => {
|
|
102
|
+
const ShellPlugin = require('../plugins/executors/shell');
|
|
103
|
+
return new ShellPlugin(config);
|
|
25
104
|
};
|
|
105
|
+
|
|
106
|
+
exports.withPython = (config = {}) => {
|
|
107
|
+
const PythonPlugin = require('../plugins/executors/python');
|
|
108
|
+
return new PythonPlugin(config);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// --- 类型/常量 ---
|
|
112
|
+
|
|
113
|
+
exports.z = require('zod');
|
|
114
|
+
exports.LLM = llm;
|
|
115
|
+
|
|
116
|
+
// --- 框架核心类(用于扩展) ---
|
|
117
|
+
|
|
118
|
+
exports.Framework = Framework;
|
|
119
|
+
exports.MainAgent = MainAgent;
|
|
120
|
+
exports.SubAgent = SubAgent;
|
|
121
|
+
exports.WorkerAgent = WorkerAgent;
|
|
122
|
+
exports.ToolRegistry = ToolRegistry;
|
|
123
|
+
exports.ToolExecutor = ToolExecutor;
|
|
124
|
+
exports.Plugin = Plugin;
|
|
125
|
+
exports.PluginManager = PluginManager;
|
|
126
|
+
exports.EventEmitter = EventEmitter;
|
|
127
|
+
exports.Logger = Logger;
|
|
128
|
+
|
|
129
|
+
// --- 向后兼容(旧路径转发) ---
|
|
130
|
+
|
|
131
|
+
const { SkillManagerPlugin, Skill, SkillMetadata } = require('../plugins/core/skill-manager');
|
|
132
|
+
const { WorkflowPlugin, WorkflowEngine, StepExecutor } = require('../plugins/core/workflow');
|
|
133
|
+
exports.SkillManagerPlugin = SkillManagerPlugin;
|
|
134
|
+
exports.Skill = Skill;
|
|
135
|
+
exports.SkillMetadata = SkillMetadata;
|
|
136
|
+
exports.WorkflowPlugin = WorkflowPlugin;
|
|
137
|
+
exports.WorkflowEngine = WorkflowEngine;
|
|
138
|
+
exports.StepExecutor = StepExecutor;
|
|
139
|
+
exports.Agent = MainAgent;
|
|
140
|
+
exports.MCPExecutorPlugin = require('./executors/mcp-executor');
|
package/src/llm/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
createAI, createModel, getAvailableProviders,
|
|
5
|
+
DEFAULT_PROVIDERS, isThinkingModel,
|
|
6
|
+
LLMProvider, ProviderRegistry, getProviderRegistry,
|
|
7
|
+
} = require('./provider');
|
|
8
|
+
const { TokenCounter, encode, estimateTokens, estimateContextTokens, shouldCompact } = require('./tokens');
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
// Provider
|
|
12
|
+
LLMProvider,
|
|
13
|
+
ProviderRegistry,
|
|
14
|
+
getProviderRegistry,
|
|
15
|
+
createAI,
|
|
16
|
+
createModel,
|
|
17
|
+
getAvailableProviders,
|
|
18
|
+
DEFAULT_PROVIDERS,
|
|
19
|
+
isThinkingModel,
|
|
20
|
+
// Token
|
|
21
|
+
TokenCounter,
|
|
22
|
+
encode,
|
|
23
|
+
estimateTokens,
|
|
24
|
+
estimateContextTokens,
|
|
25
|
+
shouldCompact,
|
|
26
|
+
};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* LLM Provider 系统
|
|
5
|
+
* 统一管理 Provider 注册、模型创建、thinking mode 判断
|
|
6
|
+
* 合并自 provider.js + registry.js
|
|
7
|
+
* 目标 ~300 行
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { createOpenAICompatible } = require('@ai-sdk/openai-compatible');
|
|
11
|
+
|
|
12
|
+
// ==================== Provider 默认配置 ====================
|
|
13
|
+
|
|
14
|
+
const DEFAULT_PROVIDERS = {
|
|
15
|
+
openai: {
|
|
16
|
+
name: 'OpenAI',
|
|
17
|
+
baseURL: 'https://api.openai.com/v1',
|
|
18
|
+
defaultModel: 'gpt-4o',
|
|
19
|
+
},
|
|
20
|
+
ollama: {
|
|
21
|
+
name: 'Ollama',
|
|
22
|
+
baseURL: 'http://localhost:11434/v1',
|
|
23
|
+
defaultModel: 'llama3',
|
|
24
|
+
},
|
|
25
|
+
lmstudio: {
|
|
26
|
+
name: 'LM Studio',
|
|
27
|
+
baseURL: 'http://localhost:1234/v1',
|
|
28
|
+
defaultModel: 'local-model',
|
|
29
|
+
},
|
|
30
|
+
deepseek: {
|
|
31
|
+
name: 'DeepSeek',
|
|
32
|
+
baseURL: 'https://api.deepseek.com/v1',
|
|
33
|
+
defaultModel: 'deepseek-chat',
|
|
34
|
+
},
|
|
35
|
+
anthropic: {
|
|
36
|
+
name: 'Anthropic',
|
|
37
|
+
baseURL: 'https://api.anthropic.com/v1',
|
|
38
|
+
defaultModel: 'claude-sonnet-4-20250514',
|
|
39
|
+
},
|
|
40
|
+
minimax: {
|
|
41
|
+
name: 'MiniMax',
|
|
42
|
+
baseURL: 'https://api.minimaxi.com/v1',
|
|
43
|
+
defaultModel: 'MiniMax-M2.7',
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const THINKING_MODELS = [
|
|
48
|
+
'deepseek-v4-pro',
|
|
49
|
+
'deepseek-v4-flash',
|
|
50
|
+
'deepseek-reasoner',
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
function isThinkingModel(model) {
|
|
54
|
+
if (!model) return false;
|
|
55
|
+
const lower = model.toLowerCase();
|
|
56
|
+
return THINKING_MODELS.some(tm => lower.includes(tm) || lower === tm);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ==================== LLMProvider ====================
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* LLMProvider — Provider 配置封装
|
|
63
|
+
* 将 provider/model/apiKey/baseURL 配置封装为对象,提供 createModel() 工厂
|
|
64
|
+
*/
|
|
65
|
+
class LLMProvider {
|
|
66
|
+
constructor(config = {}) {
|
|
67
|
+
this.provider = config.provider || 'deepseek';
|
|
68
|
+
this.model = config.model || 'deepseek-chat';
|
|
69
|
+
this.apiKey = config.apiKey;
|
|
70
|
+
this.baseURL = config.baseURL;
|
|
71
|
+
this.options = { ...config.options };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 创建 AI SDK model 实例 */
|
|
75
|
+
createModel() {
|
|
76
|
+
return _createAIClient(this.provider, this.model, this.apiKey, this.baseURL)(this.model);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** 当前模型是否 thinking mode */
|
|
80
|
+
isThinking() { return isThinkingModel(this.model); }
|
|
81
|
+
|
|
82
|
+
getConfig() {
|
|
83
|
+
return {
|
|
84
|
+
provider: this.provider,
|
|
85
|
+
model: this.model,
|
|
86
|
+
apiKey: this.apiKey,
|
|
87
|
+
baseURL: this.baseURL,
|
|
88
|
+
options: { ...this.options },
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ==================== ProviderRegistry ====================
|
|
94
|
+
|
|
95
|
+
class ProviderRegistry {
|
|
96
|
+
constructor() {
|
|
97
|
+
this._providers = new Map();
|
|
98
|
+
this._models = new Map();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
registerProvider(name, config) {
|
|
102
|
+
this._providers.set(name, {
|
|
103
|
+
name: config.name || name,
|
|
104
|
+
baseURL: config.baseURL,
|
|
105
|
+
apiKey: config.apiKey || '',
|
|
106
|
+
defaultModel: config.defaultModel || '',
|
|
107
|
+
headers: config.headers || {},
|
|
108
|
+
});
|
|
109
|
+
if (config.defaultModel) this.registerModel(config.defaultModel, name);
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
registerModel(modelId, provider, options = {}) {
|
|
114
|
+
this._models.set(modelId, { provider, ...options });
|
|
115
|
+
return this;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
getProvider(name) { return this._providers.get(name); }
|
|
119
|
+
getModel(modelId) { return this._models.get(modelId); }
|
|
120
|
+
getProviderNames() { return Array.from(this._providers.keys()); }
|
|
121
|
+
hasProvider(name) { return this._providers.has(name); }
|
|
122
|
+
|
|
123
|
+
/** 创建 AI SDK model 实例(自动 fallback 到注册的默认值) */
|
|
124
|
+
createClient(providerName, model, apiKey, baseURL) {
|
|
125
|
+
const provider = this._providers.get(providerName);
|
|
126
|
+
if (!provider) throw new Error(`Provider '${providerName}' not registered.`);
|
|
127
|
+
const finalApiKey = apiKey || provider.apiKey;
|
|
128
|
+
const finalBaseURL = baseURL || provider.baseURL;
|
|
129
|
+
const finalModel = model || provider.defaultModel;
|
|
130
|
+
if (!finalApiKey) throw new Error(`No API key for provider '${providerName}'`);
|
|
131
|
+
return _createAIClient(providerName, finalModel, finalApiKey, finalBaseURL)(finalModel);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
unregisterProvider(name) {
|
|
135
|
+
this._providers.delete(name);
|
|
136
|
+
for (const [mid, mc] of this._models) {
|
|
137
|
+
if (mc.provider === name) this._models.delete(mid);
|
|
138
|
+
}
|
|
139
|
+
return this;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ==================== 模块级单例 ====================
|
|
144
|
+
|
|
145
|
+
const _registry = new ProviderRegistry();
|
|
146
|
+
|
|
147
|
+
function getProviderRegistry() { return _registry; }
|
|
148
|
+
|
|
149
|
+
function _registerDefaultProviders() {
|
|
150
|
+
for (const [key, cfg] of Object.entries(DEFAULT_PROVIDERS)) {
|
|
151
|
+
_registry.registerProvider(key, {
|
|
152
|
+
name: cfg.name, baseURL: cfg.baseURL, defaultModel: cfg.defaultModel,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
_registerDefaultProviders();
|
|
157
|
+
|
|
158
|
+
// ==================== 内部 AI SDK 客户端工厂 ====================
|
|
159
|
+
|
|
160
|
+
function _createAIClient(provider, model, apiKey, baseURL) {
|
|
161
|
+
const providerName = (provider || 'deepseek').toLowerCase();
|
|
162
|
+
const pcfg = DEFAULT_PROVIDERS[providerName];
|
|
163
|
+
|
|
164
|
+
if (pcfg) {
|
|
165
|
+
const streamOptions = { includeUsage: true };
|
|
166
|
+
if (providerName === 'deepseek') streamOptions.includeReasoningContent = true;
|
|
167
|
+
return createOpenAICompatible({
|
|
168
|
+
name: pcfg.name,
|
|
169
|
+
baseURL: baseURL || pcfg.baseURL,
|
|
170
|
+
apiKey: apiKey || 'dummy-key',
|
|
171
|
+
headers: provider === 'anthropic' ? { 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' } : undefined,
|
|
172
|
+
models: { default: { id: model || 'deepseek-chat', streamOptions } },
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return createOpenAICompatible({
|
|
177
|
+
name: providerName || 'Custom',
|
|
178
|
+
baseURL,
|
|
179
|
+
apiKey: apiKey || 'dummy-key',
|
|
180
|
+
models: { default: { id: model || 'gpt-4' } },
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ==================== 向后兼容 API ====================
|
|
185
|
+
|
|
186
|
+
function createAI(config) {
|
|
187
|
+
return _createAIClient(config.provider, config.model, config.apiKey, config.baseURL);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function createModel(model, client) { return client(model); }
|
|
191
|
+
|
|
192
|
+
function getAvailableProviders() {
|
|
193
|
+
return Object.entries(DEFAULT_PROVIDERS).map(([k, v]) => ({ id: k, name: v.name, baseURL: v.baseURL }));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function registerProvider(name, config) { _registry.registerProvider(name, config); }
|
|
197
|
+
|
|
198
|
+
module.exports = {
|
|
199
|
+
// Class API
|
|
200
|
+
LLMProvider,
|
|
201
|
+
ProviderRegistry,
|
|
202
|
+
getProviderRegistry,
|
|
203
|
+
// 常量 & 工具函数
|
|
204
|
+
DEFAULT_PROVIDERS,
|
|
205
|
+
THINKING_MODELS,
|
|
206
|
+
isThinkingModel,
|
|
207
|
+
// 向后兼容
|
|
208
|
+
createAI,
|
|
209
|
+
createModel,
|
|
210
|
+
getAvailableProviders,
|
|
211
|
+
registerProvider,
|
|
212
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ProviderRegistry — 向后兼容转发
|
|
5
|
+
* 实际实现在 provider.js
|
|
6
|
+
* @deprecated 直接引用 src/llm/provider
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { ProviderRegistry, getProviderRegistry, registerProvider } = require('./provider');
|
|
10
|
+
|
|
11
|
+
module.exports = { ProviderRegistry, getProviderRegistry, registerProvider };
|
|
@@ -1,33 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* TokenCounter - Token 计算工具
|
|
3
5
|
* Ported from pi's compaction/compaction.ts with enhancements
|
|
4
6
|
*/
|
|
5
7
|
|
|
6
|
-
/**
|
|
7
|
-
* 简单的中英文混合 tokenizer
|
|
8
|
-
* 粗略估计:中文每个字符 2 字节,英文每个单词约 1.5 字节
|
|
9
|
-
* @param {string} text - 文本
|
|
10
|
-
* @param {number} bytesPerToken - 每 token 字节数,默认 4
|
|
11
|
-
* @returns {number} token 数量
|
|
12
|
-
*/
|
|
13
8
|
function encode(text, bytesPerToken = 4) {
|
|
14
9
|
if (!text) return 0;
|
|
15
10
|
const bytes = Buffer.byteLength(String(text), 'utf8');
|
|
16
11
|
return Math.ceil(bytes / bytesPerToken);
|
|
17
12
|
}
|
|
18
13
|
|
|
19
|
-
/**
|
|
20
|
-
* 计算 JSON 字符串的 token 数量
|
|
21
|
-
*/
|
|
22
14
|
function encodeForJSON(text) {
|
|
23
15
|
if (!text) return 0;
|
|
24
16
|
const encoded = encode(text);
|
|
25
17
|
return encoded + Math.ceil(Buffer.byteLength(JSON.stringify(text), 'utf8') / 100);
|
|
26
18
|
}
|
|
27
19
|
|
|
28
|
-
/**
|
|
29
|
-
* Safe JSON stringify that handles unserializable values
|
|
30
|
-
*/
|
|
31
20
|
function safeJsonStringify(value) {
|
|
32
21
|
try {
|
|
33
22
|
return JSON.stringify(value) ?? 'undefined';
|
|
@@ -36,10 +25,6 @@ function safeJsonStringify(value) {
|
|
|
36
25
|
}
|
|
37
26
|
}
|
|
38
27
|
|
|
39
|
-
/**
|
|
40
|
-
* Estimate token count for one message using a conservative character heuristic
|
|
41
|
-
* Ported from pi
|
|
42
|
-
*/
|
|
43
28
|
function estimateTokens(message) {
|
|
44
29
|
let chars = 0;
|
|
45
30
|
|
|
@@ -100,16 +85,10 @@ function estimateTokens(message) {
|
|
|
100
85
|
return 0;
|
|
101
86
|
}
|
|
102
87
|
|
|
103
|
-
/**
|
|
104
|
-
* Calculate total context tokens from provider usage
|
|
105
|
-
*/
|
|
106
88
|
function calculateContextTokens(usage) {
|
|
107
89
|
return usage.totalTokens || (usage.input || 0) + (usage.output || 0) + (usage.cacheRead || 0) + (usage.cacheWrite || 0);
|
|
108
90
|
}
|
|
109
91
|
|
|
110
|
-
/**
|
|
111
|
-
* Get assistant usage from message
|
|
112
|
-
*/
|
|
113
92
|
function getAssistantUsage(msg) {
|
|
114
93
|
if (msg.role === 'assistant' && msg.usage) {
|
|
115
94
|
if (msg.stopReason !== 'aborted' && msg.stopReason !== 'error' && msg.usage) {
|
|
@@ -119,9 +98,6 @@ function getAssistantUsage(msg) {
|
|
|
119
98
|
return undefined;
|
|
120
99
|
}
|
|
121
100
|
|
|
122
|
-
/**
|
|
123
|
-
* Return usage from the last successful assistant message in session entries
|
|
124
|
-
*/
|
|
125
101
|
function getLastAssistantUsage(entries) {
|
|
126
102
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
127
103
|
const entry = entries[i];
|
|
@@ -133,9 +109,6 @@ function getLastAssistantUsage(entries) {
|
|
|
133
109
|
return undefined;
|
|
134
110
|
}
|
|
135
111
|
|
|
136
|
-
/**
|
|
137
|
-
* Get last assistant usage info from messages
|
|
138
|
-
*/
|
|
139
112
|
function getLastAssistantUsageInfo(messages) {
|
|
140
113
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
141
114
|
const usage = getAssistantUsage(messages[i]);
|
|
@@ -144,9 +117,6 @@ function getLastAssistantUsageInfo(messages) {
|
|
|
144
117
|
return undefined;
|
|
145
118
|
}
|
|
146
119
|
|
|
147
|
-
/**
|
|
148
|
-
* Estimate context tokens for messages using provider usage when available
|
|
149
|
-
*/
|
|
150
120
|
function estimateContextTokens(messages) {
|
|
151
121
|
const usageInfo = getLastAssistantUsageInfo(messages);
|
|
152
122
|
|
|
@@ -177,9 +147,6 @@ function estimateContextTokens(messages) {
|
|
|
177
147
|
};
|
|
178
148
|
}
|
|
179
149
|
|
|
180
|
-
/**
|
|
181
|
-
* Return whether context usage exceeds the configured compaction threshold
|
|
182
|
-
*/
|
|
183
150
|
function shouldCompact(contextTokens, contextWindow, settings) {
|
|
184
151
|
if (!settings.enabled) return false;
|
|
185
152
|
return contextTokens > contextWindow - settings.reserveTokens;
|
|
@@ -305,5 +272,5 @@ module.exports = {
|
|
|
305
272
|
getLastAssistantUsage,
|
|
306
273
|
estimateContextTokens,
|
|
307
274
|
shouldCompact,
|
|
308
|
-
safeJsonStringify
|
|
309
|
-
};
|
|
275
|
+
safeJsonStringify,
|
|
276
|
+
};
|