@clinebot/agents 0.0.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.
Files changed (90) hide show
  1. package/README.md +145 -0
  2. package/dist/agent-input.d.ts +2 -0
  3. package/dist/agent.d.ts +56 -0
  4. package/dist/extensions.d.ts +21 -0
  5. package/dist/hooks/engine.d.ts +42 -0
  6. package/dist/hooks/index.d.ts +2 -0
  7. package/dist/hooks/lifecycle.d.ts +5 -0
  8. package/dist/hooks/node.d.ts +2 -0
  9. package/dist/hooks/subprocess-runner.d.ts +16 -0
  10. package/dist/hooks/subprocess.d.ts +268 -0
  11. package/dist/index.browser.d.ts +1 -0
  12. package/dist/index.browser.js +49 -0
  13. package/dist/index.d.ts +15 -0
  14. package/dist/index.js +49 -0
  15. package/dist/index.node.d.ts +5 -0
  16. package/dist/index.node.js +49 -0
  17. package/dist/mcp/index.d.ts +4 -0
  18. package/dist/mcp/policies.d.ts +14 -0
  19. package/dist/mcp/tools.d.ts +9 -0
  20. package/dist/mcp/types.d.ts +35 -0
  21. package/dist/message-builder.d.ts +31 -0
  22. package/dist/prompts/cline.d.ts +1 -0
  23. package/dist/prompts/index.d.ts +1 -0
  24. package/dist/runtime/agent-runtime-bus.d.ts +13 -0
  25. package/dist/runtime/conversation-store.d.ts +16 -0
  26. package/dist/runtime/lifecycle-orchestrator.d.ts +28 -0
  27. package/dist/runtime/tool-orchestrator.d.ts +39 -0
  28. package/dist/runtime/turn-processor.d.ts +21 -0
  29. package/dist/teams/index.d.ts +3 -0
  30. package/dist/teams/multi-agent.d.ts +566 -0
  31. package/dist/teams/spawn-agent-tool.d.ts +85 -0
  32. package/dist/teams/team-tools.d.ts +51 -0
  33. package/dist/tools/ask-question.d.ts +12 -0
  34. package/dist/tools/create.d.ts +59 -0
  35. package/dist/tools/execution.d.ts +61 -0
  36. package/dist/tools/formatting.d.ts +20 -0
  37. package/dist/tools/index.d.ts +11 -0
  38. package/dist/tools/registry.d.ts +26 -0
  39. package/dist/tools/validation.d.ts +27 -0
  40. package/dist/types.d.ts +826 -0
  41. package/package.json +54 -0
  42. package/src/agent-input.ts +116 -0
  43. package/src/agent.test.ts +931 -0
  44. package/src/agent.ts +1050 -0
  45. package/src/example.test.ts +564 -0
  46. package/src/extensions.ts +337 -0
  47. package/src/hooks/engine.test.ts +163 -0
  48. package/src/hooks/engine.ts +537 -0
  49. package/src/hooks/index.ts +6 -0
  50. package/src/hooks/lifecycle.ts +239 -0
  51. package/src/hooks/node.ts +18 -0
  52. package/src/hooks/subprocess-runner.ts +140 -0
  53. package/src/hooks/subprocess.test.ts +180 -0
  54. package/src/hooks/subprocess.ts +620 -0
  55. package/src/index.browser.ts +1 -0
  56. package/src/index.node.ts +21 -0
  57. package/src/index.ts +133 -0
  58. package/src/mcp/index.ts +17 -0
  59. package/src/mcp/policies.test.ts +51 -0
  60. package/src/mcp/policies.ts +53 -0
  61. package/src/mcp/tools.test.ts +76 -0
  62. package/src/mcp/tools.ts +60 -0
  63. package/src/mcp/types.ts +41 -0
  64. package/src/message-builder.test.ts +175 -0
  65. package/src/message-builder.ts +429 -0
  66. package/src/prompts/cline.ts +49 -0
  67. package/src/prompts/index.ts +1 -0
  68. package/src/runtime/agent-runtime-bus.ts +53 -0
  69. package/src/runtime/conversation-store.ts +61 -0
  70. package/src/runtime/lifecycle-orchestrator.ts +90 -0
  71. package/src/runtime/tool-orchestrator.ts +177 -0
  72. package/src/runtime/turn-processor.ts +250 -0
  73. package/src/streaming.test.ts +197 -0
  74. package/src/streaming.ts +307 -0
  75. package/src/teams/index.ts +63 -0
  76. package/src/teams/multi-agent.lifecycle.test.ts +48 -0
  77. package/src/teams/multi-agent.ts +1866 -0
  78. package/src/teams/spawn-agent-tool.test.ts +172 -0
  79. package/src/teams/spawn-agent-tool.ts +223 -0
  80. package/src/teams/team-tools.test.ts +448 -0
  81. package/src/teams/team-tools.ts +929 -0
  82. package/src/tools/ask-question.ts +78 -0
  83. package/src/tools/create.ts +104 -0
  84. package/src/tools/execution.ts +311 -0
  85. package/src/tools/formatting.ts +73 -0
  86. package/src/tools/index.ts +45 -0
  87. package/src/tools/registry.ts +52 -0
  88. package/src/tools/tools.test.ts +292 -0
  89. package/src/tools/validation.ts +73 -0
  90. package/src/types.ts +966 -0
