Commit afe932f9ca2d

Vincent Demeester <vincent@sbr.pm>
2026-02-16 14:35:36
Phase 4 Complete: Tool integration with TDD (RED-GREEN)
RED phase: - Created 4 failing tests for tool integration - Tests covered: tools in constructor, tool calling, no tools case - Verified tests fail with expected errors GREEN phase: - Added tools parameter to XmppAgent constructor - Pass tools to Pi Agent initialState - Added getTools() method for inspection - Updated system prompt to mention tool usage - Integrated statusTool in main-test.ts - All 24 tests pass ✅ Agent can now call tools! The status tool is fully functional. Total: 24 tests passing: - 15 agent wrapper - 5 status tool - 4 tool integration Next: Phase 5 (slash commands already done) or Phase 6 (XMPP integration)
src/pi/agent-wrapper-tools.test.ts
@@ -0,0 +1,58 @@
+/**
+ * Tests for Agent Tool Integration
+ * 
+ * RED phase: Write these tests first, verify they fail
+ * GREEN phase: Implement tool integration
+ * REFACTOR phase: Clean up
+ */
+
+import { describe, it, expect, beforeEach } from "vitest";
+import { XmppAgent } from "./agent-wrapper.js";
+import { getModel } from "@mariozechner/pi-ai";
+import { statusTool } from "./tools/status.js";
+
+describe("XmppAgent Tool Integration", () => {
+  let agent: XmppAgent;
+  const testJid = "test@xmpp.sbr.pm";
+  
+  beforeEach(async () => {
+    const model = getModel("google", "gemini-2.0-flash");
+    agent = new XmppAgent(testJid, model, [statusTool]);
+  });
+
+  it("should accept tools in constructor", () => {
+    expect(agent).toBeDefined();
+    const tools = agent.getTools();
+    expect(tools).toHaveLength(1);
+    expect(tools[0].name).toBe("status");
+  });
+
+  it("should allow agent to call status tool", async () => {
+    // Ask a question that should trigger the status tool
+    const response = await agent.processMessage("What is the system status?");
+    
+    expect(response).toBeDefined();
+    expect(typeof response).toBe("string");
+    // Response should contain system information
+    expect(response.toLowerCase()).toMatch(/uptime|memory|load/);
+  }, 15000); // Longer timeout for LLM call with tool
+
+  it("should handle messages that don't need tools", async () => {
+    const response = await agent.processMessage("Hello, how are you?");
+    
+    expect(response).toBeDefined();
+    expect(typeof response).toBe("string");
+    // Should respond without calling tools
+  }, 15000);
+
+  it("should work without any tools", async () => {
+    const model = getModel("google", "gemini-2.0-flash");
+    const agentNoTools = new XmppAgent(testJid, model);
+    
+    const tools = agentNoTools.getTools();
+    expect(tools).toHaveLength(0);
+    
+    const response = await agentNoTools.processMessage("Hello");
+    expect(response).toBeDefined();
+  }, 15000);
+});
src/pi/agent-wrapper.ts
@@ -3,7 +3,7 @@
  * Wraps Pi's Agent class with XMPP-specific concerns
  */
 
-import { Agent, type AgentMessage } from "@mariozechner/pi-agent-core";
+import { Agent, type AgentMessage, type AgentTool } from "@mariozechner/pi-agent-core";
 import type { Model } from "@mariozechner/pi-ai";
 import { getModelByPrefix, parseModelPrefix, getAllModelsFormatted } from "./config.js";
 
@@ -31,17 +31,19 @@ export class XmppAgent {
   public readonly jid: string;
   private agent: Agent;
   private currentModel: Model<any>;
+  private tools: AgentTool[];
 
-  constructor(jid: string, defaultModel: Model<any>) {
+  constructor(jid: string, defaultModel: Model<any>, tools: AgentTool[] = []) {
     this.jid = jid;
     this.currentModel = defaultModel;
+    this.tools = tools;
     
     // Initialize Pi Agent
     this.agent = new Agent({
       initialState: {
         model: defaultModel,
-        tools: [], // Will add tools in Phase 4
-        systemPrompt: "You are a helpful research assistant accessible via XMPP. Provide clear, concise, and accurate responses.",
+        tools: tools,
+        systemPrompt: "You are a helpful research assistant accessible via XMPP. Provide clear, concise, and accurate responses. Use available tools when appropriate to provide accurate information.",
         messages: [],
       },
     });
@@ -170,4 +172,11 @@ Current model: ${this.currentModel.provider}/${this.currentModel.id}`;
   getCurrentModel(): Model<any> {
     return this.currentModel;
   }
+
+  /**
+   * Get tools available to this agent
+   */
+  getTools(): AgentTool[] {
+    return this.tools;
+  }
 }
src/pi/main-test.ts
@@ -9,6 +9,7 @@ import { XmppClient } from "../xmpp/client.js";
 import { XmppAgent } from "./agent-wrapper.js";
 import { getModel } from "@mariozechner/pi-ai";
 import { bareJid } from "../xmpp/types.js";
+import { statusTool } from "./tools/status.js";
 
 interface XmppConfig {
   jid: string;
@@ -51,12 +52,8 @@ async function main() {
     
     if (!agent) {
       console.log(`Creating new agent for JID: ${bare}`);
-      agent = new XmppAgent(bare, defaultModel);
-      
-      // Add status tool
-      // TODO: This will be properly integrated in Phase 4 refactor
-      // For now, the agent doesn't use tools yet (empty tools array in agent-wrapper.ts)
-      
+      // Create agent with status tool
+      agent = new XmppAgent(bare, defaultModel, [statusTool]);
       agents.set(bare, agent);
     }