verybot 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of verybot might be problematic. Click here for more details.

Files changed (244) hide show
  1. package/dist/brain/agent-registry.d.ts +75 -0
  2. package/dist/brain/agent-registry.js +124 -0
  3. package/dist/brain/agent.d.ts +146 -0
  4. package/dist/brain/agent.js +680 -0
  5. package/dist/brain/channel-store.d.ts +27 -0
  6. package/dist/brain/channel-store.js +78 -0
  7. package/dist/brain/compaction.d.ts +37 -0
  8. package/dist/brain/compaction.js +214 -0
  9. package/dist/brain/context.d.ts +33 -0
  10. package/dist/brain/context.js +77 -0
  11. package/dist/brain/delegation-store.d.ts +33 -0
  12. package/dist/brain/delegation-store.js +106 -0
  13. package/dist/brain/loop.d.ts +21 -0
  14. package/dist/brain/loop.js +161 -0
  15. package/dist/brain/mcp-adapter.d.ts +39 -0
  16. package/dist/brain/mcp-adapter.js +227 -0
  17. package/dist/brain/memory-extractor.d.ts +26 -0
  18. package/dist/brain/memory-extractor.js +82 -0
  19. package/dist/brain/providers.d.ts +10 -0
  20. package/dist/brain/providers.js +69 -0
  21. package/dist/brain/queue.d.ts +18 -0
  22. package/dist/brain/queue.js +84 -0
  23. package/dist/brain/run-tools.d.ts +47 -0
  24. package/dist/brain/run-tools.js +84 -0
  25. package/dist/brain/session-key.d.ts +23 -0
  26. package/dist/brain/session-key.js +41 -0
  27. package/dist/brain/session-state.d.ts +36 -0
  28. package/dist/brain/session-state.js +51 -0
  29. package/dist/brain/session-store.d.ts +50 -0
  30. package/dist/brain/session-store.js +207 -0
  31. package/dist/brain/session.d.ts +32 -0
  32. package/dist/brain/session.js +75 -0
  33. package/dist/brain/utils.d.ts +4 -0
  34. package/dist/brain/utils.js +26 -0
  35. package/dist/brain/worker-coordinator.d.ts +25 -0
  36. package/dist/brain/worker-coordinator.js +83 -0
  37. package/dist/channels/commands.d.ts +35 -0
  38. package/dist/channels/commands.js +65 -0
  39. package/dist/channels/discord/channel.d.ts +18 -0
  40. package/dist/channels/discord/channel.js +154 -0
  41. package/dist/channels/discord/markdown.d.ts +19 -0
  42. package/dist/channels/discord/markdown.js +62 -0
  43. package/dist/channels/manager.d.ts +29 -0
  44. package/dist/channels/manager.js +100 -0
  45. package/dist/channels/slack/channel.d.ts +26 -0
  46. package/dist/channels/slack/channel.js +207 -0
  47. package/dist/channels/slack/markdown.d.ts +19 -0
  48. package/dist/channels/slack/markdown.js +62 -0
  49. package/dist/channels/specs.d.ts +21 -0
  50. package/dist/channels/specs.js +96 -0
  51. package/dist/channels/telegram/channel.d.ts +18 -0
  52. package/dist/channels/telegram/channel.js +156 -0
  53. package/dist/channels/telegram/markdown.d.ts +17 -0
  54. package/dist/channels/telegram/markdown.js +66 -0
  55. package/dist/channels/types.d.ts +26 -0
  56. package/dist/channels/types.js +1 -0
  57. package/dist/channels/whatsapp/channel.d.ts +23 -0
  58. package/dist/channels/whatsapp/channel.js +242 -0
  59. package/dist/channels/whatsapp/markdown.d.ts +20 -0
  60. package/dist/channels/whatsapp/markdown.js +51 -0
  61. package/dist/cli/config.d.ts +5 -0
  62. package/dist/cli/config.js +78 -0
  63. package/dist/cli/index.d.ts +5 -0
  64. package/dist/cli/index.js +13 -0
  65. package/dist/computer/browser/actions.d.ts +31 -0
  66. package/dist/computer/browser/actions.js +148 -0
  67. package/dist/computer/browser/manager.d.ts +55 -0
  68. package/dist/computer/browser/manager.js +496 -0
  69. package/dist/computer/browser/profile-badge.d.ts +13 -0
  70. package/dist/computer/browser/profile-badge.js +67 -0
  71. package/dist/computer/browser/screenshot.d.ts +5 -0
  72. package/dist/computer/browser/screenshot.js +21 -0
  73. package/dist/computer/browser/snapshot.d.ts +30 -0
  74. package/dist/computer/browser/snapshot.js +242 -0
  75. package/dist/computer/browser/tools.d.ts +5 -0
  76. package/dist/computer/browser/tools.js +167 -0
  77. package/dist/computer/desktop/adapter.d.ts +25 -0
  78. package/dist/computer/desktop/adapter.js +11 -0
  79. package/dist/computer/desktop/macos.d.ts +24 -0
  80. package/dist/computer/desktop/macos.js +223 -0
  81. package/dist/computer/desktop/tools.d.ts +25 -0
  82. package/dist/computer/desktop/tools.js +114 -0
  83. package/dist/config/agent-config.d.ts +41 -0
  84. package/dist/config/agent-config.js +14 -0
  85. package/dist/config/model-catalog.d.ts +22 -0
  86. package/dist/config/model-catalog.js +99 -0
  87. package/dist/config/store.d.ts +25 -0
  88. package/dist/config/store.js +143 -0
  89. package/dist/config.d.ts +103 -0
  90. package/dist/config.js +224 -0
  91. package/dist/control-ui/assets/index-BANXNUyt.js +143 -0
  92. package/dist/control-ui/assets/index-BSUFrP9R.css +1 -0
  93. package/dist/control-ui/assets/noto-sans-cyrillic-ext-wght-normal-DSNfmdVt.woff2 +0 -0
  94. package/dist/control-ui/assets/noto-sans-cyrillic-wght-normal-B2hlT84T.woff2 +0 -0
  95. package/dist/control-ui/assets/noto-sans-devanagari-wght-normal-Cv-Vwajv.woff2 +0 -0
  96. package/dist/control-ui/assets/noto-sans-greek-ext-wght-normal-12T8GTDR.woff2 +0 -0
  97. package/dist/control-ui/assets/noto-sans-greek-wght-normal-Ymb6dZNd.woff2 +0 -0
  98. package/dist/control-ui/assets/noto-sans-latin-ext-wght-normal-W1qJv59z.woff2 +0 -0
  99. package/dist/control-ui/assets/noto-sans-latin-wght-normal-BYSzYMf3.woff2 +0 -0
  100. package/dist/control-ui/assets/noto-sans-vietnamese-wght-normal-DLTJy58D.woff2 +0 -0
  101. package/dist/control-ui/index.html +14 -0
  102. package/dist/control-ui/vite.svg +1 -0
  103. package/dist/events.d.ts +2 -0
  104. package/dist/events.js +11 -0
  105. package/dist/gateway/broadcast.d.ts +5 -0
  106. package/dist/gateway/broadcast.js +33 -0
  107. package/dist/gateway/methods/chat.d.ts +24 -0
  108. package/dist/gateway/methods/chat.js +19 -0
  109. package/dist/gateway/methods/config.d.ts +13 -0
  110. package/dist/gateway/methods/config.js +14 -0
  111. package/dist/gateway/methods/models.d.ts +10 -0
  112. package/dist/gateway/methods/models.js +14 -0
  113. package/dist/gateway/methods/prompt-templates.d.ts +23 -0
  114. package/dist/gateway/methods/prompt-templates.js +82 -0
  115. package/dist/gateway/methods/scheduler.d.ts +62 -0
  116. package/dist/gateway/methods/scheduler.js +129 -0
  117. package/dist/gateway/methods/sessions.d.ts +26 -0
  118. package/dist/gateway/methods/sessions.js +54 -0
  119. package/dist/gateway/methods/skills.d.ts +35 -0
  120. package/dist/gateway/methods/skills.js +202 -0
  121. package/dist/gateway/methods/system.d.ts +12 -0
  122. package/dist/gateway/methods/system.js +39 -0
  123. package/dist/gateway/methods/tasks.d.ts +21 -0
  124. package/dist/gateway/methods/tasks.js +46 -0
  125. package/dist/gateway/methods/teams.d.ts +70 -0
  126. package/dist/gateway/methods/teams.js +374 -0
  127. package/dist/gateway/methods/tools.d.ts +6 -0
  128. package/dist/gateway/methods/tools.js +7 -0
  129. package/dist/gateway/methods/whatsapp.d.ts +19 -0
  130. package/dist/gateway/methods/whatsapp.js +35 -0
  131. package/dist/gateway/rpc.d.ts +38 -0
  132. package/dist/gateway/rpc.js +75 -0
  133. package/dist/gateway/server.d.ts +4 -0
  134. package/dist/gateway/server.js +133 -0
  135. package/dist/index.d.ts +1 -0
  136. package/dist/index.js +212 -0
  137. package/dist/integrations/github.d.ts +7 -0
  138. package/dist/integrations/github.js +133 -0
  139. package/dist/integrations/mcp.d.ts +7 -0
  140. package/dist/integrations/mcp.js +106 -0
  141. package/dist/integrations/registry.d.ts +43 -0
  142. package/dist/integrations/registry.js +258 -0
  143. package/dist/integrations/scanner.d.ts +10 -0
  144. package/dist/integrations/scanner.js +122 -0
  145. package/dist/integrations/twitter.d.ts +10 -0
  146. package/dist/integrations/twitter.js +120 -0
  147. package/dist/integrations/types.d.ts +72 -0
  148. package/dist/integrations/types.js +1 -0
  149. package/dist/logger.d.ts +16 -0
  150. package/dist/logger.js +104 -0
  151. package/dist/markdown/chunk.d.ts +9 -0
  152. package/dist/markdown/chunk.js +52 -0
  153. package/dist/markdown/ir.d.ts +37 -0
  154. package/dist/markdown/ir.js +529 -0
  155. package/dist/markdown/render.d.ts +22 -0
  156. package/dist/markdown/render.js +148 -0
  157. package/dist/markdown/table-render.d.ts +43 -0
  158. package/dist/markdown/table-render.js +219 -0
  159. package/dist/markdown/tables.d.ts +17 -0
  160. package/dist/markdown/tables.js +27 -0
  161. package/dist/memory/embedding.d.ts +16 -0
  162. package/dist/memory/embedding.js +66 -0
  163. package/dist/memory/extractor.d.ts +6 -0
  164. package/dist/memory/extractor.js +72 -0
  165. package/dist/memory/search.d.ts +15 -0
  166. package/dist/memory/search.js +57 -0
  167. package/dist/memory/store.d.ts +34 -0
  168. package/dist/memory/store.js +328 -0
  169. package/dist/memory/types.d.ts +9 -0
  170. package/dist/memory/types.js +2 -0
  171. package/dist/paths.d.ts +20 -0
  172. package/dist/paths.js +29 -0
  173. package/dist/prompt-templates/builtins.d.ts +2 -0
  174. package/dist/prompt-templates/builtins.js +72 -0
  175. package/dist/prompt-templates/store.d.ts +39 -0
  176. package/dist/prompt-templates/store.js +174 -0
  177. package/dist/prompt-templates/types.d.ts +10 -0
  178. package/dist/prompt-templates/types.js +1 -0
  179. package/dist/scheduler/connected-channels.d.ts +24 -0
  180. package/dist/scheduler/connected-channels.js +57 -0
  181. package/dist/scheduler/scheduler.d.ts +22 -0
  182. package/dist/scheduler/scheduler.js +132 -0
  183. package/dist/scheduler/store.d.ts +27 -0
  184. package/dist/scheduler/store.js +205 -0
  185. package/dist/scheduler/types.d.ts +29 -0
  186. package/dist/scheduler/types.js +1 -0
  187. package/dist/security/command-validator.d.ts +22 -0
  188. package/dist/security/command-validator.js +160 -0
  189. package/dist/security/docker-sandbox.d.ts +48 -0
  190. package/dist/security/docker-sandbox.js +218 -0
  191. package/dist/security/env-filter.d.ts +8 -0
  192. package/dist/security/env-filter.js +41 -0
  193. package/dist/skills/loader.d.ts +33 -0
  194. package/dist/skills/loader.js +132 -0
  195. package/dist/skills/prompt.d.ts +6 -0
  196. package/dist/skills/prompt.js +17 -0
  197. package/dist/skills/read-tool.d.ts +7 -0
  198. package/dist/skills/read-tool.js +24 -0
  199. package/dist/skills/scanner.d.ts +6 -0
  200. package/dist/skills/scanner.js +73 -0
  201. package/dist/skills/types.d.ts +15 -0
  202. package/dist/skills/types.js +1 -0
  203. package/dist/tasks/store.d.ts +47 -0
  204. package/dist/tasks/store.js +193 -0
  205. package/dist/tasks/types.d.ts +75 -0
  206. package/dist/tasks/types.js +32 -0
  207. package/dist/teams/store.d.ts +78 -0
  208. package/dist/teams/store.js +420 -0
  209. package/dist/teams/types.d.ts +23 -0
  210. package/dist/teams/types.js +1 -0
  211. package/dist/tools/bash.d.ts +16 -0
  212. package/dist/tools/bash.js +62 -0
  213. package/dist/tools/channel-history.d.ts +10 -0
  214. package/dist/tools/channel-history.js +43 -0
  215. package/dist/tools/delegate.d.ts +16 -0
  216. package/dist/tools/delegate.js +216 -0
  217. package/dist/tools/fs.d.ts +4 -0
  218. package/dist/tools/fs.js +335 -0
  219. package/dist/tools/integration-toggle.d.ts +14 -0
  220. package/dist/tools/integration-toggle.js +47 -0
  221. package/dist/tools/memory.d.ts +13 -0
  222. package/dist/tools/memory.js +65 -0
  223. package/dist/tools/registry.d.ts +6 -0
  224. package/dist/tools/registry.js +9 -0
  225. package/dist/tools/schedule.d.ts +8 -0
  226. package/dist/tools/schedule.js +219 -0
  227. package/dist/tools/speak.d.ts +10 -0
  228. package/dist/tools/speak.js +56 -0
  229. package/dist/tools/tasks.d.ts +29 -0
  230. package/dist/tools/tasks.js +92 -0
  231. package/dist/tools/teams.d.ts +7 -0
  232. package/dist/tools/teams.js +180 -0
  233. package/dist/tools/web-fetch.d.ts +3 -0
  234. package/dist/tools/web-fetch.js +22 -0
  235. package/dist/tts/edge.d.ts +10 -0
  236. package/dist/tts/edge.js +60 -0
  237. package/dist/tts/speak.d.ts +12 -0
  238. package/dist/tts/speak.js +81 -0
  239. package/dist/tts/transcribe.d.ts +5 -0
  240. package/dist/tts/transcribe.js +40 -0
  241. package/dist/utils.d.ts +5 -0
  242. package/dist/utils.js +22 -0
  243. package/package.json +90 -0
  244. package/verybot.js +2 -0
