@next-open-ai/openbot 0.6.16 → 0.6.66

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.
Files changed (95) hide show
  1. package/README.md +4 -4
  2. package/apps/desktop/renderer/dist/assets/index-CxDZnMBH.css +10 -0
  3. package/apps/desktop/renderer/dist/assets/index-k47Qiokg.js +93 -0
  4. package/apps/desktop/renderer/dist/index.html +2 -2
  5. package/dist/cli/cli.js +136 -0
  6. package/dist/cli/extension-cmd.d.ts +15 -0
  7. package/dist/cli/extension-cmd.js +107 -0
  8. package/dist/core/agent/agent-dir.d.ts +6 -0
  9. package/dist/core/agent/agent-dir.js +8 -0
  10. package/dist/core/agent/agent-manager.d.ts +13 -0
  11. package/dist/core/agent/agent-manager.js +88 -7
  12. package/dist/core/agent/proxy/adapters/claude-code-adapter.d.ts +2 -0
  13. package/dist/core/agent/proxy/adapters/claude-code-adapter.js +186 -0
  14. package/dist/core/agent/proxy/adapters/local-adapter.js +3 -1
  15. package/dist/core/agent/proxy/adapters/openclawx-adapter.js +3 -3
  16. package/dist/core/agent/proxy/adapters/opencode-adapter.js +65 -29
  17. package/dist/core/agent/proxy/adapters/opencode-local-runner.js +9 -0
  18. package/dist/core/agent/proxy/index.js +2 -0
  19. package/dist/core/agent/token-usage-log-extension.d.ts +14 -0
  20. package/dist/core/agent/token-usage-log-extension.js +61 -0
  21. package/dist/core/config/agent-reload-pending.js +3 -2
  22. package/dist/core/config/desktop-config.d.ts +29 -6
  23. package/dist/core/config/desktop-config.js +188 -27
  24. package/dist/core/config/provider-support-default.js +27 -0
  25. package/dist/core/extensions/index.d.ts +1 -0
  26. package/dist/core/extensions/index.js +1 -0
  27. package/dist/core/extensions/load.d.ts +11 -0
  28. package/dist/core/extensions/load.js +101 -0
  29. package/dist/core/local-llm-server/download-model.d.ts +16 -0
  30. package/dist/core/local-llm-server/download-model.js +37 -0
  31. package/dist/core/local-llm-server/index.d.ts +32 -0
  32. package/dist/core/local-llm-server/index.js +152 -0
  33. package/dist/core/local-llm-server/llm-context.d.ts +66 -0
  34. package/dist/core/local-llm-server/llm-context.js +270 -0
  35. package/dist/core/local-llm-server/model-resolve.d.ts +27 -0
  36. package/dist/core/local-llm-server/model-resolve.js +90 -0
  37. package/dist/core/local-llm-server/server.d.ts +1 -0
  38. package/dist/core/local-llm-server/server.js +234 -0
  39. package/dist/core/local-llm-server/start-from-config.d.ts +5 -0
  40. package/dist/core/local-llm-server/start-from-config.js +50 -0
  41. package/dist/core/mcp/adapter.d.ts +4 -2
  42. package/dist/core/mcp/adapter.js +10 -4
  43. package/dist/core/mcp/index.d.ts +2 -0
  44. package/dist/core/mcp/index.js +1 -0
  45. package/dist/core/mcp/operator.d.ts +11 -0
  46. package/dist/core/mcp/operator.js +41 -7
  47. package/dist/core/mcp/transport/stdio.d.ts +6 -0
  48. package/dist/core/mcp/transport/stdio.js +125 -28
  49. package/dist/core/memory/local-embedding-llama.js +8 -6
  50. package/dist/core/memory/local-embedding.d.ts +4 -3
  51. package/dist/core/memory/local-embedding.js +43 -3
  52. package/dist/core/tools/index.d.ts +1 -0
  53. package/dist/core/tools/index.js +1 -0
  54. package/dist/core/tools/truncate-result.d.ts +14 -0
  55. package/dist/core/tools/truncate-result.js +27 -0
  56. package/dist/core/tools/web-search/create-web-search-tool.d.ts +17 -0
  57. package/dist/core/tools/web-search/create-web-search-tool.js +87 -0
  58. package/dist/core/tools/web-search/index.d.ts +4 -0
  59. package/dist/core/tools/web-search/index.js +2 -0
  60. package/dist/core/tools/web-search/providers/brave.d.ts +2 -0
  61. package/dist/core/tools/web-search/providers/brave.js +87 -0
  62. package/dist/core/tools/web-search/providers/duck-duck-scrape.d.ts +2 -0
  63. package/dist/core/tools/web-search/providers/duck-duck-scrape.js +47 -0
  64. package/dist/core/tools/web-search/providers/index.d.ts +5 -0
  65. package/dist/core/tools/web-search/providers/index.js +13 -0
  66. package/dist/core/tools/web-search/types.d.ts +35 -0
  67. package/dist/core/tools/web-search/types.js +4 -0
  68. package/dist/gateway/methods/agent-chat.js +110 -42
  69. package/dist/gateway/methods/run-scheduled-task.js +2 -0
  70. package/dist/gateway/server.js +60 -13
  71. package/dist/server/agent-config/agent-config.controller.d.ts +9 -1
  72. package/dist/server/agent-config/agent-config.controller.js +11 -0
  73. package/dist/server/agent-config/agent-config.service.d.ts +29 -5
  74. package/dist/server/agent-config/agent-config.service.js +41 -1
  75. package/dist/server/agents/agents.gateway.js +1 -1
  76. package/dist/server/bootstrap.d.ts +1 -0
  77. package/dist/server/bootstrap.js +19 -2
  78. package/dist/server/config/config.controller.d.ts +107 -4
  79. package/dist/server/config/config.controller.js +185 -3
  80. package/dist/server/config/config.module.js +3 -2
  81. package/dist/server/config/config.service.d.ts +18 -1
  82. package/dist/server/config/config.service.js +68 -9
  83. package/dist/server/config/local-models.service.d.ts +67 -0
  84. package/dist/server/config/local-models.service.js +242 -0
  85. package/package.json +3 -1
  86. package/presets/preset-agents.json +125 -91
  87. package/presets/preset-config.json +24 -6
  88. package/presets/preset-providers.json +7 -0
  89. package/presets/recommended-local-models.json +36 -0
  90. package/presets/workspaces/download-assistant/skills/downloader/SKILL.md +2 -2
  91. package/presets/workspaces/office-automation/skills/rpa-helper/SKILL.md +9 -0
  92. package/presets/workspaces/self-media-bot/skills/self-media-tools/SKILL.md +9 -0
  93. package/skills/url-bookmark/SKILL.md +12 -12
  94. package/apps/desktop/renderer/dist/assets/index-BxqMW-uy.css +0 -10
  95. package/apps/desktop/renderer/dist/assets/index-DJs-wX3R.js +0 -89
