@johpaz/hive 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +44 -0
- package/README.md +310 -0
- package/package.json +96 -0
- package/packages/cli/package.json +28 -0
- package/packages/cli/src/commands/agent-run.ts +168 -0
- package/packages/cli/src/commands/agents.ts +398 -0
- package/packages/cli/src/commands/chat.ts +142 -0
- package/packages/cli/src/commands/config.ts +50 -0
- package/packages/cli/src/commands/cron.ts +161 -0
- package/packages/cli/src/commands/dev.ts +95 -0
- package/packages/cli/src/commands/doctor.ts +133 -0
- package/packages/cli/src/commands/gateway.ts +443 -0
- package/packages/cli/src/commands/logs.ts +57 -0
- package/packages/cli/src/commands/mcp.ts +175 -0
- package/packages/cli/src/commands/message.ts +77 -0
- package/packages/cli/src/commands/onboard.ts +1868 -0
- package/packages/cli/src/commands/security.ts +144 -0
- package/packages/cli/src/commands/service.ts +50 -0
- package/packages/cli/src/commands/sessions.ts +116 -0
- package/packages/cli/src/commands/skills.ts +187 -0
- package/packages/cli/src/commands/update.ts +25 -0
- package/packages/cli/src/index.ts +185 -0
- package/packages/cli/src/utils/token.ts +6 -0
- package/packages/code-bridge/README.md +78 -0
- package/packages/code-bridge/package.json +18 -0
- package/packages/code-bridge/src/index.ts +95 -0
- package/packages/code-bridge/src/process-manager.ts +212 -0
- package/packages/code-bridge/src/schemas.ts +133 -0
- package/packages/core/package.json +46 -0
- package/packages/core/src/agent/agent-loop.ts +369 -0
- package/packages/core/src/agent/compaction.ts +140 -0
- package/packages/core/src/agent/context-compiler.ts +378 -0
- package/packages/core/src/agent/context-guard.ts +91 -0
- package/packages/core/src/agent/context.ts +138 -0
- package/packages/core/src/agent/conversation-store.ts +198 -0
- package/packages/core/src/agent/curator.ts +158 -0
- package/packages/core/src/agent/hooks.ts +166 -0
- package/packages/core/src/agent/index.ts +116 -0
- package/packages/core/src/agent/llm-client.ts +503 -0
- package/packages/core/src/agent/native-tools.ts +505 -0
- package/packages/core/src/agent/prompt-builder.ts +532 -0
- package/packages/core/src/agent/providers/index.ts +167 -0
- package/packages/core/src/agent/providers.ts +1 -0
- package/packages/core/src/agent/reflector.ts +170 -0
- package/packages/core/src/agent/service.ts +64 -0
- package/packages/core/src/agent/stuck-loop.ts +133 -0
- package/packages/core/src/agent/supervisor.ts +39 -0
- package/packages/core/src/agent/tracer.ts +102 -0
- package/packages/core/src/agent/workspace.ts +110 -0
- package/packages/core/src/canvas/canvas-manager.test.ts +161 -0
- package/packages/core/src/canvas/canvas-manager.ts +319 -0
- package/packages/core/src/canvas/canvas-tools.ts +420 -0
- package/packages/core/src/canvas/emitter.ts +115 -0
- package/packages/core/src/canvas/index.ts +2 -0
- package/packages/core/src/channels/base.ts +138 -0
- package/packages/core/src/channels/discord.ts +260 -0
- package/packages/core/src/channels/index.ts +7 -0
- package/packages/core/src/channels/manager.ts +383 -0
- package/packages/core/src/channels/slack.ts +287 -0
- package/packages/core/src/channels/telegram.ts +502 -0
- package/packages/core/src/channels/webchat.ts +128 -0
- package/packages/core/src/channels/whatsapp.ts +375 -0
- package/packages/core/src/config/index.ts +12 -0
- package/packages/core/src/config/loader.ts +529 -0
- package/packages/core/src/events/event-bus.ts +169 -0
- package/packages/core/src/gateway/index.ts +5 -0
- package/packages/core/src/gateway/initializer.ts +290 -0
- package/packages/core/src/gateway/lane-queue.ts +169 -0
- package/packages/core/src/gateway/resolver.ts +108 -0
- package/packages/core/src/gateway/router.ts +124 -0
- package/packages/core/src/gateway/server.ts +3317 -0
- package/packages/core/src/gateway/session.ts +95 -0
- package/packages/core/src/gateway/slash-commands.ts +192 -0
- package/packages/core/src/heartbeat/index.ts +157 -0
- package/packages/core/src/index.ts +19 -0
- package/packages/core/src/integrations/catalog.ts +286 -0
- package/packages/core/src/integrations/env.ts +64 -0
- package/packages/core/src/integrations/index.ts +2 -0
- package/packages/core/src/memory/index.ts +1 -0
- package/packages/core/src/memory/notes.ts +68 -0
- package/packages/core/src/plugins/api.ts +128 -0
- package/packages/core/src/plugins/index.ts +2 -0
- package/packages/core/src/plugins/loader.ts +365 -0
- package/packages/core/src/resilience/circuit-breaker.ts +225 -0
- package/packages/core/src/security/google-chat.ts +269 -0
- package/packages/core/src/security/index.ts +192 -0
- package/packages/core/src/security/pairing.ts +250 -0
- package/packages/core/src/security/rate-limit.ts +270 -0
- package/packages/core/src/security/signal.ts +321 -0
- package/packages/core/src/state/store.ts +312 -0
- package/packages/core/src/storage/bun-sqlite-store.ts +188 -0
- package/packages/core/src/storage/crypto.ts +101 -0
- package/packages/core/src/storage/db-context.ts +333 -0
- package/packages/core/src/storage/onboarding.ts +1087 -0
- package/packages/core/src/storage/schema.ts +541 -0
- package/packages/core/src/storage/seed.ts +571 -0
- package/packages/core/src/storage/sqlite.ts +387 -0
- package/packages/core/src/storage/usage.ts +212 -0
- package/packages/core/src/tools/bridge-events.ts +74 -0
- package/packages/core/src/tools/browser.ts +275 -0
- package/packages/core/src/tools/codebridge.ts +421 -0
- package/packages/core/src/tools/coordinator-tools.ts +179 -0
- package/packages/core/src/tools/cron.ts +611 -0
- package/packages/core/src/tools/exec.ts +140 -0
- package/packages/core/src/tools/fs.ts +364 -0
- package/packages/core/src/tools/index.ts +12 -0
- package/packages/core/src/tools/memory.ts +176 -0
- package/packages/core/src/tools/notify.ts +113 -0
- package/packages/core/src/tools/project-management.ts +376 -0
- package/packages/core/src/tools/project.ts +375 -0
- package/packages/core/src/tools/read.ts +158 -0
- package/packages/core/src/tools/web.ts +436 -0
- package/packages/core/src/tools/workspace.ts +171 -0
- package/packages/core/src/utils/benchmark.ts +80 -0
- package/packages/core/src/utils/crypto.ts +73 -0
- package/packages/core/src/utils/date.ts +42 -0
- package/packages/core/src/utils/index.ts +4 -0
- package/packages/core/src/utils/logger.ts +388 -0
- package/packages/core/src/utils/retry.ts +70 -0
- package/packages/core/src/voice/index.ts +583 -0
- package/packages/core/tsconfig.json +9 -0
- package/packages/mcp/package.json +26 -0
- package/packages/mcp/src/config.ts +13 -0
- package/packages/mcp/src/index.ts +1 -0
- package/packages/mcp/src/logger.ts +42 -0
- package/packages/mcp/src/manager.ts +434 -0
- package/packages/mcp/src/transports/index.ts +67 -0
- package/packages/mcp/src/transports/sse.ts +241 -0
- package/packages/mcp/src/transports/websocket.ts +159 -0
- package/packages/skills/package.json +21 -0
- package/packages/skills/src/bundled/agent_management/SKILL.md +24 -0
- package/packages/skills/src/bundled/browser_automation/SKILL.md +30 -0
- package/packages/skills/src/bundled/context_compact/SKILL.md +35 -0
- package/packages/skills/src/bundled/cron_manager/SKILL.md +52 -0
- package/packages/skills/src/bundled/file_manager/SKILL.md +76 -0
- package/packages/skills/src/bundled/http_client/SKILL.md +24 -0
- package/packages/skills/src/bundled/memory/SKILL.md +42 -0
- package/packages/skills/src/bundled/project_management/SKILL.md +26 -0
- package/packages/skills/src/bundled/shell/SKILL.md +43 -0
- package/packages/skills/src/bundled/system_notify/SKILL.md +52 -0
- package/packages/skills/src/bundled/voice/SKILL.md +25 -0
- package/packages/skills/src/bundled/web_search/SKILL.md +29 -0
- package/packages/skills/src/index.ts +1 -0
- package/packages/skills/src/loader.ts +282 -0
- package/packages/tools/package.json +43 -0
- package/packages/tools/src/browser/browser.test.ts +111 -0
- package/packages/tools/src/browser/index.ts +272 -0
- package/packages/tools/src/canvas/index.ts +220 -0
- package/packages/tools/src/cron/cron.test.ts +164 -0
- package/packages/tools/src/cron/index.ts +304 -0
- package/packages/tools/src/filesystem/filesystem.test.ts +240 -0
- package/packages/tools/src/filesystem/index.ts +379 -0
- package/packages/tools/src/git/index.ts +239 -0
- package/packages/tools/src/index.ts +4 -0
- package/packages/tools/src/shell/detect-env.ts +70 -0
- package/packages/tools/tsconfig.json +9 -0
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
import { getDb } from "../storage/sqlite"
|
|
2
|
+
import { getGlobalStore } from "../storage/bun-sqlite-store"
|
|
3
|
+
import { logger } from "../utils/logger"
|
|
4
|
+
import { getUserDate, getUserTime } from "../utils/date"
|
|
5
|
+
|
|
6
|
+
const log = logger.child("prompt-builder")
|
|
7
|
+
|
|
8
|
+
export interface BuildPromptOptions {
|
|
9
|
+
agentId: string
|
|
10
|
+
userId: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function buildSystemPrompt(options: BuildPromptOptions): Promise<string> {
|
|
14
|
+
const agentId = options.agentId
|
|
15
|
+
const userId = options.userId
|
|
16
|
+
if (!userId) {
|
|
17
|
+
throw new Error("No userId provided. Please set HIVE_USER_ID or complete onboarding.");
|
|
18
|
+
}
|
|
19
|
+
const db = getDb()
|
|
20
|
+
const store = getGlobalStore(db)
|
|
21
|
+
const parts: string[] = []
|
|
22
|
+
|
|
23
|
+
const t0 = performance.now()
|
|
24
|
+
log.info(`\n[CONTEXT] Building system prompt for Agent: ${agentId}, User: ${userId}`)
|
|
25
|
+
|
|
26
|
+
const agentRow = db
|
|
27
|
+
.query<any, [string]>("SELECT * FROM agents WHERE id = ?")
|
|
28
|
+
.get(agentId)
|
|
29
|
+
|
|
30
|
+
const t1 = performance.now()
|
|
31
|
+
if (agentRow) {
|
|
32
|
+
log.info(`[CONTEXT] ✓ Layer 1: Agent identity loaded (${(t1 - t0).toFixed(2)}ms) - ID: ${agentId}`)
|
|
33
|
+
parts.push(`# AGENT IDENTITY
|
|
34
|
+
## Internal ID: ${agentRow.id}
|
|
35
|
+
## Name: ${agentRow.name}
|
|
36
|
+
## Description: ${agentRow.description || "Personal AI assistant"}
|
|
37
|
+
## Tone: ${agentRow.tone || "friendly"}
|
|
38
|
+
## Expertise: ${agentRow.expertise || "general assistance"}
|
|
39
|
+
|
|
40
|
+
You are ${agentRow.name}, ${agentRow.description || "a helpful AI assistant"}.
|
|
41
|
+
- Be ${agentRow.tone || "friendly and helpful"} in your responses
|
|
42
|
+
- Focus on your areas of expertise
|
|
43
|
+
- Never expose your internal agent_id to the user
|
|
44
|
+
`)
|
|
45
|
+
} else {
|
|
46
|
+
parts.push(`# AGENT IDENTITY
|
|
47
|
+
## Internal ID: ${agentId}
|
|
48
|
+
You are Hive, a helpful AI assistant.
|
|
49
|
+
`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const userRow = db
|
|
53
|
+
.query<any, [string]>("SELECT * FROM users WHERE id = ?")
|
|
54
|
+
.get(userId)
|
|
55
|
+
|
|
56
|
+
const t2 = performance.now()
|
|
57
|
+
if (userRow) {
|
|
58
|
+
log.info(`[CONTEXT] ✓ Layer 1: User profile loaded (${(t2 - t1).toFixed(2)}ms) - User/Thread ID: ${userId}`)
|
|
59
|
+
const userInfo: string[] = []
|
|
60
|
+
if (userRow.name) userInfo.push(`Name: ${userRow.name}`)
|
|
61
|
+
if (userRow.occupation) userInfo.push(`Occupation: ${userRow.occupation}`)
|
|
62
|
+
if (userRow.language) userInfo.push(`Language: ${userRow.language}`)
|
|
63
|
+
if (userRow.timezone) userInfo.push(`Timezone: ${userRow.timezone}`)
|
|
64
|
+
if (userRow.notes) userInfo.push(`Notes: ${userRow.notes}`)
|
|
65
|
+
|
|
66
|
+
if (userInfo.length > 0) {
|
|
67
|
+
parts.push(`# USER PROFILE
|
|
68
|
+
${userInfo.join("\n")}
|
|
69
|
+
`)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const tTools = performance.now()
|
|
75
|
+
const toolsData = await store.get("tools:active")
|
|
76
|
+
if (toolsData && toolsData.tools && Array.isArray(toolsData.tools)) {
|
|
77
|
+
log.info(`[CONTEXT] ✓ Layer 2: Tools loaded (${(performance.now() - tTools).toFixed(2)}ms) - ${toolsData.tools.length} active`)
|
|
78
|
+
const toolsList = toolsData.tools
|
|
79
|
+
.filter((t: any) => t && t.name)
|
|
80
|
+
.map((t: any) => `- **${t.name}**: ${t.description || "No description"} (${t.category || "general"})`)
|
|
81
|
+
.join("\n")
|
|
82
|
+
|
|
83
|
+
if (toolsList) {
|
|
84
|
+
parts.push(`# AVAILABLE TOOLS
|
|
85
|
+
${toolsList}
|
|
86
|
+
`)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch (e) {
|
|
90
|
+
log.warn("[prompt-builder] Failed to load tools from store:", e)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const tSkills = performance.now()
|
|
95
|
+
const skillsData = await store.get("skills:active")
|
|
96
|
+
if (skillsData && skillsData.skills && Array.isArray(skillsData.skills)) {
|
|
97
|
+
log.info(`[CONTEXT] ✓ Layer 2: Skills loaded (${(performance.now() - tSkills).toFixed(2)}ms) - ${skillsData.skills.length} active`)
|
|
98
|
+
const skillsList = skillsData.skills
|
|
99
|
+
.filter((s: any) => s && s.name)
|
|
100
|
+
.map((s: any) => `- **${s.name}**: ${s.description || "No description"}`)
|
|
101
|
+
.join("\n")
|
|
102
|
+
|
|
103
|
+
if (skillsList) {
|
|
104
|
+
parts.push(`# AVAILABLE SKILLS
|
|
105
|
+
${skillsList}
|
|
106
|
+
`)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch (e) {
|
|
110
|
+
log.warn("[prompt-builder] Failed to load skills from store:", e)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const mcpData = await store.get("mcp:connected")
|
|
115
|
+
if (mcpData && mcpData.servers && Array.isArray(mcpData.servers)) {
|
|
116
|
+
const mcpList = mcpData.servers
|
|
117
|
+
.filter((s: any) => s && s.name)
|
|
118
|
+
.map((s: any) => `- **${s.name}**: ${s.transport} (${s.command || s.url || "local"})`)
|
|
119
|
+
.join("\n")
|
|
120
|
+
|
|
121
|
+
if (mcpList) {
|
|
122
|
+
parts.push(`# CONNECTED MCP SERVERS
|
|
123
|
+
${mcpList}
|
|
124
|
+
`)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} catch (e) {
|
|
128
|
+
log.warn("[prompt-builder] Failed to load MCP from store:", e)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const tEthics = performance.now()
|
|
133
|
+
const ethicsData = await store.get("ethics:rules")
|
|
134
|
+
if (ethicsData && ethicsData.rules && Array.isArray(ethicsData.rules)) {
|
|
135
|
+
log.info(`[CONTEXT] ✓ Layer 2: Ethics loaded (${(performance.now() - tEthics).toFixed(2)}ms) - ${ethicsData.rules.length} active rules`)
|
|
136
|
+
const alwaysRules = ethicsData.rules.filter((r: any) => r.content?.startsWith("ALWAYS:"))
|
|
137
|
+
const neverRules = ethicsData.rules.filter((r: any) => r.content?.startsWith("NEVER:"))
|
|
138
|
+
const confirmRules = ethicsData.rules.filter((r: any) => r.content?.startsWith("CONFIRM:"))
|
|
139
|
+
|
|
140
|
+
const ethicsParts: string[] = []
|
|
141
|
+
if (alwaysRules.length > 0) {
|
|
142
|
+
ethicsParts.push("## Always\n" + alwaysRules.map((r: any) => `- ${r.content.replace("ALWAYS:", "").trim()}`).join("\n"))
|
|
143
|
+
}
|
|
144
|
+
if (neverRules.length > 0) {
|
|
145
|
+
ethicsParts.push("## Never\n" + neverRules.map((r: any) => `- ${r.content.replace("NEVER:", "").trim()}`).join("\n"))
|
|
146
|
+
}
|
|
147
|
+
if (confirmRules.length > 0) {
|
|
148
|
+
ethicsParts.push("## Confirm Before\n" + confirmRules.map((r: any) => `- ${r.content.replace("CONFIRM:", "").trim()}`).join("\n"))
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (ethicsParts.length > 0) {
|
|
152
|
+
parts.push(`# ETHICS GUIDELINES\n${ethicsParts.join("\n\n")}\n`)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} catch (e) {
|
|
156
|
+
log.warn("[prompt-builder] Failed to load ethics from store:", e)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Active providers (with API key OR local like Ollama) and their active models
|
|
160
|
+
try {
|
|
161
|
+
const activeProviders = db.query<any, []>(`
|
|
162
|
+
SELECT p.id, p.name, p.base_url,
|
|
163
|
+
CASE WHEN p.api_key_encrypted IS NOT NULL THEN 1 ELSE 0 END as has_api_key,
|
|
164
|
+
GROUP_CONCAT(m.name, ', ') as model_names
|
|
165
|
+
FROM providers p
|
|
166
|
+
LEFT JOIN models m ON m.provider_id = p.id AND (m.active = 1 OR m.enabled = 1)
|
|
167
|
+
WHERE p.active = 1
|
|
168
|
+
AND (
|
|
169
|
+
p.api_key_encrypted IS NOT NULL
|
|
170
|
+
OR p.base_url LIKE '%localhost%'
|
|
171
|
+
OR p.base_url LIKE '%127.0.0.1%'
|
|
172
|
+
OR p.id = 'ollama'
|
|
173
|
+
)
|
|
174
|
+
GROUP BY p.id
|
|
175
|
+
`).all()
|
|
176
|
+
|
|
177
|
+
if (activeProviders.length > 0) {
|
|
178
|
+
const providerList = activeProviders
|
|
179
|
+
.map((p: any) => {
|
|
180
|
+
const authInfo = p.has_api_key ? "API key configurada" : "local (sin API key)"
|
|
181
|
+
const base = p.base_url ? ` — ${p.base_url}` : ""
|
|
182
|
+
const modelList = p.model_names ? `\n Modelos: ${p.model_names}` : ""
|
|
183
|
+
return `- **${p.name}** (${authInfo}${base})${modelList}`
|
|
184
|
+
})
|
|
185
|
+
.join("\n")
|
|
186
|
+
|
|
187
|
+
parts.push(`# PROVIDERS Y MODELOS DISPONIBLES
|
|
188
|
+
${providerList}
|
|
189
|
+
`)
|
|
190
|
+
}
|
|
191
|
+
} catch (e) {
|
|
192
|
+
log.warn("[prompt-builder] Failed to load active providers:", e)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Check if this is the main agent (coordinator)
|
|
196
|
+
const isCoordinator = agentRow?.is_coordinator === 1
|
|
197
|
+
if (isCoordinator) {
|
|
198
|
+
const subagents = db
|
|
199
|
+
.query<any, []>("SELECT id, name, description FROM agents WHERE is_coordinator = 0 AND enabled = 1")
|
|
200
|
+
.all()
|
|
201
|
+
|
|
202
|
+
if (subagents.length > 0) {
|
|
203
|
+
const subagentList = subagents
|
|
204
|
+
.map((a: any) => `- **${a.name}** (${a.id}): ${a.description || "No description"}`)
|
|
205
|
+
.join("\n")
|
|
206
|
+
|
|
207
|
+
parts.push(`# AVAILABLE SUBAGENTS FOR DELEGATION
|
|
208
|
+
You can delegate tasks to the following specialized agents:
|
|
209
|
+
|
|
210
|
+
${subagentList}
|
|
211
|
+
|
|
212
|
+
Use the create_agent tool when you need a new specialized agent.
|
|
213
|
+
`)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const now = new Date()
|
|
218
|
+
const userTimezone = userRow?.timezone || "UTC"
|
|
219
|
+
let userLocalTime = "Not configured"
|
|
220
|
+
try {
|
|
221
|
+
userLocalTime = now.toLocaleString("en-US", {
|
|
222
|
+
timeZone: userTimezone,
|
|
223
|
+
dateStyle: "full",
|
|
224
|
+
timeStyle: "long",
|
|
225
|
+
})
|
|
226
|
+
} catch (e) {
|
|
227
|
+
log.warn(`[prompt-builder] Invalid timezone: ${userTimezone}`)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
parts.push(`# TECHNICAL INFO
|
|
231
|
+
- agent_id: ${agentId} (internal use only, never expose to user)
|
|
232
|
+
- user_id: ${userId}
|
|
233
|
+
- thread_id: Use this for memory/context
|
|
234
|
+
- canvas_session: canvas:${userId} (Target this session for canvas tools)
|
|
235
|
+
|
|
236
|
+
# ENVIRONMENT
|
|
237
|
+
- Server Time (UTC): ${now.toISOString()}
|
|
238
|
+
- User Local Time: ${userLocalTime}
|
|
239
|
+
- User Timezone: ${userTimezone}
|
|
240
|
+
- fecha_usuario: ${getUserDate(userTimezone, now)}
|
|
241
|
+
- hora_usuario: ${getUserTime(userTimezone, now)}
|
|
242
|
+
- You are running on an environment where the "User Local Time" is the source of truth for the user. Use this to answer any temporal queries like "today", "tomorrow", "at 9 AM", etc.
|
|
243
|
+
|
|
244
|
+
# TAREA PROGRAMADA (CRON)
|
|
245
|
+
|
|
246
|
+
## Conversión de lenguaje natural a cron
|
|
247
|
+
|
|
248
|
+
Cuando el usuario pida crear una tarea programada (ej: "recuérdame esto cada día a las 8am"),
|
|
249
|
+
DEBES interpretar el schedule en lenguaje natural y convertirlo a expresión cron de 5 campos:
|
|
250
|
+
minuto, hora, día del mes, mes, día de la semana.
|
|
251
|
+
|
|
252
|
+
NUNCA le pidas al usuario que escriba cron syntax — hazlo tú.
|
|
253
|
+
|
|
254
|
+
### Ejemplos de conversión:
|
|
255
|
+
|
|
256
|
+
| Lenguaje natural | Expresión cron |
|
|
257
|
+
|-----------------|----------------|
|
|
258
|
+
| "todos los días a las 8am" | 0 8 * * * |
|
|
259
|
+
| "cada lunes a las 9am" | 0 9 * * 1 |
|
|
260
|
+
| "cada hora" | 0 * * * * |
|
|
261
|
+
| "cada 30 minutos" | */30 * * * * |
|
|
262
|
+
| "los viernes a las 6pm" | 0 18 * * 5 |
|
|
263
|
+
| "el primer día de cada mes" | 0 9 1 * * |
|
|
264
|
+
| "cada día de semana a las 7am" | 0 7 * * 1-5 |
|
|
265
|
+
| "cada 15 minutos" | */15 * * * * |
|
|
266
|
+
| "a medianoche" | 0 0 * * * |
|
|
267
|
+
|
|
268
|
+
## Mostrar fechas al usuario
|
|
269
|
+
|
|
270
|
+
Cuando inmueves fechas o horas de tareas programadas al usuario, SIEMPRE:
|
|
271
|
+
1. Convierte la fecha/hora UTC a la zona horaria del usuario (${userTimezone})
|
|
272
|
+
2. Exprésala en lenguaje natural, nunca en formato técnico
|
|
273
|
+
|
|
274
|
+
MALO: "La próxima ejecución es 1738344000"
|
|
275
|
+
BUENO: "La próxima ejecución es mañana a las 8:00am"
|
|
276
|
+
|
|
277
|
+
MALO: "Schedule: 0 8 * * *"
|
|
278
|
+
BUENO: "Se ejecutará todos los días a las 8:00am"
|
|
279
|
+
|
|
280
|
+
## Cambio de timezone del usuario
|
|
281
|
+
|
|
282
|
+
Si el usuario cambia su zona horaria (ej: "ahora estoy en Ciudad de México"),
|
|
283
|
+
DEBES recalcular el next_run de todas sus tareas programadas existentes:
|
|
284
|
+
|
|
285
|
+
1. Obtén la nueva zona horaria del usuario
|
|
286
|
+
2. Para cada tarea programada, usa croner con la nueva timezone para recalcular
|
|
287
|
+
3. Actualiza cada tarea en la base de datos con el nuevo next_run
|
|
288
|
+
|
|
289
|
+
Ejemplo de cómo recalcular:
|
|
290
|
+
- Nueva timezone: America/Mexico_City
|
|
291
|
+
- Expresión cron original: 0 8 * * *
|
|
292
|
+
- Nue Calcula el próximo run usando croner con la nueva timezone
|
|
293
|
+
- Actualiza el campo next_run en la base de datos
|
|
294
|
+
`)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
// Instruir al agente para reportar progreso y narrar acciones
|
|
298
|
+
parts.push(`# COMMUNICATION GUIDELINES
|
|
299
|
+
|
|
300
|
+
## Narración Obligatoria
|
|
301
|
+
|
|
302
|
+
Durante tareas que toman tiempo en completarse, DEBES narrar tus acciones en lenguaje natural
|
|
303
|
+
ANTES y DESPUÉS de usar cada herramienta. Esto mantiene al usuario informado en tiempo real.
|
|
304
|
+
|
|
305
|
+
### Antes de usar una herramienta:
|
|
306
|
+
|
|
307
|
+
Escribe UNA frase corta explicando qué vas a hacer. Esta frase llegará al usuario inmediatamente.
|
|
308
|
+
|
|
309
|
+
Ejemplos de narración correcta:
|
|
310
|
+
- Antes de buscar en internet: "Buscando en internet información sobre..."
|
|
311
|
+
- Antes de revisar código: "Revisando el archivo auth.ts..."
|
|
312
|
+
- Antes de activar un subagente: "Activando bee_test para ejecutar los tests..."
|
|
313
|
+
- Antes de analizar documentos: "Analizando los documentos de la carpeta..."
|
|
314
|
+
- Antes de hacer un commit: "Voy a hacer commit con los cambios corregidos..."
|
|
315
|
+
- Antes de ejecutar tests: "Ejecutando los tests unitarios..."
|
|
316
|
+
|
|
317
|
+
### Después de usar una herramienta:
|
|
318
|
+
|
|
319
|
+
Escribe UNA frase corta resumiendo qué encontraste o qué hiciste. NUNCA incluyas datos técnicos
|
|
320
|
+
ni JSON en la narración. Solo el resumen humano.
|
|
321
|
+
|
|
322
|
+
Ejemplos de narración correcta:
|
|
323
|
+
- Después de encontrar errores: "Encontré 3 problemas en el código, voy a corregirlos uno por uno"
|
|
324
|
+
- Después de una búsqueda: "Encontré información relevante, analizándola..."
|
|
325
|
+
- Después de ejecutar tests: "Los tests pasaron correctamente, no hay errores"
|
|
326
|
+
- Después de analizar archivos: "Hay 5 archivos modificados en el último commit"
|
|
327
|
+
|
|
328
|
+
### Reglas importantes:
|
|
329
|
+
|
|
330
|
+
1. El resultado CRUDO de las herramientas (JSON, arrays, datos técnicos) NUNCA debe llegar al usuario
|
|
331
|
+
2. Solo tu narración en lenguaje natural llega al usuario
|
|
332
|
+
3. Si una tarea tiene múltiples pasos, menciona brevemente al inicio qué vas a hacer
|
|
333
|
+
4. Usa la herramienta "report_progress" SOLO para estados formales (started, thinking, searching, processing, writing, completed, error, info)
|
|
334
|
+
|
|
335
|
+
El usuario no debe esperar a ciegas - manténgase informado de cada paso importante.
|
|
336
|
+
`)
|
|
337
|
+
|
|
338
|
+
parts.push(`
|
|
339
|
+
# TOON FORMAT
|
|
340
|
+
|
|
341
|
+
Tool and MCP responses use TOON (Token-Oriented Object Notation), a compact JSON alternative:
|
|
342
|
+
|
|
343
|
+
## Arrays (tabular format):
|
|
344
|
+
\`\`\`
|
|
345
|
+
field1,field2,field3
|
|
346
|
+
value1,value2,value3
|
|
347
|
+
value4,value5,value6
|
|
348
|
+
\`\`\`
|
|
349
|
+
|
|
350
|
+
## Simple objects:
|
|
351
|
+
\`\`\`
|
|
352
|
+
name: Alice
|
|
353
|
+
age: 30
|
|
354
|
+
active: true
|
|
355
|
+
\`\`\`
|
|
356
|
+
|
|
357
|
+
## Nested objects:
|
|
358
|
+
\`\`\`
|
|
359
|
+
user:
|
|
360
|
+
name: John
|
|
361
|
+
profile:
|
|
362
|
+
age: 30
|
|
363
|
+
\`\`\`
|
|
364
|
+
|
|
365
|
+
Parse all tool/MCP responses as TOON format, not JSON.
|
|
366
|
+
`)
|
|
367
|
+
|
|
368
|
+
log.info(`[CONTEXT] System prompt built in ${(performance.now() - t0).toFixed(2)}ms\n`)
|
|
369
|
+
return parts.join("\n\n")
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function loadCodeBridgeContext(userId: string): Promise<string> {
|
|
373
|
+
try {
|
|
374
|
+
const db = getDb()
|
|
375
|
+
const bridges = db
|
|
376
|
+
.query<any, [string]>(
|
|
377
|
+
"SELECT name, cli_command FROM code_bridge WHERE user_id = ? AND enabled = 1 AND active = 1"
|
|
378
|
+
)
|
|
379
|
+
.all(userId)
|
|
380
|
+
|
|
381
|
+
if (!bridges || bridges.length === 0) return ""
|
|
382
|
+
|
|
383
|
+
const list = bridges
|
|
384
|
+
.map((b: any) => `- **${b.name}**: \`${b.cli_command}\``)
|
|
385
|
+
.join("\n")
|
|
386
|
+
|
|
387
|
+
return `
|
|
388
|
+
# HERRAMIENTAS CLI DISPONIBLES PARA PROYECTOS DE CÓDIGO
|
|
389
|
+
|
|
390
|
+
Tienes acceso a las siguientes herramientas de desarrollo activadas en tu entorno:
|
|
391
|
+
|
|
392
|
+
${list}
|
|
393
|
+
|
|
394
|
+
Úsalas con la herramienta de ejecución de comandos cuando trabajes en proyectos de tipo "code".
|
|
395
|
+
Casos de uso habituales: linting, tests, build, git, deploy, formateo.
|
|
396
|
+
`
|
|
397
|
+
} catch {
|
|
398
|
+
return ""
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export async function buildSystemPromptWithProjects(options: BuildPromptOptions): Promise<string> {
|
|
403
|
+
const basePrompt = await buildSystemPrompt(options)
|
|
404
|
+
|
|
405
|
+
const codeBridgeSection = await loadCodeBridgeContext(options.userId)
|
|
406
|
+
|
|
407
|
+
const projectInstructions = `
|
|
408
|
+
# GESTIÓN DE PROYECTOS Y TAREAS COMPLEJAS
|
|
409
|
+
|
|
410
|
+
Cuando recibas una solicitud que involucre código, múltiples pasos interdependientes,
|
|
411
|
+
investigación estructurada, creación de contenido extenso, análisis de datos, o cualquier
|
|
412
|
+
trabajo que requiera más de 2 herramientas o pasos — SIEMPRE debes:
|
|
413
|
+
|
|
414
|
+
1. Llamar \`project_create\` ANTES de comenzar el trabajo
|
|
415
|
+
2. Descomponer el trabajo en tareas atómicas dentro del mismo \`project_create\`
|
|
416
|
+
3. Llamar \`task_update\` al iniciar y al completar cada tarea
|
|
417
|
+
4. Llamar \`project_done\` o \`project_fail\` al finalizar
|
|
418
|
+
|
|
419
|
+
## SEÑALES DE QUE NECESITAS UN PROYECTO
|
|
420
|
+
|
|
421
|
+
- La solicitud usa verbos como: crear, construir, desarrollar, analizar, investigar, escribir, refactorizar, optimizar
|
|
422
|
+
- Tu plan mental tiene más de 2 pasos
|
|
423
|
+
- El resultado final tiene múltiples entregables (archivos, reportes, módulos)
|
|
424
|
+
- Necesitas usar más de 2 herramientas diferentes
|
|
425
|
+
- La tarea tomará varios turnos de conversación
|
|
426
|
+
|
|
427
|
+
## TIPOS DE PROYECTO
|
|
428
|
+
|
|
429
|
+
| Tipo | Cuándo usarlo |
|
|
430
|
+
|------|---------------|
|
|
431
|
+
| \`code\` | Desarrollo de software, scripts, APIs, debugging, refactoring |
|
|
432
|
+
| \`research\` | Investigación, análisis de mercado, síntesis de información |
|
|
433
|
+
| \`content\` | Escritura, documentación, presentaciones, emails extensos |
|
|
434
|
+
| \`data\` | Análisis de datos, reportes, visualizaciones |
|
|
435
|
+
| \`general\` | Cualquier otra tarea estructurada de múltiples pasos |
|
|
436
|
+
|
|
437
|
+
## HERRAMIENTAS DISPONIBLES
|
|
438
|
+
|
|
439
|
+
### \`project_create\` — Crear proyecto con tareas
|
|
440
|
+
Parámetros:
|
|
441
|
+
- \`name\`: Nombre descriptivo del proyecto
|
|
442
|
+
- \`description\`: Objetivo del proyecto
|
|
443
|
+
- \`type\`: "code" | "research" | "content" | "data" | "general"
|
|
444
|
+
- \`tasks\`: Lista de tareas con \`name\`, \`description\`, y opcionalmente \`agent_id\`
|
|
445
|
+
- Retorna: \`{ projectId, taskIds[] }\` — guarda estos IDs para las llamadas posteriores
|
|
446
|
+
|
|
447
|
+
### \`task_update\` — Actualizar estado de una tarea
|
|
448
|
+
Parámetros:
|
|
449
|
+
- \`task_id\`: ID numérico de la tarea (de taskIds[])
|
|
450
|
+
- \`status\`: "pending" | "in_progress" | "completed" | "failed" | "blocked"
|
|
451
|
+
- \`progress\`: 0–100
|
|
452
|
+
- \`result\`: Resultado o resumen al completar
|
|
453
|
+
- \`agent_id\`: Reasignar a otro agente (opcional)
|
|
454
|
+
|
|
455
|
+
### \`task_create\` — Agregar tarea extra a un proyecto existente
|
|
456
|
+
### \`project_update\` — Actualizar progreso global del proyecto
|
|
457
|
+
### \`project_done\` — Marcar proyecto completado (progress=100)
|
|
458
|
+
### \`project_fail\` — Marcar proyecto fallido con motivo
|
|
459
|
+
|
|
460
|
+
## EJEMPLO 1 — Proyecto de código
|
|
461
|
+
|
|
462
|
+
**Usuario:** "Crea una API REST en Node.js con autenticación JWT y base de datos SQLite"
|
|
463
|
+
|
|
464
|
+
\`\`\`
|
|
465
|
+
1. project_create({
|
|
466
|
+
name: "API REST Node.js con JWT",
|
|
467
|
+
description: "API con autenticación JWT y persistencia SQLite",
|
|
468
|
+
type: "code",
|
|
469
|
+
tasks: [
|
|
470
|
+
{ name: "Estructura y dependencias", description: "Inicializar proyecto, package.json, instalar deps" },
|
|
471
|
+
{ name: "Configurar SQLite", description: "Schema de base de datos, migrations, conexión" },
|
|
472
|
+
{ name: "Autenticación JWT", description: "Rutas /login y /register, middleware de verificación" },
|
|
473
|
+
{ name: "Endpoints CRUD", description: "Rutas protegidas con JWT" },
|
|
474
|
+
{ name: "Tests y documentación", description: "Tests básicos y README" }
|
|
475
|
+
]
|
|
476
|
+
})
|
|
477
|
+
→ Recibe: { projectId: "abc123", taskIds: [1, 2, 3, 4, 5] }
|
|
478
|
+
|
|
479
|
+
2. task_update({ task_id: 1, status: "in_progress", progress: 0 })
|
|
480
|
+
[realiza trabajo de la tarea 1]
|
|
481
|
+
task_update({ task_id: 1, status: "completed", progress: 100, result: "package.json y deps instalados" })
|
|
482
|
+
|
|
483
|
+
3. task_update({ task_id: 2, status: "in_progress", progress: 0 })
|
|
484
|
+
[realiza trabajo de la tarea 2]
|
|
485
|
+
task_update({ task_id: 2, status: "completed", progress: 100, result: "Schema SQL: users(id, email, password_hash)" })
|
|
486
|
+
|
|
487
|
+
4. [... continúa con tareas 3, 4, 5 ...]
|
|
488
|
+
|
|
489
|
+
5. project_done({ projectId: "abc123", summary: "API REST completa. 5 endpoints. Tests OK." })
|
|
490
|
+
\`\`\`
|
|
491
|
+
|
|
492
|
+
## EJEMPLO 2 — Proyecto con delegación a sub-agentes
|
|
493
|
+
|
|
494
|
+
**Usuario:** "Investiga competidores, crea una landing page y un plan de marketing"
|
|
495
|
+
|
|
496
|
+
\`\`\`
|
|
497
|
+
1. project_create({
|
|
498
|
+
name: "Lanzamiento de producto",
|
|
499
|
+
type: "general",
|
|
500
|
+
tasks: [
|
|
501
|
+
{ name: "Análisis de competidores", description: "Investigar 5 competidores principales" },
|
|
502
|
+
{ name: "Diseño de landing page", description: "Estructura y copy de la página" },
|
|
503
|
+
{ name: "Plan de marketing", description: "Estrategia de canales y mensajes" }
|
|
504
|
+
]
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
2. create_agent({ name: "researcher", description: "Especialista en análisis de mercado" })
|
|
508
|
+
create_agent({ name: "copywriter", description: "Especialista en contenido web y marketing" })
|
|
509
|
+
|
|
510
|
+
3. task_update({ task_id: [id tarea 1], status: "in_progress", agent_id: [id researcher] })
|
|
511
|
+
[researcher investiga]
|
|
512
|
+
task_update({ task_id: [id], status: "completed", result: "Reporte de 5 competidores completo" })
|
|
513
|
+
|
|
514
|
+
4. task_update({ task_id: [id tarea 2], status: "in_progress", agent_id: [id copywriter] })
|
|
515
|
+
[copywriter crea landing]
|
|
516
|
+
task_update({ task_id: [id], status: "completed", result: "Landing page con 5 secciones" })
|
|
517
|
+
|
|
518
|
+
5. project_done({ projectId: "...", summary: "Landing, análisis y plan de marketing completados" })
|
|
519
|
+
\`\`\`
|
|
520
|
+
|
|
521
|
+
## REGLAS CRÍTICAS
|
|
522
|
+
|
|
523
|
+
1. **SIEMPRE** crea el proyecto ANTES de empezar cualquier trabajo complejo
|
|
524
|
+
2. **NUNCA** dejes una tarea en "in_progress" sin actualizarla al terminar
|
|
525
|
+
3. Si una tarea falla, márcala como "failed" con el error en "result"
|
|
526
|
+
4. El progreso del proyecto se calcula automáticamente como promedio de las tareas
|
|
527
|
+
5. Usa \`task_create\` para agregar tareas imprevistas que surjan durante el trabajo
|
|
528
|
+
6. Narra siempre al usuario qué tarea estás ejecutando antes de hacerlo
|
|
529
|
+
`
|
|
530
|
+
|
|
531
|
+
return basePrompt + "\n\n" + projectInstructions + codeBridgeSection
|
|
532
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentRunner — thin wrapper over the native AgentLoop.
|
|
3
|
+
*
|
|
4
|
+
* Keeps the same public API (generate()) so server.ts doesn't need changes.
|
|
5
|
+
* Internally uses agent-loop.ts instead of LangGraph.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Config } from "../../config/loader.ts"
|
|
9
|
+
import { logger } from "../../utils/logger.ts"
|
|
10
|
+
import { getDb } from "../../storage/sqlite.ts"
|
|
11
|
+
import { getSupervisorGraph } from "../supervisor.ts"
|
|
12
|
+
|
|
13
|
+
export type Provider = "openai" | "anthropic" | "gemini" | "mistral" | "kimi" | "ollama" | "openrouter" | "deepseek"
|
|
14
|
+
|
|
15
|
+
export interface StepEvent {
|
|
16
|
+
type: "text" | "plan" | "tool_call" | "tool_result"
|
|
17
|
+
message: string
|
|
18
|
+
toolName?: string
|
|
19
|
+
isError?: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ModelOptions {
|
|
23
|
+
provider?: Provider
|
|
24
|
+
model?: string
|
|
25
|
+
maxTokens?: number
|
|
26
|
+
temperature?: number
|
|
27
|
+
system?: string
|
|
28
|
+
messages: Array<{ role: string; content: string }>
|
|
29
|
+
tools?: Record<string, any>
|
|
30
|
+
maxSteps?: number
|
|
31
|
+
onToken?: (token: string) => void
|
|
32
|
+
onStep?: (step: StepEvent) => Promise<void>
|
|
33
|
+
threadId?: string
|
|
34
|
+
userId?: string
|
|
35
|
+
channel?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ModelResponse {
|
|
39
|
+
content: string
|
|
40
|
+
toolCalls?: Array<{
|
|
41
|
+
id: string
|
|
42
|
+
name: string
|
|
43
|
+
args: Record<string, unknown>
|
|
44
|
+
}>
|
|
45
|
+
usage?: {
|
|
46
|
+
promptTokens: number
|
|
47
|
+
completionTokens: number
|
|
48
|
+
totalTokens: number
|
|
49
|
+
}
|
|
50
|
+
finishReason?: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class AgentRunner {
|
|
54
|
+
private config: Config
|
|
55
|
+
|
|
56
|
+
constructor(config: Config) {
|
|
57
|
+
this.config = config
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async generate(options: ModelOptions): Promise<ModelResponse> {
|
|
61
|
+
const db = getDb()
|
|
62
|
+
const coordinatorAgent = db
|
|
63
|
+
.query<any, []>("SELECT id FROM agents WHERE is_coordinator = 1 LIMIT 1")
|
|
64
|
+
.get()
|
|
65
|
+
const agentId = process.env.HIVE_AGENT_ID || coordinatorAgent?.id || "main"
|
|
66
|
+
|
|
67
|
+
const userId = options.userId || process.env.HIVE_USER_ID
|
|
68
|
+
if (!userId) {
|
|
69
|
+
throw new Error("No userId provided. Please set HIVE_USER_ID or complete onboarding.")
|
|
70
|
+
}
|
|
71
|
+
const threadId = options.threadId || userId
|
|
72
|
+
|
|
73
|
+
const agentLoop = getSupervisorGraph()
|
|
74
|
+
if (!agentLoop) {
|
|
75
|
+
throw new Error("AgentLoop not initialized")
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let lastAgentContent = ""
|
|
79
|
+
let toolCalls: ModelResponse["toolCalls"] = []
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const stream = agentLoop.stream(
|
|
83
|
+
{ messages: options.messages },
|
|
84
|
+
{
|
|
85
|
+
configurable: {
|
|
86
|
+
thread_id: threadId,
|
|
87
|
+
agent_id: agentId,
|
|
88
|
+
user_id: userId,
|
|
89
|
+
// system_prompt intentionally omitted — context-compiler builds it
|
|
90
|
+
channel: options.channel,
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
let chunkCount = 0
|
|
96
|
+
for await (const chunk of stream) {
|
|
97
|
+
chunkCount++
|
|
98
|
+
|
|
99
|
+
if (chunk.agent?.messages) {
|
|
100
|
+
const lastMsg = chunk.agent.messages[chunk.agent.messages.length - 1]
|
|
101
|
+
const hasToolCalls = (lastMsg as any)?.tool_calls?.length > 0
|
|
102
|
+
logger.info(
|
|
103
|
+
`[STREAM] chunk#${chunkCount} agent: contentLen=${lastMsg?.content?.length ?? 0} hasToolCalls=${hasToolCalls}`
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if (lastMsg?.content) {
|
|
107
|
+
const content = typeof lastMsg.content === "string" ? lastMsg.content : ""
|
|
108
|
+
lastAgentContent = content
|
|
109
|
+
if (options.onToken) options.onToken(content)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (hasToolCalls) {
|
|
113
|
+
toolCalls = (lastMsg as any).tool_calls.map((tc: any) => ({
|
|
114
|
+
id: tc.id || tc.function?.name,
|
|
115
|
+
name: tc.function?.name || tc.name,
|
|
116
|
+
args: tc.function?.arguments
|
|
117
|
+
? (typeof tc.function.arguments === "string"
|
|
118
|
+
? JSON.parse(tc.function.arguments)
|
|
119
|
+
: tc.function.arguments)
|
|
120
|
+
: {},
|
|
121
|
+
}))
|
|
122
|
+
|
|
123
|
+
const narration = lastMsg?.content || ""
|
|
124
|
+
if (options.onStep && narration) {
|
|
125
|
+
await options.onStep({ type: "text", message: narration })
|
|
126
|
+
}
|
|
127
|
+
if (options.onStep) {
|
|
128
|
+
for (const tc of toolCalls) {
|
|
129
|
+
await options.onStep({
|
|
130
|
+
type: "tool_call",
|
|
131
|
+
toolName: tc.name,
|
|
132
|
+
message: `Calling tool: \`${tc.name}\``,
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (chunk.tools?.messages) {
|
|
140
|
+
const lastMsg = chunk.tools.messages[chunk.tools.messages.length - 1]
|
|
141
|
+
if (lastMsg?.content && options.onStep) {
|
|
142
|
+
await options.onStep({
|
|
143
|
+
type: "tool_result",
|
|
144
|
+
message: typeof lastMsg.content === "string" ? lastMsg.content : "",
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
logger.debug(`[STREAM] done. totalChunks=${chunkCount} lastAgentContent length=${lastAgentContent.length}`)
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
content: lastAgentContent,
|
|
154
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
155
|
+
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
156
|
+
finishReason: "stop",
|
|
157
|
+
}
|
|
158
|
+
} catch (error) {
|
|
159
|
+
logger.error("AgentRunner error:", error)
|
|
160
|
+
throw error
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function createAgentRunner(config: Config): AgentRunner {
|
|
166
|
+
return new AgentRunner(config)
|
|
167
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./providers/index.ts";
|