@@ -0,0 +1,161 @@
1
+ import { streamText, stepCountIs } from "ai";
2
+ import { emit } from "../events.js";
3
+ import { logger } from "../logger.js";
4
+ export async function runLoop(opts) {
5
+ const { model, system, messages, tools, sessionKey, maxSteps = 20, agentId, silent = false, sessionLabel } = opts;
6
+ const tag = `[${sessionLabel ?? sessionKey}]`;
7
+ const runStart = Date.now();
8
+ logger.info(`\n${tag} ════════════════════════════════════════`);
9
+ logger.info(`\x1b[36m${tag} model: ${typeof model === "string" ? model : model.modelId} tools: [${Object.keys(tools).join(", ")}]\x1b[0m`);
10
+ logger.info(`${tag} maxSteps: ${maxSteps}`);
11
+ logger.info(`${tag} ── SYSTEM PROMPT ──\n${system}`);
12
+ logger.info(`${tag} ── MESSAGES (${messages.length}) ──`);
13
+ for (const msg of messages) {
14
+ const body = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
15
+ logger.info(`${tag} [${msg.role}] ${body.slice(0, 2000)}`);
16
+ }
17
+ let stepNum = 0;
18
+ let stepStart = Date.now();
19
+ const result = streamText({
20
+ model,
21
+ system,
22
+ messages,
23
+ tools,
24
+ stopWhen: stepCountIs(maxSteps),
25
+ onStepFinish({ text, reasoningText, toolCalls, toolResults, usage }) {
26
+ stepNum++;
27
+ const elapsed = Date.now() - stepStart;
28
+ logger.info(`${tag} ── step ${stepNum}/${maxSteps} (${elapsed}ms) ──`);
29
+ if (reasoningText) {
30
+ logger.info(`${tag} 🧠 reasoning: ${reasoningText.slice(0, 1500)}`);
31
+ }
32
+ if (toolCalls.length > 0) {
33
+ for (const tc of toolCalls) {
34
+ logger.info(`${tag} 🔧 call: ${tc.toolName}(${JSON.stringify(tc.input)})`);
35
+ }
36
+ if (!silent) {
37
+ emit("agent", {
38
+ sessionKey,
39
+ agentId,
40
+ tools: toolCalls.map((tc) => ({ name: tc.toolName, args: tc.input })),
41
+ });
42
+ }
43
+ }
44
+ if (toolResults.length > 0) {
45
+ for (const tr of toolResults) {
46
+ const out = tr.output;
47
+ // Skip binary/image data from logs
48
+ const isImage = Buffer.isBuffer(out) ||
49
+ (typeof out === "object" && out !== null && "data" in out && Buffer.isBuffer(out.data));
50
+ if (isImage) {
51
+ const size = Buffer.isBuffer(out) ? out.length : out.data.length;
52
+ logger.info(`${tag} ← ${tr.toolName}: [image ${size} bytes]`);
53
+ }
54
+ else {
55
+ const full = String(out);
56
+ if (looksLikeHtml(full)) {
57
+ logger.info(`${tag} ← ${tr.toolName} (${full.length} chars): [HTML] ${extractHtmlSummary(full)}`);
58
+ }
59
+ else {
60
+ logger.info(`${tag} ← ${tr.toolName} (${full.length} chars): ${full.slice(0, 1500)}`);
61
+ }
62
+ }
63
+ }
64
+ }
65
+ if (text) {
66
+ logger.info(`${tag} 💬 text: ${text.slice(0, 500)}`);
67
+ }
68
+ if (usage) {
69
+ logger.info(`${tag} 📊 tokens: ${usage.inputTokens} in / ${usage.outputTokens} out`);
70
+ }
71
+ stepStart = Date.now();
72
+ },
73
+ });
74
+ // Consume fullStream (not textStream) so errors surface here instead of
75
+ // becoming unhandled rejections from an unconsumed internal pipeline.
76
+ let collected = "";
77
+ let lastError = null;
78
+ try {
79
+ for await (const event of result.fullStream) {
80
+ if (event.type === "text-delta") {
81
+ collected += event.text;
82
+ if (!silent) {
83
+ emit("chat", {
84
+ sessionKey,
85
+ agentId,
86
+ state: "delta",
87
+ delta: event.text,
88
+ });
89
+ }
90
+ }
91
+ else if (event.type === "error") {
92
+ lastError = event.error;
93
+ }
94
+ }
95
+ }
96
+ catch (err) {
97
+ // fullStream flush may throw NoOutputGeneratedError — capture it
98
+ if (!lastError)
99
+ lastError = err;
100
+ }
101
+ // If no text was produced and we captured an error, re-throw it
102
+ // so Agent.handleMessage can send a friendly error to the user.
103
+ if (!collected && lastError) {
104
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
105
+ }
106
+ const final = await result;
107
+ const text = collected.length > 0 ? collected : "(no response)";
108
+ const totalMs = Date.now() - runStart;
109
+ logger.info(`${tag} ── FINAL REPLY (${text.length} chars, ${stepNum} steps, ${totalMs}ms) ──`);
110
+ logger.info(`${tag} ${text}`);
111
+ logger.info(`${tag} ════════════════════════════════════════\n`);
112
+ if (!silent) {
113
+ emit("chat", {
114
+ sessionKey,
115
+ agentId,
116
+ state: "final",
117
+ message: { role: "assistant", content: text },
118
+ usage: final.usage,
119
+ });
120
+ }
121
+ const resp = await result.response;
122
+ return {
123
+ text,
124
+ responseMessages: (resp.messages ?? []),
125
+ };
126
+ }
127
+ /** When the model ends on a tool call, try to pull text from the last tool result. */
128
+ function extractFallbackText(final) {
129
+ for (let i = final.steps.length - 1; i >= 0; i--) {
130
+ const results = final.steps[i].toolResults;
131
+ if (results && results.length > 0) {
132
+ const last = results[results.length - 1].output;
133
+ return typeof last === "string" ? last : JSON.stringify(last);
134
+ }
135
+ }
136
+ return undefined;
137
+ }
138
+ function lastUserMessage(messages) {
139
+ for (let i = messages.length - 1; i >= 0; i--) {
140
+ if (messages[i].role === "user") {
141
+ const content = messages[i].content;
142
+ const text = typeof content === "string" ? content : JSON.stringify(content);
143
+ return text.slice(0, 200);
144
+ }
145
+ }
146
+ return "(empty)";
147
+ }
148
+ /** Quick check for HTML-heavy content that would be noisy in logs. */
149
+ function looksLikeHtml(s) {
150
+ const start = s.slice(0, 500);
151
+ return start.includes("<!DOCTYPE") || start.includes("<html") || (start.match(/<\w/g)?.length ?? 0) > 5;
152
+ }
153
+ /** Pull title + meta description from raw HTML for a compact summary. */
154
+ function extractHtmlSummary(html) {
155
+ const title = html.match(/<title[^>]*>(.*?)<\/title>/is)?.[1]?.trim() ?? "";
156
+ const desc = html.match(/<meta[^>]+name=["']description["'][^>]+content=["']([^"']+)/i)?.[1]?.trim() ??
157
+ html.match(/<meta[^>]+content=["']([^"']+)["'][^>]+name=["']description["']/i)?.[1]?.trim() ??
158
+ "";
159
+ const parts = [title && `title="${title}"`, desc && `desc="${desc.slice(0, 150)}"`].filter(Boolean);
160
+ return parts.length > 0 ? parts.join(" | ") : "(no meta)";
161
+ }
@@ -0,0 +1,39 @@
1
+ import type { LanguageModel, ToolSet } from "ai";
2
+ import type { McpSdkServerConfigWithInstance } from "@anthropic-ai/claude-agent-sdk";
3
+ import { z } from "zod";
4
+ export interface AdaptedTools {
5
+ model: LanguageModel;
6
+ tools: ToolSet;
7
+ /** Cleanup function to shut down any HTTP MCP servers (Codex CLI strategy). */
8
+ cleanup?: () => Promise<void>;
9
+ }
10
+ /** Options forwarded to adapt strategies. */
11
+ export interface AdaptOptions {
12
+ sandboxEnabled?: boolean;
13
+ }
14
+ /** Extracted tool entry from an AI SDK ToolSet. */
15
+ interface UsableTool {
16
+ name: string;
17
+ description: string;
18
+ inputSchema: z.ZodObject<z.ZodRawShape>;
19
+ execute: (args: Record<string, unknown>) => Promise<unknown>;
20
+ }
21
+ /** Extract the usable tools from an AI SDK ToolSet (skips proprietary / incomplete tools). */
22
+ export declare function extractUsableTools(tools: ToolSet): UsableTool[];
23
+ /**
24
+ * Convert an AI SDK ToolSet into a Claude Code MCP server config.
25
+ * Each tool's `inputSchema` (Zod) is passed through directly,
26
+ * and `execute` is wrapped in the MCP `handler` envelope.
27
+ */
28
+ export declare function toolSetToMcpServer(tools: ToolSet, serverName?: string): McpSdkServerConfigWithInstance;
29
+ /** Start an HTTP MCP server on loopback with the given tools registered. */
30
+ export declare function startHttpMcpServer(usable: UsableTool[]): Promise<{
31
+ url: string;
32
+ cleanup: () => Promise<void>;
33
+ }>;
34
+ /**
35
+ * Adapt tools for MCP-based providers (Claude Code, Codex CLI).
36
+ * Non-MCP providers pass through unchanged.
37
+ */
38
+ export declare function adaptTools(provider: string, modelId: string, model: LanguageModel, tools: ToolSet, options?: AdaptOptions): Promise<AdaptedTools>;
39
+ export {};
@@ -0,0 +1,227 @@
1
+ import http from "node:http";
2
+ import { createCustomMcpServer, createClaudeCode } from "ai-sdk-provider-claude-code";
3
+ import { createCodexCli } from "ai-sdk-provider-codex-cli";
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
6
+ import { logger } from "../logger.js";
7
+ import { CLAUDE_CODE_PROVIDER, CODEX_CLI_PROVIDER, CODEX_CLI_ENV } from "./providers.js";
8
+ /** Tool names that use Anthropic's proprietary format and should be skipped. */
9
+ const SKIP_TOOLS = new Set(["computer"]);
10
+ const MCP_SERVER_NAME = "verybot-tools";
11
+ const MCP_HTTP_PATH = "/mcp";
12
+ /** Force OAuth login by clearing API key from the SDK environment. */
13
+ const CLAUDE_CODE_ENV = { ANTHROPIC_API_KEY: undefined };
14
+ // ---------------------------------------------------------------------------
15
+ // Port pool — allocate from a fixed range and reclaim on cleanup
16
+ // ---------------------------------------------------------------------------
17
+ const MCP_PORT_RANGE_START = 19400;
18
+ const MCP_PORT_RANGE_END = 19999;
19
+ const availablePorts = new Set(Array.from({ length: MCP_PORT_RANGE_END - MCP_PORT_RANGE_START + 1 }, (_, i) => MCP_PORT_RANGE_START + i));
20
+ function allocatePort() {
21
+ const port = availablePorts.values().next().value;
22
+ if (port === undefined)
23
+ throw new Error(`No available MCP ports in range ${MCP_PORT_RANGE_START}-${MCP_PORT_RANGE_END}`);
24
+ availablePorts.delete(port);
25
+ return port;
26
+ }
27
+ function releasePort(port) {
28
+ availablePorts.add(port);
29
+ }
30
+ /** Extract the usable tools from an AI SDK ToolSet (skips proprietary / incomplete tools). */
31
+ export function extractUsableTools(tools) {
32
+ const result = [];
33
+ for (const [name, tool] of Object.entries(tools)) {
34
+ if (SKIP_TOOLS.has(name)) {
35
+ logger.info(`[mcp-adapter] skip "${name}" (proprietary format)`);
36
+ continue;
37
+ }
38
+ const { description, inputSchema, execute } = tool;
39
+ if (!execute || !inputSchema) {
40
+ logger.info(`[mcp-adapter] skip "${name}" — execute=${!!execute} inputSchema=${!!inputSchema}`);
41
+ continue;
42
+ }
43
+ logger.info(`[mcp-adapter] + "${name}"`);
44
+ result.push({ name, description: description ?? name, inputSchema, execute });
45
+ }
46
+ return result;
47
+ }
48
+ /** Serialize a tool result into a string for MCP text content. */
49
+ function formatResult(result) {
50
+ if (result === undefined || result === null)
51
+ return "";
52
+ if (typeof result === "string")
53
+ return result;
54
+ if (Buffer.isBuffer(result))
55
+ return `[binary ${result.length} bytes]`;
56
+ if (typeof result === "object" && "data" in result && Buffer.isBuffer(result.data)) {
57
+ return `[image ${(result.data).length} bytes]`;
58
+ }
59
+ return JSON.stringify(result);
60
+ }
61
+ /** Wrap an execute function in the MCP CallToolResult envelope. */
62
+ function wrapToolHandler(name, execute) {
63
+ return async (args) => {
64
+ try {
65
+ const result = await execute(args);
66
+ return { content: [{ type: "text", text: formatResult(result) }] };
67
+ }
68
+ catch (err) {
69
+ const message = err instanceof Error ? err.message : String(err);
70
+ logger.warn(`[mcp-adapter] Tool "${name}" error: ${message}`);
71
+ return {
72
+ content: [{ type: "text", text: `Error: ${message}` }],
73
+ isError: true,
74
+ };
75
+ }
76
+ };
77
+ }
78
+ // ---------------------------------------------------------------------------
79
+ // Claude Code in-process MCP server (unchanged behavior)
80
+ // ---------------------------------------------------------------------------
81
+ /**
82
+ * Convert an AI SDK ToolSet into a Claude Code MCP server config.
83
+ * Each tool's `inputSchema` (Zod) is passed through directly,
84
+ * and `execute` is wrapped in the MCP `handler` envelope.
85
+ */
86
+ export function toolSetToMcpServer(tools, serverName = MCP_SERVER_NAME) {
87
+ const usable = extractUsableTools(tools);
88
+ logger.info(`[mcp-adapter] Converting ${Object.keys(tools).length} AI SDK tools for server "${serverName}"`);
89
+ const mcpTools = {};
90
+ for (const { name, description, inputSchema, execute } of usable) {
91
+ mcpTools[name] = { description, inputSchema, handler: wrapToolHandler(name, execute) };
92
+ }
93
+ const toolCount = Object.keys(mcpTools).length;
94
+ logger.info(`[mcp-adapter] Registered ${toolCount} tools as MCP server "${serverName}"`);
95
+ return createCustomMcpServer({ name: serverName, tools: mcpTools });
96
+ }
97
+ // ---------------------------------------------------------------------------
98
+ // Strategy: Claude Code (in-process MCP)
99
+ // ---------------------------------------------------------------------------
100
+ function claudeCodeStrategy(modelId, _model, tools, options) {
101
+ const mcpServer = toolSetToMcpServer(tools);
102
+ const claudeCode = createClaudeCode({
103
+ defaultSettings: {
104
+ permissionMode: "bypassPermissions",
105
+ env: CLAUDE_CODE_ENV,
106
+ mcpServers: { [MCP_SERVER_NAME]: mcpServer },
107
+ ...(options.sandboxEnabled && { sandbox: { enabled: true } }),
108
+ verbose: logger.level === "debug",
109
+ logger: {
110
+ debug: (msg) => logger.debug(`[claude-code-sdk] ${msg}`),
111
+ info: (msg) => logger.info(`[claude-code-sdk] ${msg}`),
112
+ warn: (msg) => logger.warn(`[claude-code-sdk] ${msg}`),
113
+ error: (msg) => logger.error(`[claude-code-sdk] ${msg}`),
114
+ },
115
+ },
116
+ });
117
+ logger.info(`[mcp-adapter] Created claude-code provider with MCP server "${MCP_SERVER_NAME}" for model "${modelId}"`);
118
+ return {
119
+ model: claudeCode(modelId),
120
+ tools: {}, // tools are in the MCP server; pass empty set to AI SDK
121
+ };
122
+ }
123
+ // ---------------------------------------------------------------------------
124
+ // Strategy: Codex CLI (external HTTP MCP server)
125
+ // ---------------------------------------------------------------------------
126
+ /** Start an HTTP MCP server on loopback with the given tools registered. */
127
+ export async function startHttpMcpServer(usable) {
128
+ const mcpServer = new McpServer({ name: MCP_SERVER_NAME, version: "1.0.0" }, { capabilities: { tools: {} } });
129
+ for (const { name, description, inputSchema, execute } of usable) {
130
+ mcpServer.tool(name, description, inputSchema.shape, async (args) => {
131
+ return wrapToolHandler(name, execute)(args);
132
+ });
133
+ }
134
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
135
+ await mcpServer.connect(transport);
136
+ const createHandler = () => http.createServer(async (req, res) => {
137
+ if (req.url !== MCP_HTTP_PATH) {
138
+ res.writeHead(404).end("Not Found");
139
+ return;
140
+ }
141
+ try {
142
+ await transport.handleRequest(req, res);
143
+ }
144
+ catch (err) {
145
+ logger.error(`[mcp-adapter] HTTP MCP request error: ${err instanceof Error ? err.message : err}`);
146
+ if (!res.headersSent) {
147
+ res.writeHead(500).end("Internal Server Error");
148
+ }
149
+ }
150
+ });
151
+ // Try up to MAX_PORT_RETRIES ports in case one is already in use externally
152
+ const MAX_PORT_RETRIES = 3;
153
+ let port = 0;
154
+ let httpServer;
155
+ for (let attempt = 0; attempt < MAX_PORT_RETRIES; attempt++) {
156
+ port = allocatePort();
157
+ httpServer = createHandler();
158
+ httpServer.unref(); // allow process to exit even if cleanup is missed
159
+ try {
160
+ await new Promise((resolve, reject) => {
161
+ httpServer.listen(port, "127.0.0.1", () => resolve());
162
+ httpServer.on("error", reject);
163
+ });
164
+ break;
165
+ }
166
+ catch (err) {
167
+ releasePort(port);
168
+ if (attempt === MAX_PORT_RETRIES - 1)
169
+ throw err;
170
+ logger.warn(`[mcp-adapter] Port ${port} in use, retrying with another port`);
171
+ }
172
+ }
173
+ const url = `http://127.0.0.1:${port}${MCP_HTTP_PATH}`;
174
+ logger.info(`[mcp-adapter] HTTP MCP server listening on ${url} (${usable.length} tools)`);
175
+ const cleanup = async () => {
176
+ logger.info(`[mcp-adapter] Shutting down HTTP MCP server on port ${port}`);
177
+ await mcpServer.close();
178
+ await new Promise((resolve) => httpServer.close(() => resolve()));
179
+ releasePort(port);
180
+ };
181
+ return { url, cleanup };
182
+ }
183
+ async function codexCliStrategy(modelId, _model, tools, _options) {
184
+ const usable = extractUsableTools(tools);
185
+ logger.info(`[mcp-adapter] Setting up Codex CLI HTTP MCP server for model "${modelId}"`);
186
+ const { url, cleanup } = await startHttpMcpServer(usable);
187
+ // Agent handles its own sandboxing; fullAuto bypasses Codex CLI's interactive approval
188
+ const codexCli = createCodexCli({
189
+ defaultSettings: {
190
+ fullAuto: true,
191
+ env: CODEX_CLI_ENV,
192
+ rmcpClient: true,
193
+ mcpServers: {
194
+ [MCP_SERVER_NAME]: { transport: "http", url },
195
+ },
196
+ verbose: logger.level === "debug",
197
+ logger: {
198
+ debug: (msg) => logger.debug(`[codex-cli-sdk] ${msg}`),
199
+ info: (msg) => logger.info(`[codex-cli-sdk] ${msg}`),
200
+ warn: (msg) => logger.warn(`[codex-cli-sdk] ${msg}`),
201
+ error: (msg) => logger.error(`[codex-cli-sdk] ${msg}`),
202
+ },
203
+ },
204
+ });
205
+ return {
206
+ model: codexCli(modelId),
207
+ tools: {}, // tools are in the MCP server; pass empty set to AI SDK
208
+ cleanup,
209
+ };
210
+ }
211
+ // ---------------------------------------------------------------------------
212
+ // Strategy dispatcher
213
+ // ---------------------------------------------------------------------------
214
+ const STRATEGIES = {
215
+ [CLAUDE_CODE_PROVIDER]: claudeCodeStrategy,
216
+ [CODEX_CLI_PROVIDER]: codexCliStrategy,
217
+ };
218
+ /**
219
+ * Adapt tools for MCP-based providers (Claude Code, Codex CLI).
220
+ * Non-MCP providers pass through unchanged.
221
+ */
222
+ export async function adaptTools(provider, modelId, model, tools, options = {}) {
223
+ const strategy = STRATEGIES[provider];
224
+ if (!strategy)
225
+ return { model, tools };
226
+ return strategy(modelId, model, tools, options);
227
+ }
@@ -0,0 +1,26 @@
1
+ import type { LanguageModel, ModelMessage } from "ai";
2
+ import type { MemoryStore } from "../memory/store.js";
3
+ import type { EmbeddingProvider } from "../memory/embedding.js";
4
+ import { extractFacts } from "../memory/extractor.js";
5
+ import type { SessionStateMap } from "./session-state.js";
6
+ /**
7
+ * Handles memory extraction from conversation sessions.
8
+ * Tracks per-session message counts and batches extraction.
9
+ */
10
+ export declare class MemoryExtractor {
11
+ private model;
12
+ private memoryStore;
13
+ private embeddingProvider;
14
+ constructor(model: LanguageModel, memoryStore: MemoryStore, embeddingProvider: EmbeddingProvider | null);
15
+ /** Update the model used for extraction (e.g. after config reload). */
16
+ setModel(model: LanguageModel): void;
17
+ /**
18
+ * Increment extraction counter and trigger extraction if threshold reached.
19
+ * Non-blocking: extraction runs in background.
20
+ */
21
+ trackAndMaybeExtract(sessionKey: string, sessions: SessionStateMap, messages: ModelMessage[], teamId?: string): void;
22
+ /** Extract and save facts from messages to memory store. */
23
+ extractAndSaveFacts(sessionKey: string, messages: Parameters<typeof extractFacts>[1], teamId?: string): Promise<void>;
24
+ /** Extract remaining facts from all active sessions (call before shutdown). */
25
+ flushAll(sessions: SessionStateMap): Promise<void>;
26
+ }
@@ -0,0 +1,82 @@
1
+ import { randomUUID } from "crypto";
2
+ import { extractFacts } from "../memory/extractor.js";
3
+ import { deriveMemoryTeamId } from "./session-key.js";
4
+ import { logger } from "../logger.js";
5
+ /** Run fact extraction every N user messages (not every message). */
6
+ const EXTRACT_EVERY_N = 5;
7
+ /**
8
+ * Handles memory extraction from conversation sessions.
9
+ * Tracks per-session message counts and batches extraction.
10
+ */
11
+ export class MemoryExtractor {
12
+ model;
13
+ memoryStore;
14
+ embeddingProvider;
15
+ constructor(model, memoryStore, embeddingProvider) {
16
+ this.model = model;
17
+ this.memoryStore = memoryStore;
18
+ this.embeddingProvider = embeddingProvider;
19
+ }
20
+ /** Update the model used for extraction (e.g. after config reload). */
21
+ setModel(model) {
22
+ this.model = model;
23
+ }
24
+ /**
25
+ * Increment extraction counter and trigger extraction if threshold reached.
26
+ * Non-blocking: extraction runs in background.
27
+ */
28
+ trackAndMaybeExtract(sessionKey, sessions, messages, teamId) {
29
+ const state = sessions.get(sessionKey);
30
+ if (!state)
31
+ return;
32
+ const count = state.messagesSinceExtraction + 1;
33
+ state.messagesSinceExtraction = count;
34
+ if (count >= EXTRACT_EVERY_N) {
35
+ state.messagesSinceExtraction = 0;
36
+ const recent = messages.slice(-EXTRACT_EVERY_N * 2);
37
+ this.extractAndSaveFacts(sessionKey, recent, teamId).catch((err) => {
38
+ logger.warn(`Fact extraction failed: ${err instanceof Error ? err.message : err}`);
39
+ });
40
+ }
41
+ }
42
+ /** Extract and save facts from messages to memory store. */
43
+ async extractAndSaveFacts(sessionKey, messages, teamId) {
44
+ const facts = await extractFacts(this.model, messages);
45
+ if (facts.length === 0)
46
+ return;
47
+ // Embed all facts in parallel when a provider is available
48
+ const embeddings = this.embeddingProvider
49
+ ? await Promise.all(facts.map((f) => this.embeddingProvider.embed(f)))
50
+ : facts.map(() => undefined);
51
+ let saved = 0;
52
+ for (let i = 0; i < facts.length; i++) {
53
+ const wasSaved = this.memoryStore.save({
54
+ id: randomUUID(),
55
+ fact: facts[i],
56
+ source: sessionKey,
57
+ timestamp: Date.now(),
58
+ embedding: embeddings[i],
59
+ teamId,
60
+ });
61
+ if (wasSaved)
62
+ saved++;
63
+ }
64
+ if (saved > 0) {
65
+ logger.info(`[${sessionKey}] Saved ${saved} new memories (${facts.length - saved} duplicates skipped)`);
66
+ }
67
+ }
68
+ /** Extract remaining facts from all active sessions (call before shutdown). */
69
+ async flushAll(sessions) {
70
+ for (const [sessionKey, state] of sessions.entries()) {
71
+ if (state.messagesSinceExtraction > 0) {
72
+ try {
73
+ const flushTeamId = deriveMemoryTeamId(sessionKey);
74
+ await this.extractAndSaveFacts(sessionKey, state.session.getMessages(), flushTeamId);
75
+ }
76
+ catch (err) {
77
+ logger.warn(`Shutdown extraction failed for ${sessionKey}: ${err instanceof Error ? err.message : err}`);
78
+ }
79
+ }
80
+ }
81
+ }
82
+ }
@@ -0,0 +1,10 @@
1
+ import type { LanguageModel } from "ai";
2
+ /** Provider identifier for Claude Code (used for MCP adapter detection). */
3
+ export declare const CLAUDE_CODE_PROVIDER = "claude-code";
4
+ /** Provider identifier for Codex CLI (used for MCP adapter detection). */
5
+ export declare const CODEX_CLI_PROVIDER = "codex-cli";
6
+ /** Force subscription auth by clearing API key from the SDK environment. */
7
+ export declare const CODEX_CLI_ENV: {
8
+ readonly OPENAI_API_KEY: "";
9
+ };
10
+ export declare function getModel(provider: string, modelId: string): LanguageModel;
@@ -0,0 +1,69 @@
1
+ import { anthropic } from "@ai-sdk/anthropic";
2
+ import { createClaudeCode } from "ai-sdk-provider-claude-code";
3
+ import { createCodexCli } from "ai-sdk-provider-codex-cli";
4
+ import { openai } from "@ai-sdk/openai";
5
+ import { google } from "@ai-sdk/google";
6
+ import { xai } from "@ai-sdk/xai";
7
+ import { mistral } from "@ai-sdk/mistral";
8
+ import { groq } from "@ai-sdk/groq";
9
+ import { togetherai } from "@ai-sdk/togetherai";
10
+ import { deepseek } from "@ai-sdk/deepseek";
11
+ import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
12
+ /** Provider identifier for Claude Code (used for MCP adapter detection). */
13
+ export const CLAUDE_CODE_PROVIDER = "claude-code";
14
+ /** Provider identifier for Codex CLI (used for MCP adapter detection). */
15
+ export const CODEX_CLI_PROVIDER = "codex-cli";
16
+ /** Force subscription auth by clearing API key from the SDK environment. */
17
+ export const CODEX_CLI_ENV = { OPENAI_API_KEY: "" };
18
+ /** Base Claude Code provider (OAuth auth, no tools). Tools are added per-session in mcp-adapter.ts. */
19
+ const claudeCode = createClaudeCode({
20
+ defaultSettings: {
21
+ permissionMode: "bypassPermissions",
22
+ env: { ANTHROPIC_API_KEY: undefined },
23
+ },
24
+ });
25
+ /**
26
+ * Base Codex CLI provider (full-auto, no tools). Tools are added per-session in mcp-adapter.ts.
27
+ * fullAuto bypasses Codex CLI's interactive approval — the agent handles its own sandboxing.
28
+ */
29
+ const codexCliBase = createCodexCli({
30
+ defaultSettings: {
31
+ fullAuto: true,
32
+ env: CODEX_CLI_ENV,
33
+ },
34
+ });
35
+ const providers = {
36
+ anthropic: (id) => anthropic(id),
37
+ [CLAUDE_CODE_PROVIDER]: (id) => claudeCode(id),
38
+ [CODEX_CLI_PROVIDER]: (id) => codexCliBase(id),
39
+ openai: (id) => openai(id),
40
+ google: (id) => google(id),
41
+ xai: (id) => xai(id),
42
+ mistral: (id) => mistral(id),
43
+ groq: (id) => groq(id),
44
+ togetherai: (id) => togetherai(id),
45
+ deepseek: (id) => deepseek(id),
46
+ openrouter: (id) => {
47
+ const provider = createOpenAICompatible({
48
+ name: "openrouter",
49
+ baseURL: "https://openrouter.ai/api/v1",
50
+ apiKey: process.env.OPENROUTER_API_KEY,
51
+ });
52
+ return provider(id);
53
+ },
54
+ ollama: (id) => {
55
+ const provider = createOpenAICompatible({
56
+ name: "ollama",
57
+ baseURL: process.env.OLLAMA_BASE_URL ?? "http://localhost:11434/v1",
58
+ apiKey: "ollama",
59
+ });
60
+ return provider(id);
61
+ },
62
+ };
63
+ export function getModel(provider, modelId) {
64
+ const factory = providers[provider];
65
+ if (!factory) {
66
+ throw new Error(`Unknown provider: ${provider}. Available: ${Object.keys(providers).join(", ")}`);
67
+ }
68
+ return factory(modelId);
69
+ }
@@ -0,0 +1,18 @@
1
+ export type QueueMode = "sequential" | "collect";
2
+ export declare class MessageQueue {
3
+ private lanes;
4
+ private mode;
5
+ private processMessage;
6
+ constructor(opts: {
7
+ mode?: QueueMode;
8
+ processMessage: (sessionKey: string, text: string, signal: AbortSignal) => Promise<string>;
9
+ });
10
+ /** Enqueue a message. Returns a promise that resolves with the agent reply. */
11
+ enqueue(sessionKey: string, text: string): Promise<string>;
12
+ /** Abort the current run for a session. */
13
+ abort(sessionKey: string): boolean;
14
+ /** Remove the lane for a cleared session. Aborts any in-flight run first. */
15
+ deleteLane(sessionKey: string): void;
16
+ private drain;
17
+ private getOrCreateLane;
18
+ }