@@ -11,8 +11,8 @@
11
11
  <link
12
12
  href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Roboto+Mono:wght@400;500&display=swap"
13
13
  rel="stylesheet">
14
- <script type="module" crossorigin src="/assets/index-DJs-wX3R.js"></script>
15
- <link rel="stylesheet" crossorigin href="/assets/index-BxqMW-uy.css">
14
+ <script type="module" crossorigin src="/assets/index-k47Qiokg.js"></script>
15
+ <link rel="stylesheet" crossorigin href="/assets/index-CxDZnMBH.css">
16
16
  </head>
17
17
 
18
18
  <body>
package/dist/cli/cli.js CHANGED
@@ -6,7 +6,11 @@ import { Command } from "commander";
6
6
  import { getOpenbotAgentDir } from "../core/agent/agent-dir.js";
7
7
  import { run } from "../core/agent/run.js";
8
8
  import { loadDesktopAgentConfig, getBoundAgentIdForCli, setProviderApiKey, setDefaultModel, getDesktopConfigList, syncDesktopConfigToModelsJson, ensureDesktopConfigInitialized, } from "../core/config/desktop-config.js";
9
+ import { downloadModel, DEFAULT_LLM_MODEL_URI, } from "../core/local-llm-server/download-model.js";
10
+ import { startLocalLlmServer, stopLocalLlmServer, } from "../core/local-llm-server/index.js";
11
+ import { LOCAL_LLM_CACHE_DIR, isModelFileInCache, toModelPathForStart, } from "../core/local-llm-server/model-resolve.js";
9
12
  import { writeGatewayPid, removeGatewayPidFile, serviceInstall, serviceUninstall, serviceStop, } from "./service.js";
13
+ import { installExtension, listExtensions, uninstallExtension } from "./extension-cmd.js";
10
14
  const require = createRequire(import.meta.url);
11
15
  const PKG = require("../../package.json");
12
16
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -189,6 +193,138 @@ configCmd
189
193
  await syncDesktopConfigToModelsJson();
190
194
  console.log("[openbot] Synced desktop providers to agent models.json");
191
195
  });
