Commit 18a4ef1

mo khan <mo@mokhan.ca>
2026-01-23 22:25:10
refactor: change fetch interface to emit a separate event for thinking, saying and tool_calls
1 parent 8283231
Changed files (4)
lib/elelem/net/claude.rb
@@ -38,7 +38,7 @@ module Elelem
           handle_event(event, tool_calls, &block)
         end
 
-        finalize_tool_calls(tool_calls)
+        finalize_tool_calls(tool_calls, &block)
       end
 
       private
@@ -72,19 +72,21 @@ module Elelem
 
         case delta["type"]
         when "text_delta"
-          block.call(content: delta["text"], thinking: nil)
+          block.call(type: "saying", text: delta["text"])
         when "thinking_delta"
-          block.call(content: nil, thinking: delta["thinking"])
+          block.call(type: "thinking", text: delta["thinking"])
         when "input_json_delta"
           tool_calls.last[:args] << delta["partial_json"].to_s if tool_calls.any?
         end
       end
 
-      def finalize_tool_calls(tool_calls)
+      def finalize_tool_calls(tool_calls, &block)
         tool_calls.each do |tool_call|
           args = tool_call.delete(:args)
           tool_call[:arguments] = args.empty? ? {} : JSON.parse(args)
+          block.call(type: "tool_call", id: tool_call[:id], name: tool_call[:name], arguments: tool_call[:arguments])
         end
+        tool_calls
       end
 
       def stream(messages, system_prompt, tools)
lib/elelem/net/ollama.rb
@@ -35,11 +35,14 @@ module Elelem
         message = event["message"] || {}
 
         unless event["done"]
-          block.call(content: message["content"], thinking: message["thinking"])
+          block.call(type: "saying", text: message["content"]) if message["content"]
+          block.call(type: "thinking", text: message["thinking"]) if message["thinking"]
         end
 
         if message["tool_calls"]
-          tool_calls.concat(parse_tool_calls(message["tool_calls"]))
+          parsed = parse_tool_calls(message["tool_calls"])
+          parsed.each { |tc| block.call(type: "tool_call", **tc) }
+          tool_calls.concat(parsed)
         end
       end
 
lib/elelem/net/openai.rb
@@ -18,7 +18,7 @@ module Elelem
           handle_event(event, tool_calls, &block)
         end
 
-        finalize_tool_calls(tool_calls)
+        finalize_tool_calls(tool_calls, &block)
       end
 
       private
@@ -30,7 +30,7 @@ module Elelem
       def handle_event(event, tool_calls, &block)
         delta = event.dig("choices", 0, "delta") || {}
 
-        block.call(content: delta["content"], thinking: nil) if delta["content"]
+        block.call(type: "saying", text: delta["content"]) if delta["content"]
 
         accumulate_tool_calls(delta["tool_calls"], tool_calls) if delta["tool_calls"]
       end
@@ -72,13 +72,15 @@ module Elelem
         end
       end
 
-      def finalize_tool_calls(tool_calls)
+      def finalize_tool_calls(tool_calls, &block)
         tool_calls.values.map do |tool_call|
-          {
+          result = {
             id: tool_call[:id],
             name: tool_call[:name],
             arguments: JSON.parse(tool_call[:args])
           }
+          block.call(type: "tool_call", **result)
+          result
         end
       end
     end
lib/elelem/agent.rb
@@ -142,11 +142,20 @@ module Elelem
     end
 
     def fetch_response(ctx)
-      content = ""
-      tool_calls = client.fetch(combined_history + ctx, toolbox.to_a) do |delta|
-        content += delta[:content].to_s
-        terminal.print(terminal.think(delta[:thinking])) if delta[:thinking]
+      content = String.new
+      tool_calls = []
+
+      client.fetch(combined_history + ctx, toolbox.to_a) do |event|
+        case event[:type]
+        when "saying"
+          content << event[:text].to_s
+        when "thinking"
+          terminal.print(terminal.think(event[:text]))
+        when "tool_call"
+          tool_calls << { id: event[:id], name: event[:name], arguments: event[:arguments] }
+        end
       end
+
       [content, tool_calls]
     rescue => e
       terminal.say "\n  ✗ #{e.message}"
@@ -177,8 +186,8 @@ module Elelem
       text = messages.map { |message| { role: message[:role], content: message[:content] } }.to_json
 
       String.new.tap do |buffer|
-        client.fetch([{ role: "user", content: "Summarize key facts:\n#{text}" }], []) do |d|
-          buffer << d[:content].to_s
+        client.fetch([{ role: "user", content: "Summarize key facts:\n#{text}" }], []) do |event|
+          buffer << event[:text].to_s if event[:type] == "saying"
         end
       end
     end