@@ -0,0 +1,564 @@
1
+ /**
2
+ * Example: Building Agentic Loops with Sub-Agent Spawning
3
+ *
4
+ * This example demonstrates how to use the @clinebot/agents, @clinebot/llms,
5
+ * and @clinebot/llms packages together to build an agentic loop where the
6
+ * main agent can spawn sub-agents to handle specialized tasks.
7
+ *
8
+ * Features demonstrated:
9
+ * - Creating an Agent with a user-provided system prompt
10
+ * - Defining custom tools
11
+ * - Spawning sub-agents via tool calls (LLM can create new agents)
12
+ * - Handling agent events for streaming output
13
+ * - Using the models package to query available models
14
+ */
15
+
16
+ import { providers } from "@clinebot/llms";
17
+ import { Agent, type AgentEvent, createTool, type Tool } from "./index.js";
18
+
19
+ // Note: When workspace is linked, you can also import from @clinebot/llms:
20
+ // import { getModel, queryModels } from "@clinebot/llms"
21
+
22
+ // =============================================================================
23
+ // Configuration
24
+ // =============================================================================
25
+
26
+ interface AgentSpawnerConfig {
27
+ /** Provider ID (e.g., "anthropic", "openai", "gemini") */
28
+ providerId: string;
29
+ /** Model ID to use */
30
+ modelId: string;
31
+ /** API key for the provider */
32
+ apiKey: string;
33
+ /** Optional base URL for the API */
34
+ baseUrl?: string;
35
+ /** Maximum iterations for spawned sub-agents */
36
+ subAgentMaxIterations?: number;
37
+ }
38
+
39
+ // =============================================================================
40
+ // Sub-Agent Tool
41
+ // =============================================================================
42
+
43
+ interface SpawnAgentInput {
44
+ systemPrompt: string;
45
+ task: string;
46
+ maxIterations?: number;
47
+ }
48
+
49
+ interface SpawnAgentOutput {
50
+ text: string;
51
+ iterations: number;
52
+ finishReason: string;
53
+ usage: {
54
+ inputTokens: number;
55
+ outputTokens: number;
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Creates a tool that allows the LLM to spawn new agents with custom system prompts.
61
+ *
62
+ * This enables patterns like:
63
+ * - Task delegation: Main agent spawns specialists for specific tasks
64
+ * - Parallel execution: Multiple sub-agents working on different aspects
65
+ * - Chain-of-thought delegation: Breaking complex problems into sub-problems
66
+ */
67
+ function createSpawnAgentTool(
68
+ config: AgentSpawnerConfig,
69
+ ): Tool<SpawnAgentInput, SpawnAgentOutput> {
70
+ return createTool<SpawnAgentInput, SpawnAgentOutput>({
71
+ name: "spawn_agent",
72
+ description: `Spawn a new AI agent with a custom system prompt to handle a specific task.
73
+ The spawned agent will run independently and return its final result.
74
+ Use this when you need to delegate a specialized task that benefits from a focused system prompt.
75
+
76
+ Examples of when to use:
77
+ - Complex code analysis that needs a specialized reviewer persona
78
+ - Creative writing that benefits from a specific author's style
79
+ - Technical explanations that need domain expertise
80
+ - Multi-step tasks that benefit from focused attention`,
81
+ inputSchema: {
82
+ type: "object",
83
+ properties: {
84
+ systemPrompt: {
85
+ type: "string",
86
+ description:
87
+ "The system prompt for the spawned agent. This defines the agent's persona, capabilities, and behavior.",
88
+ },
89
+ task: {
90
+ type: "string",
91
+ description:
92
+ "The specific task or question for the spawned agent to handle.",
93
+ },
94
+ maxIterations: {
95
+ type: "integer",
96
+ description:
97
+ "Maximum number of iterations for the sub-agent (default: 10)",
98
+ minimum: 1,
99
+ maximum: 50,
100
+ },
101
+ },
102
+ required: ["systemPrompt", "task"],
103
+ },
104
+ execute: async (input, context) => {
105
+ const { systemPrompt, task, maxIterations } = input;
106
+
107
+ // Create the sub-agent with the provided system prompt
108
+ const subAgent = new Agent({
109
+ providerId: config.providerId,
110
+ modelId: config.modelId,
111
+ apiKey: config.apiKey,
112
+ baseUrl: config.baseUrl,
113
+ systemPrompt,
114
+ tools: [], // Sub-agents don't get the spawn tool to prevent infinite recursion
115
+ maxIterations: maxIterations ?? config.subAgentMaxIterations ?? 10,
116
+ abortSignal: context.abortSignal,
117
+ onEvent: (event) => {
118
+ // Log sub-agent events with prefix for debugging
119
+ if (event.type === "content_start" && event.contentType === "text") {
120
+ console.log(`[SubAgent] ${event.text ?? ""}`);
121
+ }
122
+ },
123
+ });
124
+
125
+ // Run the sub-agent
126
+ const result = await subAgent.run(task);
127
+
128
+ return {
129
+ text: result.text,
130
+ iterations: result.iterations,
131
+ finishReason: result.finishReason,
132
+ usage: {
133
+ inputTokens: result.usage.inputTokens,
134
+ outputTokens: result.usage.outputTokens,
135
+ },
136
+ };
137
+ },
138
+ timeoutMs: 300000, // 5 minutes for sub-agent execution
139
+ retryable: false, // Don't retry sub-agent spawns
140
+ });
141
+ }
142
+
143
+ // =============================================================================
144
+ // Additional Example Tools
145
+ // =============================================================================
146
+
147
+ interface CalculateInput {
148
+ expression: string;
149
+ }
150
+
151
+ /**
152
+ * A simple calculation tool for demonstration
153
+ */
154
+ const calculatorTool = createTool<
155
+ CalculateInput,
156
+ { result: number; expression: string }
157
+ >({
158
+ name: "calculate",
159
+ description:
160
+ "Perform basic mathematical calculations. Supports +, -, *, /, and ** (power).",
161
+ inputSchema: {
162
+ type: "object",
163
+ properties: {
164
+ expression: {
165
+ type: "string",
166
+ description:
167
+ "The mathematical expression to evaluate (e.g., '2 + 2', '10 * 5', '2 ** 8')",
168
+ },
169
+ },
170
+ required: ["expression"],
171
+ },
172
+ execute: async ({ expression }) => {
173
+ // Basic sanitization - only allow numbers and operators
174
+ const sanitized = expression.replace(/[^0-9+\-*/.()\s^]/g, "");
175
+ // Replace ^ with ** for power operations
176
+ const jsExpression = sanitized.replace(/\^/g, "**");
177
+
178
+ // Simple and safe math evaluation using basic parsing
179
+ const result = evaluateMathExpression(jsExpression);
180
+ return { result, expression: jsExpression };
181
+ },
182
+ });
183
+
184
+ /**
185
+ * Simple math expression evaluator (safer than eval/Function)
186
+ */
187
+ function evaluateMathExpression(expr: string): number {
188
+ // Remove whitespace
189
+ const cleaned = expr.replace(/\s/g, "");
190
+
191
+ // Basic recursive descent parser for safety
192
+ let pos = 0;
193
+
194
+ function parseNumber(): number {
195
+ let numStr = "";
196
+ while (pos < cleaned.length) {
197
+ const char = cleaned[pos];
198
+ if (char === undefined || !/[0-9.]/.test(char)) {
199
+ break;
200
+ }
201
+ numStr += char;
202
+ pos++;
203
+ }
204
+ return Number.parseFloat(numStr);
205
+ }
206
+
207
+ function parseFactor(): number {
208
+ if (cleaned[pos] === "(") {
209
+ pos++; // skip '('
210
+ const result = parseExpression();
211
+ pos++; // skip ')'
212
+ return result;
213
+ }
214
+ if (cleaned[pos] === "-") {
215
+ pos++;
216
+ return -parseFactor();
217
+ }
218
+ return parseNumber();
219
+ }
220
+
221
+ function parsePower(): number {
222
+ let left = parseFactor();
223
+ while (pos < cleaned.length && cleaned.slice(pos, pos + 2) === "**") {
224
+ pos += 2;
225
+ left = left ** parseFactor();
226
+ }
227
+ return left;
228
+ }
229
+
230
+ function parseTerm(): number {
231
+ let left = parsePower();
232
+ while (
233
+ pos < cleaned.length &&
234
+ (cleaned[pos] === "*" || cleaned[pos] === "/")
235
+ ) {
236
+ const op = cleaned[pos++];
237
+ const right = parsePower();
238
+ left = op === "*" ? left * right : left / right;
239
+ }
240
+ return left;
241
+ }
242
+
243
+ function parseExpression(): number {
244
+ let left = parseTerm();
245
+ while (
246
+ pos < cleaned.length &&
247
+ (cleaned[pos] === "+" || cleaned[pos] === "-")
248
+ ) {
249
+ const op = cleaned[pos++];
250
+ const right = parseTerm();
251
+ left = op === "+" ? left + right : left - right;
252
+ }
253
+ return left;
254
+ }
255
+
256
+ return parseExpression();
257
+ }
258
+
259
+ interface DateTimeInput {
260
+ format: "iso" | "unix" | "readable" | "components";
261
+ timezone?: string;
262
+ }
263
+
264
+ type DateTimeOutput =
265
+ | { datetime: string; timezone: string }
266
+ | { timestamp: number; milliseconds: number }
267
+ | {
268
+ year: number;
269
+ month: number;
270
+ day: number;
271
+ hour: number;
272
+ minute: number;
273
+ second: number;
274
+ dayOfWeek: string;
275
+ };
276
+
277
+ /**
278
+ * A tool to get current date/time
279
+ */
280
+ const dateTimeTool = createTool<DateTimeInput, DateTimeOutput>({
281
+ name: "get_datetime",
282
+ description: "Get the current date and time in various formats",
283
+ inputSchema: {
284
+ type: "object",
285
+ properties: {
286
+ format: {
287
+ type: "string",
288
+ description:
289
+ "Output format: 'iso', 'unix', 'readable', or 'components'",
290
+ enum: ["iso", "unix", "readable", "components"],
291
+ },
292
+ timezone: {
293
+ type: "string",
294
+ description:
295
+ "Timezone (e.g., 'UTC', 'America/New_York'). Defaults to local timezone.",
296
+ },
297
+ },
298
+ required: ["format"],
299
+ },
300
+ execute: async ({ format, timezone }) => {
301
+ const now = new Date();
302
+
303
+ switch (format) {
304
+ case "iso":
305
+ return { datetime: now.toISOString(), timezone: timezone ?? "UTC" };
306
+ case "unix":
307
+ return {
308
+ timestamp: Math.floor(now.getTime() / 1000),
309
+ milliseconds: now.getTime(),
310
+ };
311
+ case "readable":
312
+ return {
313
+ datetime: now.toLocaleString("en-US", {
314
+ timeZone: timezone ?? undefined,
315
+ dateStyle: "full",
316
+ timeStyle: "long",
317
+ }),
318
+ timezone: timezone ?? "local",
319
+ };
320
+ case "components":
321
+ return {
322
+ year: now.getFullYear(),
323
+ month: now.getMonth() + 1,
324
+ day: now.getDate(),
325
+ hour: now.getHours(),
326
+ minute: now.getMinutes(),
327
+ second: now.getSeconds(),
328
+ dayOfWeek: now.toLocaleDateString("en-US", { weekday: "long" }),
329
+ };
330
+ default:
331
+ return { datetime: now.toISOString(), timezone: "UTC" };
332
+ }
333
+ },
334
+ });
335
+
336
+ // =============================================================================
337
+ // Event Handler
338
+ // =============================================================================
339
+
340
+ /**
341
+ * Handle agent events for logging/debugging
342
+ */
343
+ function handleAgentEvent(event: AgentEvent): void {
344
+ switch (event.type) {
345
+ case "iteration_start":
346
+ console.log(`\n--- Iteration ${event.iteration} ---`);
347
+ break;
348
+ case "content_start":
349
+ if (event.contentType === "text") {
350
+ process.stdout.write(event.text ?? "");
351
+ } else if (event.contentType === "reasoning") {
352
+ if (!event.redacted) {
353
+ console.log(`[Thinking] ${event.reasoning ?? ""}`);
354
+ }
355
+ } else if (event.contentType === "tool") {
356
+ console.log(`\n[Tool] Calling ${event.toolName}...`);
357
+ }
358
+ break;
359
+ case "content_end":
360
+ if (event.contentType === "tool") {
361
+ if (event.error) {
362
+ console.log(`[Tool] ${event.toolName} failed: ${event.error}`);
363
+ } else {
364
+ console.log(
365
+ `[Tool] ${event.toolName} completed in ${event.durationMs}ms`,
366
+ );
367
+ }
368
+ }
369
+ break;
370
+ case "usage":
371
+ console.log(
372
+ `[Usage] In: ${event.totalInputTokens}, Out: ${event.totalOutputTokens}`,
373
+ );
374
+ break;
375
+ case "done":
376
+ console.log(
377
+ `\n--- Done (${event.reason}) after ${event.iterations} iterations ---`,
378
+ );
379
+ break;
380
+ case "error":
381
+ console.error(`[Error] ${event.error.message}`);
382
+ break;
383
+ }
384
+ }
385
+
386
+ // =============================================================================
387
+ // Main Agent Factory
388
+ // =============================================================================
389
+
390
+ /**
391
+ * Creates a main orchestrator agent that can spawn sub-agents.
392
+ *
393
+ * @param systemPrompt - The system prompt defining the main agent's behavior
394
+ * @param config - Configuration for the agent and sub-agent spawning
395
+ * @param additionalTools - Additional tools to provide to the main agent
396
+ */
397
+ export function createOrchestratorAgent(
398
+ systemPrompt: string,
399
+ config: AgentSpawnerConfig,
400
+ additionalTools: Tool[] = [],
401
+ ): Agent {
402
+ // Create the spawn agent tool
403
+ const spawnTool = createSpawnAgentTool(config);
404
+
405
+ // Combine all tools (cast to Tool[] since generics are contravariant in input)
406
+ const tools = [
407
+ spawnTool,
408
+ calculatorTool,
409
+ dateTimeTool,
410
+ ...additionalTools,
411
+ ] as Tool[];
412
+
413
+ // Create and return the agent
414
+ return new Agent({
415
+ providerId: config.providerId,
416
+ modelId: config.modelId,
417
+ apiKey: config.apiKey,
418
+ baseUrl: config.baseUrl,
419
+ systemPrompt,
420
+ tools,
421
+ maxIterations: 50,
422
+ onEvent: handleAgentEvent,
423
+ });
424
+ }
425
+
426
+ // =============================================================================
427
+ // Example Usage
428
+ // =============================================================================
429
+
430
+ /**
431
+ * Example: Run the orchestrator agent with a task
432
+ */
433
+ async function runExample(): Promise<void> {
434
+ // Check for API key
435
+ const apiKey = process.env.ANTHROPIC_API_KEY;
436
+ if (!apiKey) {
437
+ console.error("Please set ANTHROPIC_API_KEY environment variable");
438
+ process.exit(1);
439
+ }
440
+
441
+ // Model to use
442
+ const modelId = "claude-sonnet-4-20250514";
443
+ console.log(`Using model: ${modelId}`);
444
+ console.log();
445
+
446
+ // Create the orchestrator agent
447
+ const orchestrator = createOrchestratorAgent(
448
+ `You are an intelligent orchestrator that can delegate tasks to specialized sub-agents.
449
+
450
+ When faced with complex tasks, consider:
451
+ 1. Breaking them into smaller, focused sub-tasks
452
+ 2. Spawning specialized agents with appropriate system prompts
453
+ 3. Synthesizing results from multiple agents
454
+
455
+ You have access to:
456
+ - spawn_agent: Create a new agent with a custom system prompt
457
+ - calculate: Perform mathematical calculations
458
+ - get_datetime: Get current date/time
459
+
460
+ Be strategic about when to spawn agents vs. handling tasks directly.`,
461
+ {
462
+ providerId: "anthropic",
463
+ modelId: "claude-sonnet-4-20250514",
464
+ apiKey,
465
+ subAgentMaxIterations: 5,
466
+ },
467
+ );
468
+
469
+ // Example task that might benefit from sub-agent delegation
470
+ const task = `I need help with two things:
471
+ 1. Calculate the compound interest on $10,000 at 5% annual rate for 10 years
472
+ 2. Write a haiku about programming
473
+
474
+ For the haiku, please spawn a specialized creative writing agent to handle it.`;
475
+
476
+ console.log("=".repeat(60));
477
+ console.log("Task:", task);
478
+ console.log("=".repeat(60));
479
+
480
+ // Run the agent
481
+ const result = await orchestrator.run(task);
482
+
483
+ // Print summary
484
+ console.log(`\n${"=".repeat(60)}`);
485
+ console.log("Final Result:");
486
+ console.log("=".repeat(60));
487
+ console.log(result.text);
488
+ console.log("\nStats:");
489
+ console.log(` - Iterations: ${result.iterations}`);
490
+ console.log(
491
+ ` - Total tokens: ${result.usage.inputTokens + result.usage.outputTokens}`,
492
+ );
493
+ console.log(` - Tool calls: ${result.toolCalls.length}`);
494
+ console.log(` - Duration: ${result.durationMs}ms`);
495
+ }
496
+
497
+ // =============================================================================
498
+ // Alternative: Lower-level Provider Usage
499
+ // =============================================================================
500
+
501
+ /**
502
+ * Example of using @clinebot/llms directly for more control
503
+ */
504
+ async function runLowLevelExample(): Promise<void> {
505
+ const apiKey = process.env.ANTHROPIC_API_KEY;
506
+ if (!apiKey) {
507
+ console.error("Please set ANTHROPIC_API_KEY environment variable");
508
+ process.exit(1);
509
+ }
510
+
511
+ // Create a handler directly using @clinebot/llms
512
+ const config: providers.ProviderConfig = {
513
+ providerId: "anthropic",
514
+ apiKey,
515
+ modelId: "claude-sonnet-4-20250514",
516
+ };
517
+
518
+ const handler = providers.createHandler(config);
519
+
520
+ // Use the handler to create a message stream
521
+ const stream = handler.createMessage(
522
+ "You are a helpful assistant.",
523
+ [{ role: "user", content: "Say hello in 3 languages." }],
524
+ [], // No tools for this simple example
525
+ );
526
+
527
+ // Process the stream
528
+ console.log("Response: ");
529
+ for await (const chunk of stream) {
530
+ if (chunk.type === "text") {
531
+ process.stdout.write(chunk.text);
532
+ }
533
+ }
534
+ console.log();
535
+ }
536
+
537
+ // =============================================================================
538
+ // Exports for Library Usage
539
+ // =============================================================================
540
+
541
+ export {
542
+ createSpawnAgentTool,
543
+ calculatorTool,
544
+ dateTimeTool,
545
+ handleAgentEvent,
546
+ type AgentSpawnerConfig,
547
+ type SpawnAgentInput,
548
+ type SpawnAgentOutput,
549
+ };
550
+
551
+ // =============================================================================
552
+ // CLI Entry Point
553
+ // =============================================================================
554
+
555
+ // Run if executed directly
556
+ if (import.meta.url === `file://${process.argv[1]}`) {
557
+ const mode = process.argv[2] ?? "orchestrator";
558
+
559
+ if (mode === "low-level") {
560
+ runLowLevelExample().catch(console.error);
561
+ } else {
562
+ runExample().catch(console.error);
563
+ }
564
+ }