196
+ // Extension: 在 ~/.openbot/plugins 下通过 npm 包安装/列出/卸载扩展,Server 运行时从该目录加载
197
+ const extensionCmd = program
198
+ .command("extension")
199
+ .description("Install, list, or uninstall extensions (npm packages in ~/.openbot/plugins)");
200
+ extensionCmd
201
+ .command("install <pkg>")
202
+ .description("Install an extension package (e.g. openbot extension install my-extension)")
203
+ .action((pkg) => {
204
+ installExtension(pkg);
205
+ });
206
+ extensionCmd
207
+ .command("list")
208
+ .description("List installed extensions")
209
+ .action(() => {
210
+ const list = listExtensions();
211
+ if (list.length === 0) {
212
+ console.log("No extensions installed. Run: openbot extension install <package>");
213
+ return;
214
+ }
215
+ console.log("Installed extensions:\n");
216
+ console.table(list.map((r) => ({ Package: r.name, Spec: r.spec })));
217
+ });
218
+ extensionCmd
219
+ .command("uninstall <pkg>")
220
+ .description("Uninstall an extension package")
221
+ .action((pkg) => {
222
+ uninstallExtension(pkg);
223
+ });
224
+ // 本地模型:下载与启动服务
225
+ const localCmd = program
226
+ .command("local")
227
+ .description("下载本地 GGUF 模型与启动本地 LLM 服务");
228
+ localCmd
229
+ .command("download")
230
+ .description("下载推荐模型到 ~/.openbot/.cached_models/,不指定模型时下载 Qwen 3.5 4B")
231
+ .argument("[modelUri]", "模型 URI(如 hf:unsloth/Qwen3.5-4B-GGUF/Qwen3.5-4B-Q5_K_M.gguf),不传则下载 Qwen 3.5 4B")
232
+ .option("--mirror", "使用国内镜像 hf-mirror.com 下载")
233
+ .action(async (modelUri, opts) => {
234
+ const uri = (modelUri || "").trim() || DEFAULT_LLM_MODEL_URI;
235
+ console.log(`[openbot] 下载模型: ${uri}`);
236
+ if (opts.mirror)
237
+ console.log("[openbot] 使用国内镜像 hf-mirror.com");
238
+ try {
239
+ const path = await downloadModel(uri, {
240
+ useMirror: opts.mirror,
241
+ onProgress: (p) => {
242
+ const percent = p.totalSize ? Math.round((p.downloadedSize / p.totalSize) * 100) : (p.percent ?? 0);
243
+ const mb = (p.downloadedSize / 1024 / 1024).toFixed(1);
244
+ const totalMb = p.totalSize ? (p.totalSize / 1024 / 1024).toFixed(1) : "?";
245
+ process.stderr.write(`\r[openbot] 下载中 ${percent}% (${mb} / ${totalMb} MB)`);
246
+ },
247
+ });
248
+ console.log(`\n[openbot] 已保存: ${path}`);
249
+ }
250
+ catch (err) {
251
+ const msg = err instanceof Error ? err.message : String(err);
252
+ console.error("\n[openbot] 下载失败:", msg);
253
+ process.exit(1);
254
+ }
255
+ });
256
+ localCmd
257
+ .command("start")
258
+ .description("启动本地 LLM 服务(至少指定 --llm 或 --embedding 之一)")
259
+ .option("--llm <uriOrFile>", "LLM 模型:hf: URI 或已下载文件名,不传则使用桌面缺省模型")
260
+ .option("--embedding <uriOrFile>", "Embedding 模型:hf: URI 或已下载文件名(可选)")
261
+ .option("--context-size <n>", "上下文长度(token 数),默认 32768 或环境变量 LOCAL_LLM_CONTEXT_MAX", (v) => parseInt(v, 10) || 32768)
262
+ .option("--port <port>", "服务端口", "11435")
263
+ .action(async (opts) => {
264
+ let llmPath;
265
+ let embPath;
266
+ if (opts.llm?.trim()) {
267
+ const llmArg = opts.llm.trim();
268
+ if (!llmArg.startsWith("hf:") && !isModelFileInCache(llmArg, LOCAL_LLM_CACHE_DIR)) {
269
+ console.error("[openbot] 模型未下载或路径不存在,请先执行: openbot local download [modelUri]");
270
+ process.exit(1);
271
+ }
272
+ llmPath = toModelPathForStart(llmArg, LOCAL_LLM_CACHE_DIR);
273
+ }
274
+ else {
275
+ const agentConfig = await loadDesktopAgentConfig("default");
276
+ const defaultModel = agentConfig?.model?.trim();
277
+ if (defaultModel) {
278
+ llmPath = toModelPathForStart(defaultModel, LOCAL_LLM_CACHE_DIR);
279
+ if (!isModelFileInCache(defaultModel, LOCAL_LLM_CACHE_DIR)) {
280
+ console.error("[openbot] 缺省模型未下载,请先执行: openbot local download");
281
+ process.exit(1);
282
+ }
283
+ }
284
+ }
285
+ if (opts.embedding?.trim()) {
286
+ const embArg = opts.embedding.trim();
287
+ if (!embArg.startsWith("hf:") && !isModelFileInCache(embArg, LOCAL_LLM_CACHE_DIR)) {
288
+ console.error("[openbot] Embedding 模型未下载或路径不存在,请先执行: openbot local download <embedding-uri>");
289
+ process.exit(1);
290
+ }
291
+ embPath = toModelPathForStart(embArg, LOCAL_LLM_CACHE_DIR);
292
+ }
293
+ if (!llmPath && !embPath) {
294
+ console.error("[openbot] 请至少指定 --llm 或 --embedding,或先配置桌面缺省模型");
295
+ process.exit(1);
296
+ }
297
+ const contextSize = opts.contextSize ??
298
+ (process.env.LOCAL_LLM_CONTEXT_MAX ? parseInt(process.env.LOCAL_LLM_CONTEXT_MAX, 10) : undefined) ??
299
+ 32768;
300
+ const port = parseInt(opts.port || "11435", 10);
301
+ try {
302
+ const handle = await startLocalLlmServer({
303
+ port,
304
+ llmModelPath: llmPath,
305
+ embeddingModelPath: embPath,
306
+ contextSize,
307
+ });
308
+ console.log(`[openbot] 本地模型服务已启动: ${handle.baseUrl}`);
309
+ console.log("[openbot] 按 Ctrl+C 停止服务");
310
+ await new Promise((resolve) => {
311
+ process.on("SIGINT", () => {
312
+ stopLocalLlmServer();
313
+ resolve();
314
+ });
315
+ process.on("SIGTERM", () => {
316
+ stopLocalLlmServer();
317
+ resolve();
318
+ });
319
+ });
320
+ process.exit(0);
321
+ }
322
+ catch (err) {
323
+ const msg = err instanceof Error ? err.message : String(err);
324
+ console.error("[openbot] 启动失败:", msg);
325
+ process.exit(1);
326
+ }
327
+ });
192
328
  (async () => {
193
329
  await ensureDesktopConfigInitialized();
194
330
  await program.parseAsync(process.argv);
@@ -0,0 +1,15 @@
1
+ /**
2
+ * 安装扩展:将包加入 dependencies 并在 plugins 目录下执行 npm install
3
+ */
4
+ export declare function installExtension(pkgSpec: string): void;
5
+ /**
6
+ * 列出已安装的扩展(package.json 的 dependencies)
7
+ */
8
+ export declare function listExtensions(): {
9
+ name: string;
10
+ spec: string;
11
+ }[];
12
+ /**
13
+ * 卸载扩展:从 dependencies 移除并在 plugins 目录下执行 npm install
14
+ */
15
+ export declare function uninstallExtension(pkgName: string): void;
@@ -0,0 +1,107 @@
1
+ /**
2
+ * openbot extension install / list / uninstall
3
+ * 在 ~/.openbot/plugins 下维护 package.json 与 node_modules,Server 运行时从该目录加载扩展。
4
+ */
5
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
+ import { execSync } from "node:child_process";
7
+ import { join } from "node:path";
8
+ import { getOpenbotPluginsDir } from "../core/agent/agent-dir.js";
9
+ const PLUGINS_PACKAGE_NAME = "openbot-plugins-root";
10
+ function ensurePluginsDir() {
11
+ const dir = getOpenbotPluginsDir();
12
+ if (!existsSync(dir)) {
13
+ mkdirSync(dir, { recursive: true });
14
+ }
15
+ const pkgPath = join(dir, "package.json");
16
+ if (!existsSync(pkgPath)) {
17
+ writeFileSync(pkgPath, JSON.stringify({
18
+ name: PLUGINS_PACKAGE_NAME,
19
+ version: "1.0.0",
20
+ private: true,
21
+ description: "OpenBot extension packages (managed by openbot extension install)",
22
+ dependencies: {},
23
+ }, null, 2), "utf-8");
24
+ }
25
+ return dir;
26
+ }
27
+ function readPluginsPackageJson(pluginsDir) {
28
+ const pkgPath = join(pluginsDir, "package.json");
29
+ if (!existsSync(pkgPath))
30
+ return {};
31
+ try {
32
+ return JSON.parse(readFileSync(pkgPath, "utf-8"));
33
+ }
34
+ catch {
35
+ return {};
36
+ }
37
+ }
38
+ function writePluginsPackageJson(pluginsDir, pkg) {
39
+ const pkgPath = join(pluginsDir, "package.json");
40
+ const full = {
41
+ name: PLUGINS_PACKAGE_NAME,
42
+ version: "1.0.0",
43
+ private: true,
44
+ description: "OpenBot extension packages (managed by openbot extension install)",
45
+ ...pkg,
46
+ };
47
+ writeFileSync(pkgPath, JSON.stringify(full, null, 2), "utf-8");
48
+ }
49
+ /**
50
+ * 解析用户输入的 pkgSpec(如 "foo" 或 "foo@1.0.0")为 { name, spec }。
51
+ */
52
+ function parsePkgSpec(pkgSpec) {
53
+ const at = pkgSpec.indexOf("@");
54
+ if (at === -1)
55
+ return { name: pkgSpec.trim(), spec: "*" };
56
+ return { name: pkgSpec.slice(0, at).trim(), spec: pkgSpec.slice(at + 1).trim() || "*" };
57
+ }
58
+ /**
59
+ * 安装扩展:将包加入 dependencies 并在 plugins 目录下执行 npm install
60
+ */
61
+ export function installExtension(pkgSpec) {
62
+ const pluginsDir = ensurePluginsDir();
63
+ const { name, spec } = parsePkgSpec(pkgSpec);
64
+ const pkg = readPluginsPackageJson(pluginsDir);
65
+ const deps = { ...pkg.dependencies };
66
+ deps[name] = spec; // 覆盖为本次指定的版本
67
+ writePluginsPackageJson(pluginsDir, { ...pkg, dependencies: deps });
68
+ execSync("npm install", {
69
+ cwd: pluginsDir,
70
+ stdio: "inherit",
71
+ });
72
+ console.log(`[openbot] Installed extension: ${name} (in ${pluginsDir})`);
73
+ }
74
+ /**
75
+ * 列出已安装的扩展(package.json 的 dependencies)
76
+ */
77
+ export function listExtensions() {
78
+ const pluginsDir = getOpenbotPluginsDir();
79
+ if (!existsSync(pluginsDir))
80
+ return [];
81
+ const pkg = readPluginsPackageJson(pluginsDir);
82
+ const deps = pkg.dependencies ?? {};
83
+ return Object.entries(deps).map(([name, spec]) => ({ name, spec }));
84
+ }
85
+ /**
86
+ * 卸载扩展:从 dependencies 移除并在 plugins 目录下执行 npm install
87
+ */
88
+ export function uninstallExtension(pkgName) {
89
+ const pluginsDir = getOpenbotPluginsDir();
90
+ if (!existsSync(pluginsDir)) {
91
+ console.warn("[openbot] No plugins directory found.");
92
+ return;
93
+ }
94
+ const pkg = readPluginsPackageJson(pluginsDir);
95
+ const deps = { ...(pkg.dependencies ?? {}) };
96
+ if (!(pkgName in deps)) {
97
+ console.warn(`[openbot] Extension "${pkgName}" is not installed.`);
98
+ return;
99
+ }
100
+ delete deps[pkgName];
101
+ writePluginsPackageJson(pluginsDir, { ...pkg, dependencies: deps });
102
+ execSync("npm install", {
103
+ cwd: pluginsDir,
104
+ stdio: "inherit",
105
+ });
106
+ console.log(`[openbot] Uninstalled extension: ${pkgName}`);
107
+ }
@@ -8,6 +8,12 @@ export declare function getOpenbotAgentDir(): string;
8
8
  * 可通过环境变量 OPENBOT_WORKSPACE_DIR 覆盖
9
9
  */
10
10
  export declare function getOpenbotWorkspaceDir(): string;
11
+ /**
12
+ * 获取 openbot 扩展(插件)目录(默认 ~/.openbot/plugins)
13
+ * 可通过环境变量 OPENBOT_PLUGINS_DIR 覆盖
14
+ * openbot extension install 会在此目录下维护 package.json 与 node_modules
15
+ */
16
+ export declare function getOpenbotPluginsDir(): string;
11
17
  /**
12
18
  * 确保 agent 目录存在,并创建默认配置文件
13
19
  */
@@ -15,6 +15,14 @@ export function getOpenbotAgentDir() {
15
15
  export function getOpenbotWorkspaceDir() {
16
16
  return process.env.OPENBOT_WORKSPACE_DIR ?? join(homedir(), ".openbot", "workspace");
17
17
  }
18
+ /**
19
+ * 获取 openbot 扩展(插件)目录(默认 ~/.openbot/plugins)
20
+ * 可通过环境变量 OPENBOT_PLUGINS_DIR 覆盖
21
+ * openbot extension install 会在此目录下维护 package.json 与 node_modules
22
+ */
23
+ export function getOpenbotPluginsDir() {
24
+ return process.env.OPENBOT_PLUGINS_DIR ?? join(homedir(), ".openbot", "plugins");
25
+ }
18
26
  /**
19
27
  * 确保 agent 目录存在,并创建默认配置文件
20
28
  */
@@ -60,10 +60,23 @@ export declare class AgentManager {
60
60
  maxSessions?: number;
61
61
  targetAgentId?: string;
62
62
  mcpServers?: McpServerConfig[] | McpServersStandardFormat;
63
+ /** MCP 单次返回最大 token;不配置则不限制 */
64
+ mcpMaxResultTokens?: number;
63
65
  /** 自定义系统提示词(来自 agent 配置),会与技能等一起组成最终 systemPrompt */
64
66
  systemPrompt?: string;
65
67
  /** 是否使用长记忆(memory_recall/save_experience);默认 true */
66
68
  useLongMemory?: boolean;
69
+ /** 在线搜索:启用时注册 web_search 工具 */
70
+ webSearch?: {
71
+ enabled: boolean;
72
+ provider: "brave" | "duck-duck-scrape";
73
+ apiKey?: string;
74
+ timeoutSeconds?: number;
75
+ cacheTtlMinutes?: number;
76
+ maxResults?: number;
77
+ /** 单次搜索返回最大 token;不配置则不限制;前端默认 64K */
78
+ maxResultTokens?: number;
79
+ };
67
80
  }): Promise<AgentSession>;
68
81
  /** 按复合 key 获取(key = sessionId + "::" + agentId) */
69
82
  getSession(compositeKey: string): AgentSession | undefined;
@@ -1,10 +1,11 @@
1
- import { createAgentSession, AuthStorage, DefaultResourceLoader, ModelRegistry, SessionManager as CoreSessionManager, createReadTool, createWriteTool, createEditTool, createBashTool, createFindTool, createGrepTool, createLsTool } from "@mariozechner/pi-coding-agent";
1
+ import { createAgentSession, AuthStorage, DefaultResourceLoader, ModelRegistry, SessionManager as CoreSessionManager, SettingsManager, createReadTool, createWriteTool, createEditTool, createBashTool, createFindTool, createGrepTool, createLsTool } from "@mariozechner/pi-coding-agent";
2
2
  import { join } from "node:path";
3
3
  import { existsSync, mkdirSync } from "node:fs";
4
4
  import { createCompactionMemoryExtensionFactory } from "../memory/compaction-extension.js";
5
+ import { loadExtensionFactories } from "../extensions/index.js";
5
6
  import { addMemory } from "../memory/index.js";
6
7
  import { persistStoredCompactionForSession, persistStoredCompactionForBusinessSession, } from "../memory/persist-compaction-on-close.js";
7
- import { createBrowserTool, createSaveExperienceTool, createMemoryRecallTool, createInstallSkillTool, createSwitchAgentTool, createListAgentsTool, createCreateAgentTool, createGetBookmarkTagsTool, createSaveBookmarkTool, createAddBookmarkTagTool } from "../tools/index.js";
8
+ import { createBrowserTool, createSaveExperienceTool, createMemoryRecallTool, createInstallSkillTool, createSwitchAgentTool, createListAgentsTool, createCreateAgentTool, createGetBookmarkTagsTool, createSaveBookmarkTool, createAddBookmarkTagTool, createWebSearchTool } from "../tools/index.js";
8
9
  /** Agent Session 缓存 key:sessionId + "::" + agentId,同一业务 session 下不同 agent 各自一个 Core Session */
9
10
  const COMPOSITE_KEY_SEP = "::";
10
11
  function toCompositeKey(sessionId, agentId) {
@@ -12,10 +13,16 @@ function toCompositeKey(sessionId, agentId) {
12
13
  }
13
14
  import { createMcpToolsForSession } from "../mcp/index.js";
14
15
  import { registerBuiltInApiProviders } from "@mariozechner/pi-ai/dist/providers/register-builtins.js";
15
- import { getOpenbotAgentDir, getOpenbotWorkspaceDir, ensureDefaultAgentDir } from "./agent-dir.js";
16
+ import { getOpenbotAgentDir, getOpenbotWorkspaceDir } from "./agent-dir.js";
16
17
  import { formatSkillsForPrompt } from "./skills.js";
18
+ import { createTokenUsageLogExtensionFactory, setTokenUsageInitialStats, } from "./token-usage-log-extension.js";
17
19
  // Ensure all built-in providers are registered
18
20
  registerBuiltInApiProviders();
21
+ /** 粗略按字符估算 token(中英混合约 1/3,纯英文约 1/4) */
22
+ function estTokensFromChars(chars) {
23
+ return Math.ceil(chars / 3);
24
+ }
25
+ const TOKEN_USAGE_LOG_PREFIX = "[token-usage]";
19
26
  /** system prompt 中每个技能描述最大字符数,超出截断以省 token */
20
27
  const MAX_SKILL_DESC_IN_PROMPT = 250;
21
28
  /**
@@ -123,9 +130,17 @@ For downloads, provide either a direct URL or a selector to click.`;
123
130
  agentDir: this.agentDir,
124
131
  noSkills: true, // Disable SDK's built-in skills logic to take full control
125
132
  additionalSkillPaths: this.resolveSkillPaths(workspaceDir),
126
- extensionFactories: sessionId && onUpdateLatestCompaction
127
- ? [createCompactionMemoryExtensionFactory(sessionId, onUpdateLatestCompaction)]
128
- : [],
133
+ extensionFactories: (() => {
134
+ const compositeKeyForLoader = sessionId && identity?.agentId
135
+ ? sessionId + COMPOSITE_KEY_SEP + identity.agentId
136
+ : "";
137
+ const tokenLog = createTokenUsageLogExtensionFactory(compositeKeyForLoader);
138
+ const base = sessionId && onUpdateLatestCompaction
139
+ ? [createCompactionMemoryExtensionFactory(sessionId, onUpdateLatestCompaction)]
140
+ : [];
141
+ const pluginFactories = loadExtensionFactories();
142
+ return [tokenLog, ...base, ...pluginFactories];
143
+ })(),
129
144
  systemPromptOverride: (base) => {
130
145
  const loadedSkills = loader.getSkills().skills;
131
146
  const basePrompt = this.buildSystemPrompt(loadedSkills);
@@ -203,13 +218,22 @@ For downloads, provide either a direct URL or a selector to click.`;
203
218
  const provider = options.provider ?? process.env.OPENBOT_PROVIDER ?? "deepseek";
204
219
  const modelId = options.modelId ?? process.env.OPENBOT_MODEL ?? "deepseek-chat";
205
220
  const apiKey = options.apiKey;
206
- ensureDefaultAgentDir(this.agentDir);
221
+ // local provider:指向本地 node-llama-cpp 子进程服务
222
+ if (provider === "local") {
223
+ const localBaseUrl = process.env.LOCAL_LLM_BASE_URL ?? "http://127.0.0.1:11435/v1";
224
+ process.env.OPENAI_API_KEY = process.env.OPENAI_API_KEY || "local";
225
+ process.env.OPENAI_BASE_URL = localBaseUrl;
226
+ }
207
227
  const authPath = join(this.agentDir, "auth.json");
208
228
  const modelsPath = join(this.agentDir, "models.json");
209
229
  const authStorage = new AuthStorage(authPath);
210
230
  if (apiKey) {
211
231
  authStorage.setRuntimeApiKey(provider, apiKey);
212
232
  }
233
+ // local 无需真实 API Key,显式设置占位凭证,避免 SDK 走默认凭证链(如 AWS)导致 "Could not load credentials from any providers"
234
+ if (provider === "local") {
235
+ authStorage.setRuntimeApiKey("local", process.env.OPENAI_API_KEY || "local");
236
+ }
213
237
  if (await authStorage.hasAuth(provider)) {
214
238
  const key = await authStorage.getApiKey(provider);
215
239
  if (key) {
@@ -245,6 +269,8 @@ For downloads, provide either a direct URL or a selector to click.`;
245
269
  return process.env.MOONSHOT_API_KEY || process.env.KIMI_API_KEY || process.env.OPENAI_API_KEY;
246
270
  if (p === "openai" || p === "openai-custom")
247
271
  return process.env.OPENAI_API_KEY;
272
+ if (p === "local")
273
+ return process.env.OPENAI_API_KEY || "local";
248
274
  return process.env.OPENAI_API_KEY;
249
275
  });
250
276
  const loader = this.createResourceLoader(sessionWorkspaceDir, sessionId, options.systemPrompt, { agentId, workspace: workspaceName }, (summary) => this.sessionLatestCompactionSummary.set(compositeKey, summary));
@@ -262,7 +288,9 @@ For downloads, provide either a direct URL or a selector to click.`;
262
288
  const mcpTools = await createMcpToolsForSession({
263
289
  mcpServers: options.mcpServers,
264
290
  sessionId,
291
+ mcpMaxResultTokens: options.mcpMaxResultTokens,
265
292
  });
293
+ const webSearchTool = options.webSearch?.enabled === true ? createWebSearchTool(options.webSearch) : null;
266
294
  const customTools = [
267
295
  createBrowserTool(sessionWorkspaceDir),
268
296
  createSaveExperienceTool(sessionId),
@@ -274,11 +302,57 @@ For downloads, provide either a direct URL or a selector to click.`;
274
302
  createGetBookmarkTagsTool(),
275
303
  createSaveBookmarkTool(),
276
304
  createAddBookmarkTagTool(),
305
+ ...(webSearchTool ? [webSearchTool] : []),
277
306
  ...mcpTools,
278
307
  ];
308
+ // 分类打印 token 占用估算(字符数 + 估算 token),便于分析超长请求来源
309
+ try {
310
+ const loadedSkills = loader.getSkills().skills;
311
+ const shortSkills = loadedSkills.map((s) => ({
312
+ ...s,
313
+ description: s.description.length <= MAX_SKILL_DESC_IN_PROMPT
314
+ ? s.description
315
+ : s.description.slice(0, MAX_SKILL_DESC_IN_PROMPT) + "…",
316
+ }));
317
+ const skillsBlock = formatSkillsForPrompt(shortSkills);
318
+ const basePrompt = this.buildSystemPrompt(loadedSkills);
319
+ const withCustom = options.systemPrompt?.trim()
320
+ ? options.systemPrompt.trim() + "\n\n" + basePrompt
321
+ : basePrompt;
322
+ const sessionIdentity = { agentId, workspace: workspaceName };
323
+ const withIdentity = sessionIdentity?.agentId
324
+ ? `[Session identity] You are the agent with ID: ${sessionIdentity.agentId}, workspace: ${sessionIdentity.workspace || sessionIdentity.agentId}. When asked which agent you are, answer according to this identity.\n\n` + withCustom
325
+ : withCustom;
326
+ const systemPromptChars = withIdentity.length;
327
+ const toolsDefs = [
328
+ ...Object.values(coreTools),
329
+ ...customTools,
330
+ ].map((t) => ({
331
+ name: t?.name ?? "",
332
+ description: typeof t?.description === "string" ? t.description : "",
333
+ parameters: t?.parameters ?? {},
334
+ }));
335
+ const toolsJsonChars = JSON.stringify(toolsDefs).length;
336
+ const systemPromptEstTokens = estTokensFromChars(systemPromptChars);
337
+ const skillsBlockEstTokens = estTokensFromChars(skillsBlock.length);
338
+ const toolsDefsEstTokens = estTokensFromChars(toolsJsonChars);
339
+ console.log(`${TOKEN_USAGE_LOG_PREFIX} session create (${compositeKey}) | systemPrompt chars=${systemPromptChars} estTokens=${systemPromptEstTokens} | skillsBlock chars=${skillsBlock.length} estTokens=${skillsBlockEstTokens} | toolsDefs chars=${toolsJsonChars} estTokens=${toolsDefsEstTokens}`);
340
+ setTokenUsageInitialStats(compositeKey, {
341
+ systemPromptEstTokens,
342
+ skillsBlockEstTokens,
343
+ toolsDefsEstTokens,
344
+ });
345
+ console.log(`${TOKEN_USAGE_LOG_PREFIX} compaction (SDK): 触发条件 contextTokens > contextWindow - reserveTokens (默认 16384);保留最近 keepRecentTokens (默认 20000)。配置见 pi 文档 settings.json 或传入 settingsManager。`);
346
+ }
347
+ catch (e) {
348
+ console.warn(`${TOKEN_USAGE_LOG_PREFIX} session create log failed:`, e);
349
+ }
279
350
  const { session } = await createAgentSession({
280
351
  agentDir: this.agentDir,
281
352
  sessionManager: CoreSessionManager.inMemory(),
353
+ settingsManager: SettingsManager.inMemory({
354
+ compaction: { enabled: true, reserveTokens: 16384, keepRecentTokens: 20000 },
355
+ }),
282
356
  authStorage,
283
357
  modelRegistry,
284
358
  cwd: sessionWorkspaceDir,
@@ -291,6 +365,13 @@ For downloads, provide either a direct URL or a selector to click.`;
291
365
  console.log(`Setting model to ${model.provider}/${model.id} (workspace: ${workspaceName})`);
292
366
  await session.setModel(model);
293
367
  }
368
+ else if (provider && modelId) {
369
+ // 配置了 provider/model 但在 models.json 中找不到,避免发请求后收到 400 model is required
370
+ const hint = provider === "ollama" || provider === "openai-custom"
371
+ ? "若使用 Ollama,请确保模型名与终端中 `ollama list` 显示的名称完全一致(如 qwen3:4b),并在「模型配置」中保存。"
372
+ : "请检查「模型配置」中该 Provider 下是否已添加并保存该模型。";
373
+ throw new Error(`未找到模型 ${provider}/${modelId}。${hint}`);
374
+ }
294
375
  this.sessions.set(compositeKey, session);
295
376
  this.sessionLastActiveAt.set(compositeKey, now);
296
377
  return session;
@@ -0,0 +1,2 @@
1
+ import type { IAgentProxyAdapter } from "../types.js";
2
+ export declare const claudeCodeAdapter: IAgentProxyAdapter;